From dab1ed15c155743c986c2cde2ec9ef3f50b0bfa7 Mon Sep 17 00:00:00 2001 From: nycki Date: Thu, 16 Oct 2025 11:00:28 -0700 Subject: [PATCH] mathdice --- content/index.md | 6 ++ static/a/card-mathdice.png | Bin 0 -> 2091 bytes static/a/card-qrplay.png | Bin 4334 -> 2377 bytes static/mathdice/index.html | 29 +++++++ static/mathdice/mathdice.css | 19 +++++ static/mathdice/mathdice.js | 143 +++++++++++++++++++++++++++++++++++ static/mathdice/readme.md | 1 + 7 files changed, 198 insertions(+) create mode 100644 static/a/card-mathdice.png create mode 100644 static/mathdice/index.html create mode 100644 static/mathdice/mathdice.css create mode 100644 static/mathdice/mathdice.js create mode 100644 static/mathdice/readme.md diff --git a/content/index.md b/content/index.md index b9342c4..864d406 100644 --- a/content/index.md +++ b/content/index.md @@ -110,6 +110,12 @@ Sticker by [djuan](https://linktr.ee/mkiiisystem)! **[Braceless JS](https://github.com/nycki93/braceless-javascript/), 2018.** You can do a lot with 'one-line' functions. Let's play with that! + +
+ + +**[Mathdice](./mathdice/), 2017.** Practice your number skills with random puzzles. Check with the auto-solver! +
diff --git a/static/a/card-mathdice.png b/static/a/card-mathdice.png new file mode 100644 index 0000000000000000000000000000000000000000..de371cc9041b3dabdafbe5a9410542719f7ff1b0 GIT binary patch literal 2091 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 aLxz8ks|@|Z{cw(>0w#tQ$a4K_F@FIDyhCsR literal 4334 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 MLra4iy}y(G122RQ#Q*>R diff --git a/static/mathdice/index.html b/static/mathdice/index.html new file mode 100644 index 0000000..f1fc5ad --- /dev/null +++ b/static/mathdice/index.html @@ -0,0 +1,29 @@ + + + + Mathdice Solver 1.0 + + + + + + +

Your numbers are:

+
+ + + + +
+ +
+ + + + +
+
+ +
+ + diff --git a/static/mathdice/mathdice.css b/static/mathdice/mathdice.css new file mode 100644 index 0000000..e4c88e3 --- /dev/null +++ b/static/mathdice/mathdice.css @@ -0,0 +1,19 @@ +body { + font-family: sans-serif; +} + +.dice { + display: inline-block; + box-sizing: border-box; + border: gray outset 6px; + text-align: center; + + font-size: 80px; + width: 100px; + height: 100px; +} + +#target { + border-color: gold; + width: 200px; +} diff --git a/static/mathdice/mathdice.js b/static/mathdice/mathdice.js new file mode 100644 index 0000000..0b97fa1 --- /dev/null +++ b/static/mathdice/mathdice.js @@ -0,0 +1,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)}`; +} diff --git a/static/mathdice/readme.md b/static/mathdice/readme.md new file mode 100644 index 0000000..3ed07a9 --- /dev/null +++ b/static/mathdice/readme.md @@ -0,0 +1 @@ +An html mathdice game.