From d989e9d4735d04e095501150f5c497742e9a5ddd Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Mon, 7 Jul 2025 20:17:32 -0400 Subject: [PATCH] FULLY SUPPORT GROUPS FOIASHFOIASHFOIASFDH :D --- src/artigen/dice/dice.d.ts | 1 + src/artigen/dice/generateFormattedRoll.ts | 13 +-- src/artigen/dice/groupHandler.ts | 31 +++--- src/artigen/math/mathTokenizer.ts | 118 ++++++++++++++++++++-- 4 files changed, 132 insertions(+), 31 deletions(-) diff --git a/src/artigen/dice/dice.d.ts b/src/artigen/dice/dice.d.ts index 81b358a..2d64a7f 100644 --- a/src/artigen/dice/dice.d.ts +++ b/src/artigen/dice/dice.d.ts @@ -6,6 +6,7 @@ type RollType = '' | 'roll20' | 'fate' | 'cwod' | 'ova'; // RollSet is used to preserve all information about a calculated roll export interface RollSet { type: RollType; + rollGrpIdx?: number; origIdx: number; roll: number; size: number; diff --git a/src/artigen/dice/generateFormattedRoll.ts b/src/artigen/dice/generateFormattedRoll.ts index 22ec698..0b901e4 100644 --- a/src/artigen/dice/generateFormattedRoll.ts +++ b/src/artigen/dice/generateFormattedRoll.ts @@ -1,7 +1,6 @@ import { log, LogTypes as LT } from '@Log4Deno'; -import { FormattedRoll, RollModifiers } from 'artigen/dice/dice.d.ts'; -import { executeRoll } from 'artigen/dice/executeRoll.ts'; +import { ExecutedRoll, FormattedRoll, RollModifiers } from 'artigen/dice/dice.d.ts'; import { loopCountCheck } from 'artigen/managers/loopManager.ts'; @@ -9,23 +8,21 @@ import { rollCounter } from 'artigen/utils/counter.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { createRollDistMap } from 'artigen/utils/rollDist.ts'; -// generateFormattedRoll(rollConf, modifiers) returns one SolvedStep +// generateFormattedRoll(executedRoll, modifiers) returns one SolvedStep // generateFormattedRoll handles creating and formatting the completed rolls into the SolvedStep format -export const generateFormattedRoll = (rollConf: string, modifiers: RollModifiers): FormattedRoll => { +export const formatRoll = (executedRoll: ExecutedRoll, modifiers: RollModifiers): FormattedRoll => { let tempTotal = 0; let tempDetails = '['; let tempCrit = false; let tempFail = false; let tempComplex = false; - // Generate the roll, passing flags thru - const executedRoll = executeRoll(rollConf, modifiers); - // Loop thru all parts of the roll to document everything that was done to create the total roll + loggingEnabled && log(LT.LOG, `Formatting roll ${JSON.stringify(executedRoll)}`); executedRoll.rollSet.forEach((e) => { loopCountCheck(); - loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); + loggingEnabled && log(LT.LOG, `At ${JSON.stringify(e)}`); let preFormat = ''; let postFormat = ''; diff --git a/src/artigen/dice/groupHandler.ts b/src/artigen/dice/groupHandler.ts index f88e229..8eb16ef 100644 --- a/src/artigen/dice/groupHandler.ts +++ b/src/artigen/dice/groupHandler.ts @@ -208,25 +208,22 @@ export const handleGroup = ( } } else { loggingEnabled && log(LT.LOG, `In single-mode ${JSON.stringify(commaParts)} ${groupModifiers} ${JSON.stringify(groupConf)}`); - if (groupModifiers.trim()) { - // Handle special case where the group modifiers are applied across the dice rolled - // ex from roll20 docs: {4d6+3d8}k4 - Roll 4 d6's and 3 d8's, out of those 7 dice the highest 4 are kept and summed up. - // TODO AAAAAAAAAAAAAAAAA - retData = {}; - } else { - // why did you put this in a group, that was entirely pointless - loggingEnabled && log(LT.LOG, `Solving commaPart: ${commaParts[0]}`); - const [tempData, tempCounts, tempDists] = tokenizeMath(commaParts[0], modifiers, previousResults, prevGrpReturnData); - const data = tempData[0]; + const [tempData, tempCounts, tempDists] = tokenizeMath( + commaParts[0], + modifiers, + previousResults, + prevGrpReturnData, + groupModifiers.trim() ? groupConf : null, + ); + const data = tempData[0]; - loggingEnabled && log(LT.LOG, `Solved Math for Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`); + loggingEnabled && log(LT.LOG, `Solved Math for Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`); - countDetails.push(...tempCounts); - rollDists.push(...tempDists); - data.initConfig = `{${data.initConfig}}`; - data.rollDetails = `{${data.rollDetails}}`; - retData = data; - } + countDetails.push(...tempCounts); + rollDists.push(...tempDists); + data.initConfig = `{${data.initConfig}}${groupModifiers.trim() ? groupModifiers.replaceAll(' ', '') : ''}`; + data.rollDetails = `{${data.rollDetails}}`; + retData = data; } // Handle merging back any nested groups to prevent an internalGrp marker from sneaking out diff --git a/src/artigen/math/mathTokenizer.ts b/src/artigen/math/mathTokenizer.ts index f616431..cfa2a18 100644 --- a/src/artigen/math/mathTokenizer.ts +++ b/src/artigen/math/mathTokenizer.ts @@ -4,8 +4,8 @@ import { ReturnData } from 'artigen/artigen.d.ts'; import { MathConf, SolvedStep } from 'artigen/math/math.d.ts'; -import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts'; -import { generateFormattedRoll } from 'artigen/dice/generateFormattedRoll.ts'; +import { CountDetails, ExecutedRoll, GroupConf, RollDistributionMap, RollModifiers, RollSet } from 'artigen/dice/dice.d.ts'; +import { formatRoll } from 'artigen/dice/generateFormattedRoll.ts'; import { loopCountCheck } from 'artigen/managers/loopManager.ts'; @@ -15,6 +15,8 @@ import { closeInternalGrp, cmdSplitRegex, internalWrapRegex, mathSplitRegex, ope import { legalMathOperators } from 'artigen/utils/legalMath.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { assertParenBalance } from 'artigen/utils/parenBalance.ts'; +import { executeRoll } from 'artigen/dice/executeRoll.ts'; +import { compareOrigIdx, compareRolls } from 'artigen/utils/sortFuncs.ts'; // minusOps are operators that will cause a negative sign to collapse into a number (in cases like + - 1) const minusOps = ['(', '^', '**', '*', '/', '%', '+', '-']; @@ -25,9 +27,11 @@ export const tokenizeMath = ( modifiers: RollModifiers, previousResults: number[], groupResults: ReturnData[], + groupConf: GroupConf | null = null, ): [ReturnData[], CountDetails[], RollDistributionMap[]] => { const countDetails: CountDetails[] = []; const rollDists: RollDistributionMap[] = []; + const executedRolls: Map = new Map(); loggingEnabled && log(LT.LOG, `Parsing roll ${cmd} | ${JSON.stringify(modifiers)} | ${JSON.stringify(previousResults)}`); @@ -177,10 +181,15 @@ export const tokenizeMath = ( } } else if (![...allOps, ...legalMathOperators].includes(curMathConfStr)) { // If nothing else has handled it by now, try it as a roll - const formattedRoll = generateFormattedRoll(curMathConfStr, modifiers); - mathConf[i] = formattedRoll.solvedStep; - countDetails.push(formattedRoll.countDetails); - rollDists.push(formattedRoll.rollDistributions); + const executedRoll = executeRoll(curMathConfStr, modifiers); + if (groupConf) { + executedRolls.set(i, executedRoll); + } else { + const formattedRoll = formatRoll(executedRoll, modifiers); + mathConf[i] = formattedRoll.solvedStep; + countDetails.push(formattedRoll.countDetails); + rollDists.push(formattedRoll.rollDistributions); + } } // Identify if we are in a state where the current number is a negative number @@ -203,6 +212,103 @@ export const tokenizeMath = ( } } + // Handle applying the group config + if (groupConf) { + loggingEnabled && log(LT.LOG, `Applying groupConf to executedRolls | ${JSON.stringify(groupConf)} ${JSON.stringify(executedRolls.entries().toArray())}`); + // Merge all rollSets into one array, adding the idx into each rollSet to allow separating them back out + const allRollSets: RollSet[] = []; + const executedRollArr = executedRolls.entries().toArray(); + executedRollArr.forEach(([rollGroupIdx, executedRoll]) => { + executedRoll.rollSet.forEach((roll) => (roll.rollGrpIdx = rollGroupIdx)); + allRollSets.push(...executedRoll.rollSet); + }); + loggingEnabled && log(LT.LOG, `raw rollSets: ${JSON.stringify(allRollSets)}`); + + // Handle drop or keep operations + if (groupConf.drop.on || groupConf.keep.on || groupConf.dropHigh.on || groupConf.keepLow.on) { + allRollSets.sort(compareRolls); + let dropCount = 0; + + // For normal drop and keep, simple subtraction is enough to determine how many to drop + // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop + if (groupConf.drop.on) { + dropCount = groupConf.drop.count; + if (dropCount > allRollSets.length) { + dropCount = allRollSets.length; + } + } else if (groupConf.keep.on) { + dropCount = allRollSets.length - groupConf.keep.count; + if (dropCount < 0) { + dropCount = 0; + } + } // For inverted drop and keep, order must be flipped to greatest to least before the simple subtraction can determine how many to drop + // Protections are in to prevent the dropCount from going below 0 or more than the valid rolls to drop + else if (groupConf.dropHigh.on) { + allRollSets.reverse(); + dropCount = groupConf.dropHigh.count; + if (dropCount > allRollSets.length) { + dropCount = allRollSets.length; + } + } else if (groupConf.keepLow.on) { + allRollSets.reverse(); + dropCount = allRollSets.length - groupConf.keepLow.count; + if (dropCount < 0) { + dropCount = 0; + } + } + + let i = 0; + while (dropCount > 0 && i < allRollSets.length) { + loopCountCheck(); + + loggingEnabled && log(LT.LOG, `Handling group dropping | Dropping ${dropCount}, looking at ${JSON.stringify(allRollSets[i])}`); + + if (!allRollSets[i].dropped && !allRollSets[i].rerolled) { + allRollSets[i].dropped = true; + allRollSets[i].success = false; + allRollSets[i].fail = false; + allRollSets[i].matchLabel = ''; + dropCount--; + } + + i++; + } + + allRollSets.sort(compareOrigIdx); + } + + // Handle marking new successes/fails + if (groupConf.success.on || groupConf.fail.on) { + allRollSets.forEach((rs) => { + loopCountCheck(); + + if (!rs.dropped && !rs.rerolled) { + if (groupConf.success.on && groupConf.success.range.includes(rs.roll)) { + rs.success = true; + rs.matchLabel = 'S'; + } + if (groupConf.fail.on && groupConf.fail.range.includes(rs.roll)) { + rs.fail = true; + rs.matchLabel = 'F'; + } + } + }); + } + + // Handle separating the rollSets back out, recalculating the success/fail count, assigning them to the correct mathConf slots + executedRollArr.forEach(([rollGroupIdx, executedRoll]) => { + // Update flags on executedRoll + executedRoll.countSuccessOverride = executedRoll.countSuccessOverride || groupConf.success.on; + executedRoll.countFailOverride = executedRoll.countFailOverride || groupConf.fail.on; + executedRoll.rollSet = allRollSets.filter((rs) => rs.rollGrpIdx === rollGroupIdx); + + const formattedRoll = formatRoll(executedRoll, modifiers); + mathConf[rollGroupIdx] = formattedRoll.solvedStep; + countDetails.push(formattedRoll.countDetails); + rollDists.push(formattedRoll.rollDistributions); + }); + } + // Now that mathConf is parsed, send it into the solver const tempSolved = mathSolver(mathConf);