latin: unistrokes circles matched separately
[sheet.git] / word / quiz.js
1 Array.prototype.shuffle = function () {
2         for (let i = this.length - 1; i > 0; i--) {
3                 const j = Math.floor(Math.random() * (i + 1)); // random index 0..i
4                 [this[i], this[j]] = [this[j], this[i]]; // swap elements
5         }
6         return this;
7 };
8
9 class WordQuiz {
10         dataselect(json) {
11                 this.data = json;
12                 this.cats = {}; // category lookup
13                 for (let i in json) {
14                         let cat = json[i][3];
15                         if (this.cats[cat]) {
16                                 this.cats[cat].push(i);
17                         }
18                         else {
19                                 this.cats[cat] = [i];
20                         }
21                 }
22                 return this.datafilter(json);
23         }
24
25         datafilter(json) {
26                 // find viable rows from json data
27                 let rows = Object.values(json);
28
29                 if (this.preset.cat !== undefined) {
30                         rows = [];
31                         let children = this.cats[this.preset.cat];
32                         for (let loop = 0; children.length && loop < 20; loop++) {
33                                 rows.push(...children);
34                                 children = children.map(cat => this.cats[cat]).filter(is => is).flat();
35                         }
36                         rows = rows.map(row => json[row]).filter(row => row[2]);
37                 }
38                 if (this.preset.level !== undefined) {
39                         rows = rows.filter(row => row[1] <= this.preset.level);
40                 }
41
42                 {
43                         let cats = new Set();
44                         let subcats = rows.map(row => row[3]); // direct parents
45                         for (let loop = 0; subcats.length && loop < 20; loop++) {
46                                 subcats.forEach(cat => cats.add(cat));
47                                 subcats = subcats.map(row => json[row] && json[row][3]).filter(val => val); // recurse grandparents
48                         }
49                         rows = rows.filter(row => !cats.has(row[2])); // remove referenced categories
50                 }
51                 return rows.shuffle();
52         }
53
54         load(dataurl) {
55                 this.preset = {};
56                 let input;
57                 if (input = window.location.hash.match(/\d+/)) {
58                         this.preset.cat = input[0];
59                 }
60                 if (window.location.hash.match(/a/)) {
61                         this.preset.level = 3;
62                 }
63
64                 fetch(dataurl).then(res => res.json()).then(json => {
65                         this.words = this.dataselect(json)
66                         this.setup();
67                 });
68         }
69
70         log(...args) {
71                 this.history.push([new Date().toISOString(), ...args]);
72         }
73
74         stop(...args) {
75                 this.log(...args);
76                 window.onbeforeunload = null;
77                 fetch('/word/report', {method: 'POST', body: JSON.stringify(this.history)});
78         }
79
80         constructor(dataurl) {
81                 this.load(dataurl);
82                 this.history = [];
83                 window.onbeforeunload = e => {
84                         this.stop('abort');
85                 };
86         }
87 }