Skip to content

Commit 53f5f8b

Browse files
adrianocolatom-wolfe
authored andcommitted
Added the ability to set a limit for rolls and dice faces (#20)
1 parent 97f5d43 commit 53f5f8b

5 files changed

+59
-4
lines changed

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ const result = dice.roll("1d20").total;
8282
console.log(result); // Outputs 4.
8383
```
8484

85+
#### Limiting the number of rolls or sides
86+
87+
Limit the number of rolls or dice sides by providing a configuration object to the `Dice` constructor:
88+
89+
```typescript
90+
const dice = new Dice(null, null, {
91+
maxRollTimes: 20, // limit to 20 rolls
92+
maxDiceSides: 100, // limit to 100 dice faces
93+
});
94+
const result1 = dice.roll("50d10");
95+
console.log(result1.errors); // Outputs ["Invalid number of rolls: 50. Maximum allowed: 20."]
96+
const result2 = dice.roll("10d500");
97+
console.log(result2.errors); // Outputs ["Invalid number of dice sides: 500. Maximum allowed: 500."]
98+
```
99+
85100
#### Dice Expression Syntax
86101

87102
The dice rolling syntax is based on the system used by Roll20, a detailed explanation of which can be found on the [Roll20 Wiki](https://wiki.roll20.net/Dice_Reference#Roll20_Dice_Specification).

spec/interpreter/dice-interpreter.evaluate.dice.spec.ts

+20
Original file line numberDiff line numberDiff line change
@@ -191,5 +191,25 @@ describe('DiceInterpreter', () => {
191191
interpreter.evaluate(dice, errors);
192192
expect(errors.length).toBeGreaterThanOrEqual(1);
193193
});
194+
it('throws on invalid number of rolls (if specified a limit of 3 rolls and rolls 5d6).', () => {
195+
const dice = Ast.Factory.create(Ast.NodeType.Dice);
196+
dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5));
197+
dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6));
198+
199+
const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4), null, {maxRollTimes: 3});
200+
const errors: Interpreter.InterpreterError[] = [];
201+
interpreter.evaluate(dice, errors);
202+
expect(errors.length).toBeGreaterThanOrEqual(1);
203+
});
204+
it('throws on invalid number of dice sides (if specified a limit of 6 sides and rolls 2d10).', () => {
205+
const dice = Ast.Factory.create(Ast.NodeType.Dice);
206+
dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2));
207+
dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 10));
208+
209+
const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4), null, {maxDiceSides: 6});
210+
const errors: Interpreter.InterpreterError[] = [];
211+
interpreter.evaluate(dice, errors);
212+
expect(errors.length).toBeGreaterThanOrEqual(1);
213+
});
194214
});
195215
});

src/dice.class.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { DiceLexer } from './lexer/dice-lexer.class';
77
import { Parser } from './parser';
88
import { DiceParser } from './parser/dice-parser.class';
99
import { RandomProvider } from './random';
10+
import { InterpreterOptions } from './interpreter/interpreter-options.interface';
1011

1112
export class Dice {
1213
constructor(
1314
protected functions?: FunctionDefinitionList,
14-
protected randomProvider?: RandomProvider
15+
protected randomProvider?: RandomProvider,
16+
protected options?: InterpreterOptions,
1517
) { }
1618

1719
roll(input: string | CharacterStream): DiceResult {
@@ -31,7 +33,7 @@ export class Dice {
3133
}
3234

3335
protected createInterpreter(): DiceInterpreter {
34-
return new DiceInterpreter(this.functions, this.randomProvider, this.createGenerator());
36+
return new DiceInterpreter(this.functions, this.randomProvider, this.createGenerator(), this.options);
3537
}
3638

3739
protected createGenerator(): DiceGenerator {

src/interpreter/dice-interpreter.class.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DiceResult } from './dice-result.class';
66
import { InterpreterError } from './error-message.class';
77
import { FunctionDefinitionList } from './function-definition-list.class';
88
import { Interpreter } from './interpreter.interface';
9+
import { InterpreterOptions } from './interpreter-options.interface';
910

1011
interface SortedDiceRolls {
1112
rolls: Ast.ExpressionNode[];
@@ -16,12 +17,14 @@ export class DiceInterpreter implements Interpreter<DiceResult> {
1617
protected functions: FunctionDefinitionList;
1718
protected random: RandomProvider;
1819
protected generator: DiceGenerator;
20+
protected options: InterpreterOptions;
1921

20-
constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator) {
22+
constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator, options?: InterpreterOptions) {
2123
this.functions = DefaultFunctionDefinitions;
2224
(<any>Object).assign(this.functions, functions);
2325
this.random = random || new DefaultRandomProvider();
2426
this.generator = generator || new DiceGenerator();
27+
this.options = options || {};
2528
}
2629

2730
interpret(expression: Ast.ExpressionNode): DiceResult {
@@ -129,8 +132,19 @@ export class DiceInterpreter implements Interpreter<DiceResult> {
129132
evaluateDice(expression: Ast.ExpressionNode, errors: InterpreterError[]): number {
130133
if (!this.expectChildCount(expression, 2, errors)) { return 0; }
131134
const num = Math.round(this.evaluate(expression.getChild(0), errors));
135+
const { maxRollTimes, maxDiceSides } = this.options;
136+
if (maxRollTimes && num > maxRollTimes) {
137+
errors.push(new InterpreterError(`Invalid number of rolls: ${num}. Maximum allowed: ${maxRollTimes}.`, expression));
138+
return null;
139+
}
140+
132141
const sides = expression.getChild(1);
133-
expression.setAttribute('sides', this.evaluate(sides, errors));
142+
const sidesValue = this.evaluate(sides, errors);
143+
if (maxDiceSides && sidesValue > maxDiceSides) {
144+
errors.push(new InterpreterError(`Invalid number of dice sides: ${sidesValue}. Maximum allowed: ${maxDiceSides}.`, expression));
145+
return null;
146+
}
147+
expression.setAttribute('sides', sidesValue);
134148

135149
expression.clearChildren();
136150

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface InterpreterOptions {
2+
maxRollTimes?: number;
3+
maxDiceSides?: number;
4+
}

0 commit comments

Comments
 (0)