diff --git a/src/solver/customRollers/cwod.ts b/src/solver/customRollers/cwod.ts new file mode 100644 index 0000000..054e6fe --- /dev/null +++ b/src/solver/customRollers/cwod.ts @@ -0,0 +1,60 @@ +import config from '../../../config.ts'; +import { + // Log4Deno deps + log, + LT, +} from '../../../deps.ts'; +import { RollSet } from '../solver.d.ts'; +import { genRoll, loggingEnabled } from '../rollUtils.ts'; + +export const rollCWOD = (rollStr: string): RollSet[] => { + // Parse the roll + const cwodParts = rollStr.split('cwod'); + const cwodConf = { + dieCount: parseInt(cwodParts[0] || '1'), + difficulty: parseInt(cwodParts[1] || '10'), + }; + + // Begin counting the number of loops to prevent from getting into an infinite loop + let loopCount = 0; + + // Roll the roll + const rollSet = []; + + const templateRoll: RollSet = { + type: 'cwod', + origidx: 0, + roll: 0, + dropped: false, + rerolled: false, + exploding: false, + critHit: false, + critFail: false, + }; + + for (let i = 0; i < cwodConf.dieCount; i++) { + loggingEnabled && log(LT.LOG, `Handling cwod ${rollStr} | Initial rolling ${i} of ${JSON.stringify(cwodConf)}`); + // If loopCount gets too high, stop trying to calculate infinity + if (loopCount > config.limits.maxLoops) { + throw new Error('MaxLoopsExceeded'); + } + + // Copy the template to fill out for this iteration + const rolling = JSON.parse(JSON.stringify(templateRoll)); + + // Roll this die + rolling.roll = genRoll(10, false, false); + rolling.origidx = i; + + // Set success/fail flags + rolling.critHit = rolling.roll >= cwodConf.difficulty; + rolling.critFail = rolling.roll === 1; + + // Add in the new roll + rollSet.push(rolling); + + loopCount++; + } + + return rollSet; +}; \ No newline at end of file diff --git a/src/solver/parser.ts b/src/solver/parser.ts index 6043072..ab4a6a5 100644 --- a/src/solver/parser.ts +++ b/src/solver/parser.ts @@ -7,7 +7,7 @@ import { import config from '../../config.ts'; import { RollModifiers } from '../mod.d.ts'; -import { CountDetails, ReturnData, SolvedRoll, SolvedStep } from './solver.d.ts'; +import { CountDetails, ReturnData, SolvedRoll, SolvedStep, RollType } from './solver.d.ts'; import { compareTotalRolls, escapeCharacters, loggingEnabled } from './rollUtils.ts'; import { formatRoll } from './rollFormatter.ts'; import { fullSolver } from './solver.ts'; diff --git a/src/solver/rollFormatter.ts b/src/solver/rollFormatter.ts index 6289ad7..9336c24 100644 --- a/src/solver/rollFormatter.ts +++ b/src/solver/rollFormatter.ts @@ -6,7 +6,7 @@ import { import { roll } from './roller.ts'; import { rollCounter } from './counter.ts'; -import { RollFormat } from './solver.d.ts'; +import { RollFormat, RollType } from './solver.d.ts'; import { loggingEnabled } from './rollUtils.ts'; // formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep @@ -16,6 +16,7 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: let tempDetails = '['; let tempCrit = false; let tempFail = false; + let tempRollType: RollType = ''; // Generate the roll, passing flags thru const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll); @@ -23,12 +24,21 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: // Loop thru all parts of the roll to document everything that was done to create the total roll tempRollSet.forEach((e) => { loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); + tempRollType = e.type; let preFormat = ''; let postFormat = ''; if (!e.dropped && !e.rerolled) { // If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail - tempTotal += e.roll; + switch(e.type) { + case 'ova': + case 'roll20': + tempTotal += e.roll; + break; + case 'cwod': + tempTotal += e.critHit ? 1 : 0; + break; + } if (e.critHit) { tempCrit = true; } @@ -58,6 +68,9 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll: }); // After the looping is done, remove the extra " + " from the details and cap it with the closing ] tempDetails = tempDetails.substring(0, tempDetails.length - 3); + if (tempRollSet[0]?.type === 'cwod') { + tempDetails += `, ${tempRollSet.filter(e => e.critHit).length} Successes, ${tempRollSet.filter(e => e.critFail).length} Fails`; + } tempDetails += ']'; return { diff --git a/src/solver/roller.ts b/src/solver/roller.ts index e85139f..1141507 100644 --- a/src/solver/roller.ts +++ b/src/solver/roller.ts @@ -1,11 +1,12 @@ import config from '../../config.ts'; import { - log, // Log4Deno deps + log, LT, } from '../../deps.ts'; -import { RollSet } from './solver.d.ts'; +import { RollSet, RollConf } from './solver.d.ts'; +import { rollCWOD } from './customRollers/cwod.ts'; import { compareOrigidx, compareRolls, genRoll, loggingEnabled } from './rollUtils.ts'; // roll(rollStr, maximiseRoll, nominalRoll) returns RollSet @@ -24,7 +25,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea const dpts = rollStr.split('d'); // Initialize the configuration to store the parsed data - const rollConf = { + const rollConf: RollConf = { dieCount: 0, dieSize: 0, drop: { @@ -69,7 +70,8 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea } // Fill out the die count, first item will either be an int or empty string, short circuit execution will take care of replacing the empty string with a 1 - const tempDC = (dpts.shift() || '1').replace(/\D/g, ''); + const rawDC = dpts.shift() || '1'; + const tempDC = rawDC.replace(/\D/g, ''); rollConf.dieCount = parseInt(tempDC); // Finds the end of the die size/beginnning of the additional options @@ -88,8 +90,12 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea throw new Error('YouNeedAD'); } - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`); - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`); + if (rawDC.endsWith('cwo')) { + return rollCWOD(rollStr); + } + + loggingEnabled && log(LT.LOG, `Handling roll20 ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`); + loggingEnabled && log(LT.LOG, `Handling roll20 ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`); // Finish parsing the roll if (remains.length > 0) { @@ -100,7 +106,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Loop until all remaining args are parsed while (remains.length > 0) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing remains ${remains}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing remains ${remains}`); // Find the next number in the remains to be able to cut out the rule name let afterSepIdx = remains.search(/\d/); if (afterSepIdx < 0) { @@ -158,7 +164,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.reroll.on = true; for (let i = tNum; i <= rollConf.dieSize; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing r> ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing r> ${i}`); rollConf.reroll.nums.push(i); } break; @@ -169,7 +175,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.reroll.on = true; for (let i = 1; i <= tNum; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing r< ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing r< ${i}`); rollConf.reroll.nums.push(i); } break; @@ -183,7 +189,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.critScore.on = true; for (let i = tNum; i <= rollConf.dieSize; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cs> ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing cs> ${i}`); rollConf.critScore.range.push(i); } break; @@ -191,7 +197,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.critScore.on = true; for (let i = 0; i <= tNum; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cs< ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing cs< ${i}`); rollConf.critScore.range.push(i); } break; @@ -205,7 +211,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.critFail.on = true; for (let i = tNum; i <= rollConf.dieSize; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cf> ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing cf> ${i}`); rollConf.critFail.range.push(i); } break; @@ -213,7 +219,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.critFail.on = true; for (let i = 0; i <= tNum; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing cf< ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing cf< ${i}`); rollConf.critFail.range.push(i); } break; @@ -246,7 +252,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why) rollConf.exploding.on = true; for (let i = tNum; i <= rollConf.dieSize; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing !> ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing !> ${i}`); rollConf.exploding.nums.push(i); } break; @@ -257,7 +263,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why) rollConf.exploding.on = true; for (let i = 1; i <= tNum; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsing !< ${i}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Parsing !< ${i}`); rollConf.exploding.nums.push(i); } break; @@ -280,7 +286,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Since only one drop or keep option can be active, count how many are active to throw the right error let dkdkCnt = 0; [rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach((e) => { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Checking if drop/keep is on ${e}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Checking if drop/keep is on ${e}`); if (e) { dkdkCnt++; } @@ -330,7 +336,8 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea */ // Initialize a templet rollSet to copy multiple times - const templateRoll = { + const templateRoll: RollSet = { + type: 'roll20', origidx: 0, roll: 0, dropped: false, @@ -345,7 +352,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // Initial rolling, not handling reroll or exploding here for (let i = 0; i < rollConf.dieCount; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Initial rolling ${i} of ${JSON.stringify(rollConf)}`); // If loopCount gets too high, stop trying to calculate infinity if (loopCount > config.limits.maxLoops) { throw new Error('MaxLoopsExceeded'); @@ -379,7 +386,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea // If needed, handle rerolling and exploding dice now if (rollConf.reroll.on || rollConf.exploding.on) { for (let i = 0; i < rollSet.length; i++) { - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Handling rerolling and exploding ${JSON.stringify(rollSet[i])}`); // If loopCount gets too high, stop trying to calculate infinity if (loopCount > config.limits.maxLoops) { throw new Error('MaxLoopsExceeded'); @@ -456,7 +463,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea throw new Error('MaxLoopsExceeded'); } - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Setting originalIdx on ${JSON.stringify(rollSet[j])}`); rollSet[j].origidx = j; if (rollSet[j].rerolled) { @@ -510,7 +517,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea throw new Error('MaxLoopsExceeded'); } - loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`); + loggingEnabled && log(LT.LOG, `handling roll20 ${rollStr} | Dropping dice ${dropCount} ${JSON.stringify(rollSet[i])}`); // Skip all rolls that were rerolled if (!rollSet[i].rerolled) { rollSet[i].dropped = true; diff --git a/src/solver/solver.d.ts b/src/solver/solver.d.ts index 8862acf..9d7a3dc 100644 --- a/src/solver/solver.d.ts +++ b/src/solver/solver.d.ts @@ -1,7 +1,10 @@ // solver.ts custom types +export type RollType = '' | 'roll20' | 'cwod' | 'ova'; + // RollSet is used to preserve all information about a calculated roll export type RollSet = { + type: RollType; origidx: number; roll: number; dropped: boolean; @@ -55,3 +58,43 @@ export type SolvedRoll = { line3: string; counts: CountDetails; }; + +// RollConf is used by the roll20 setup +export type RollConf = { + dieCount: number; + dieSize: number; + drop: { + on: boolean; + count: number; + }; + keep: { + on: boolean; + count: number; + }; + dropHigh: { + on: boolean; + count: number; + }; + keepLow: { + on: boolean; + count: number; + }; + reroll: { + on: boolean; + once: boolean; + nums: number[]; + }; + critScore: { + on: boolean; + range: number[]; + }; + critFail: { + on: boolean; + range: number[]; + }; + exploding: { + on: boolean; + once: boolean; + nums: number[]; + }; +} diff --git a/src/solver/solver.ts b/src/solver/solver.ts index 4127fae..1b59bd2 100644 --- a/src/solver/solver.ts +++ b/src/solver/solver.ts @@ -10,7 +10,7 @@ import { LT, } from '../../deps.ts'; -import { SolvedStep } from './solver.d.ts'; +import { SolvedStep, RollType } from './solver.d.ts'; import { loggingEnabled } from './rollUtils.ts'; // fullSolver(conf, wrapDetails) returns one condensed SolvedStep @@ -19,6 +19,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails: // Initialize PEMDAS const signs = ['^', '*', '/', '%', '+', '-']; const stepSolve = { + rollType: '', total: 0, details: '', containsCrit: false,