// drill.js var transformations = []; var log; Array.prototype.randomElement = function () { return this[Math.floor(Math.random() * this.length)] } // From: http://stackoverflow.com/a/2897510 new function ($) { $.fn.getCursorPosition = function () { var input = this.get(0); if (!input) return; // No (input) element found if ('selectionStart' in input) { // Standard-compliant browsers return input.selectionStart; } else if (document.selection) { // IE input.fmcus(); var sel = document.selection.createRange(); var selLen = document.selection.createRange().text.length; sel.moveStart('character', -input.value.length); return sel.text.length - selLen; } } }(jQuery); // From: http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area new function ($) { $.fn.setCursorPosition = function (pos) { if (this.setSelectionRange) { this.setSelectionRange(pos, pos); } else if (this.createTextRange) { var range = this.createTextRange(); range.collapse(true); if (pos < 0) { pos = $(this).val().length + pos; } range.moveEnd('character', pos); range.moveStart('character', pos); range.select(); } } }(jQuery); // Waaaayy overkill here but these ranges were taken from http://www.unicode.org/charts/ and I kinda got carried away. var japaneseTextPattern = /^[\u{3040}-\u{309f}\u{30a0}-\u{30ff}\u{3190}-\u{319f}\u{31f0}-\u{31ff}\u{3400}-\u{4dbf}\u{4e00}-\u{9ffc}\u{f900}-\u{faff}\u{ff00}-\u{ffef}\u{1b000}-\u{1b0ff}\u{1b100}-\u{1b12f}\u{1b130}-\u{1b16f}\u{20000}-\u{2a6dd}\u{2a700}-\u{2b734}\u{2b740}-\u{2b81d}\u{2b820}-\u{2cea1}\u{2ceb0}-\u{2ebe0}\u{2f800}-\u{2fa1f}\u{30000}-\u{3134a}]*$/u; function commaList(items, conjunction) { if (conjunction == undefined) { conjunction = "and"; } var result = ""; for (var i = 0; i < items.length; i++) { result = result + items[i]; if (i < (items.length - 2)) { result += ", "; } if (i == (items.length - 2)) { result += " " + conjunction + " "; } } return result; } function resetLog() { log = { "history": [] }; } function kanaForm(words) { if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { return word.split(/.\[([^\]]*)\]/).join(""); } ); } function kanjiForm(words) { if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { return word.split(/(.)\[[^\]]*\]/).join(""); } ); } function getVerbForms(entry) { var result = { "kanji": {}, "hiragana": {}, "furigana": {} }; Object.keys(words[entry].conjugations).forEach(function (key) { result["kanji"][key] = kanjiForm(words[entry].conjugations[key].forms); result["hiragana"][key] = kanaForm(words[entry].conjugations[key].forms); result["furigana"][key] = words[entry].conjugations[key].forms; }); return result; } function wordWithFurigana(words) { var options = getOptions(); if (words.constructor !== Array) { words = [words]; } return words.map(function (word) { var bits = word.split(/(.)\[([^\]]*)\]/); while (bits.length > 1) { if (options["kana"]) { bits[0] = bits[0] + bits[2] + bits[3]; } else if (options["furigana_always"]) { bits[0] = bits[0] + "" + bits[1] + "" + bits[3]; } else { bits[0] = bits[0] + bits[1] + bits[3]; } bits.splice(1, 3); } return bits[0]; }); } function processAnswerKey() { var el = $('#answer'); var pos = el.getCursorPosition(); var val = el.val(); var last1 = val.slice(pos - 1, pos); var last2 = val.slice(pos - 2, pos); var last3 = val.slice(pos - 3, pos); var replace1 = { "a": "あ", "i": "い", "u": "う", "e": "え", "o": "お" }; var replace2 = { "ka": "か", "ki": "き", "ku": "く", "ke": "け", "ko": "こ", "sa": "さ", "si": "し", "su": "す", "se": "せ", "so": "そ", "ta": "た", "ti": "ち", "tu": "つ", "te": "て", "to": "と", "na": "な", "ni": "に", "nu": "ぬ", "ne": "ね", "no": "の", "ha": "は", "hi": "ひ", "hu": "ふ", "he": "へ", "ho": "ほ", "ma": "ま", "mi": "み", "mu": "む", "me": "め", "mo": "も", "ra": "ら", "ri": "り", "ru": "る", "re": "れ", "ro": "ろ", "ga": "が", "gi": "ぎ", "gu": "ぐ", "ge": "げ", "go": "ご", "za": "ざ", "zi": "じ", "zu": "ず", "ze": "ぜ", "zo": "ぞ", "da": "だ", "di": "ぢ", "du": "づ", "de": "で", "do": "ど", "ba": "ば", "bi": "び", "bu": "ぶ", "be": "べ", "bo": "ぼ", "pa": "ぱ", "pi": "ぴ", "pu": "ぷ", "pe": "ぺ", "po": "ぽ", "qa": "くぁ", "qi": "くぃ", "qu": "く", "qe": "くぇ", "qo": "くぉ", "wa": "わ", "wi": "うぃ", "wu": "う", "we": "うぇ", "wo": "を", "ya": "や", "yi": "い", "yu": "ゆ", "ye": "いぇ", "yo": "よ", "fa": "ふぁ", "fi": "ふぃ", "fu": "ふ", "fe": "ふぇ", "fo": "ふぉ", "ja": "じゃ", "ji": "じ", "ju": "じゅ", "je": "じぇ", "jo": "じょ", "la": "ぁ", "li": "ぃ", "lu": "ぅ", "le": "ぇ", "lo": "ぉ", "za": "ざ", "zi": "じ", "zu": "ず", "ze": "ぜ", "zo": "ぞ", "xa": "ぁ", "xi": "ぃ", "xu": "ぅ", "xe": "ぇ", "xo": "ぉ", "ca": "か", "ci": "し", "cu": "く", "ce": "せ", "co": "こ", "va": "ヴぁ", "vi": "ヴぃ", "vu": "ヴ", "ve": "ヴぇ", "vo": "ヴぉ", "lu": "っ", "nn": "ん", "n'": "ん", "nb": "んb", "nc": "んc", "nd": "んd", "nf": "んf", "ng": "んg", "nh": "んh", "nj": "んj", "nk": "んk", "nl": "んl", "nm": "んm", "np": "んp", "nq": "んq", "nr": "んr", "ns": "んs", "nt": "んt", "nv": "んv", "nw": "んw", "nx": "んx", "nz": "んz", "aa": "っa", "bb": "っb", "cc": "っc", "dd": "っd", "ee": "っe", "ff": "っf", "gg": "っg", "hh": "っh", "ii": "っi", "jj": "っj", "kk": "っk", "ll": "っl", "mm": "っm", "oo": "っo", "pp": "っp", "qq": "っq", "rr": "っr", "ss": "っs", "tt": "っt", "uu": "っu", "vv": "っv", "ww": "っw", "xx": "っx", "yy": "っy", "zz": "っz", }; var replace3 = { "kya": "きゃ", "kyi": "きぃ", "kyu": "きゅ", "kye": "きぇ", "kyo": "きょ", "sha": "しゃ", "shi": "し", "shu": "しゅ", "she": "しぇ", "sho": "しょ", "cha": "ちゃ", "chi": "ち", "chu": "ちゅ", "che": "ちぇ", "cho": "ちょ", "nya": "にゃ", "nyi": "にぃ", "nyu": "にゅ", "nye": "にぇ", "nyo": "にょ", "hya": "ひゃ", "hyi": "ひぃ", "hyu": "ひゅ", "hye": "ひぇ", "hyo": "ひょ", "mya": "みゃ", "myi": "みぃ", "myu": "みゅ", "mye": "みぇ", "myo": "みょ", "rya": "りゃ", "ryi": "りぃ", "ryu": "りゅ", "rye": "りぇ", "ryo": "りょ", "gya": "ぎゃ", "gyi": "ぎぃ", "gyu": "ぎゅ", "gye": "ぎぇ", "gyo": "ぎょ", "zya": "じゃ", "zyi": "じぃ", "zyu": "じゅ", "zye": "じぇ", "zyo": "じょ", "dya": "ぢゃ", "dyi": "ぢぃ", "dyu": "ぢゅ", "dye": "ぢぇ", "dyo": "ぢょ", "bya": "びゃ", "byi": "びぃ", "byu": "びゅ", "bye": "びぇ", "byo": "びょ", "pya": "ぴゃ", "pyi": "ぴぃ", "pyu": "ぴゅ", "pye": "ぴぇ", "pyo": "ぴょ", "shi": "し", "tsu": "つ", }; if (replace3[last3]) { val = val.slice(0, pos - 3) + replace3[last3] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 3 + replace3[last3].length); } else if (replace2[last2]) { val = val.slice(0, pos - 2) + replace2[last2] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 2 + replace2[last2].length); } else if (replace1[last1]) { val = val.slice(0, pos - 1) + replace1[last1] + val.slice(pos, -1); el.val(val); el.setCursorPosition(pos - 1 + replace1[last1].length); } } function validQuestion(entry, forms, transformation, options) { var valid = true; transformation.tags.forEach(function (type) { if (options[type] == false) { valid = false; } }); if (options[words[entry].group] == false) { valid = false; } if (!forms["furigana"][transformation.from]) valid = false; if (!forms["furigana"][transformation.to]) valid = false; if (valid) { if (options.questionFocus != "none") { if (options.questionFocus == 'tetakei') { // console.log("tetakei", words[entry].conjugations[transformation.from].tetakei, words[entry].conjugations[transformation.to].tetakei) if (words[entry].conjugations[transformation.from].tetakei == words[entry].conjugations[transformation.to].tetakei) { valid = false; } } else if (transformation.type != options.questionFocus) { valid = false; } } } return valid; } function generateQuestion() { var questionText = { "affirmative": "What is the affirmative form of", "negative": "What is the negative form of", "present": "What is the present form of", "past": "What is the past form of", "plain": "What is the plain form of", "polite": "What is the polite form of", "て": "What is the て form of", "non-て": "What is the non-て form of", "potential": "What is the potential form of", "non-potential": "What is the non-potential form of", "imperative": "What is the imperative form of", "non-imperative": "What is the non-imperative form of", "causative": "What is the causative form of", "non-causative": "What is the non-causative form of", "passive": "What is the passive form of", "active": "What is the active form of", "progressive": "What is the progressive form of", "non-progressive": "What is the non-progressive form of", "'desire'": "What is the 'desire' form of", "'non-desire'": "What is the 'non-desire' form of", "volitional": "What is the volitional form of", "non-volitional": "What is the non-volitional form of" }; var entry; var to_form; var from_form; var forms; var options = getOptions(); var count = 0; while (true) { if (count++ == 10000) { showSplash(); return; } entry = Object.keys(words).randomElement(); transformation = transformations.randomElement(); from_form = transformation.from; to_form = transformation.to; forms = getVerbForms(entry); var valid = validQuestion(entry, forms, transformation, getOptions()); // Modify the chance of trick questions so that they appear on average 25% // of the time. When trick questions are active then 50% of the // transformation structure are trick questions and so a 33% filter here // will achieve the 25% because this test is only performed when a trick // question has been selected. if (transformation.tags.indexOf('trick') != -1) { if (Math.random() > 0.333) { valid = false; } } if (valid) { break; } } var kanjiForms = forms["kanji"]; var kanaForms = forms["hiragana"]; var furiganaForms = forms["furigana"]; var givenWord; if (options["kana"]) { givenWord = kanaForms[from_form].randomElement(); } else { givenWord = wordWithFurigana(furiganaForms[from_form]).randomElement(); } var questionFirstHalf = questionText[transformation.phrase]; var questionSecondHalf = givenWord + "?"; var question = questionFirstHalf + questionSecondHalf; var answer = kanjiForms[to_form]; var answer2 = kanaForms[to_form]; var answerWithFurigana = wordWithFurigana(furiganaForms[to_form]); if (options["kana"]) { answer = answer2; answerWithFurigana = kanaForms[to_form]; } $('#questionFirstHalf').html(questionFirstHalf); $('#questionSecondHalf').html(questionSecondHalf); window.questionData = { entry: entry, transformation: transformation, question: question, answer: answer, answer2: answer2, answerWithFurigana: answerWithFurigana, givenWord: givenWord, }; // Construct the explanation page. var data = window.questionData; var groupLabels = { "godan" : "godan verb", "ichidan" : "ichidan verb", "iku" : "godan verb", "suru" : "suru verb", "kuru" : "special verb", "i-adjective" : "い adjective", "ii" : "i-adjective", "na-adjective" : "な adjective", }; var dictionary = words[data.entry].conjugations["dictionary"].forms; if (words[data.entry].group == "na-adjective") { dictionary = dictionary.replace(/だ$/, '') } if (!options["kana"]) { dictionary = wordWithFurigana(dictionary); } else { dictionary = kanaForm(dictionary); } $('#explain-given').html(givenWord); $('#explain-given-tags').html(data.transformation.from_tags.map(function (tag) { return "" + tag + ""; }).join(" ")); $('.explain-given-dictionary').html(dictionary); $('#explain-group').html(groupLabels[words[data.entry].group]); $('.explain-transform').html(data.transformation.phrase); $('.explain-answer-tags').html(data.transformation.to_tags.map(function (tag) { return "" + tag + ""; }).join(" ")); $('.explain-answer-tags2').html(data.transformation.to_tags.join(" ")); $('.explain-answer').html(commaList(questionData.answerWithFurigana, "or")); $('.explain-answer-as-list').empty(); questionData.answerWithFurigana.forEach(function (answer) { $('.explain-answer-as-list').append("