-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement unscrambling reaction tests
Part of #718
- Loading branch information
1 parent
8114d69
commit 40010bd
Showing
7 changed files
with
227 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
141 changes: 141 additions & 0 deletions
141
javascript/features/reaction_tests/strategies/unscramble_strategy.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// Copyright 2020 Las Venturas Playground. All rights reserved. | ||
// Use of this source code is governed by the MIT license, a copy of which can | ||
// be found in the LICENSE file. | ||
|
||
import { Strategy } from 'features/reaction_tests/strategy.js'; | ||
|
||
import { random } from 'base/random.js'; | ||
|
||
// File that contains the word definitions for scrambled word games. | ||
const kWordDefinitionFile = 'data/scrambled_words.json'; | ||
|
||
// Utility function to determine whether the given |charCode| should be scrambled. | ||
function shouldScramble(charCode) { | ||
return (charCode >= 48 && charCode <= 57) || /* numbers */ | ||
(charCode >= 65 && charCode <= 90) || /* upper case */ | ||
(charCode >= 97 && charCode <= 122); /* lower case */ | ||
} | ||
|
||
// This strategy works by scrambling the characters in a list of predefined phrases, and requiring | ||
// players to guess the right word. The actual list is defined in |kWordDefinitionFile|. | ||
export class UnscrambleStrategy extends Strategy { | ||
// How many players should be on the server in order to consider this strategy? | ||
static kMinimumPlayerCount = 1; | ||
|
||
answer_ = null; | ||
scrambled_ = null; | ||
settings_ = null; | ||
words_ = new Set(); | ||
|
||
constructor(settings) { | ||
super(); | ||
|
||
this.settings_ = settings; | ||
} | ||
|
||
// Gets the answer to the current reaction test. May be NULL. | ||
get answer() { return this.answer_; } | ||
|
||
// Gets the scrambled answer, primarily for testing purposes. | ||
get scrambled() { return this.scrambled_; } | ||
|
||
// Initializes the list of words that players should unscramble. Will be called when a test is | ||
// starting, to avoid doing this too often while running tests. | ||
ensureWordListInitialized() { | ||
if (this.words_.size) | ||
return; | ||
|
||
const words = JSON.parse(readFile(kWordDefinitionFile)); | ||
for (const word of words) { | ||
if (word.startsWith('__')) | ||
continue; // considered as a note/comment, ignore it | ||
|
||
this.words_.add(word); | ||
} | ||
} | ||
|
||
// Starts a new test provided by this strategy. The question must be determined, and it should | ||
// be announced to people in-game and available through Nuwani accordingly. | ||
start(announceFn, nuwani, prize) { | ||
this.ensureWordListInitialized(); | ||
|
||
// Pick a random phrase from the loaded word list. | ||
this.answer_ = ([ ...this.words_ ][ random(this.words_.size) ]).toUpperCase(); | ||
this.scrambled_ = this.scramble(this.answer_); | ||
|
||
// Announce the test to all in-game participants. | ||
announceFn(Message.REACTION_TEST_ANNOUNCE_UNSCRAMBLE, this.scrambled_, prize); | ||
|
||
// Announce the test to everyone reading along through Nuwani. | ||
nuwani().echo('reaction-unscramble', this.scrambled_, prize); | ||
} | ||
|
||
// Scrambles the given |phrase| and returns the result. We decide which characters are valid to | ||
// be scrambled, then scramble them, re-compose the word, and return it as a string. | ||
scramble(phrase) { | ||
if (phrase.trim() !== phrase) | ||
console.log(`[reaction test] warning: "${phrase}" has excess spacing.`); | ||
|
||
const kStaticPercentage = | ||
this.settings_().getValue('playground/reaction_test_unscramble_fixed'); | ||
|
||
const words = phrase.split(' '); | ||
const result = []; | ||
|
||
for (const word of words) { | ||
const characters = []; | ||
const fixed = new Set(); | ||
|
||
// (1) Decide which characters are supposed to be fixed. | ||
for (let index = 0; index < word.length; ++index) { | ||
if (random(100) < kStaticPercentage) | ||
fixed.add(index); | ||
} | ||
|
||
// (1) Collect all the characters in the |word|. | ||
for (let index = 0; index < word.length; ++index) { | ||
if (!shouldScramble(word.charCodeAt(index)) || fixed.has(index)) | ||
continue; | ||
|
||
characters.push(word.charAt(index)); | ||
} | ||
|
||
const shuffled = this.shuffle(characters); | ||
const composed = []; | ||
|
||
// (2) Re-compose the scrambled word. | ||
for (let index = 0; index < word.length; ++index) { | ||
if (shouldScramble(word.charCodeAt(index)) && !fixed.has(index)) | ||
composed.push(shuffled.shift()); | ||
else | ||
composed.push(word.charAt(index)); | ||
} | ||
|
||
result.push(composed.join('')); | ||
} | ||
|
||
// (3) Join the composed word together, then translate to upper case. | ||
return result.join(' '); | ||
} | ||
|
||
// Shuffles the given |array| with the Fisher-Yates algorithm: | ||
// https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle | ||
shuffle(array) { | ||
let counter = array.length; | ||
|
||
while (counter > 0) { | ||
const index = random(counter--); | ||
const temp = array[counter]; | ||
|
||
array[counter] = array[index]; | ||
array[index] = temp; | ||
} | ||
|
||
return array; | ||
} | ||
|
||
// Verifies whether the |message| is, or contains, the answer to this reaction test. | ||
verify(message) { | ||
return message.toUpperCase().startsWith(this.answer_); | ||
} | ||
} |
79 changes: 79 additions & 0 deletions
79
javascript/features/reaction_tests/strategies/unscramble_strategy.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// Copyright 2020 Las Venturas Playground. All rights reserved. | ||
// Use of this source code is governed by the MIT license, a copy of which can | ||
// be found in the LICENSE file. | ||
|
||
import { UnscrambleStrategy } from 'features/reaction_tests/strategies/unscramble_strategy.js'; | ||
|
||
import { range } from 'base/range.js'; | ||
import { symmetricDifference } from 'base/set_extensions.js'; | ||
|
||
describe('UnscrambleStrategy', (it, beforeEach) => { | ||
let announceFn = null; | ||
let nuwani = null; | ||
let settings = null; | ||
|
||
beforeEach(() => { | ||
const driver = server.featureManager.loadFeature('reaction_tests'); | ||
|
||
announceFn = driver.__proto__.announceToPlayers.bind(driver); | ||
nuwani = server.featureManager.loadFeature('nuwani'); | ||
settings = server.featureManager.loadFeature('settings'); | ||
}); | ||
|
||
it('is able to scramble words while maintaining the correct answer', assert => { | ||
const strategy = new UnscrambleStrategy(() => settings); | ||
|
||
// (1) Test the shuffling function. | ||
const values = range(100); | ||
const shuffled = strategy.shuffle(values); | ||
|
||
assert.equal(values.length, shuffled.length); | ||
assert.equal(symmetricDifference(new Set(values), new Set(shuffled)).size, 0); | ||
|
||
// (2) Test the scrambling function. | ||
const input = 'This abcdef is xyz123 a test'; | ||
const scrambled = strategy.scramble(input); | ||
|
||
assert.equal(input.length, scrambled.length); | ||
assert.equal(symmetricDifference(new Set(values), new Set(shuffled)).size, 0); | ||
}); | ||
|
||
it('is able to change difficulty level through static letters', assert => { | ||
const strategy = new UnscrambleStrategy(() => settings); | ||
|
||
// (1) Don't scramble the words at all. | ||
settings.setValue('playground/reaction_test_unscramble_fixed', 100); | ||
|
||
strategy.start(announceFn, () => nuwani, 1234); | ||
assert.equal(strategy.answer, strategy.scrambled); | ||
|
||
// (2) Heavily scramble the words, with on fixed letters. | ||
settings.setValue('playground/reaction_test_unscramble_fixed', 0); | ||
|
||
strategy.start(announceFn, () => nuwani, 1234); | ||
assert.notEqual(strategy.answer, strategy.scrambled); | ||
}); | ||
|
||
it('announces new tests to in-game players and Nuwani users', assert => { | ||
const gunther = server.playerManager.getById(/* Gunther= */ 0); | ||
const strategy = new UnscrambleStrategy(() => settings); | ||
|
||
assert.equal(gunther.messages.length, 0); | ||
assert.equal(nuwani.messagesForTesting.length, 0); | ||
|
||
strategy.start(announceFn, () => nuwani, 1234); | ||
|
||
assert.equal(gunther.messages.length, 1); | ||
assert.equal( | ||
gunther.messages[0], | ||
Message.format(Message.REACTION_TEST_ANNOUNCE_UNSCRAMBLE, strategy.scrambled, 1234)); | ||
|
||
assert.equal(nuwani.messagesForTesting.length, 1); | ||
assert.deepEqual(nuwani.messagesForTesting[0], { | ||
tag: 'reaction-unscramble', | ||
params: [ strategy.scrambled, 1234 ] | ||
}); | ||
|
||
strategy.stop(); | ||
}); | ||
}); |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.