Browse Source

Initial version, rolls 4 asphodice with rerolls

tags/v0.1.3
bertieb 3 years ago
commit
110a574b8a
2 changed files with 255 additions and 0 deletions
  1. +185
    -0
      dice.ts
  2. +70
    -0
      tsconfig.json

+ 185
- 0
dice.ts View File

@@ -0,0 +1,185 @@
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));
}

+ 70
- 0
tsconfig.json View File

@@ -0,0 +1,70 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": [ "node" ], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

Loading…
Cancel
Save