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