-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmath.js
187 lines (165 loc) · 6.74 KB
/
math.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
Array.prototype.slideBy = function(windo) {
let end = 1 + this.length - windo;
return [...Array(end).keys()].map(i => this.slice(i, i + windo));
};
let rint = max => Math.floor(max * Math.random());
let numDigits = n => Math.log10(n) + 1 | 0;
// Generates a set of random numbers that sum to the first element of the
// returned array
let makeSum = (min, max, n=2, minNumber=1) => {
let range = max - min;
let divisions = [...Array(n).keys()].map(_ => min + rint(range));
// The `divisions` array holds all the numbers you'll hit along the way to
// your sum, including the sum.
divisions.sort((a, b) => a - b);
// Create an array of the numbers you're going to be summing
let nums = [
divisions[0], ...divisions.slideBy(2).map(([a, b]) => b - a)];
// HACK: avoid zeros ie < minNumber. If there's a zero, just regenerate
if (nums.findIndex(n=>n<minNumber) >= 0) return makeSum(min, max, n, minNumber);
// The last number of `divisions` is the sum
let sum = divisions.pop();
return [sum, ...nums];
};
// min and max affect the numbers, not the final product.
let makeProduct = (min, max, n=2) => {
let range = max - min;
let nums = [...Array(n).keys()].map(_ => min + rint(range));
return [nums.reduce((prev, x) => prev * x), ...nums];
};
function resetTimer() {
var display = $('div.time');
if (display) display.innerHTML = '';
}
function timer(e) {
let form = $('form');
if (!form.startTime) {
form.startTime = Date.now();
} else {
let problems = [...$('form').querySelectorAll('label')];
if (problems.every(l => l.classList.contains('right'))) {
$('div.time').innerHTML = `
Time elapsed: ${Math.round((Date.now() - form.startTime) / 1000)}s`;
}
}
}
function checkInput(input) {
let label = input.closest('label');
if (!input.value.length) {
label.classList.remove('right');
label.classList.remove('wrong');
return;
}
if (parseInt(input.value, 10) === input.solution) {
if (!label.classList.replace('wrong', 'right')) {
label.classList.add('right');
}
let nextProblem = label.nextSibling;
if (!nextProblem || nextProblem.classList.contains('right') || !nextProblem.matches('label')) {
nextProblem = label.parentNode.querySelector('label:not(.right)');
}
if (nextProblem) {
setTimeout(_ => nextProblem.querySelector('input').select(), 10);
}
} else {
if (!label.classList.replace('right', 'wrong')) {
label.classList.add('wrong');
}
setTimeout(() => input.select(), 10);
}
}
function checkAnswers(e) {
e.preventDefault();
if (e.target.tagName === 'INPUT') {
checkInput(e.target);
} else {
[...$('form').querySelectorAll('input[type=text]')].forEach(checkInput);
}
}
function generateProblems(numProbs, min, max, [sum_range, sub_range, mult_range, div_range]) {
let form = $el('form', {className: 'problem'});
let maxlength = numDigits(max) + 1;
for (let i = 0; i < numProbs; i++) {
let problem_type = Math.random();
let equation;
if (problem_type < sub_range[1]) {
// Addition or subtraction
let numNums = qs.getI('setSize', 2);
let sum = makeSum(min, max, numNums);
if (problem_type < sum_range[1]) {
if (numNums > 2) {
equation = $el('label');
let nums = $el('div', {className:'nums'});
nums.append(...sum.slice(1).map(n => $el('span', {innerText:n})));
nums.append($el('div', {className:'operator', innerText:'+'}));
equation.append(nums);
} else {
equation = $el('label', {innerHTML: `${sum.slice(1).join(' + ')} =`});
}
equation.appendChild($el('input', {solution: sum[0], type:'text', maxlength: maxlength}));
} else {
equation = $el('label', {innerHTML: `${sum.slice(0, -1).join(' − ')} =`});
equation.appendChild($el('input', {solution: sum[sum.length-1], type:'text', maxlength: maxlength}));
}
} else {
// Multiplcation or division
let prod = makeProduct(min, max, qs.getI('setSize', 2));
if (problem_type < mult_range[1]) {
equation = $el('label', {innerHTML: `${prod.slice(1).join(' × ')} =`});
equation.appendChild($el('input', {solution: prod[0], type:'text', maxlength: maxlength}));
} else {
equation = $el('label', {innerHTML: `${prod.slice(0, -1).join(' ÷ ')} =`});
equation.appendChild($el('input', {solution: prod[prod.length-1], type:'text', maxlength: maxlength}));
}
}
// Icons for right and wrong
equation.appendChild($el('i', {className: 'cil-check right'}));
equation.appendChild($el('i', {className: 'cil-x wrong'}));
form.appendChild(equation);
}
// TODO: use a delegated event handler
[...form.querySelectorAll('input[type=text]')].forEach(input => {
input.addEventListener('blur', checkAnswers);
input.addEventListener('focus', () => input.closest('label').classList.add('active'));
input.addEventListener('blur', () => input.closest('label').classList.remove('active'));
});
form.appendChild($el('input', {type: 'submit'}));
form.addEventListener('submit', checkAnswers);
setTimeout(_ => form.querySelector('input').focus(), 10);
// hook in the timing functionality
form.addEventListener('focusout', timer);
form.addEventListener('submit', timer);
resetTimer();
return form;
}
addEventListener('DOMContentLoaded', _ => {
let container = $('.math');
let numProbs = qs.getI('n', 8);
let min = qs.getI('min', 0);
let max = qs.getI('max', 11);
let sum_parts = qs.getI('sums', 1);
let difference_parts = qs.getI('subs', 0);
let multiplication_parts = qs.getI('mults', 0);
let division_parts = qs.getI('divs', 0);
let total = sum_parts + difference_parts + multiplication_parts + division_parts;
let sum_range = [0, sum_parts / total];
let difference_range = [sum_range[1], (difference_parts + sum_parts) / total];
let multiplication_range = [difference_range[1],
(multiplication_parts + difference_parts + sum_parts) / total];
let division_range = [multiplication_range[1], 1];
let style = $el('style');
$('head').appendChild(style);
style.sheet.insertRule(`label>input{width:${2 * numDigits(max)}rem}`);
container.appendChild(generateProblems(numProbs, min, max,
[sum_range, difference_range, multiplication_range, division_range]));
let regenerate = $el('button', {className: 'regenerate', innerText: 'Regenerate'});
regenerate.addEventListener('click', e => {
e.preventDefault();
container.replaceChild(
generateProblems(numProbs, min, max, [sum_range, difference_range, multiplication_range, division_range]),
container.childNodes[0]);
});
container.appendChild(regenerate);
container.appendChild($el('input', {type:'checkbox'}));
container.appendChild($el('div', {className:'time'}));
});