diff --git a/gulpfile.js b/gulpfile.js index a9b51f19e..356f31ad6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -168,6 +168,7 @@ const refactoredSrc = [ "./src/js/test/layout-emulator.js", "./src/js/test/poetry.js", "./src/js/test/today-tracker.js", + "./src/js/test/weak-spot.js", "./src/js/replay.js", ]; diff --git a/src/js/input-controller.js b/src/js/input-controller.js index 3874c84fb..272e1e3d7 100644 --- a/src/js/input-controller.js +++ b/src/js/input-controller.js @@ -23,6 +23,7 @@ import * as Focus from "./focus"; import * as ShiftTracker from "./shift-tracker"; import * as Replay from "./replay.js"; import * as MonkeyPower from "./monkey-power"; +import * as WeakSpot from "./weak-spot"; $("#wordsInput").keypress((event) => { event.preventDefault(); @@ -660,6 +661,7 @@ function handleAlpha(event) { ); } } + WeakSpot.updateScore(nextCharInWord, thisCharCorrect); if (thisCharCorrect) { Sound.playClick(Config.playSoundOnClick); diff --git a/src/js/test/test-logic.js b/src/js/test/test-logic.js index 44191f9f3..050b15e25 100644 --- a/src/js/test/test-logic.js +++ b/src/js/test/test-logic.js @@ -32,6 +32,7 @@ import * as Replay from "./replay.js"; import * as MonkeyPower from "./monkey-power"; import * as Poetry from "./poetry.js"; import * as TodayTracker from "./today-tracker"; +import * as WeakSpot from "./weak-spot"; let glarsesMode = false; @@ -573,6 +574,8 @@ export async function init() { UpdateConfig.setPunctuation(false, true); UpdateConfig.setNumbers(false, true); randomWord = Misc.getASCII(); + } else if (Config.funbox === "weakspot") { + randomWord = WeakSpot.getWord(wordset); } if (Config.punctuation) { diff --git a/src/js/test/weak-spot.js b/src/js/test/weak-spot.js new file mode 100644 index 000000000..24ca5834f --- /dev/null +++ b/src/js/test/weak-spot.js @@ -0,0 +1,48 @@ +import * as TestStats from "./test-stats"; + +const adjustRate = 0.02; +const wordSamples = 20; + +// TODO: base these on WPM or something instead of constants +const defaultScore = 500; +const incorrectPenalty = 5000; + +let scores = {}; + +export function updateScore(char, isCorrect) { + let score = 0.0; + const timings = TestStats.keypressTimings.spacing.array; + if (timings.length > 0) { + score += timings[timings.length - 1]; + } + if (!isCorrect) { + score += incorrectPenalty; + } + if (!(char in scores)) { + scores[char] = defaultScore; + } + // Keep an exponential moving average of the score over time. + scores[char] = score * adjustRate + scores[char] * (1 - adjustRate); +} + +export function getWord(wordset) { + let highScore; + let randomWord; + for (let i = 0; i < wordSamples; i++) { + let newWord = wordset[Math.floor(Math.random() * wordset.length)]; + let newScore = score(newWord); + if (i == 0 || newScore > highScore) { + randomWord = newWord; + highScore = newScore; + } + } + return randomWord; +} + +function score(word) { + let total = 0.0; + for (const c of word) { + total += c in scores ? scores[c] : defaultScore; + } + return total / word.length; +} diff --git a/static/funbox/_list.json b/static/funbox/_list.json index 6b6a8d3a1..de66df4fa 100644 --- a/static/funbox/_list.json +++ b/static/funbox/_list.json @@ -108,6 +108,11 @@ "name": "poetry", "type": "script", "info": "Practice typing some beautiful prose." + }, + { + "name": "weakspot", + "type": "script", + "info": "Focus on slow and mistyped letters." } ]