From e69806c44371918c82c4a3dd8043271d4cbbc60e Mon Sep 17 00:00:00 2001 From: Ean Milligan Date: Sun, 27 Apr 2025 04:34:01 -0400 Subject: [PATCH] Add additional safeties on rerolling, cleanse other numlists --- src/solver/parser.ts | 3 +++ src/solver/roller.ts | 36 +++++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/solver/parser.ts b/src/solver/parser.ts index 76be89f..1a56127 100644 --- a/src/solver/parser.ts +++ b/src/solver/parser.ts @@ -330,6 +330,9 @@ export const parseRoll = (fullCmd: string, modifiers: RollModifiers): SolvedRoll } errorMsg += ' cannot be zero'; break; + case 'NoRerollOnAllSides': + errorMsg = 'Error: Cannot reroll all sides of a die, must have at least one side that does not get rerolled'; + break; case 'CritScoreMinGtrMax': errorMsg = 'Formatting Error: CritScore maximum cannot be greater than minimum, check formatting and flip min/max'; break; diff --git a/src/solver/roller.ts b/src/solver/roller.ts index e8ed3e0..82e9d5f 100644 --- a/src/solver/roller.ts +++ b/src/solver/roller.ts @@ -221,7 +221,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea case 'r=': // Configure Reroll (this can happen multiple times) rollConf.reroll.on = true; - rollConf.reroll.nums.push(tNum); + !rollConf.reroll.nums.includes(tNum) && rollConf.reroll.nums.push(tNum); break; case 'ro>': rollConf.reroll.once = true; @@ -233,7 +233,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing r> ${i}`); - rollConf.reroll.nums.push(i); + !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); } break; case 'ro<': @@ -246,14 +246,14 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing r< ${i}`); - rollConf.reroll.nums.push(i); + !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); } break; case 'cs': case 'cs=': // Configure CritScore for one number (this can happen multiple times) rollConf.critScore.on = true; - rollConf.critScore.range.push(tNum); + !rollConf.critScore.range.includes(tNum) && rollConf.critScore.range.push(tNum); break; case 'cs>': // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why) @@ -262,7 +262,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing cs> ${i}`); - rollConf.critScore.range.push(i); + !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); } break; case 'cs<': @@ -272,14 +272,14 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing cs< ${i}`); - rollConf.critScore.range.push(i); + !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); } break; case 'cf': case 'cf=': // Configure CritFail for one number (this can happen multiple times) rollConf.critFail.on = true; - rollConf.critFail.range.push(tNum); + !rollConf.critFail.range.includes(tNum) && rollConf.critFail.range.push(tNum); break; case 'cf>': // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why) @@ -288,7 +288,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing cf> ${i}`); - rollConf.critFail.range.push(i); + !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); } break; case 'cf<': @@ -298,7 +298,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing cf< ${i}`); - rollConf.critFail.range.push(i); + !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); } break; case '!': @@ -309,7 +309,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea rollConf.exploding.on = true; if (afterNumIdx > 0) { // User gave a number to explode on, save it - rollConf.exploding.nums.push(tNum); + !rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); } else { // User did not give number, use cs afterNumIdx = 1; @@ -321,7 +321,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea case '!!=': // Configure Exploding (this can happen multiple times) rollConf.exploding.on = true; - rollConf.exploding.nums.push(tNum); + !rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); break; case '!>': case '!o>': @@ -333,7 +333,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing !> ${i}`); - rollConf.exploding.nums.push(i); + !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); } break; case '!<': @@ -346,7 +346,7 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea loopCountCheck(++loopCount); loggingEnabled && log(LT.LOG, `${loopCount} Handling ${rollType} ${rollStr} | Parsing !< ${i}`); - rollConf.exploding.nums.push(i); + !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); } break; default: @@ -381,6 +381,13 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea } } + // Filter rollConf num lists to only include valid numbers + const validNumFilter = (curNum: number) => curNum <= rollConf.dieSize && curNum > 0; + rollConf.reroll.nums = rollConf.reroll.nums.filter(validNumFilter); + rollConf.critScore.range = rollConf.critScore.range.filter(validNumFilter); + rollConf.critFail.range = rollConf.critFail.range.filter(validNumFilter); + rollConf.exploding.nums = rollConf.exploding.nums.filter(validNumFilter); + // Verify the parse, throwing errors for every invalid config if (rollConf.dieCount < 0) { throw new Error('NoZerosAllowed_base'); @@ -414,6 +421,9 @@ export const roll = (rollStr: string, maximizeRoll: boolean, nominalRoll: boolea if (rollConf.reroll.on && rollConf.reroll.nums.includes(0)) { throw new Error('NoZerosAllowed_reroll'); } + if (rollConf.reroll.on && rollConf.reroll.nums.length === rollConf.dieSize) { + throw new Error('NoRerollOnAllSides'); + } // Roll the roll const rollSet = [];