From 7f8904e1b6f50b9cbcf1742c3bccc458dc485e62 Mon Sep 17 00:00:00 2001 From: Lee Langley-Rees Date: Thu, 22 Apr 2021 02:45:21 +0100 Subject: [PATCH] Allow specifying the maximum iterations on exploding dice --- src/modifiers/ComparisonModifier.js | 5 +- src/modifiers/ExplodeModifier.js | 17 ++- src/modifiers/KeepModifier.js | 1 + src/modifiers/Modifier.js | 51 +++++++- src/parser/grammars/grammar.pegjs | 6 +- tests/RollGroup.test.js | 8 +- tests/dice/FudgeDice.test.js | 16 +-- tests/dice/PercentileDice.test.js | 16 +-- tests/dice/StandardDice.test.js | 16 +-- tests/modifiers/ExplodeModifier.test.js | 128 +++++++++++++++----- tests/modifiers/Modifier.test.js | 110 ++++++++++++++++- tests/modifiers/ReRollModifier.test.js | 3 +- tests/parser/Parser.test.js | 152 ++++++++++++++++++++++++ 13 files changed, 453 insertions(+), 76 deletions(-) diff --git a/src/modifiers/ComparisonModifier.js b/src/modifiers/ComparisonModifier.js index 0ebaa89e..2905249f 100644 --- a/src/modifiers/ComparisonModifier.js +++ b/src/modifiers/ComparisonModifier.js @@ -26,11 +26,12 @@ class ComparisonModifier extends Modifier { * Create a `ComparisonModifier` instance. * * @param {ComparePoint} [comparePoint] The comparison object + * @param {number|null} [limit=null] The maximum iteration limit per roll. * * @throws {TypeError} `comparePoint` must be an instance of `ComparePoint` or `undefined` */ - constructor(comparePoint) { - super(); + constructor(comparePoint, limit = null) { + super(limit); if (comparePoint) { this.comparePoint = comparePoint; diff --git a/src/modifiers/ExplodeModifier.js b/src/modifiers/ExplodeModifier.js index 70382794..11abb321 100644 --- a/src/modifiers/ExplodeModifier.js +++ b/src/modifiers/ExplodeModifier.js @@ -19,11 +19,12 @@ class ExplodeModifier extends ComparisonModifier { * @param {ComparePoint} [comparePoint=null] The comparison object * @param {boolean} [compound=false] Whether to compound or not * @param {boolean} [penetrate=false] Whether to penetrate or not + * @param {number|null} [limit=null] The maximum iteration limit per roll. * * @throws {TypeError} comparePoint must be a `ComparePoint` object */ - constructor(comparePoint = null, compound = false, penetrate = false) { - super(comparePoint); + constructor(comparePoint = null, compound = false, penetrate = false, limit = null) { + super(comparePoint, limit); this[compoundSymbol] = !!compound; this[penetrateSymbol] = !!penetrate; @@ -58,7 +59,14 @@ class ExplodeModifier extends ComparisonModifier { * @returns {string} */ get notation() { - return `!${this.compound ? '!' : ''}${this.penetrate ? 'p' : ''}${super.notation}`; + let notation = `!${this.compound ? '!' : ''}${this.penetrate ? 'p' : ''}`; + + // if the max iterations has been changed, add it to the notation + if (this.maxIterations && (this.maxIterations !== this.constructor.defaultMaxIterations)) { + notation = `${notation}${this.maxIterations}`; + } + + return `${notation}${super.notation}`; } /** @@ -153,12 +161,13 @@ class ExplodeModifier extends ComparisonModifier { * }} */ toJSON() { - const { compound, penetrate } = this; + const { compound, maxIterations, penetrate } = this; return Object.assign( super.toJSON(), { compound, + maxIterations, penetrate, }, ); diff --git a/src/modifiers/KeepModifier.js b/src/modifiers/KeepModifier.js index 017bdb60..8c093a1d 100644 --- a/src/modifiers/KeepModifier.js +++ b/src/modifiers/KeepModifier.js @@ -96,6 +96,7 @@ class KeepModifier extends Modifier { if (value === Infinity) { throw new RangeError('qty must be a finite number'); } + if (!isNumeric(value) || (value < 1)) { throw new TypeError('qty must be a positive finite integer'); } diff --git a/src/modifiers/Modifier.js b/src/modifiers/Modifier.js index 77c7585b..82e3d3ad 100644 --- a/src/modifiers/Modifier.js +++ b/src/modifiers/Modifier.js @@ -1,3 +1,7 @@ +import { isNumeric } from '../utilities/math.js'; + +const maxIterationsSymbol = Symbol('max-iterations'); + /** * A `Modifier` is the base modifier class that all others extend from. * @@ -10,8 +14,13 @@ class Modifier { /** * Create a `Modifier` instance. + * + * @param {number|null} [limit=null] The maximum iteration limit per roll. */ - constructor() { + constructor(limit = null) { + // set the maximum iteration limit + this.maxIterations = limit; + // set the modifier's sort order this.order = 999; } @@ -38,16 +47,48 @@ class Modifier { } /* eslint-enable class-methods-use-this */ - /* eslint-disable class-methods-use-this */ /** - * The maximum number of iterations that the modifier can apply to a single die roll + * The maximum number of iterations that the modifier can apply to a single die roll. * - * @returns {number} `1000` + * @returns {number} */ get maxIterations() { + return this[maxIterationsSymbol]; + } + + /** + * Set the maximum number of iterations that the modifier can apply to a single die roll. + * + * @param {number} value + */ + set maxIterations(value) { + const absoluteMax = this.constructor.defaultMaxIterations; + + // if falsey, then set to the default max + if (!value && (value !== 0)) { + this[maxIterationsSymbol] = absoluteMax; + return; + } + + if (!isNumeric(value)) { + throw new TypeError('maxIterations must be a number'); + } + + if ((value === Infinity) || (value < 1)) { + throw new RangeError(`maxIterations must be a number between 1 and ${absoluteMax}`); + } + + this[maxIterationsSymbol] = Math.floor(Math.min(value, absoluteMax)); + } + + /** + * The default number of max iterations. + * + * @return {number} + */ + static get defaultMaxIterations() { return 1000; } - /* eslint-enable class-methods-use-this */ /* eslint-disable class-methods-use-this */ /** diff --git a/src/parser/grammars/grammar.pegjs b/src/parser/grammars/grammar.pegjs index 2a1549d3..70b339b7 100644 --- a/src/parser/grammars/grammar.pegjs +++ b/src/parser/grammars/grammar.pegjs @@ -62,8 +62,8 @@ Modifier // Explode, Penetrate, Compound modifier ExplodeModifier - = "!" compound:"!"? penetrate:"p"? comparePoint:ComparePoint? { - return new Modifiers.ExplodeModifier(comparePoint, !!compound, !!penetrate); + = "!" compound:"!"? penetrate:"p"? iterationLimit:IterationLimit? comparePoint:ComparePoint? { + return new Modifiers.ExplodeModifier(comparePoint, !!compound, !!penetrate, iterationLimit); } // Target / Success and Failure modifier @@ -134,6 +134,8 @@ ComparePoint CompareOperator = "!=" / "<=" / ">=" / "=" / ">" / "<" +IterationLimit = IntegerNumber + /** * Mathematical diff --git a/tests/RollGroup.test.js b/tests/RollGroup.test.js index 740c296a..0408849e 100644 --- a/tests/RollGroup.test.js +++ b/tests/RollGroup.test.js @@ -286,13 +286,13 @@ describe('RollGroup', () => { test('modifiers list always returns in correct order', () => { // create modifiers and define their order - const mod1 = new Modifier('m1'); + const mod1 = new Modifier(); mod1.order = 4; - const mod2 = new Modifier('m2'); + const mod2 = new Modifier(); mod2.order = 3; - const mod3 = new Modifier('m3'); + const mod3 = new Modifier(); mod3.order = 1; - const mod4 = new Modifier('m4'); + const mod4 = new Modifier(); mod4.order = 2; // create the dice instance diff --git a/tests/dice/FudgeDice.test.js b/tests/dice/FudgeDice.test.js index a71df0c6..8f189798 100644 --- a/tests/dice/FudgeDice.test.js +++ b/tests/dice/FudgeDice.test.js @@ -171,7 +171,7 @@ describe('FudgeDice', () => { describe('Modifiers', () => { test('setting modifiers in constructor calls setter', () => { const spy = jest.spyOn(FudgeDice.prototype, 'modifiers', 'set'); - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); new FudgeDice(null, 1, modifiers); @@ -182,7 +182,7 @@ describe('FudgeDice', () => { }); test('can set modifiers with Map', () => { - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); const die = new FudgeDice(null, 1); die.modifiers = modifiers; @@ -192,7 +192,7 @@ describe('FudgeDice', () => { }); test('can set modifiers with Object', () => { - const modifier = new Modifier('m'); + const modifier = new Modifier(); const die = new FudgeDice(null, 1); die.modifiers = { foo: modifier }; @@ -202,7 +202,7 @@ describe('FudgeDice', () => { }); test('can set modifiers with Array', () => { - const modifiers = [new Modifier('m')]; + const modifiers = [new Modifier()]; const die = new FudgeDice(null, 1); die.modifiers = modifiers; @@ -238,13 +238,13 @@ describe('FudgeDice', () => { test('modifiers list always returns in correct order', () => { // create modifiers and define their order - const mod1 = new Modifier('m1'); + const mod1 = new Modifier(); mod1.order = 4; - const mod2 = new Modifier('m2'); + const mod2 = new Modifier(); mod2.order = 3; - const mod3 = new Modifier('m3'); + const mod3 = new Modifier(); mod3.order = 1; - const mod4 = new Modifier('m4'); + const mod4 = new Modifier(); mod4.order = 2; // create the dice instance diff --git a/tests/dice/PercentileDice.test.js b/tests/dice/PercentileDice.test.js index 926696d2..68cd3e42 100644 --- a/tests/dice/PercentileDice.test.js +++ b/tests/dice/PercentileDice.test.js @@ -104,7 +104,7 @@ describe('PercentileDice', () => { describe('Modifiers', () => { test('setting modifiers in constructor calls setter', () => { const spy = jest.spyOn(PercentileDice.prototype, 'modifiers', 'set'); - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); new PercentileDice(1, modifiers); @@ -115,7 +115,7 @@ describe('PercentileDice', () => { }); test('can set modifiers with Map', () => { - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); const die = new PercentileDice(); die.modifiers = modifiers; @@ -125,7 +125,7 @@ describe('PercentileDice', () => { }); test('can set modifiers with Object', () => { - const modifier = new Modifier('m'); + const modifier = new Modifier(); const die = new PercentileDice(1); die.modifiers = { foo: modifier }; @@ -135,7 +135,7 @@ describe('PercentileDice', () => { }); test('can set modifiers with Array', () => { - const modifiers = [new Modifier('m')]; + const modifiers = [new Modifier()]; const die = new PercentileDice(1); die.modifiers = modifiers; @@ -171,13 +171,13 @@ describe('PercentileDice', () => { test('modifiers list always returns in correct order', () => { // create modifiers and define their order - const mod1 = new Modifier('m1'); + const mod1 = new Modifier(); mod1.order = 4; - const mod2 = new Modifier('m2'); + const mod2 = new Modifier(); mod2.order = 3; - const mod3 = new Modifier('m3'); + const mod3 = new Modifier(); mod3.order = 1; - const mod4 = new Modifier('m4'); + const mod4 = new Modifier(); mod4.order = 2; // create the dice instance diff --git a/tests/dice/StandardDice.test.js b/tests/dice/StandardDice.test.js index 07d85f51..e5818153 100644 --- a/tests/dice/StandardDice.test.js +++ b/tests/dice/StandardDice.test.js @@ -393,7 +393,7 @@ describe('StandardDice', () => { describe('Modifiers', () => { test('setting modifiers in constructor calls setter', () => { const spy = jest.spyOn(StandardDice.prototype, 'modifiers', 'set'); - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); new StandardDice(6, 8, modifiers); @@ -405,7 +405,7 @@ describe('StandardDice', () => { }); test('can set modifiers with Map', () => { - const modifiers = new Map(Object.entries({ foo: new Modifier('m') })); + const modifiers = new Map(Object.entries({ foo: new Modifier() })); const die = new StandardDice(6, 8); die.modifiers = modifiers; @@ -415,7 +415,7 @@ describe('StandardDice', () => { }); test('can set modifiers with Object', () => { - const modifier = new Modifier('m'); + const modifier = new Modifier(); const die = new StandardDice(6, 8); die.modifiers = { foo: modifier }; @@ -425,7 +425,7 @@ describe('StandardDice', () => { }); test('can set modifiers with Array', () => { - const modifiers = [new Modifier('m')]; + const modifiers = [new Modifier()]; const die = new StandardDice(6, 8); die.modifiers = modifiers; @@ -461,13 +461,13 @@ describe('StandardDice', () => { test('modifiers list always returns in correct order', () => { // create modifiers and define their order - const mod1 = new Modifier('m1'); + const mod1 = new Modifier(); mod1.order = 4; - const mod2 = new Modifier('m2'); + const mod2 = new Modifier(); mod2.order = 3; - const mod3 = new Modifier('m3'); + const mod3 = new Modifier(); mod3.order = 1; - const mod4 = new Modifier('m4'); + const mod4 = new Modifier(); mod4.order = 2; // create the dice instance diff --git a/tests/modifiers/ExplodeModifier.test.js b/tests/modifiers/ExplodeModifier.test.js index f307b80c..2116acc1 100644 --- a/tests/modifiers/ExplodeModifier.test.js +++ b/tests/modifiers/ExplodeModifier.test.js @@ -17,7 +17,7 @@ describe('ExplodeModifier', () => { compound: false, isComparePoint: expect.any(Function), penetrate: false, - maxIterations: 1000, + maxIterations: ExplodeModifier.defaultMaxIterations, name: 'explode', notation: '!', run: expect.any(Function), @@ -111,56 +111,117 @@ describe('ExplodeModifier', () => { }); }); + describe('Limit', () => { + test('gets set in constructor', () => { + const mod = new ExplodeModifier(null, null, null, 3); + + expect(mod.maxIterations).toBe(3); + expect(mod.notation).toBe('!3'); + }); + }); + describe('Notation', () => { - test('explode', () => { - let mod = new ExplodeModifier(new ComparePoint('>=', 45)); - expect(mod.notation).toEqual('!>=45'); + describe('explode', () => { + test('without limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 45)); + expect(mod.notation).toEqual('!>=45'); + + mod = new ExplodeModifier(new ComparePoint('<', 1)); + expect(mod.notation).toEqual('!<1'); + + mod = new ExplodeModifier(new ComparePoint('<=', 678997595)); + expect(mod.notation).toEqual('!<=678997595'); + }); + + test('with limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 45), false, false, 34); + expect(mod.notation).toEqual('!34>=45'); - mod = new ExplodeModifier(new ComparePoint('<', 1)); - expect(mod.notation).toEqual('!<1'); + mod = new ExplodeModifier(new ComparePoint('<', 1), false, false, 6); + expect(mod.notation).toEqual('!6<1'); - mod = new ExplodeModifier(new ComparePoint('<=', 678997595)); - expect(mod.notation).toEqual('!<=678997595'); + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), false, false, 798); + expect(mod.notation).toEqual('!798<=678997595'); + }); }); - test('compound', () => { - let mod = new ExplodeModifier(new ComparePoint('>=', 16), true); - expect(mod.notation).toEqual('!!>=16'); + describe('compound', () => { + test('without limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), true); + expect(mod.notation).toEqual('!!>=16'); + + mod = new ExplodeModifier(new ComparePoint('<', 79), true); + expect(mod.notation).toEqual('!!<79'); + + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true); + expect(mod.notation).toEqual('!!<=678997595'); + }); + + test('with limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), true, false, 67); + expect(mod.notation).toEqual('!!67>=16'); - mod = new ExplodeModifier(new ComparePoint('<', 79), true); - expect(mod.notation).toEqual('!!<79'); + mod = new ExplodeModifier(new ComparePoint('<', 79), true, false, 891); + expect(mod.notation).toEqual('!!891<79'); - mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true); - expect(mod.notation).toEqual('!!<=678997595'); + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true, false, 15); + expect(mod.notation).toEqual('!!15<=678997595'); + }); }); - test('penetrate', () => { - let mod = new ExplodeModifier(new ComparePoint('>=', 16), false, true); - expect(mod.notation).toEqual('!p>=16'); + describe('penetrate', () => { + test('without limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), false, true); + expect(mod.notation).toEqual('!p>=16'); + + mod = new ExplodeModifier(new ComparePoint('<', 79), false, true); + expect(mod.notation).toEqual('!p<79'); + + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), false, true); + expect(mod.notation).toEqual('!p<=678997595'); + }); - mod = new ExplodeModifier(new ComparePoint('<', 79), false, true); - expect(mod.notation).toEqual('!p<79'); + test('with limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), false, true, 82); + expect(mod.notation).toEqual('!p82>=16'); - mod = new ExplodeModifier(new ComparePoint('<=', 678997595), false, true); - expect(mod.notation).toEqual('!p<=678997595'); + mod = new ExplodeModifier(new ComparePoint('<', 79), false, true, 678); + expect(mod.notation).toEqual('!p678<79'); + + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), false, true, 1); + expect(mod.notation).toEqual('!p1<=678997595'); + }); }); - test('compound and penetrate', () => { - let mod = new ExplodeModifier(new ComparePoint('>=', 16), true, true); - expect(mod.notation).toEqual('!!p>=16'); + describe('compound and penetrate', () => { + test('without limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), true, true); + expect(mod.notation).toEqual('!!p>=16'); - mod = new ExplodeModifier(new ComparePoint('<', 79), true, true); - expect(mod.notation).toEqual('!!p<79'); + mod = new ExplodeModifier(new ComparePoint('<', 79), true, true); + expect(mod.notation).toEqual('!!p<79'); - mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true, true); - expect(mod.notation).toEqual('!!p<=678997595'); + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true, true); + expect(mod.notation).toEqual('!!p<=678997595'); + }); + + test('with limit', () => { + let mod = new ExplodeModifier(new ComparePoint('>=', 16), true, true, 7); + expect(mod.notation).toEqual('!!p7>=16'); + + mod = new ExplodeModifier(new ComparePoint('<', 79), true, true, 68); + expect(mod.notation).toEqual('!!p68<79'); + + mod = new ExplodeModifier(new ComparePoint('<=', 678997595), true, true, 309); + expect(mod.notation).toEqual('!!p309<=678997595'); + }); }); }); describe('Output', () => { test('JSON output is correct', () => { const cp = new ComparePoint('<=', 3); - const mod = new ExplodeModifier(cp, true, true); + const mod = new ExplodeModifier(cp, true, true, 56); // json encode, to get the encoded string, then decode so we can compare the object // this allows us to check that the output is correct, but ignoring the order of the @@ -168,9 +229,10 @@ describe('ExplodeModifier', () => { expect(JSON.parse(JSON.stringify(mod))).toEqual({ comparePoint: cp.toJSON(), compound: true, - penetrate: true, + maxIterations: 56, name: 'explode', - notation: '!!p<=3', + notation: '!!p56<=3', + penetrate: true, type: 'modifier', }); }); @@ -479,7 +541,7 @@ describe('ExplodeModifier', () => { describe('Iteration limit', () => { test('has iteration limit', () => { - expect(mod.maxIterations).toBe(1000); + expect(mod.maxIterations).toBe(ExplodeModifier.defaultMaxIterations); }); test('infinite explode stops at iteration limit `!>0`', () => { diff --git a/tests/modifiers/Modifier.test.js b/tests/modifiers/Modifier.test.js index c741bad3..50588abb 100644 --- a/tests/modifiers/Modifier.test.js +++ b/tests/modifiers/Modifier.test.js @@ -9,13 +9,121 @@ describe('Modifier', () => { expect(mod).toBeInstanceOf(Modifier); expect(mod).toEqual(expect.objectContaining({ - maxIterations: 1000, + maxIterations: Modifier.defaultMaxIterations, name: 'modifier', notation: '', run: expect.any(Function), toJSON: expect.any(Function), toString: expect.any(Function), })); + + expect(Modifier.defaultMaxIterations).toBe(1000); + }); + }); + + describe('Iterations', () => { + test('Gets set in constructor', () => { + const mod = new Modifier(4); + + expect(mod.maxIterations).toBe(4); + }); + + test('must be numeric', () => { + let mod = new Modifier(789); + expect(mod.maxIterations).toBe(789); + + mod = new Modifier(145); + expect(mod.maxIterations).toBe(145); + + mod = new Modifier('64'); + expect(mod.maxIterations).toBe(64); + + expect(() => { + new Modifier([678]); + }).toThrow(TypeError); + + expect(() => { + new Modifier('foo'); + }).toThrow(TypeError); + + expect(() => { + new Modifier(true); + }).toThrow(TypeError); + + expect(() => { + new Modifier({}); + }).toThrow(TypeError); + }); + + test('must be positive non-zero', () => { + expect(() => { + new Modifier(0); + }).toThrow(RangeError); + + expect(() => { + new Modifier(-1); + }).toThrow(RangeError); + + expect(() => { + new Modifier(-657); + }).toThrow(RangeError); + + expect(() => { + new Modifier(-41798643); + }).toThrow(RangeError); + }); + + test('float gets floored to integer', () => { + let mod = new Modifier(5.76); + expect(mod.maxIterations).toBe(5); + + mod = new Modifier(16.3783); + expect(mod.maxIterations).toBe(16); + + mod = new Modifier(179.1); + expect(mod.maxIterations).toBe(179); + }); + + test('greater than default max is set to default max', () => { + let mod = new Modifier(Modifier.defaultMaxIterations - 1); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations - 1); + + mod = new Modifier(Modifier.defaultMaxIterations); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + + mod = new Modifier(Modifier.defaultMaxIterations + 1); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + + mod = new Modifier(Modifier.defaultMaxIterations + 3894); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + + mod = new Modifier(Modifier.defaultMaxIterations + 7865915); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + }); + + test('falsey is set to default max', () => { + let mod = new Modifier(false); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + + mod = new Modifier(null); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + + mod = new Modifier(undefined); + expect(mod.maxIterations).toBe(Modifier.defaultMaxIterations); + }); + + test('can be changed', () => { + const mod = new Modifier(45); + expect(mod.maxIterations).toBe(45); + + mod.maxIterations = 178; + expect(mod.maxIterations).toBe(178); + + mod.maxIterations = 1; + expect(mod.maxIterations).toBe(1); + + mod.maxIterations = 893; + expect(mod.maxIterations).toBe(893); }); }); diff --git a/tests/modifiers/ReRollModifier.test.js b/tests/modifiers/ReRollModifier.test.js index 54ae7f83..58a1c1c9 100644 --- a/tests/modifiers/ReRollModifier.test.js +++ b/tests/modifiers/ReRollModifier.test.js @@ -15,6 +15,7 @@ describe('ReRollModifier', () => { expect(mod).toEqual(expect.objectContaining({ comparePoint: undefined, isComparePoint: expect.any(Function), + maxIterations: ReRollModifier.defaultMaxIterations, name: 're-roll', notation: 'r', once: false, @@ -299,7 +300,7 @@ describe('ReRollModifier', () => { describe('Iteration limit', () => { test('has iteration limit', () => { - expect(mod.maxIterations).toBe(1000); + expect(mod.maxIterations).toBe(ReRollModifier.defaultMaxIterations); }); test('infinite re-roll stops at iteration limit `r>0`', () => { diff --git a/tests/parser/Parser.test.js b/tests/parser/Parser.test.js index 27c561ea..9013710d 100644 --- a/tests/parser/Parser.test.js +++ b/tests/parser/Parser.test.js @@ -549,6 +549,158 @@ describe('Parser', () => { penetrate: true, })); }); + + describe('Change max iterations', () => { + test('explode `d6!4`', () => { + const parsed = Parser.parse('d6!4'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(StandardDice); + + expect(parsed[0].sides).toEqual(6); + expect(parsed[0].qty).toEqual(1); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '=', + value: 6, + }), + compound: false, + maxIterations: 4, + penetrate: false, + })); + }); + + test('compound `2d7!!678`', () => { + const parsed = Parser.parse('2d7!!678'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(StandardDice); + + expect(parsed[0].sides).toEqual(7); + expect(parsed[0].qty).toEqual(2); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '=', + value: 7, + }), + compound: true, + maxIterations: 678, + penetrate: false, + })); + }); + + test('penetrate `5d%!p95`', () => { + const parsed = Parser.parse('5d%!p95'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(PercentileDice); + + expect(parsed[0].sides).toEqual('%'); + expect(parsed[0].qty).toEqual(5); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '=', + value: 100, + }), + compound: false, + maxIterations: 95, + penetrate: true, + })); + }); + + test('can explode with compare point `4dF.2!1>=0', () => { + const parsed = Parser.parse('4dF.2!1>=0'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(FudgeDice); + + expect(parsed[0].sides).toEqual('F.2'); + expect(parsed[0].qty).toEqual(4); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '>=', + value: 0, + }), + compound: false, + maxIterations: 1, + penetrate: false, + })); + }); + + test('can explode with compare point `dF.1!!2<=1', () => { + const parsed = Parser.parse('dF.1!!2<=1'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(FudgeDice); + + expect(parsed[0].sides).toEqual('F.1'); + expect(parsed[0].qty).toEqual(1); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '<=', + value: 1, + }), + compound: true, + maxIterations: 2, + penetrate: false, + })); + }); + + test('can explode with compare point `360d%!!p192<50', () => { + const parsed = Parser.parse('360d%!!p192<50'); + + expect(parsed).toBeInstanceOf(Array); + expect(parsed).toHaveLength(1); + expect(parsed[0]).toBeInstanceOf(PercentileDice); + + expect(parsed[0].sides).toEqual('%'); + expect(parsed[0].qty).toEqual(360); + + expect(parsed[0].modifiers.has('explode')).toBe(true); + + const mod = parsed[0].modifiers.get('explode'); + expect(mod).toBeInstanceOf(ExplodeModifier); + expect(mod.toJSON()).toEqual(expect.objectContaining({ + comparePoint: expect.objectContaining({ + operator: '<', + value: 50, + }), + compound: true, + maxIterations: 192, + penetrate: true, + })); + }); + }); }); describe('Keep', () => {