diff --git a/dice.ts b/dice.ts index a91d5ac..9cfb810 100644 --- a/dice.ts +++ b/dice.ts @@ -1,14 +1,23 @@ const util = require('util'); +/** + * Counts the occurrences of a number in an array of numbers + * + * @param inputArray - array of numbers + * @param checkNumber - number to count occurrences of + * @returns number of occurrences + * + */ function countOccurrencesOfNumber(inputArray: Array, checkNumber: number): number { - /* - return inputArray.reduce(function(n: number, val: number) { - return n + (val === checkNumber); - }, 0); - */ return inputArray.filter(c => c === checkNumber).length; } +/** + * Generate a random integer between *1* and `max` (inclusive) + * + * @param max - upper bound of random integer to generate + * @returns random integer + */ function randIntMinOne(max: number): number { return (1 + Math.floor(Math.random() * Math.floor(max))); } @@ -35,19 +44,33 @@ interface DiceResult { balance?: number; } - +/** + * Simple d10, implements `roll()` + */ class D10 implements Dice { sides: number = 10; type: string = "d10" constructor() { - // this.sides = 10; } [util.inspect.custom](): string { return this.type; } + /** + * Roll a specified number of dice + * + * @param numberToRoll - integer number of dice to roll + * @returns DiceResult: .total and .dice (individual dice) + * + * @example Roll 4d10 + * + * ``` + * D10.roll(4) + * { total: 16, dice [ 3, 5, 6, 2] } + * + */ roll(numberToRoll: number): DiceResult { let results: DiceResult = { total: 0, dice: [] }; for (let i = 0; i < numberToRoll; i++) { @@ -66,24 +89,38 @@ class Asphodice extends D10 { readonly failCrit: number = 1; readonly successCutOff: number = 6; // this or larger + /** + * Re-roll the high dice (ie >= 6) for a chance of failure + * happens on eg 1 + * + * @remarks + * 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 + * + * @param rerollDice + * @returns rerollOutcome + * + * @example Re-roll High Dice on 1 + * ``` + * rerollHighDice([8, 1, 1, 4, 7, 10]); + * [ 1, 1, 4, 7, 10, 6] + * ``` + * + * ## Explanation + * + * - first filter dice to be not 10 and >= 6 (see {@link Asphodice.successCutOff}) + * - rerollCandidates are [8, 7] + * - max([8, 7] = 8 + * - indexOf(8) = 0 + * - rerollResult = 6 (see {@link randIntMinOne()}) + * - `splice()` replaced dice out of array = [ 1, 1, 4, 7, 10 ] + * - `push()` new dice = [ 1, 1, 4, 7, 10, 6] + */ rerollHighDice (rerollDice: Array): Array { - // 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 = rerollDice.filter(die => (die < 10 && die >= this.successCutOff)); let maxValue: number = Math.max(...rerollCandidates); let maxIndex = rerollDice.indexOf(maxValue); @@ -94,12 +131,17 @@ class Asphodice extends D10 { return rerollDice } + /** + * Re-roll low dice (ie < 6) for chance of success (ie getting >= 6) + * + * @remarks + * + * Generally happens on a 10. + * + * @see {@link Asphodice.rerollHighDice} for a fuller explanation + * for what process in opposite direction + */ rerollLowDice (rerollDice: Array): Array { - // 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 = rerollDice.filter(die => (die > 1 && die < this.successCutOff)); let minValue: number = Math.min(...rerollCandidates); let minIndex = rerollDice.indexOf(minValue); @@ -110,30 +152,86 @@ class Asphodice extends D10 { return rerollDice } + /** + * Used to determine if all dice are above Asphodice.successCutOff + * + * @param checkDice - Array of Dice to test + * + * @see {@link Asphodice.roll} where it is used to determine if + * a re-roll can be skipped + * + * @see {@link allBelowCutOff} also. + */ allAboveCutOff (checkDice: Array): 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)); } + /** + * Used to determine if all dice are below Asphodice.successCutOff + * + * @param checkDice - Array of Dice to test + * + * @see {@link Asphodice.roll} where it is used to determine if + * a re-roll can be skipped + * + * @see {@link allAboveCutOff} also. + */ allBelowCutOff (checkDice: Array): 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)); } + /** + * Determine balance of outcomes - ie successes minus failures + * + * @remarks + * + * Success = 1, failure = -1 + * + * Particularly for Asphodice, we aren't terribly concerned with the + * numeric sum total of the dice values, we are more concerned with + * the overall outcome (see {@link Outcomes}) and for narrative purposes + * perhaps the balance of outcomes; for example, 1 success may be + * narrated differently than 4 successes. + * + * **Special note**: This ignores crits! **End special note** + * + * TODO: Special consideration of a single die roll (?) + * + * @param resultDice - the final dice after any re-rolls + * @returns Single positive/negative number with balance of roll outcomes + * + * @example Count Outcomes of 5 Dice + * ``` + * countOutcomeBalance([ 7, 4, 4, 9, 1 ]) + * -1 + * ``` + * + * - 7 and 9 are above successCutOff → +2 + * - 4, 4, 1 are below successCutOff → -3 + */ countOutcomeBalance (resultDice: Array): number{ - // Return a positive / negative number representing - // the overall outcomes: each failure (eg < 6) is -1 - // while each success (eg >= 6) is +1 - // fun with ternary operators return resultDice.reduce( (acc: number, curr: number) => { return (curr < this.successCutOff) ? acc - 1 : acc + 1 }, 0); + // PS also good practice to supply initialValue (0) } + /** + * Roll an Asphodie or Asphodice + * + * @param numToRoll + * @returns a {@link DiceResult} with additional properties: + * - reroll: boolean - whether we rerolled + * - olddice: Array - original roll + * - dice: Array - final outcome + * - balance: number - +/- of outcomes (successes/failures) + */ roll (numToRoll: number): DiceResult { let results: DiceResult = { total: 0, dice: [] };