Properly handle mt! or !m (or other future no number mashups)

This commit is contained in:
Ean Milligan 2025-06-28 05:16:48 -04:00
parent 0e009441ca
commit 5f58f9cae8
2 changed files with 121 additions and 61 deletions

View File

@ -0,0 +1,52 @@
export const DiceOptions = Object.freeze({
Drop: 'd',
DropLow: 'dl',
DropHigh: 'dh',
Keep: 'k',
KeepLow: 'kl',
KeepHigh: 'kh',
Reroll: 'r',
RerollLt: 'r<',
RerollGtr: 'r>',
RerollEqu: 'r=',
RerollOnce: 'ro',
RerollOnceLt: 'ro<',
RerollOnceGtr: 'ro>',
RerollOnceEqu: 'ro=',
CritSuccess: 'cs',
CritSuccessLt: 'cs<',
CritSuccessGtr: 'cs>',
CritSuccessEqu: 'cs=',
CritFail: 'cf',
CritFailLt: 'cf<',
CritFailGtr: 'cf>',
CritFailEqu: 'cf=',
Exploding: '!',
ExplodingLt: '!<',
ExplodingGtr: '!>',
ExplodingEqu: '!=',
ExplodeOnce: '!o',
ExplodeOnceLt: '!o<',
ExplodeOnceGtr: '!o>',
ExplodeOnceEqu: '!o=',
PenetratingExplosion: '!p',
PenetratingExplosionLt: '!p<',
PenetratingExplosionGtr: '!p>',
PenetratingExplosionEqu: '!p=',
CompoundingExplosion: '!!',
CompoundingExplosionLt: '!!<',
CompoundingExplosionGtr: '!!>',
CompoundingExplosionEqu: '!!=',
Matching: 'm',
MatchingTotal: 'mt',
});
// Should be ordered such that 'mt' will be encountered before 'm'
export const NumberlessDiceOptions = [
DiceOptions.MatchingTotal,
DiceOptions.Matching,
DiceOptions.CompoundingExplosion,
DiceOptions.PenetratingExplosion,
DiceOptions.ExplodeOnce,
DiceOptions.Exploding,
];

View File

@ -5,6 +5,7 @@ import { RollConf } from 'artigen/dice/dice.d.ts';
import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts'; import { getLoopCount, loopCountCheck } from 'artigen/managers/loopManager.ts';
import { loggingEnabled } from 'artigen/utils/logFlag.ts'; import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { DiceOptions, NumberlessDiceOptions } from 'artigen/dice/diceOptions.ts';
const throwDoubleSepError = (sep: string): void => { const throwDoubleSepError = (sep: string): void => {
throw new Error(`DoubleSeparator_${sep}`); throw new Error(`DoubleSeparator_${sep}`);
@ -198,11 +199,24 @@ export const getRollConf = (rollStr: string): RollConf => {
if (afterSepIdx < 0) { if (afterSepIdx < 0) {
afterSepIdx = remains.length; afterSepIdx = remains.length;
} }
// Determine if afterSepIdx needs to be moved up (cases like mt! or !mt)
const tempSep = remains.slice(0, afterSepIdx);
let noNumberAfter = false;
NumberlessDiceOptions.some((opt) => {
if (tempSep.startsWith(opt) && tempSep !== opt) {
afterSepIdx = opt.length;
noNumberAfter = true;
return true;
}
return tempSep === opt;
});
// Save the rule name to tSep and remove it from remains // Save the rule name to tSep and remove it from remains
const tSep = remains.slice(0, afterSepIdx); const tSep = remains.slice(0, afterSepIdx);
remains = remains.slice(afterSepIdx); remains = remains.slice(afterSepIdx);
// Find the next non-number in the remains to be able to cut out the count/num // Find the next non-number in the remains to be able to cut out the count/num
let afterNumIdx = remains.search(/\D/); let afterNumIdx = noNumberAfter ? 0 : remains.search(/\D/);
if (afterNumIdx < 0) { if (afterNumIdx < 0) {
afterNumIdx = remains.length; afterNumIdx = remains.length;
} }
@ -211,8 +225,8 @@ export const getRollConf = (rollStr: string): RollConf => {
// Switch on rule name // Switch on rule name
switch (tSep) { switch (tSep) {
case 'dl': case DiceOptions.Drop:
case 'd': case DiceOptions.DropLow:
if (rollConf.drop.on) { if (rollConf.drop.on) {
// Ensure we do not override existing settings // Ensure we do not override existing settings
throwDoubleSepError(tSep); throwDoubleSepError(tSep);
@ -221,8 +235,8 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.drop.on = true; rollConf.drop.on = true;
rollConf.drop.count = tNum; rollConf.drop.count = tNum;
break; break;
case 'kh': case DiceOptions.Keep:
case 'k': case DiceOptions.KeepHigh:
if (rollConf.keep.on) { if (rollConf.keep.on) {
// Ensure we do not override existing settings // Ensure we do not override existing settings
throwDoubleSepError(tSep); throwDoubleSepError(tSep);
@ -231,7 +245,7 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.keep.on = true; rollConf.keep.on = true;
rollConf.keep.count = tNum; rollConf.keep.count = tNum;
break; break;
case 'dh': case DiceOptions.DropHigh:
if (rollConf.dropHigh.on) { if (rollConf.dropHigh.on) {
// Ensure we do not override existing settings // Ensure we do not override existing settings
throwDoubleSepError(tSep); throwDoubleSepError(tSep);
@ -240,7 +254,7 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.dropHigh.on = true; rollConf.dropHigh.on = true;
rollConf.dropHigh.count = tNum; rollConf.dropHigh.count = tNum;
break; break;
case 'kl': case DiceOptions.KeepLow:
if (rollConf.keepLow.on) { if (rollConf.keepLow.on) {
// Ensure we do not override existing settings // Ensure we do not override existing settings
throwDoubleSepError(tSep); throwDoubleSepError(tSep);
@ -249,20 +263,20 @@ export const getRollConf = (rollStr: string): RollConf => {
rollConf.keepLow.on = true; rollConf.keepLow.on = true;
rollConf.keepLow.count = tNum; rollConf.keepLow.count = tNum;
break; break;
case 'ro': case DiceOptions.RerollOnce:
case 'ro=': case DiceOptions.RerollOnceEqu:
rollConf.reroll.once = true; rollConf.reroll.once = true;
// falls through as ro/ro= functions the same as r/r= in this context // falls through as ro/ro= functions the same as r/r= in this context
case 'r': case DiceOptions.Reroll:
case 'r=': case DiceOptions.RerollEqu:
// Configure Reroll (this can happen multiple times) // Configure Reroll (this can happen multiple times)
rollConf.reroll.on = true; rollConf.reroll.on = true;
!rollConf.reroll.nums.includes(tNum) && rollConf.reroll.nums.push(tNum); !rollConf.reroll.nums.includes(tNum) && rollConf.reroll.nums.push(tNum);
break; break;
case 'ro>': case DiceOptions.RerollOnceGtr:
rollConf.reroll.once = true; rollConf.reroll.once = true;
// falls through as ro> functions the same as r> in this context // falls through as ro> functions the same as r> in this context
case 'r>': case DiceOptions.RerollGtr:
// 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++) {
@ -272,10 +286,10 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i);
} }
break; break;
case 'ro<': case DiceOptions.RerollOnceLt:
rollConf.reroll.once = true; rollConf.reroll.once = true;
// falls through as ro< functions the same as r< in this context // falls through as ro< functions the same as r< in this context
case 'r<': case DiceOptions.RerollLt:
// 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++) {
@ -285,13 +299,13 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i); !rollConf.reroll.nums.includes(i) && rollConf.reroll.nums.push(i);
} }
break; break;
case 'cs': case DiceOptions.CritSuccess:
case 'cs=': case DiceOptions.CritSuccessEqu:
// Configure CritScore for one number (this can happen multiple times) // Configure CritScore for one number (this can happen multiple times)
rollConf.critScore.on = true; rollConf.critScore.on = true;
!rollConf.critScore.range.includes(tNum) && rollConf.critScore.range.push(tNum); !rollConf.critScore.range.includes(tNum) && rollConf.critScore.range.push(tNum);
break; break;
case 'cs>': case DiceOptions.CritSuccessGtr:
// 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++) {
@ -301,7 +315,7 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i);
} }
break; break;
case 'cs<': case DiceOptions.CritSuccessLt:
// 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++) {
@ -311,13 +325,13 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i); !rollConf.critScore.range.includes(i) && rollConf.critScore.range.push(i);
} }
break; break;
case 'cf': case DiceOptions.CritFail:
case 'cf=': case DiceOptions.CritFailEqu:
// Configure CritFail for one number (this can happen multiple times) // Configure CritFail for one number (this can happen multiple times)
rollConf.critFail.on = true; rollConf.critFail.on = true;
!rollConf.critFail.range.includes(tNum) && rollConf.critFail.range.push(tNum); !rollConf.critFail.range.includes(tNum) && rollConf.critFail.range.push(tNum);
break; break;
case 'cf>': case DiceOptions.CritFailGtr:
// 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++) {
@ -327,7 +341,7 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i);
} }
break; break;
case 'cf<': case DiceOptions.CritFailLt:
// 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++) {
@ -337,32 +351,29 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i); !rollConf.critFail.range.includes(i) && rollConf.critFail.range.push(i);
} }
break; break;
case '!': case DiceOptions.Exploding:
case '!o': case DiceOptions.ExplodeOnce:
case '!p': case DiceOptions.PenetratingExplosion:
case '!!': case DiceOptions.CompoundingExplosion:
// Configure Exploding // Configure Exploding
rollConf.exploding.on = true; rollConf.exploding.on = true;
if (afterNumIdx > 0) { if (afterNumIdx > 0) {
// User gave a number to explode on, save it // User gave a number to explode on, save it
!rollConf.exploding.nums.includes(tNum) && 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;
} }
break; break;
case '!=': case DiceOptions.ExplodingEqu:
case '!o=': case DiceOptions.ExplodeOnceEqu:
case '!p=': case DiceOptions.PenetratingExplosionEqu:
case '!!=': case DiceOptions.CompoundingExplosionEqu:
// Configure Exploding (this can happen multiple times) // Configure Exploding (this can happen multiple times)
rollConf.exploding.on = true; rollConf.exploding.on = true;
!rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum); !rollConf.exploding.nums.includes(tNum) && rollConf.exploding.nums.push(tNum);
break; break;
case '!>': case DiceOptions.ExplodingGtr:
case '!o>': case DiceOptions.ExplodeOnceGtr:
case '!p>': case DiceOptions.PenetratingExplosionGtr:
case '!!>': case DiceOptions.CompoundingExplosionGtr:
// 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++) {
@ -372,10 +383,10 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i);
} }
break; break;
case '!<': case DiceOptions.ExplodingLt:
case '!o<': case DiceOptions.ExplodeOnceLt:
case '!p<': case DiceOptions.PenetratingExplosionLt:
case '!!<': case DiceOptions.CompoundingExplosionLt:
// 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++) {
@ -385,15 +396,12 @@ export const getRollConf = (rollStr: string): RollConf => {
!rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i); !rollConf.exploding.nums.includes(i) && rollConf.exploding.nums.push(i);
} }
break; break;
case 'm': case DiceOptions.Matching:
case 'mt': case DiceOptions.MatchingTotal:
rollConf.match.on = true; rollConf.match.on = true;
if (afterNumIdx > 0) { if (afterNumIdx > 0) {
// User gave a number to explode on, save it // User gave a number to work with, save it
rollConf.match.minCount = tNum; rollConf.match.minCount = tNum;
} else {
// User did not give number, use cs
afterNumIdx = 1;
} }
break; break;
default: default:
@ -403,25 +411,25 @@ export const getRollConf = (rollStr: string): RollConf => {
// Followup switch to avoid weird duplicated code // Followup switch to avoid weird duplicated code
switch (tSep) { switch (tSep) {
case '!o': case DiceOptions.ExplodeOnce:
case '!o=': case DiceOptions.ExplodeOnceLt:
case '!o>': case DiceOptions.ExplodeOnceGtr:
case '!o<': case DiceOptions.ExplodeOnceEqu:
rollConf.exploding.once = true; rollConf.exploding.once = true;
break; break;
case '!p': case DiceOptions.PenetratingExplosion:
case '!p=': case DiceOptions.PenetratingExplosionLt:
case '!p>': case DiceOptions.PenetratingExplosionGtr:
case '!p<': case DiceOptions.PenetratingExplosionEqu:
rollConf.exploding.penetrating = true; rollConf.exploding.penetrating = true;
break; break;
case '!!': case DiceOptions.CompoundingExplosion:
case '!!=': case DiceOptions.CompoundingExplosionLt:
case '!!>': case DiceOptions.CompoundingExplosionGtr:
case '!!<': case DiceOptions.CompoundingExplosionEqu:
rollConf.exploding.compounding = true; rollConf.exploding.compounding = true;
break; break;
case 'mt': case DiceOptions.MatchingTotal:
rollConf.match.returnTotal = true; rollConf.match.returnTotal = true;
break; break;
} }