@@ -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)); | |||||
} |
@@ -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. */ | |||||
} | |||||
} |