mathdice
All checks were successful
/ build (push) Successful in 45s

This commit is contained in:
Nycki 2025-10-16 11:00:28 -07:00
parent 6a6dac7b01
commit dab1ed15c1
7 changed files with 198 additions and 0 deletions

View file

@ -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! **[Braceless JS](https://github.com/nycki93/braceless-javascript/), 2018.** You can do a lot with 'one-line' functions. Let's play with that!
</div>
<div class="card">
<a href="./mathdice/"><img src="/a/card-mathdice.png"></a>
**[Mathdice](./mathdice/), 2017.** Practice your number skills with random puzzles. Check with the auto-solver!
</div> </div>
<div class="card"> <div class="card">
<a href="https://museumofzzt.com/file/p/PortalZZT_v0_5.zip"><img src="/a/card-portal-zzt.png"></a> <a href="https://museumofzzt.com/file/p/PortalZZT_v0_5.zip"><img src="/a/card-portal-zzt.png"></a>

BIN
static/a/card-mathdice.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,29 @@
<!doctype html>
<html>
<head>
<title>Mathdice Solver 1.0</title>
<!-- By Nick "Nupa" Lamicela, 2018 -->
<meta charset="utf-8"/>
<link rel="stylesheet" href="mathdice.css"/>
<script src="mathdice.js" defer></script>
</head>
<body>
<p>Your numbers are:</p>
<form>
<input type="text" name="die1" class="scoring dice" />
<input type="text" name="die2" class="scoring dice" />
<input type="text" name="die3" class="scoring dice" />
<input type="text" name="target" id="target" class="dice" />
</form>
<br/>
<button id="roll-button">Roll dice</button>
<button id="solve-button">Solve the puzzle</button>
<br/>
<br/>
<div id="solution"/>
</body>
</html>

View file

@ -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;
}

143
static/mathdice/mathdice.js Normal file
View file

@ -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)}`;
}

View file

@ -0,0 +1 @@
An html mathdice game.