From 7be4280508f20be8a967877ea226d9210fee52c1 Mon Sep 17 00:00:00 2001 From: Stefan Zermatten Date: Tue, 30 Jun 2020 14:40:20 +0200 Subject: [PATCH] Began implementing dice rolls in the maths parser --- .../api/creature/actions/applyDamage.js | 10 +-- .../api/creature/actions/applyProperties.js | 4 +- .../afterComputation/evaluateAndRollString.js | 77 +++++++++++++++++++ .../substituteRollsWithFunctions.js | 20 +++++ app/imports/math.js | 12 +++ 5 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 app/imports/api/creature/computation/afterComputation/evaluateAndRollString.js create mode 100644 app/imports/api/creature/computation/afterComputation/substituteRollsWithFunctions.js diff --git a/app/imports/api/creature/actions/applyDamage.js b/app/imports/api/creature/actions/applyDamage.js index b2634071..0caa31d2 100644 --- a/app/imports/api/creature/actions/applyDamage.js +++ b/app/imports/api/creature/actions/applyDamage.js @@ -1,4 +1,4 @@ -import evaluateString from '/imports/api/creature/computation/afterComputation/evaluateString.js'; +import evaluateAndRollString from '/imports/api/creature/computation/afterComputation/evaluateAndRollString.js'; export default function applyDamage({ prop, @@ -11,9 +11,9 @@ export default function applyDamage({ ...creature.variables, ...actionContext, }; - let {result, errors} = evaluateString(prop.amount, scope); - if (Meteor.isClient) errors.forEach(e => console.error(e)); - if (Number.isFinite(result)) { - damageTargets.forEach() + let {result, errors} = evaluateAndRollString(prop.amount, scope); + if (Meteor.isClient){ + errors.forEach(e => console.error(e)); + console.log(result); } } diff --git a/app/imports/api/creature/actions/applyProperties.js b/app/imports/api/creature/actions/applyProperties.js index 3831b5a8..7aa98f4c 100644 --- a/app/imports/api/creature/actions/applyProperties.js +++ b/app/imports/api/creature/actions/applyProperties.js @@ -1,5 +1,5 @@ import applyAction from '/imports/api/creature/actions/applyAction.js'; -//import applyDamage from '/imports/api/creature/actions/applyDamage.js'; +import applyDamage from '/imports/api/creature/actions/applyDamage.js'; import applyBuff from '/imports/api/creature/actions/applyBuff.js'; function applyProperty(options){ @@ -19,7 +19,7 @@ function applyProperty(options){ applyAction(options); return true; case 'damage': - // applyDamage(options); + applyDamage(options); return true; case 'adjustment': // applyAdjustment(options); diff --git a/app/imports/api/creature/computation/afterComputation/evaluateAndRollString.js b/app/imports/api/creature/computation/afterComputation/evaluateAndRollString.js new file mode 100644 index 00000000..23802246 --- /dev/null +++ b/app/imports/api/creature/computation/afterComputation/evaluateAndRollString.js @@ -0,0 +1,77 @@ +import math from '/imports/math.js'; +import bareSymbolSubtitutor from '/imports/api/creature/computation/utility/bareSymbolSubtitutor.js'; +import substituteRollsWithFunctions from '/imports/api/creature/computation/afterComputation/substituteRollsWithFunctions.js' + +export default function evaluateAndRollString(string, scope){ + let errors = []; + if (!string){ + errors.push('No string provided'); + return {result: string, errors}; + } + + if (!scope) errors.push('No scope provided'); + + // Parse the string using mathjs + let calc; + try { + calc = math.parse(string); + } catch (e) { + errors.push(e); + return {result: string, errors}; + } + + // Replace all bare symbols with symbol.value + let transformedCalc = calc.transform(bareSymbolSubtitutor(scope)); + // Replace all rolls with the function to call them + transformedCalc = calc.transform(substituteRollsWithFunctions); + + // Evaluate the expression to a number or return with substitutions + try { + let result = transformedCalc.evaluate(scope); + return {result, errors}; + } catch (e1){ + errors.push(e1); + try { + let result = simplifyWithAccessors(transformedCalc, scope).toHTML(); + return {result, errors}; + } catch (e2){ + errors.push(e2); + return {result: transformedCalc.toHTML(), errors}; + } + } +} + +function simplifyWithAccessors(calc, scope){ + let noAccessorCalc = calc.transform(substituteAccessors(scope)); + return math.simplify(noAccessorCalc); +} + +// returns a function to replace all accessors with either their resolved value +// or a symbol to simplify with +function substituteAccessors(scope){ + return function(node){ + if (node.isAccessorNode){ + try { + return evaluateAccessor(node, scope); + } catch (e) { + return replaceAccessorWithSymbol(node); + } + } else { + return node; + } + } +} + +// Throws error if symbol is undefined in scope +function evaluateAccessor(node, scope){ + let value = node.evaluate(scope); + if (value === undefined){ + throw 'Undefined symbol' + } + return new math.ConstantNode(value); +} + +function replaceAccessorWithSymbol(node){ + let symbolNode = new math.SymbolNode(node.toString()); + return symbolNode; +} diff --git a/app/imports/api/creature/computation/afterComputation/substituteRollsWithFunctions.js b/app/imports/api/creature/computation/afterComputation/substituteRollsWithFunctions.js new file mode 100644 index 00000000..2442e6c8 --- /dev/null +++ b/app/imports/api/creature/computation/afterComputation/substituteRollsWithFunctions.js @@ -0,0 +1,20 @@ +import math from '/imports/math.js'; + +const diceRegex = /d\d+/; + +export default function substituteRollsWithFunctions(node){ + // TODO also replace dx as 1dx + if ( + node.isOperatorNode && + node.fn === 'multiply' && + node.implicit && + node.args[1].isSymbolNode && + diceRegex.test(node.args[1].name) + ){ + let diceSize = node.args[1].name.slice(1); + let diceSizeNode = new math.ConstantNode(diceSize); + return new math.FunctionNode('roll', [node.args[0], diceSizeNode]); + } else { + return node; + } +} diff --git a/app/imports/math.js b/app/imports/math.js index 8ab9dcc9..4b985968 100644 --- a/app/imports/math.js +++ b/app/imports/math.js @@ -1,8 +1,20 @@ import { create, all } from 'mathjs'; +import { Random } from 'meteor/random' + const math = create(all); math.import({ 'if': function(pred, a, b) { return pred ? a : b; + }, + 'roll': function(number, diceSize){ + if (number > 100) throw 'Can only roll 100 dice at a time'; + let rollTotal = 0; + let i, roll; + for (i = 0; i < number; i++){ + roll = ~~(Random.fraction() * diceSize) + 1 + rollTotal += roll; + } + return rollTotal; } });