-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmathdice.js
143 lines (131 loc) · 3.59 KB
/
mathdice.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
"use strict";
const OPERATORS = {
add: {
apply(a, b) {return a + b},
toString() {return "+"}
},
subtract: {
apply(a, b) {return a - b},
toString() {return "-"}
},
multiply: {
apply(a, b) {return a * b},
toString() {return "*"}
},
divide: {
apply(a, b) {return a / b},
toString() {return "/"}
},
power: {
apply(a, b) {return a ** b},
toString() {return "^"}
}
}
// Assign event handlers.
document.getElementById("roll-button").onclick = generateProblem;
document.getElementById("solve-button").onclick = showSolution;
/**
* Roll dice and generate a mathdice problem. There are three "key" values
* (d6), and one "target" roll (1d12 * 1d12).
*/
function generateProblem () {
// mouse:mice :: douse:dice
const diceList = document.querySelectorAll('.scoring');
Array.prototype.forEach.call(diceList, douse => {
douse.value = roll_d(6);
})
const target = document.getElementById('target');
target.value = roll_d(12) * roll_d(12);
}
/**
* Solve the problem! Read in the values on screen and run them through the
* solution algorithm.
*/
function showSolution () {
const diceList = document.querySelectorAll('.scoring');
const diceValues = Array.prototype.map.call(diceList, douse =>
Number(douse.value)
)
const targetValue = Number(document.getElementById('target').value);
const solution = solve(diceValues, targetValue);
document.getElementById('solution').innerHTML = solution;
}
/**
* Generate a random number between 1 and n, inclusive.
* @param {number} n
*/
function roll_d (n) {
const roll = Math.floor(n * Math.random()) + 1;
return roll;
}
/**
* Evaluate an expression! An expression is either
* 1) a number, or
* 2) two expressions connected with an operator.
* @param {number | Array} expr
* @returns {number}
*/
function evaluateExpression (expr) {
if (!Array.isArray(expr)) return expr;
const [a, op, b] = expr;
const valueA = evaluateExpression(a);
const valueB = evaluateExpression(b);
return op.apply(valueA, valueB);
}
/**
* Write an expression as a string.
* @param {number | Array} expr
* @returns {string}
*/
function writeExpression (expr) {
if (!Array.isArray(expr)) return expr;
const [a, op, b] = expr;
let strA = writeExpression(a);
if (Array.isArray(a)) {strA = `(${strA})`}
let strB = writeExpression(b);
if (Array.isArray(b)) {strB = `(${strB})`}
return `${strA} ${op.toString()} ${strB}`;
}
/**
* Solve a mathdice problem!
* @param {number[]} scoringDice An array of "key" values used in the puzzle.
* @param {number} target The "target" value to try and produce.
* @returns {string} A string representation of the optimal answer.
*/
function solve(scoringDice, target) {
// Possible solutions: 2 * 3! * 5^2 = 300. Reasonable to brute force.
const [a, b, c] = scoringDice;
const dicePermutations = [
[a, b, c],
[a, c, b],
[b, a, c],
[b, c, a],
[c, a, b],
[c, b, a]
];
const templates = [
(a, b, c, x, y) => [a, x, [b, y, c]],
(a, b, c, x, y) => [[a, x, b], y, c]
];
// I don't even care how brutally hard-coded this is, I don't NEED the
// general case right now.
const guesses = [];
for (const keyX in OPERATORS) {
const x = OPERATORS[keyX];
for (const keyY in OPERATORS) {
const y = OPERATORS[keyY];
dicePermutations.forEach( ([a, b, c]) => {
templates.forEach( f => {
guesses.push(f(a, b, c, x, y));
})
})
}
}
// Find the best guess.
const scores = guesses.map( guess =>
Math.abs(target - evaluateExpression(guess))
)
const bestScore = Math.min(...scores);
const bestGuess = guesses[scores.indexOf(bestScore)];
return `${writeExpression(bestGuess)} = ${evaluateExpression(bestGuess)}`;
}