export class DenominatorTooLargeError extends Error {} export function Distribution(floor, ceil, denominator, fill) { if(denominator > Number.MAX_SAFE_INTEGER) { throw DenominatorTooLargeError("Distribution denominator unsafe") } let length = (ceil + 1) - floor let buffer = new ArrayBuffer(length * 4) let data = new Uint32Array(buffer) if(Number.isInteger(fill)) { data.fill(fill) } return { floor, ceil, denominator, data } } export function Die(size) { return Distribution(1, size, size, 1) } export function Index(dist, result) { return result - dist.floor } export function Set(dist, result, prob) { return dist.data.set([ prob ], Index(dist, result)) } export function Get(dist, result) { return dist.data.at(Index(dist, result)) } export function Inc(dist, result, amount = 1) { let i = Index(dist, result) return dist.data.set([ dist.data.at(i) + amount ], i) } export function Includes(dist, result) { return dist.floor <= result && result <= dist.ceil } export function Convolve(d1, d2) { let ax1 = d1.floor + d2.floor let ax2 = d1.ceil + d2.ceil let Pc = Distribution(ax1, ax2, d1.denominator * d2.denominator, 0) for(let S = ax1; S <= ax2; S++) { for(let r1 = d1.floor; r1 <= d1.ceil; r1++) { let r2 = S - r1 if(Includes(d2, r2)) { Inc(Pc, S, Get(d1, r1) * Get(d2, r2)) } } } return Pc } export function Highest(d1, d2) { let hd, ld if(d1.ceil >= d2.ceil) { hd = d1, ld = d2 } else { hd = d2, ld = d1 } let Ph = Distribution(Math.max(d1.floor, d2.floor), hd.ceil, d1.denominator * d2.denominator, 0) for(let r1 = hd.floor; r1 <= hd.ceil; r1++) { for(let r2 = ld.floor; r2 <= ld.ceil; r2++) { let S = Math.max(r2, r1) Inc(Ph, S, Get(hd, r1) * Get(ld, r2)) } } return Ph } export function Lowest(d1, d2) { let ld, hd if(d1.floor <= d2.floor) { ld = d1, hd = d2 } else { ld = d2, hd = d1 } let Ph = Distribution(ld.floor, Math.min(d1.ceil, d2.ceil), d1.denominator * d2.denominator, 0) for(let r1 = ld.floor; r1 <= ld.ceil; r1++) { for(let r2 = hd.floor; r2 <= hd.ceil; r2++) { let S = Math.min(r2, r1) Inc(Ph, S, Get(ld, r1) * Get(hd, r2)) } } return Ph } export function ForEach(dist, func) { for(let i = 0; i < dist.data.length; i++) { func(i + dist.floor, dist.data.at(i)) } } export function Floats(dist) { let buf = new ArrayBuffer(dist.data.length * 4) let view = new Float32Array(buf) ForEach(dist, (i) => { view[Index(dist, i)] = Get(dist, i) / dist.denominator }) return view } export function Count(dist, amount) { let counter = amount for(let i = 0; i < dist.data.length; i++) { counter -= dist.data.at(i) if(counter <= 0) { return i + dist.floor } } } const MAX_32_BIT_INT = 4294967295 export function Roll(dist) { let i = crypto.getRandomValues(new Uint32Array(1))[0] i = Math.ceil((i / MAX_32_BIT_INT) * dist.denominator) return Count(dist, i) } /* 2 11.11% ████████████ 3 22.22% ████████████ # 4 33.33% ████████████ 5 22.22% ████████████ 6 1.11% ████████████ */ export function Visualize(dist, highlight = 13, tableWidth = 13) { let largestProb = 0 let items = [] ForEach(dist, (i, v) => { if(v > largestProb) largestProb = v items.push([ i, v ]) }) let v = '' let probUnits = tableWidth / largestProb for(let [ res, prob ] of items) { let percentage = prob / dist.denominator percentage *= 100 v += res === highlight ? '#' : ' ' v += res.toString().padStart(3, ' ') v += ' ' v += percentage.toFixed(2).padStart(5) + '%' v += ' ' let u = prob * probUnits let fb = Math.floor(u) let r = u - fb v += ('\u2588'.repeat(fb) + fractionalBlockCharacter(r)).padEnd(tableWidth, '\u2500') v += '\n' } return v } export function fractionalBlockCharacter(float) { if(float >= 7/8) return '\u2589' if(float >= 3/4) return '\u258A' if(float >= 5/8) return '\u258B' if(float >= 1/2) return '\u258C' if(float >= 3/8) return '\u258D' if(float >= 1/4) return '\u258E' if(float >= 1/8) return '\u258F' return '' } // console.log( // ) let exD = Die(6) exD.floor += 3 exD.ceil += 3 // TODO: Change ceil back to a function so this is less weird console.log( // Die(6).data.length Visualize( // Convolve( // Die(4), Highest(Die(8), exD) // ) ) )