210 lines
4.9 KiB
JavaScript
210 lines
4.9 KiB
JavaScript
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)
|
|
// )
|
|
)
|
|
) |