143 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			143 lines
		
	
	
	
		
			3.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| "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)}`;
 | |
| }
 |