From ac8602f5981148027b1a1356cb5e36d98a0e367e Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Mon, 28 Apr 2025 19:17:01 -0400 Subject: [PATCH] Add support for floor/abs/other math operators --- src/solver/parser.ts | 59 +++++++++++++++++++++++++---------------- src/solver/rollUtils.ts | 2 ++ src/solver/solver.ts | 24 ++++++++++++----- 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/src/solver/parser.ts b/src/solver/parser.ts index fbf85fb..d3f9338 100644 --- a/src/solver/parser.ts +++ b/src/solver/parser.ts @@ -8,14 +8,14 @@ import config from '../../config.ts'; import { RollModifiers } from '../mod.d.ts'; import { CountDetails, ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts'; -import { compareTotalRolls, compareTotalRollsReverse, escapeCharacters, loggingEnabled } from './rollUtils.ts'; +import { compareTotalRolls, compareTotalRollsReverse, escapeCharacters, legalMathOperators, loggingEnabled } from './rollUtils.ts'; import { formatRoll } from './rollFormatter.ts'; import { fullSolver } from './solver.ts'; // parseRoll(fullCmd, modifiers) // parseRoll handles converting fullCmd into a computer readable format for processing, and finally executes the solving export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => { - const operators = ['^', '*', '/', '%', '+', '-', '(', ')']; + const operators = ['(', ')', '^', '*', '/', '%', '+', '-']; const returnMsg = { error: false, errorCode: '', @@ -78,14 +78,17 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll // Evaluate all rolls into stepSolve format and all numbers into floats for (let i = 0; i < mathConf.length; i++) { loggingEnabled && log(LT.LOG, `Parsing roll ${fullCmd} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`); - if (mathConf[i].toString().length === 0) { + + const strMathConfI = mathConf[i].toString(); + + if (strMathConfI.length === 0) { // If its an empty string, get it out of here mathConf.splice(i, 1); i--; - } else if (mathConf[i] == parseFloat(mathConf[i].toString())) { + } else if (mathConf[i] == parseFloat(strMathConfI)) { // If its a number, parse the number out - mathConf[i] = parseFloat(mathConf[i].toString()); - } else if (mathConf[i].toString().toLowerCase() === 'e') { + mathConf[i] = parseFloat(strMathConfI); + } else if (strMathConfI.toLowerCase() === 'e') { // If the operand is the constant e, create a SolvedStep for it mathConf[i] = { total: Math.E, @@ -93,25 +96,21 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll containsCrit: false, containsFail: false, }; - } else if (mathConf[i].toString().toLowerCase() === 'fart' || mathConf[i].toString().toLowerCase() === '๐Ÿ’ฉ') { + } else if (strMathConfI.toLowerCase() === 'fart' || strMathConfI.toLowerCase() === '๐Ÿ’ฉ') { mathConf[i] = { total: 7, details: '๐Ÿ’ฉ', containsCrit: false, containsFail: false, }; - } else if (mathConf[i].toString().toLowerCase() === 'sex') { + } else if (strMathConfI.toLowerCase() === 'sex') { mathConf[i] = { total: 69, details: '( อกยฐ อœส– อกยฐ)', containsCrit: false, containsFail: false, }; - } else if ( - mathConf[i].toString().toLowerCase() === 'inf' || - mathConf[i].toString().toLowerCase() === 'infinity' || - mathConf[i].toString().toLowerCase() === 'โˆž' - ) { + } else if (strMathConfI.toLowerCase() === 'inf' || strMathConfI.toLowerCase() === 'infinity' || strMathConfI.toLowerCase() === 'โˆž') { // If the operand is the constant Infinity, create a SolvedStep for it mathConf[i] = { total: Infinity, @@ -119,7 +118,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll containsCrit: false, containsFail: false, }; - } else if (mathConf[i].toString().toLowerCase() === 'pi' || mathConf[i].toString().toLowerCase() === '๐œ‹') { + } else if (strMathConfI.toLowerCase() === 'pi' || strMathConfI.toLowerCase() === '๐œ‹') { // If the operand is the constant pi, create a SolvedStep for it mathConf[i] = { total: Math.PI, @@ -127,7 +126,7 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll containsCrit: false, containsFail: false, }; - } else if (mathConf[i].toString().toLowerCase() === 'pie') { + } else if (strMathConfI.toLowerCase() === 'pie') { // If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them) mathConf[i] = { total: Math.PI, @@ -149,23 +148,37 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll ], ); i += 2; - } else if (!operators.includes(mathConf[i].toString())) { + } else if (!legalMathOperators.includes(strMathConfI) && legalMathOperators.some((mathOp) => strMathConfI.endsWith(mathOp))) { + // Identify when someone does something weird like 4floor(2.5) and split 4 and floor + const matchedMathOp = legalMathOperators.filter((mathOp) => strMathConfI.endsWith(mathOp))[0]; + mathConf[i] = parseFloat(strMathConfI.replace(matchedMathOp, '')); + + mathConf.splice(i + 1, 0, ...['*', matchedMathOp]); + i += 2; + } else if (![...operators, ...legalMathOperators].includes(strMathConfI)) { // If nothing else has handled it by now, try it as a roll - const formattedRoll = formatRoll(mathConf[i].toString(), modifiers); + const formattedRoll = formatRoll(strMathConfI, modifiers); mathConf[i] = formattedRoll.solvedStep; tempCountDetails.push(formattedRoll.countDetails); } // Identify if we are in a state where the current number is a negative number if (mathConf[i - 1] === '-' && ((!mathConf[i - 2] && mathConf[i - 2] !== 0) || mathConf[i - 2] === '(')) { - if (typeof mathConf[i] === 'number') { - mathConf[i] = mathConf[i] * -1; + if (typeof mathConf[i] === 'string') { + // Current item is a mathOp, need to insert a "-1 *" before it + mathConf.splice(i - 1, 1, ...[parseFloat('-1'), '*']); + i += 2; } else { - ( mathConf[i]).total = ( mathConf[i]).total * -1; - ( mathConf[i]).details = `-${( mathConf[i]).details}`; + // Handle normally, just set current item to negative + if (typeof mathConf[i] === 'number') { + mathConf[i] = mathConf[i] * -1; + } else { + ( mathConf[i]).total = ( mathConf[i]).total * -1; + ( mathConf[i]).details = `-${( mathConf[i]).details}`; + } + mathConf.splice(i - 1, 1); + i--; } - mathConf.splice(i - 1, 1); - i--; } } diff --git a/src/solver/rollUtils.ts b/src/solver/rollUtils.ts index 6b5d537..234371a 100644 --- a/src/solver/rollUtils.ts +++ b/src/solver/rollUtils.ts @@ -8,6 +8,8 @@ import { RollModifiers } from '../mod.d.ts'; import { ReturnData, RollSet } from './solver.d.ts'; export const loggingEnabled = false; +export const legalMath = [Math.abs, Math.ceil, Math.floor, Math.round, Math.sqrt, Math.cbrt]; +export const legalMathOperators = legalMath.map((oper) => oper.name); // genRoll(size) returns number // genRoll rolls a die of size size and returns the result diff --git a/src/solver/solver.ts b/src/solver/solver.ts index 5284663..954f5cc 100644 --- a/src/solver/solver.ts +++ b/src/solver/solver.ts @@ -11,7 +11,7 @@ import { } from '../../deps.ts'; import { SolvedStep } from './solver.d.ts'; -import { loggingEnabled } from './rollUtils.ts'; +import { legalMath, legalMathOperators, loggingEnabled } from './rollUtils.ts'; // fullSolver(conf, wrapDetails) returns one condensed SolvedStep // fullSolver is a function that recursively solves the full roll and math @@ -35,7 +35,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: while (conf.includes('(')) { loggingEnabled && log(LT.LOG, `Evaluating roll ${JSON.stringify(conf)} | Looking for (`); // Get first open parenthesis - const openParenIdx = conf.indexOf('('); + let openParenIdx = conf.indexOf('('); let closeParenIdx = -1; let nextParenIdx = 0; @@ -66,18 +66,28 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: // Replace the items between openParenIdx and closeParenIdx (including the parens) with its solved equivalent conf.splice(openParenIdx, closeParenIdx - openParenIdx + 1, parenSolve); + // Determine if previous idx is a Math operator and execute it + if (openParenIdx - 1 > -1 && legalMathOperators.includes(conf[openParenIdx - 1].toString())) { + // Update total and details of parenSolve + parenSolve.total = legalMath[legalMathOperators.indexOf(conf[openParenIdx - 1].toString())](parenSolve.total); + parenSolve.details = `${conf[openParenIdx - 1]}${parenSolve.details}`; + + conf.splice(openParenIdx - 1, 2, parenSolve); + // shift openParenIdx as we have just removed something before it + openParenIdx--; + } + // Determining if we need to add in a multiplication sign to handle implicit multiplication (like "(4)2" = 8) - // insertedMult flags if there was a multiplication sign inserted before the parens - let insertedMult = 0; // Check if a number was directly before openParenIdx and slip in the "*" if needed if (openParenIdx - 1 > -1 && !signs.includes(conf[openParenIdx - 1].toString())) { - insertedMult = 1; conf.splice(openParenIdx, 0, '*'); + // shift openParenIdx as we have just added something before it + openParenIdx++; } // Check if a number is directly after the closing paren and slip in the "*" if needed // openParenIdx is used here as the conf array has already been collapsed down - if (openParenIdx + 1 + insertedMult < conf.length && !signs.includes(conf[openParenIdx + 1 + insertedMult].toString())) { - conf.splice(openParenIdx + 1 + insertedMult, 0, '*'); + if (openParenIdx + 1 < conf.length && !signs.includes(conf[openParenIdx + 1].toString())) { + conf.splice(openParenIdx + 1, 0, '*'); } }