Skip to content

Commit

Permalink
Added the ability to set a limit for rolls and dice faces (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianocola authored and tom-wolfe committed Oct 16, 2018
1 parent 97f5d43 commit 53f5f8b
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 4 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ const result = dice.roll("1d20").total;
console.log(result); // Outputs 4.
```

#### Limiting the number of rolls or sides

Limit the number of rolls or dice sides by providing a configuration object to the `Dice` constructor:

```typescript
const dice = new Dice(null, null, {
maxRollTimes: 20, // limit to 20 rolls
maxDiceSides: 100, // limit to 100 dice faces
});
const result1 = dice.roll("50d10");
console.log(result1.errors); // Outputs ["Invalid number of rolls: 50. Maximum allowed: 20."]
const result2 = dice.roll("10d500");
console.log(result2.errors); // Outputs ["Invalid number of dice sides: 500. Maximum allowed: 500."]
```

#### Dice Expression Syntax

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).
Expand Down
20 changes: 20 additions & 0 deletions spec/interpreter/dice-interpreter.evaluate.dice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,25 @@ describe('DiceInterpreter', () => {
interpreter.evaluate(dice, errors);
expect(errors.length).toBeGreaterThanOrEqual(1);
});
it('throws on invalid number of rolls (if specified a limit of 3 rolls and rolls 5d6).', () => {
const dice = Ast.Factory.create(Ast.NodeType.Dice);
dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 5));
dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 6));

const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4), null, {maxRollTimes: 3});
const errors: Interpreter.InterpreterError[] = [];
interpreter.evaluate(dice, errors);
expect(errors.length).toBeGreaterThanOrEqual(1);
});
it('throws on invalid number of dice sides (if specified a limit of 6 sides and rolls 2d10).', () => {
const dice = Ast.Factory.create(Ast.NodeType.Dice);
dice.addChild(Ast.Factory.create(Ast.NodeType.Number).setAttribute('value', 2));
dice.addChild(Ast.Factory.create(Ast.NodeType.DiceSides).setAttribute('value', 10));

const interpreter = new Interpreter.DiceInterpreter(null, new MockRandomProvider(4), null, {maxDiceSides: 6});
const errors: Interpreter.InterpreterError[] = [];
interpreter.evaluate(dice, errors);
expect(errors.length).toBeGreaterThanOrEqual(1);
});
});
});
6 changes: 4 additions & 2 deletions src/dice.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import { DiceLexer } from './lexer/dice-lexer.class';
import { Parser } from './parser';
import { DiceParser } from './parser/dice-parser.class';
import { RandomProvider } from './random';
import { InterpreterOptions } from './interpreter/interpreter-options.interface';

export class Dice {
constructor(
protected functions?: FunctionDefinitionList,
protected randomProvider?: RandomProvider
protected randomProvider?: RandomProvider,
protected options?: InterpreterOptions,
) { }

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

protected createInterpreter(): DiceInterpreter {
return new DiceInterpreter(this.functions, this.randomProvider, this.createGenerator());
return new DiceInterpreter(this.functions, this.randomProvider, this.createGenerator(), this.options);
}

protected createGenerator(): DiceGenerator {
Expand Down
18 changes: 16 additions & 2 deletions src/interpreter/dice-interpreter.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DiceResult } from './dice-result.class';
import { InterpreterError } from './error-message.class';
import { FunctionDefinitionList } from './function-definition-list.class';
import { Interpreter } from './interpreter.interface';
import { InterpreterOptions } from './interpreter-options.interface';

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

constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator) {
constructor(functions?: FunctionDefinitionList, random?: RandomProvider, generator?: DiceGenerator, options?: InterpreterOptions) {
this.functions = DefaultFunctionDefinitions;
(<any>Object).assign(this.functions, functions);
this.random = random || new DefaultRandomProvider();
this.generator = generator || new DiceGenerator();
this.options = options || {};
}

interpret(expression: Ast.ExpressionNode): DiceResult {
Expand Down Expand Up @@ -129,8 +132,19 @@ export class DiceInterpreter implements Interpreter<DiceResult> {
evaluateDice(expression: Ast.ExpressionNode, errors: InterpreterError[]): number {
if (!this.expectChildCount(expression, 2, errors)) { return 0; }
const num = Math.round(this.evaluate(expression.getChild(0), errors));
const { maxRollTimes, maxDiceSides } = this.options;
if (maxRollTimes && num > maxRollTimes) {
errors.push(new InterpreterError(`Invalid number of rolls: ${num}. Maximum allowed: ${maxRollTimes}.`, expression));
return null;
}

const sides = expression.getChild(1);
expression.setAttribute('sides', this.evaluate(sides, errors));
const sidesValue = this.evaluate(sides, errors);
if (maxDiceSides && sidesValue > maxDiceSides) {
errors.push(new InterpreterError(`Invalid number of dice sides: ${sidesValue}. Maximum allowed: ${maxDiceSides}.`, expression));
return null;
}
expression.setAttribute('sides', sidesValue);

expression.clearChildren();

Expand Down
4 changes: 4 additions & 0 deletions src/interpreter/interpreter-options.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface InterpreterOptions {
maxRollTimes?: number;
maxDiceSides?: number;
}

0 comments on commit 53f5f8b

Please sign in to comment.