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 0000000..de371cc Binary files /dev/null and b/static/a/card-mathdice.png differ diff --git a/static/a/card-qrplay.png b/static/a/card-qrplay.png index 75e0862..46d551f 100644 Binary files a/static/a/card-qrplay.png and b/static/a/card-qrplay.png differ 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.