word/quiz: objects to access row array elements
authorMischa POSLAWSKY <perl@shiar.org>
Sun, 19 Jun 2022 00:20:22 +0000 (02:20 +0200)
committerMischa POSLAWSKY <perl@shiar.org>
Mon, 11 Jul 2022 02:02:34 +0000 (04:02 +0200)
word/finder.js
word/memory.js
word/multichoice.js
word/quiz.js
word/wijzer.js

index e720bfdf47dc52cfdad840a6609042c6854907cd..630db8e8f8ff21af26f979f17742658e32f91988 100644 (file)
@@ -1,6 +1,5 @@
 class WordFinder extends WordQuiz {
        namehtml(name) {
-               //let wbr = new RegExp('\w{4} [^aoeuiyc\W] [rl]?+ \K (?= [^aoeuiy\W] [rl]? [aoeuiy] \w)', 'g');
                let aliases = name.split('/');
                let html = aliases.shift();
                html = html.replace(/\((.+)\)/, '<small>$1</small>');
@@ -11,29 +10,29 @@ class WordFinder extends WordQuiz {
        }
 
        add(catitem, rows) {
-               rows.forEach(ref => {
-                       const [title, level, imgid, subs] = this.data[ref];
+               rows.forEach(word => {
+                       if (!word) return;
                        const worditem = put(catitem, 'li');
                        const figitem = put(worditem, 'figure');
-                       if (imgid) {
-                               put(figitem, 'img[src=$]', `/data/word/32/${imgid}.jpg`);
+                       if (word.imgid) {
+                               put(figitem, 'img[src=$]', word.thumb());
                        }
-                       if (title) {
+                       if (word.title) {
                                put(figitem, 'figcaption', {
-                                       innerHTML: this.namehtml(title),
+                                       innerHTML: this.namehtml(word.title),
                                });
                        }
-                       put(worditem, '.level' + level);
-                       if (level <= 1 && subs.length >= 4) {
+                       put(worditem, '.level' + word.level);
+                       if (word.level <= 1 && word.subs.length >= 4) {
                                put(worditem, '.large');
                        }
-                       if (subs.length) {
+                       if (word.subs.length) {
                                // delve into subcategory
                                put(worditem, '.parent');
-                               this.add(put(worditem, 'ul'), subs);
+                               this.add(put(worditem, 'ul'), word.subs);
                        }
                        if (this.preset.debug) {
-                               put(figitem, '[title=$]', `id ${ref} level ${level}`);
+                               put(figitem, '[title=$]', `id ${ref} level ${word.level}`);
                        }
                });
        }
@@ -51,7 +50,7 @@ class WordFinder extends WordQuiz {
                }
                this.form.innerHTML = '';
                put(this.form, 'p', 'Under construction.');
-               for (let cat of this.preset.cat || this.data[''][3]) {
+               for (let cat of this.data.root()) {
                        this.add(put(this.form, 'ul.gallery'), [cat]);
                }
        }
index a70ec06638ac20ce07a2ec60bc2aff3803617ff8..b539b2da4776d3831660a1d8cc4ddca82fd7111a 100644 (file)
@@ -69,7 +69,7 @@ class WordMemory extends WordQuiz {
                        let cols = Math.round(Math.sqrt(count) * aspect**.5);
                        count = cols * Math.ceil(count / cols);
                        this.form.style['grid-template-columns'] = `repeat(${cols}, 1fr)`;
-                       cards = this.words.splice(0, count>>1).map(row => row[2]);
+                       cards = this.words.splice(0, count>>1).map(row => row.imgid);
                        cards.push(...cards.map(val => -val));
                }
                else {
index 7402507aa90e33c1fb481c3840918ec93c455da8..300a32fa73e42558c886d7c7a53741eeafd81d04 100644 (file)
@@ -3,17 +3,16 @@ class WordMultichoice extends WordQuiz {
                if (this.words.length < 4) return;
                let word = this.words.shift();
                let form = put(this.form,
-                       '+img[src=$]+ul', `/data/word/32/${word[2]}.jpg`,
+                       '+img[src=$]+ul', word.thumb()
                );
 
-               let answers = [word[0], this.words[0][0], this.words[1][0], this.words[2][0]]
+               let answers = [word, this.words[0], this.words[1], this.words[2]]
                        .shuffle()
-               this.log('ask', word[2], answers);
+               this.log('ask', word.id, answers.map(w => w.id));
                answers.forEach(suggest => {
-                       let label = suggest.replace(/\/.*/, '');
-                       let option = put(form, 'li', label, {onclick: () => {
-                               this.log('pick', suggest, null, word[0]);
-                               if (suggest != word[0]) {
+                       let option = put(form, 'li', suggest.label, {onclick: () => {
+                               this.log('pick', suggest.id, null, word.id);
+                               if (suggest.label != word.label) {
                                        // incorrect
                                        put(option, '.wrong');
                                        return;
index 3243b19ed5c2f52ef019868a32136f253bf41481..5283628407e51fbe84ff5cc54ea02f1c7e88c623 100644 (file)
@@ -6,59 +6,108 @@ Array.prototype.shuffle = function () {
        return this;
 };
 
-Set.prototype.filter = function (f) {
-       return new Set([...this].filter(f));
-};
+class Words {
+       constructor(data, root = undefined) {
+               this.data = data;
+               this.selection = root || this.data[''][3];
+               this.visible = new Set(root || Object.keys(data).flatMap(id => id && parseInt(id)));
+               if (root) {
+                       let children = root;
+                       for (let loop = 0; children.length && loop < 20; loop++) {
+                               for (let child of children) this.visible.add(child);
+                               children = children.map(cat => data[cat][3]).filter(is => is).flat();
+                       }
+               }
+       }
+
+       filter(f) {
+               // keep only matching entries, and root selection regardless
+               this.visible = new Set([...this.visible].filter(f).concat(this.selection));
+       }
+
+       *root() {
+               for (let i of this.selection) {
+                       if (!this.has(i)) {
+                               continue;
+                       }
+                       yield this.get(i);
+               }
+       }
+
+       *random() {
+               let order = [...this.visible.keys()].shuffle();
+               for (let i of order) {
+                       if (!this.has(i)) {
+                               continue;
+                       }
+                       yield this.get(i);
+               }
+       }
+
+       has(id) {
+               return this.visible.has(id);
+       }
+
+       subs(id) {
+               let refs = this.data[id][3];
+               if (!refs) {
+                       return [];
+               }
+               for (let ref of refs) {
+                       // retain orphaned references in grandparent categories
+                       if (!this.has(ref)) {
+                               refs = refs.concat(this.subs(ref));
+                       }
+               }
+               return refs;
+       }
+
+       get(id) {
+               if (!this.has(id)) {
+                       return;
+               }
+               const p = this;
+               const row = this.data[id];
+               return row && {
+                       id: id,
+                       title: row[0],
+                       get label() {
+                               return row[0].replace(/\/.*/, ''); // primary form
+                       },
+                       level: row[1],
+                       imgid: row[2],
+                       thumb(size = 32) {
+                               return `/data/word/${size}/${row[2]}.jpg`;
+                       },
+                       get subs() {
+                               return p.subs(id).map(e => p.get(e));
+                       },
+               };
+       }
+}
 
 class WordQuiz {
        dataselect(json) {
                this.data = this.datafilter(json);
-               let rows = Object.values(this.data);
-               rows = rows.filter(row => !row[3].length); // remove referenced categories
-               return rows.shuffle();
+               return [...this.data.random()];
        }
 
        datafilter(json) {
                // find viable rows from json data
-               let ids = new Set(Object.keys(json));
-               const selection = {...json}; // clone
-
-               for (let cat of selection[''][3]) {
-                       if (selection[cat])
-                       selection[cat][1] = 0; // keep root categories
-               }
+               const selection = new Words(json, this.preset.cat);
 
-               if (this.preset.cat !== undefined) {
-                       ids.clear();
-                       let children = this.preset.cat;
-                       for (let loop = 0; children.length && loop < 20; loop++) {
-                               for (let child of children) ids.add(child.toString());
-                               children = children.map(cat => json[cat][3]).filter(is => is).flat()
-                       }
-               }
                if (this.preset.images) {
-                       ids = ids.filter(id => json[id][2]);
+                       selection.filter(id => json[id][2]);
                }
                if (this.preset.level !== undefined) {
-                       ids = ids.filter(id => json[id][1] <= this.preset.level);
+                       selection.filter(id => json[id][1] <= this.preset.level);
                }
 
-               // keep only wanted ids
-               for (let id in selection) {
-                       if (id && !ids.has(id)) {
-                               delete selection[id];
-                       }
+               if (this.preset.distinct) {
+                       // remove referenced categories
+                       selection.filter(id => !selection.get(id).subs.length);
                }
 
-               // retain orphaned references in grandparent categories
-               for (let id in selection) {
-                       selection[id][3] = function subresolve(subs) {
-                               //console.log(subs);
-                               return (subs || []).flatMap(sub =>
-                                       sub in selection ? [sub] : json[sub] ? subresolve(json[sub][3]) : []
-                               );
-                       }(selection[id][3]);
-               }
                return selection;
        }
 
index 2f73052e2fd93117134bbee055f39c567bcc2ea9..7ae3d3ef758216f7c1498f24f165f6bb168c49cb 100644 (file)
@@ -12,8 +12,8 @@ class WordWijzer extends WordQuiz {
 
                this.question.innerHTML = '';
                put(this.question,
-                       '[data-id=$] img[src=$]', word[2],
-                       `/data/word/32/${word[2]}.jpg`
+                       '[data-id=$] img[src=$]', word.id,
+                       word.thumb()
                );
        }
 
@@ -36,9 +36,8 @@ class WordWijzer extends WordQuiz {
                let answers = put(this.form, 'ul');
                this.words
                        .forEach((answer, seq) => {
-                               let label = answer[0].replace(/\/.*/, ''); // primary form
                                put(answers, 'li[data-id=$][onclick=""]',
-                                       answer[2], label, {
+                                       answer.id, answer.label, {
                                                onclick: e => this.verify(e),
                                                index: seq,
                                        }