|
- const util = require('util');
-
- function countOccurrencesOfNumber(inputArray: Array<number>, checkNumber: number): number {
- /*
- return inputArray.reduce(function(n: number, val: number) {
- return n + (val === checkNumber);
- }, 0);
- */
- return inputArray.filter(c => c === checkNumber).length;
- }
-
- function randIntMinOne(max: number): number {
- return (1 + Math.floor(Math.random() * Math.floor(max)));
- }
-
- interface Dice {
- readonly sides: number;
- readonly type: string;
- }
-
- enum Outcomes {
- Success = "Success",
- Fail = "Failure",
- CritSuccess = "Critical Success",
- CritFail = "Critical Failure",
- Other = "Something Else"
- }
-
- interface DiceResult {
- total: number;
- dice: Array<number>;
- olddice?: Array<number>;
- reroll?: boolean;
- outcome?: Outcomes;
-
- }
-
- class D10 implements Dice {
- sides: number = 10;
- type: string = "d10"
-
- constructor() {
- // this.sides = 10;
- }
-
- [util.inspect.custom](): string {
- return this.type;
- }
-
- roll(numberToRoll: number): DiceResult {
- let results: DiceResult = { total: 0, dice: [] };
- for (let i = 0; i < numberToRoll; i++) {
- results.dice.push(randIntMinOne(this.sides));
- }
-
- results.total = results.dice.reduce((acc: number, curr: number) => acc + curr);
-
- return results;
- }
- }
-
- class Asphodice extends D10 {
- readonly type: string = "asphodice";
- readonly passCrit: number = 10;
- readonly failCrit: number = 1;
- readonly successCutOff: number = 6; // this or larger
-
- rerollHighDice (rerollDice: Array<number>): Array<number> {
- // Re-roll the high dice (ie >= 6) for a chance of failure
- // happens on eg 1
- //
- // Basically we want to remove a die from the original result,
- // as long as that die isn't a 10, and is above the
- // cutoff. So we filter to get rerollCandidates, find the
- // highest value and get the index of that value in the
- // original results. We use that to splice() out (remove) that
- // value and push the rerolled dice onto the modified array
- //
- // Example:
- // rerollDice: [ 8, 1, 1, 4, 7, 10] --> filter (not 10, >= 6)
- // rerollCandidates : [8, 7] --> maxValue: 8
- // maxIndex: 0 (in original array)
- // rerollResult: 6
- // rerollOutcome: [ 1, 1, 4, 7, 10, 6] --> return
-
- let rerollCandidates: Array<number> = rerollDice.filter(die => (die < 10 && die >= this.successCutOff));
- let maxValue: number = Math.max(...rerollCandidates);
- let maxIndex = rerollDice.indexOf(maxValue);
- let rerollResult: number = randIntMinOne(this.sides);
-
- rerollDice.splice(maxIndex, 1);
- rerollDice.push(rerollResult);
- return rerollDice
- }
-
- rerollLowDice (rerollDice: Array<number>): Array<number> {
- // Re-roll the low dice (ie < 6) for a chance of success
- // happens on eg 10
- //
- // see this.rerollHighDice() for a full explanation
-
- let rerollCandidates: Array<number> = rerollDice.filter(die => (die > 1 && die < this.successCutOff));
- let minValue: number = Math.min(...rerollCandidates);
- let minIndex = rerollDice.indexOf(minValue);
- let rerollResult: number = randIntMinOne(this.sides);
-
- rerollDice.splice(minIndex, 1);
- rerollDice.push(rerollResult);
- return rerollDice
- }
-
- allAboveCutOff (checkDice: Array<number>): boolean {
- // if filtering those *below* cutoff results in empty set
- // then all must be above cutoff
- return ((checkDice.filter(die => die < this.successCutOff).length == 0));
- }
-
- allBelowCutOff (checkDice: Array<number>): boolean {
- // if filtering those *above or equal to* cutoff results in empty set
- // then all must be below cutoff
- return ((checkDice.filter(die => die >= this.successCutOff).length == 0));
- }
-
- roll (numToRoll: number): DiceResult {
- let results: DiceResult = { total: 0, dice: [] };
-
- // Initial roll
- for (let i = 0; i < numToRoll; i++) {
- results.dice.push(randIntMinOne(this.sides));
- }
-
- // Check for re-rolls
- // 1. if no re-rolls we can finish here
- if (!(results.dice.includes(this.passCrit) || results.dice.includes(this.failCrit))) {
- // results.total = results.dice.reduce((acc: number, curr: number) => acc + curr);
- results.reroll = false;
- } else {
- // count successes and fails
- let rerollGood = countOccurrencesOfNumber(results.dice, this.passCrit);
- let rerollBad = countOccurrencesOfNumber(results.dice, this.failCrit);
-
- // 2. only reroll if they don't cancel each other out
- if (rerollGood == rerollBad) {
- // console.log("Good = Bad, no need to reroll");
- results.reroll = false;
- // results.total = results.dice.reduce((acc: number, curr: number) => acc + curr);
- }
- // If all dice are above/below cutoff we don't need to reroll
- // 3a. Above
- else if (this.allAboveCutOff(results.dice)) {
- console.log("All above cutoff, auto-success", results.dice);
- results.reroll = false;
- }
- // 3b. Below
- else if (this.allBelowCutOff(results.dice)) {
- console.log("All below cutoff, auto-fail", results.dice);
- results.reroll = false;
- }
- // Reroll
- else {
- // Reminder: arr1 = arr2 is a copy by reference!
- let olddice = results.dice.slice();
- if (rerollGood > rerollBad) {
- // Re-roll low (<6) dice for chance of success
- results.dice = this.rerollLowDice(results.dice);
- } else {
- // Re-roll high (>=6) dice for chance of failure
- results.dice = this.rerollHighDice(results.dice);
- }
- results.olddice = olddice;
- results.reroll = true;
- }
- }
-
- results.total = results.dice.reduce((acc: number, curr: number) => acc + curr);
- return results;
- }
- }
-
- let asphodice: Asphodice = new Asphodice();
- let number: number = 4;
- for (let i = 0; i < 10; i++) {
- console.log("Rolling", number, asphodice);
- console.log(asphodice.roll(4));
- }
|