Files
kana-test/main.js
T
2021-10-15 11:53:09 +02:00

328 lines
10 KiB
JavaScript

/* exported app, utils */
const kanas = {
columns: [ 'A', 'I', 'U', 'E', 'O' ],
rows: [
'',
'K',
'S',
'T',
'N',
'H',
'M',
'Y',
'R',
'W',
'(N)',
],
hiraganas: [
'あいうえお',
'かきくけこ',
'さしすせそ',
'たちつてと',
'なにぬねの',
'はひふへほ',
'まみむめも',
'や ゆ よ',
'らりるれろ',
'わ を',
'ん',
],
katakanas: [
'アイウエオ',
'カキクケコ',
'サシスセソ',
'タチツテト',
'ナニヌネノ',
'ハヒフヘホ',
'マミムメモ',
'ヤ ユ ヨ',
'ラリルレロ',
'ワ ヲ',
'ン',
],
exceptions: {
SI: 'SHI',
TI: 'CHI',
TU: 'TSU',
HU: 'FU',
},
traps: {
hiraganas: [
'うえふら',
'いりにけはほ',
'あおぬめ',
'れねわ',
'しも',
'つてと',
'るろそ',
'まはほよ',
'くへ',
'たなを',
'えん',
'きさち',
],
katakanas: [],
},
mappings: [
[ 0, 1 ],
[ 1, 0 ],
[ 0, 2 ],
[ 2, 0 ],
[ 1, 2 ],
[ 2, 1 ],
],
};
const utils = {
randint: function (min, max) {
return Math.floor(Math.random() * (max - min)) + min;
},
randindex: function (array, ...toIgnore) {
if (array.length === 0 || toIgnore.length >= array.length) {
return -1;
}
let index;
do {
index = this.randint(0, array.length);
} while (this.contains(toIgnore, index));
return index;
},
randitem: function (array, ...toIgnore) {
if (array.length === 0) {
return undefined;
}
return array[this.randindex(array, ...toIgnore)];
},
randindexes: function (array, number, ...toIgnore) {
number = Math.min(number, array.length - toIgnore.length);
const output = [];
for (let i = 0; i < number; i++) {
output.push(this.randindex(array, ...output, ...toIgnore));
}
return output;
},
randitems: function (array, number) {
const indexes = this.randindexes(array, number);
return indexes.map(i => array[i]);
},
shuffle: function (array) {
const output = [ ...array ];
for (let i = 0; i < array.length; i++) {
const i1 = this.randindex(array);
const i2 = this.randindex(array, i1);
[ output[i1], output[i2] ] = [ output[i2], output[i1] ];
}
return output;
},
contains: function (array, item) {
return array.indexOf(item) >= 0;
},
unique: function (array) {
return [ ...new Set(array) ];
},
};
Array.prototype.shuffle = function () {
return utils.shuffle(this);
};
Array.prototype.contains = function (item) {
return utils.contains(this, item);
};
Array.prototype.unique = function () {
return utils.unique(this);
};
const cookies = {
set: function (name, value, days = undefined) {
const maxAge = !days ? undefined : days * 864e2;
document.cookie = `${name}=${encodeURIComponent(value)}${maxAge ? `;max-age=${maxAge};` : ''}`;
},
get: function (name) {
return document.cookie.split('; ').reduce(function (r, v) {
const parts = v.split('=');
return parts[0] === name ? decodeURIComponent(parts[1]) : r;
}, '');
},
};
let app = {
el: '#app',
data: {
title: 'Kana Test',
score: 0,
highscore: 0,
savedHighscores: {},
options: {
available: kanas.rows,
selected: kanas.rows,
mappings: [ 0, 1 ],
answers: 6,
},
kanas: [],
question: 'あ',
lastQuestions: [ '', '', '' ],
answers: [ 'A', 'I', 'U', 'O' ],
wrongAnswers: [],
},
watch: {
options: {
handler: 'changeOption',
deep: true,
},
},
methods: {
findKana: function(v) {
const self = this;
return self.kanas.filter((kana) => {
return kana.contains(v);
})[0];
},
buildKanas: function () {
const self = this;
self.kanas = [];
self.options.selected.forEach((prefix) => {
const row = kanas.rows.indexOf(prefix);
if (prefix === '(N)') {
self.kanas.push([ 'N', kanas.hiraganas[row][0], kanas.katakanas[row][0], row, 0 ]);
} else {
kanas.columns.forEach((suffix, column) => {
const text = kanas.exceptions[prefix + suffix]
? kanas.exceptions[prefix + suffix]
: prefix + suffix;
if (kanas.hiraganas[row][column] !== ' ') {
self.kanas.push([ text, kanas.hiraganas[row][column], kanas.katakanas[row][column], row, column ]);
}
});
}
});
},
findSimilars: function(kanaIndex, mapping) {
const self = this;
const kana = self.kanas[kanaIndex];
const similarIndexes = [];
if (mapping.contains(1)) {
const traps = kanas.traps.hiraganas.filter(line => line.includes(kana[1]));
utils.randitems(traps, 2).forEach(line => {
const trapKana = utils.randitem(line.split(''), line.indexOf(kana[1]));
similarIndexes.push(self.kanas.indexOf(self.findKana(trapKana)));
});
}
if (mapping.contains(2)) {
const traps = kanas.traps.katakanas.filter(line => line.includes(kana[2]));
utils.randitems(traps, 2).forEach(line => {
const trapKana = utils.randitem(line.split(''), line.indexOf(kana[2]));
similarIndexes.push(self.kanas.indexOf(self.findKana(trapKana)));
});
}
if (kana[0] !== 'N') {
const sameRow = self.kanas.filter((kana2, i) => !similarIndexes.contains(i) && kana2 !== kana && kana2[3] === kana[3]);
if (sameRow.length > 0) {
similarIndexes.push(self.kanas.indexOf(utils.randitem(sameRow)));
}
const sameColumn = self.kanas.filter((kana2, i) => !similarIndexes.contains(i) && kana2 !== kana && kana2[4] === kana[4]);
if (sameColumn.length > 0) {
similarIndexes.push(self.kanas.indexOf(utils.randitem(sameColumn)));
}
}
return similarIndexes.unique();
},
generateQuestion: function () {
const self = this;
const mapping = kanas.mappings[utils.randitem(self.options.mappings)];
const questionIndex = utils.randindex(self.kanas, self.lastQuestions);
self.lastQuestions.pop(0);
self.lastQuestions.push(questionIndex);
const similarIndexes = self.findSimilars(questionIndex, mapping);
const otherIndexes = utils.randindexes(self.kanas, Math.max(0, self.options.answers - similarIndexes.length - 1), questionIndex, ...similarIndexes );
self.question = self.kanas[questionIndex][mapping[0]];
self.answers = [ questionIndex ]
.concat(similarIndexes)
.concat(otherIndexes)
.map((index) => self.kanas[index][mapping[1]]);
self.answers = self.answers.shuffle();
self.wrongAnswers = [];
/*
* setTimeout(() => {
* this.answer(self.question);
* });
*/
},
answer: function (v) {
const self = this;
const question = self.findKana(self.question);
if (question.contains(v)) {
self.score += 1;
if (self.score > self.highscore) {
self.highscore = self.score;
self.savedHighscores[btoa(JSON.stringify(self.options))] = self.highscore;
cookies.set('highscores', btoa(JSON.stringify(self.savedHighscores)));
}
self.generateQuestion();
} else {
self.score = 0;
self.wrongAnswers.push(v);
}
document.activeElement.blur();
},
changeOption: function () {
const self = this;
if (self.options.mappings.length === 0) {
self.options.mappings.push(0);
}
if (self.options.selected.length === 0) {
self.options.selected.push('');
}
cookies.set('options', btoa(JSON.stringify(self.options)));
self.highscore = self.savedHighscores[btoa(JSON.stringify(self.options))] ?? 0;
self.score = 0;
self.buildKanas();
self.generateQuestion();
},
readCookies: function() {
const self = this;
const savedOptions = cookies.get('options');
if (savedOptions) {
try {
self.options = JSON.parse(atob(savedOptions));
} catch (e) { /* ignored */ }
}
const savedHighscores = cookies.get('highscores');
if (savedHighscores) {
try {
self.savedHighscores = JSON.parse(atob(savedHighscores));
self.highscore = self.savedHighscores[btoa(JSON.stringify(self.options))] ?? 0;
} catch (e) { /* ignored */ }
}
},
},
mounted: function () {
const self = this;
console.log('app mounted');
setTimeout(() => {
self.$el.setAttribute('style', '');
});
self.buildKanas();
self.readCookies();
self.generateQuestion();
},
};
window.onload = () => {
app = new Vue(app);
};