Add initial group support, only support SUM mode (ie no modifiers allowed)

This commit is contained in:
Ean Milligan 2025-07-07 02:12:53 -04:00
parent cb3cb6777d
commit cbac134f79
5 changed files with 191 additions and 15 deletions

View File

@ -5,15 +5,16 @@ import config from '~config';
import { ReturnData } from 'artigen/artigen.d.ts'; import { ReturnData } from 'artigen/artigen.d.ts';
import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts'; import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { handleGroup } from 'artigen/dice/groupHandler.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts'; import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { tokenizeMath } from 'artigen/math/mathTokenizer.ts'; import { tokenizeMath } from 'artigen/math/mathTokenizer.ts';
import { reduceCountDetails } from 'artigen/utils/counter.ts'; import { reduceCountDetails } from 'artigen/utils/counter.ts';
import { closeInternal, cmdSplitRegex, internalWrapRegex, openInternal } from 'artigen/utils/escape.ts'; import { closeInternal, closeInternalGrp, internalGrpWrapRegex, internalWrapRegex, openInternal, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { assertGroupBalance, getMatchingGroupIdx, getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts'; import { assertGroupBalance, getMatchingGroupIdx, getMatchingInternalGrpIdx, getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts';
import { basicReducer } from 'artigen/utils/reducers.ts'; import { basicReducer } from 'artigen/utils/reducers.ts';
// tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls // tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls
@ -131,38 +132,78 @@ export const tokenizeCmd = (
} }
return [returnData, countDetails, rollDists]; return [returnData, countDetails, rollDists];
} else { } else {
// Check for any groups and handle them? // Check for any groups and handle them
const groupParts = cmd const groupParts = cmd
.join('') .join('')
.split(/([{,}])/g) .split(/([{}])/g)
.filter((x) => x); .filter((x) => x);
const groupResults: ReturnData[] = [];
if (groupParts.includes('{')) { if (groupParts.includes('{')) {
assertGroupBalance(groupParts); assertGroupBalance(groupParts);
} }
while (groupParts.includes('{')) { while (groupParts.includes('{')) {
loggingEnabled && log(LT.LOG, `Handling Groups | Current cmd: ${JSON.stringify(groupParts)}`); loggingEnabled && log(LT.LOG, `Handling Groups | Current cmd: ${JSON.stringify(groupParts)}`);
const openIdx = groupParts.indexOf('}');
const closeIdx = getMatchingGroupIdx; const openIdx = groupParts.indexOf('{');
const temp = cmd.join('').replaceAll('{', '').replaceAll('}', '').replaceAll(',', ''); const closeIdx = getMatchingGroupIdx(groupParts, openIdx);
cmd = temp.split(cmdSplitRegex);
const currentGrp = groupParts.slice(openIdx + 1, closeIdx);
const [tempData, tempCounts, tempDists] = handleGroup(currentGrp, '', modifiers, previousResults);
const data = tempData[0];
log(LT.LOG, `Solved Group is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)} ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Merge result back into groupParts
groupParts.splice(openIdx, closeIdx - openIdx + 1, `${openInternalGrp}${groupResults.length}${closeInternalGrp}`);
groupResults.push(data);
} }
const cmdForMath = groupParts.join(''); const cmdForMath = groupParts.join('');
loggingEnabled && log(LT.LOG, `Tokenizing math ${cmdForMath}`); loggingEnabled && log(LT.LOG, `Tokenizing math ${cmdForMath}`);
// Solve the math and rolls for this cmd // Solve the math and rolls for this cmd
const [tempData, tempCounts, tempDists] = tokenizeMath(cmdForMath, modifiers, previousResults); const [tempData, tempCounts, tempDists] = tokenizeMath(cmdForMath, modifiers, previousResults, groupResults);
const data = tempData[0]; const data = tempData[0];
loggingEnabled && loggingEnabled &&
log(LT.LOG, `Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)} ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`); log(
LT.LOG,
`Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)} ${JSON.stringify(groupResults)} ${
JSON.stringify(
tempCounts,
)
} ${JSON.stringify(tempDists)}`,
);
// Merge counts // Merge counts
countDetails.push(...tempCounts); countDetails.push(...tempCounts);
rollDists.push(...tempDists); rollDists.push(...tempDists);
// Handle merging group data into initConfig first since a group could "smuggle" a returnData in it
const tempInitConf = data.initConfig.split(internalGrpWrapRegex).filter((x) => x);
loggingEnabled && log(LT.LOG, `Split solved math into tempInitConf ${JSON.stringify(tempInitConf)}`);
while (tempInitConf.includes(openInternalGrp)) {
loopCountCheck();
const openIdx = tempInitConf.indexOf(openInternalGrp);
const closeIdx = getMatchingInternalGrpIdx(tempInitConf, openIdx);
// Take first groupResult out of array
const dataToMerge = groupResults.shift();
// Replace the found pair with the nested tempInitConfig and result
tempInitConf.splice(openIdx, closeIdx - openIdx + 1, `${dataToMerge?.initConfig}`);
loggingEnabled && log(LT.LOG, `Current tempInitConf state ${JSON.stringify(tempInitConf)}`);
}
// Handle merging returnData into tempData // Handle merging returnData into tempData
const initConf = data.initConfig.split(internalWrapRegex).filter((x) => x); const initConf = tempInitConf
loggingEnabled && log(LT.LOG, `Split solved math initConfig ${JSON.stringify(initConf)}`); .join('')
.split(internalWrapRegex)
.filter((x) => x);
loggingEnabled && log(LT.LOG, `Split tempInitConfig into initConf ${JSON.stringify(initConf)}`);
while (initConf.includes(openInternal)) { while (initConf.includes(openInternal)) {
loopCountCheck(); loopCountCheck();

View File

@ -0,0 +1,112 @@
import { log, LogTypes as LT } from '@Log4Deno';
import { ReturnData } from 'artigen/artigen.d.ts';
import { CountDetails, RollDistributionMap, RollModifiers } from 'artigen/dice/dice.d.ts';
import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { tokenizeMath } from 'artigen/math/mathTokenizer.ts';
import { closeInternalGrp, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { getMatchingGroupIdx } from 'artigen/utils/parenBalance.ts';
export const handleGroup = (
groupParts: string[],
groupModifiers: string,
modifiers: RollModifiers,
previousResults: number[],
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
const returnData: ReturnData[] = [];
const countDetails: CountDetails[] = [];
const rollDists: RollDistributionMap[] = [];
// Nested groups still exist, unwrap them
while (groupParts.includes('{')) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Handling Nested Groups | Current cmd: ${JSON.stringify(groupParts)}`);
const openIdx = groupParts.indexOf('}');
const closeIdx = getMatchingGroupIdx(groupParts, openIdx);
const currentGrp = groupParts.slice(openIdx + 1, closeIdx);
const [tempData, tempCounts, tempDists] = handleGroup(currentGrp, '', modifiers, previousResults);
const data = tempData[0];
loggingEnabled && log(LT.LOG, `Solved Nested Group is back ${JSON.stringify(data)} | ${JSON.stringify(tempCounts)} ${JSON.stringify(tempDists)}`);
countDetails.push(...tempCounts);
rollDists.push(...tempDists);
// Merge result back into groupParts
groupParts.splice(openIdx, closeIdx - openIdx + 1, `${openInternalGrp}${data.rollTotal}${closeInternalGrp}`);
}
// Handle the items in the groups
const commaParts = groupParts
.join('')
.split(',')
.filter((x) => x);
if (commaParts.length > 1) {
loggingEnabled && log(LT.LOG, `In multi-mode ${JSON.stringify(commaParts)} ${groupModifiers}`);
// Handle "normal operation" of group
const groupResults: ReturnData[] = [];
for (const part of commaParts) {
loopCountCheck();
loggingEnabled && log(LT.LOG, `Solving commaPart: ${part}`);
const [tempData, tempCounts, tempDists] = tokenizeMath(part, modifiers, previousResults, []);
const data = tempData[0];
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);
groupResults.push(data);
}
if (groupModifiers) {
// Handle the provided modifiers
} else {
// Sum mode
const data = groupResults.reduce((prev, cur) => ({
rollTotal: prev.rollTotal + cur.rollTotal,
rollPreFormat: '',
rollPostFormat: '',
rollDetails: prev.rollDetails + ' + ' + cur.rollDetails,
containsCrit: prev.containsCrit || cur.containsCrit,
containsFail: prev.containsFail || cur.containsFail,
initConfig: prev.initConfig + ', ' + cur.initConfig,
isComplex: prev.isComplex || cur.isComplex,
}));
data.initConfig = `{${data.initConfig}}`;
data.rollDetails = `{${data.rollDetails}}`;
returnData.push(data);
}
} else {
loggingEnabled && log(LT.LOG, `In single-mode ${JSON.stringify(commaParts)} ${groupModifiers}`);
if (groupModifiers) {
// 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.
} 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, []);
const data = tempData[0];
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}}`;
returnData.push(data);
}
}
return [returnData, countDetails, rollDists];
};

View File

@ -11,7 +11,7 @@ import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { mathSolver } from 'artigen/math/mathSolver.ts'; import { mathSolver } from 'artigen/math/mathSolver.ts';
import { cmdSplitRegex, internalWrapRegex } from 'artigen/utils/escape.ts'; import { closeInternalGrp, cmdSplitRegex, internalWrapRegex, openInternalGrp } from 'artigen/utils/escape.ts';
import { legalMathOperators } from 'artigen/utils/legalMath.ts'; import { legalMathOperators } from 'artigen/utils/legalMath.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { assertParenBalance } from 'artigen/utils/parenBalance.ts'; import { assertParenBalance } from 'artigen/utils/parenBalance.ts';
@ -20,7 +20,12 @@ import { assertParenBalance } from 'artigen/utils/parenBalance.ts';
const minusOps = ['(', '^', '**', '*', '/', '%', '+', '-']; const minusOps = ['(', '^', '**', '*', '/', '%', '+', '-'];
const allOps = [...minusOps, ')']; const allOps = [...minusOps, ')'];
export const tokenizeMath = (cmd: string, modifiers: RollModifiers, previousResults: number[]): [ReturnData[], CountDetails[], RollDistributionMap[]] => { export const tokenizeMath = (
cmd: string,
modifiers: RollModifiers,
previousResults: number[],
groupResults: ReturnData[],
): [ReturnData[], CountDetails[], RollDistributionMap[]] => {
const countDetails: CountDetails[] = []; const countDetails: CountDetails[] = [];
const rollDists: RollDistributionMap[] = []; const rollDists: RollDistributionMap[] = [];
@ -54,6 +59,18 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers, previousResu
} else if (mathConf[i] == parseFloat(curMathConfStr)) { } else if (mathConf[i] == parseFloat(curMathConfStr)) {
// If its a number, parse the number out // If its a number, parse the number out
mathConf[i] = parseFloat(curMathConfStr); mathConf[i] = parseFloat(curMathConfStr);
} else if (curMathConfStr.startsWith(openInternalGrp)) {
const groupIdx = parseInt(curMathConfStr.substring(1, curMathConfStr.indexOf(closeInternalGrp)));
if (groupIdx >= groupResults.length) {
throw new Error('InternalGroupMachineBroke');
}
mathConf[i] = {
total: groupResults[groupIdx].rollTotal,
details: groupResults[groupIdx].rollDetails,
containsCrit: groupResults[groupIdx].containsCrit,
containsFail: groupResults[groupIdx].containsFail,
isComplex: groupResults[groupIdx].isComplex,
};
} else if (curMathConfStr.toLowerCase() === 'e') { } else if (curMathConfStr.toLowerCase() === 'e') {
// If the operand is the constant e, create a SolvedStep for it // If the operand is the constant e, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {

View File

@ -30,3 +30,8 @@ export const cmdSplitRegex = new RegExp(`(${escapePrefixPostfix(config.prefix)})
export const openInternal = '\u2045'; export const openInternal = '\u2045';
export const closeInternal = '\u2046'; export const closeInternal = '\u2046';
export const internalWrapRegex = new RegExp(`([${openInternal}${closeInternal}])`, 'g'); export const internalWrapRegex = new RegExp(`([${openInternal}${closeInternal}])`, 'g');
// Internal Group is used for marking handled groups
export const openInternalGrp = '\u2e20';
export const closeInternalGrp = '\u2e21';
export const internalGrpWrapRegex = new RegExp(`([${openInternalGrp}${closeInternalGrp}])`, 'g');

View File

@ -6,7 +6,7 @@ import { loopCountCheck } from 'artigen/managers/loopManager.ts';
import { MathConf } from 'artigen/math/math.d.ts'; import { MathConf } from 'artigen/math/math.d.ts';
import { closeInternal, openInternal } from 'artigen/utils/escape.ts'; import { closeInternal, closeInternalGrp, openInternal, openInternalGrp } from 'artigen/utils/escape.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
const checkBalance = (conf: MathConf[], openStr: string, closeStr: string, errorType: string, getMatching: boolean, openIdx: number): number => { const checkBalance = (conf: MathConf[], openStr: string, closeStr: string, errorType: string, getMatching: boolean, openIdx: number): number => {
@ -60,5 +60,6 @@ export const assertPrePostBalance = (conf: MathConf[]) => checkBalance(conf, con
// getMatchingXIdx gets the matching X, also partially verifies the conf has balanced X // getMatchingXIdx gets the matching X, also partially verifies the conf has balanced X
export const getMatchingGroupIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '{', '}', 'Group', true, openIdx); export const getMatchingGroupIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '{', '}', 'Group', true, openIdx);
export const getMatchingInternalIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, openInternal, closeInternal, 'Internal', true, openIdx); export const getMatchingInternalIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, openInternal, closeInternal, 'Internal', true, openIdx);
export const getMatchingInternalGrpIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, openInternalGrp, closeInternalGrp, 'InternalGrp', true, openIdx);
export const getMatchingParenIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '(', ')', 'Paren', true, openIdx); export const getMatchingParenIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, '(', ')', 'Paren', true, openIdx);
export const getMatchingPostfixIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', true, openIdx); export const getMatchingPostfixIdx = (conf: MathConf[], openIdx: number): number => checkBalance(conf, config.prefix, config.postfix, 'PrefixPostfix', true, openIdx);