114 lines
2.4 KiB
TypeScript
114 lines
2.4 KiB
TypeScript
import { sum } from "./utils/sum";
|
|
|
|
export class Dice {
|
|
private count!: number;
|
|
private sides!: number;
|
|
private diceString: string;
|
|
|
|
toString() {
|
|
return this.diceString;
|
|
}
|
|
|
|
constructor(dice: string) {
|
|
this.parseDice(dice);
|
|
this.diceString = dice;
|
|
}
|
|
|
|
private parseDice(dice: string) {
|
|
const [c, s] = dice.split(/[dD]/);
|
|
this.count = Number(c);
|
|
this.sides = Number(s);
|
|
}
|
|
|
|
public roll() {
|
|
let results = [];
|
|
for (let i = 0; i < this.count; i++) {
|
|
results.push(this.rollSingle());
|
|
}
|
|
return {
|
|
total: sum(...results),
|
|
max: Math.max(...results),
|
|
min: Math.min(...results),
|
|
results,
|
|
};
|
|
}
|
|
|
|
public rollMax() {
|
|
return this.roll().max;
|
|
}
|
|
public rollMin() {
|
|
return this.roll().min;
|
|
}
|
|
|
|
private rollSingle() {
|
|
return Math.ceil(Math.random() * this.sides);
|
|
}
|
|
|
|
public rollAvg() {
|
|
return this.roll().total / this.count;
|
|
}
|
|
|
|
public rollTimes(times: number) {
|
|
let total = 0;
|
|
for (let i = 0; i < times; i++) {
|
|
total += this.roll().total;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
public rollTimesAvg(times: number) {
|
|
return this.rollTimes(times) / times;
|
|
}
|
|
|
|
public getNormalizedRollDistribution(): Record<number, number> {
|
|
const distribution: Record<number, number> = this.computeDistribution();
|
|
|
|
// Normalize the distribution
|
|
const totalOutcomes = Math.pow(this.sides, this.count);
|
|
for (const sum in distribution) {
|
|
if (distribution.hasOwnProperty(sum)) {
|
|
distribution[sum] /= totalOutcomes;
|
|
}
|
|
}
|
|
|
|
return distribution;
|
|
}
|
|
|
|
public getRollDistribution(): Record<number, number> {
|
|
return this.computeDistribution();
|
|
}
|
|
|
|
public computeDistribution(): Record<number, number> {
|
|
const maxSum = this.count * this.sides;
|
|
const dp: number[][] = Array.from({ length: this.count + 1 }, () =>
|
|
Array(maxSum + 1).fill(0)
|
|
);
|
|
|
|
dp[0][0] = 1;
|
|
|
|
for (let dice = 1; dice <= this.count; dice++) {
|
|
for (let sum = 0; sum <= maxSum; sum++) {
|
|
for (let face = 1; face <= this.sides; face++) {
|
|
if (sum >= face) {
|
|
dp[dice][sum] += dp[dice - 1][sum - face];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const distribution: Record<number, number> = {};
|
|
for (let sum = this.count; sum <= maxSum; sum++) {
|
|
distribution[sum] = dp[this.count][sum];
|
|
}
|
|
|
|
return distribution;
|
|
}
|
|
|
|
// STATIC
|
|
static isDice(d: string) {
|
|
return /\d+[dD]\d+/.test(d);
|
|
}
|
|
}
|
|
|
|
// globalThis.Dice = Dice;
|