Skip to content

Instantly share code, notes, and snippets.

@freddi301
Last active September 25, 2019 09:29
Show Gist options
  • Save freddi301/531eebf581b292db51bee06f3717036b to your computer and use it in GitHub Desktop.
Save freddi301/531eebf581b292db51bee06f3717036b to your computer and use it in GitHub Desktop.
Type Faster
<html>
<head>
<style>
.type-chars {
font-family: monospace;
font-size: 30px;
}
</style>
</head>
<body>
<textarea id="text" class="type-chars" cols="70" rows="4" readonly></textarea>
<textarea id="type" class="type-chars" cols="70" rows="4" autofocus></textarea>
<div>
<h4>train characters<h4>
<textarea id="characters" cols="70" rows="10"></textarea>
</div>
<pre id="score"></pre>
<script src="https://cdn.jsdelivr.net/lodash/4.17.4/lodash.min.js"></script>
<script src="https://unpkg.com/[email protected]/bundles/Rx.min.js"></script>
<script>
const trainCharactersElement = document.getElementById('characters');
const textElement = document.getElementById('text');
const typeElement = document.getElementById('type');
const SPECIAL_CHARS = ['Alt', 'Shift', 'AltGr', 'Backspace'];
const COMBINATION_LENGTH = 4;
const text = new Rx.BehaviorSubject;
text.forEach(value => textElement.value = value || "");
const type = new Rx.BehaviorSubject;
type.forEach(value => typeElement.value = value || "");
function getExpectedCharacter() {
return text.getValue()[type.getValue().length]
}
function wrongChar(expectedChar, gotChar) {
if (~trainCharacters.getValue().indexOf(expectedChar)) getComboScore(expectedChar).mistakes += 3;
if (~trainCharacters.getValue().indexOf(gotChar)) getComboScore(gotChar).mistakes += 1;
}
function rightChar(char) {
type.next(typeElement.value + char);
}
const charSubject = new Rx.Subject;
typeElement.addEventListener('keydown', e => {
if (~SPECIAL_CHARS.indexOf(e.key)) return;
const expectedChar = getExpectedCharacter();
if (e.key != expectedChar) {
wrongChar(expectedChar, e.key);
e.preventDefault();
} else {
rightChar(e.key);
charSubject.next(e.key);
e.preventDefault();
}
});
const exerciseComplete = new Rx.Subject;
typeElement.addEventListener('keyup', e => {
if (type.getValue() == text.getValue()) {
exerciseComplete.next();
}
});
const scorePerCombination = {};
function startExercise() {
text.next(genExercise());
type.next("");
const charTimestamps = charSubject.map(char => ({ time: new Date, char }));
const charDuration = charTimestamps
.scan((memo, current) => ({ last: current, duration: { char: current.char, duration: current.time - memo.last.time } }), { last: { time: new Date } })
.skip(1).pluck('duration').filter(d => d.char != " ");
const charCombination = charDuration
.scan((memo, current) => {
memo.queue.unshift(current);
memo.queue = memo.queue.slice(0, COMBINATION_LENGTH);
memo.combinations = [];
let last = { char: "", duration: 0 };
memo.queue.forEach(item => memo.combinations.push(last = { char: item.char + last.char, duration: last.duration + item.duration }));
return memo;
}, { queue: [] }).skip(1).pluck('combinations')
charCombination.forEach(combos => {
combos.forEach(combo => {
const current = getComboScore(combo.char);
current.times += 1;
current.duration += combo.duration;
current.average = current.duration / current.times;
});
});
}
function getComboScore(char) {
if (!scorePerCombination[char.length]) scorePerCombination[char.length] = {};
if (!scorePerCombination[char.length][char]) {
scorePerCombination[char.length][char] = { char, times: Math.random(), duration: Math.random(), average: Math.random(), mistakes: Math.random() };
}
return scorePerCombination[char.length][char];
}
function extract(stats) {
return _.flatten(Object.values(stats)).map(_.property('char'))
}
exerciseComplete.forEach(startExercise);
const trainCharacters = new Rx.BehaviorSubject;
trainCharacters.next("qwertyuiopasdfghjklzxcvbnm");
trainCharacters.forEach(value => trainCharactersElement.value = value);
trainCharactersElement.addEventListener('input', e => trainCharacters.next(trainCharactersElement.value));
trainCharacters.forEach(chars => Array.from(chars).forEach(getComboScore));
function genExercise () {
const lessTyped = _.mapValues(scorePerCombination, board => _.orderBy(Object.values(board), 'times', 'asc').slice(0,1));
const mostMistaken = _.mapValues(scorePerCombination, board => _.orderBy(Object.values(board), 'mistakes', 'desc').slice(0,1));
const slowest = _.mapValues(scorePerCombination, board => _.orderBy(Object.values(board), 'average', 'desc').slice(0,1));
const random1 = _.orderBy(Object.values(scorePerCombination["1"] || {}), 'times', 'asc').slice(0,5).map(_.property('char')).join('');
const random2 = _.orderBy(Object.values(scorePerCombination["1"] || {}), 'average', 'asc').slice(0,5).map(_.property('char')).join('');
const random3 = _.orderBy(Object.values(scorePerCombination["2"] || {}), 'times', 'asc').slice(0,3).map(_.property('char')).join('');
const random4 = _.orderBy(Object.values(scorePerCombination["2"] || {}), 'average', 'asc').slice(0,3).map(_.property('char')).join('');
// return _.shuffle(trainCharacters.getValue()).join('');
return extract(lessTyped)
.concat(extract(slowest))
.concat(extract(mostMistaken))
.concat(random1)
.concat(random2)
.concat(random3)
.concat(random4)
.join(" ");
};
const scoreElement = document.getElementById('score');
exerciseComplete.forEach(() => { scoreElement.innerText = JSON.stringify(scorePerCombination, null, 2); });
exerciseComplete.next();
</script>
</body>
</html>
@freddi301
Copy link
Author

freddi301 commented Aug 31, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment