Basic support added for CWOD rolls

This commit is contained in:
Ean Milligan (Bastion) 2022-06-29 03:01:20 -04:00
parent 61607dc75d
commit 6ad0d2ca2b
6 changed files with 149 additions and 25 deletions

View File

@ -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;
};

View File

@ -7,7 +7,7 @@ import {
import config from '../../config.ts'; import config from '../../config.ts';
import { RollModifiers } from '../mod.d.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 { compareTotalRolls, escapeCharacters, loggingEnabled } from './rollUtils.ts';
import { formatRoll } from './rollFormatter.ts'; import { formatRoll } from './rollFormatter.ts';
import { fullSolver } from './solver.ts'; import { fullSolver } from './solver.ts';

View File

@ -6,7 +6,7 @@ import {
import { roll } from './roller.ts'; import { roll } from './roller.ts';
import { rollCounter } from './counter.ts'; import { rollCounter } from './counter.ts';
import { RollFormat } from './solver.d.ts'; import { RollFormat, RollType } from './solver.d.ts';
import { loggingEnabled } from './rollUtils.ts'; import { loggingEnabled } from './rollUtils.ts';
// formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep // formatRoll(rollConf, maximiseRoll, nominalRoll) returns one SolvedStep
@ -16,6 +16,7 @@ export const formatRoll = (rollConf: string, maximiseRoll: boolean, nominalRoll:
let tempDetails = '['; let tempDetails = '[';
let tempCrit = false; let tempCrit = false;
let tempFail = false; let tempFail = false;
let tempRollType: RollType = '';
// Generate the roll, passing flags thru // Generate the roll, passing flags thru
const tempRollSet = roll(rollConf, maximiseRoll, nominalRoll); 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 // Loop thru all parts of the roll to document everything that was done to create the total roll
tempRollSet.forEach((e) => { tempRollSet.forEach((e) => {
loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`); loggingEnabled && log(LT.LOG, `Formatting roll ${rollConf} | ${JSON.stringify(e)}`);
tempRollType = e.type;
let preFormat = ''; let preFormat = '';
let postFormat = ''; let postFormat = '';
if (!e.dropped && !e.rerolled) { if (!e.dropped && !e.rerolled) {
// If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail // If the roll was not dropped or rerolled, add it to the stepTotal and flag the critHit/critFail
switch(e.type) {
case 'ova':
case 'roll20':
tempTotal += e.roll; tempTotal += e.roll;
break;
case 'cwod':
tempTotal += e.critHit ? 1 : 0;
break;
}
if (e.critHit) { if (e.critHit) {
tempCrit = true; 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 ] // After the looping is done, remove the extra " + " from the details and cap it with the closing ]
tempDetails = tempDetails.substring(0, tempDetails.length - 3); 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 += ']'; tempDetails += ']';
return { return {

View File

@ -1,11 +1,12 @@
import config from '../../config.ts'; import config from '../../config.ts';
import { import {
log,
// Log4Deno deps // Log4Deno deps
log,
LT, LT,
} from '../../deps.ts'; } 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'; import { compareOrigidx, compareRolls, genRoll, loggingEnabled } from './rollUtils.ts';
// roll(rollStr, maximiseRoll, nominalRoll) returns RollSet // roll(rollStr, maximiseRoll, nominalRoll) returns RollSet
@ -24,7 +25,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
const dpts = rollStr.split('d'); const dpts = rollStr.split('d');
// Initialize the configuration to store the parsed data // Initialize the configuration to store the parsed data
const rollConf = { const rollConf: RollConf = {
dieCount: 0, dieCount: 0,
dieSize: 0, dieSize: 0,
drop: { 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 // 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); rollConf.dieCount = parseInt(tempDC);
// Finds the end of the die size/beginnning of the additional options // 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'); throw new Error('YouNeedAD');
} }
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Count: ${rollConf.dieCount}`); if (rawDC.endsWith('cwo')) {
loggingEnabled && log(LT.LOG, `Handling roll ${rollStr} | Parsed Die Size: ${rollConf.dieSize}`); 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 // Finish parsing the roll
if (remains.length > 0) { if (remains.length > 0) {
@ -100,7 +106,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Loop until all remaining args are parsed // Loop until all remaining args are parsed
while (remains.length > 0) { 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 // Find the next number in the remains to be able to cut out the rule name
let afterSepIdx = remains.search(/\d/); let afterSepIdx = remains.search(/\d/);
if (afterSepIdx < 0) { 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) // Configure reroll for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true; rollConf.reroll.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { 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); rollConf.reroll.nums.push(i);
} }
break; 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) // Configure reroll for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.reroll.on = true; rollConf.reroll.on = true;
for (let i = 1; i <= tNum; i++) { 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); rollConf.reroll.nums.push(i);
} }
break; 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) // Configure CritScore for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { 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); rollConf.critScore.range.push(i);
} }
break; 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) // Configure CritScore for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critScore.on = true; rollConf.critScore.on = true;
for (let i = 0; i <= tNum; i++) { 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); rollConf.critScore.range.push(i);
} }
break; 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) // Configure CritFail for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { 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); rollConf.critFail.range.push(i);
} }
break; 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) // Configure CritFail for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.critFail.on = true; rollConf.critFail.on = true;
for (let i = 0; i <= tNum; i++) { 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); rollConf.critFail.range.push(i);
} }
break; 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) // Configure Exploding for all numbers greater than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true; rollConf.exploding.on = true;
for (let i = tNum; i <= rollConf.dieSize; i++) { 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); rollConf.exploding.nums.push(i);
} }
break; 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) // Configure Exploding for all numbers less than or equal to tNum (this could happen multiple times, but why)
rollConf.exploding.on = true; rollConf.exploding.on = true;
for (let i = 1; i <= tNum; i++) { 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); rollConf.exploding.nums.push(i);
} }
break; 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 // Since only one drop or keep option can be active, count how many are active to throw the right error
let dkdkCnt = 0; let dkdkCnt = 0;
[rollConf.drop.on, rollConf.keep.on, rollConf.dropHigh.on, rollConf.keepLow.on].forEach((e) => { [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) { if (e) {
dkdkCnt++; dkdkCnt++;
} }
@ -330,7 +336,8 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
*/ */
// Initialize a templet rollSet to copy multiple times // Initialize a templet rollSet to copy multiple times
const templateRoll = { const templateRoll: RollSet = {
type: 'roll20',
origidx: 0, origidx: 0,
roll: 0, roll: 0,
dropped: false, dropped: false,
@ -345,7 +352,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
// Initial rolling, not handling reroll or exploding here // Initial rolling, not handling reroll or exploding here
for (let i = 0; i < rollConf.dieCount; i++) { 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 gets too high, stop trying to calculate infinity
if (loopCount > config.limits.maxLoops) { if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded'); 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 needed, handle rerolling and exploding dice now
if (rollConf.reroll.on || rollConf.exploding.on) { if (rollConf.reroll.on || rollConf.exploding.on) {
for (let i = 0; i < rollSet.length; i++) { 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 gets too high, stop trying to calculate infinity
if (loopCount > config.limits.maxLoops) { if (loopCount > config.limits.maxLoops) {
throw new Error('MaxLoopsExceeded'); throw new Error('MaxLoopsExceeded');
@ -456,7 +463,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
throw new Error('MaxLoopsExceeded'); 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; rollSet[j].origidx = j;
if (rollSet[j].rerolled) { if (rollSet[j].rerolled) {
@ -510,7 +517,7 @@ export const roll = (rollStr: string, maximiseRoll: boolean, nominalRoll: boolea
throw new Error('MaxLoopsExceeded'); 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 // Skip all rolls that were rerolled
if (!rollSet[i].rerolled) { if (!rollSet[i].rerolled) {
rollSet[i].dropped = true; rollSet[i].dropped = true;

View File

@ -1,7 +1,10 @@
// solver.ts custom types // solver.ts custom types
export type RollType = '' | 'roll20' | 'cwod' | 'ova';
// RollSet is used to preserve all information about a calculated roll // RollSet is used to preserve all information about a calculated roll
export type RollSet = { export type RollSet = {
type: RollType;
origidx: number; origidx: number;
roll: number; roll: number;
dropped: boolean; dropped: boolean;
@ -55,3 +58,43 @@ export type SolvedRoll = {
line3: string; line3: string;
counts: CountDetails; 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[];
};
}

View File

@ -10,7 +10,7 @@ import {
LT, LT,
} from '../../deps.ts'; } from '../../deps.ts';
import { SolvedStep } from './solver.d.ts'; import { SolvedStep, RollType } from './solver.d.ts';
import { loggingEnabled } from './rollUtils.ts'; import { loggingEnabled } from './rollUtils.ts';
// fullSolver(conf, wrapDetails) returns one condensed SolvedStep // fullSolver(conf, wrapDetails) returns one condensed SolvedStep
@ -19,6 +19,7 @@ export const fullSolver = (conf: (string | number | SolvedStep)[], wrapDetails:
// Initialize PEMDAS // Initialize PEMDAS
const signs = ['^', '*', '/', '%', '+', '-']; const signs = ['^', '*', '/', '%', '+', '-'];
const stepSolve = { const stepSolve = {
rollType: <RollType> '',
total: 0, total: 0,
details: '', details: '',
containsCrit: false, containsCrit: false,