import type { RNG } from './RNG.js'; export class RandUtil { constructor(protected rng: RNG) { } public nextFloat1(): Promise { return this.rng.nextFloat1(); } public async nextRange(min: number, max: number): Promise { const range = max - min; return await this.nextFloat1() * (range) + min; } public async nextRangeInt(min: number, max: number): Promise { const range = max - min; return await this.rng.nextInt(range) + min; } public nextInt(max?: number): Promise { return this.rng.nextInt(max); } public async nextBit(): Promise { const view = new DataView(await this.rng.nextBits(1) as ArrayBufferLike); return view.getUint8(0) != 0; } public async nextBool(prob = 0.5): Promise { if (prob >= 1) { return true; } if (prob === 0.5){ return this.nextBit(); } if (prob <= 0){ return false; } return await this.nextFloat1() < prob; } public async shuffle(srcArray: T[]): Promise { const cnt = srcArray.length; if(cnt === 0){ return []; } if (cnt > this.rng.getMaxInt()) { throw 'Invalid random int range'; } const result: T[] = Array.from(srcArray); for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) { const destIdx = await this.rng.nextInt(cnt - srcIdx - 1) + srcIdx; if(srcIdx === destIdx){ continue; } [result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]]; } return result; } //Object는 integer key에 예외가 있어 shuffleAssoc은 없음 public async choice(items: T[] | Record | Set): Promise { if (items instanceof Array) { if(items.length === 0){ throw new Error('Empty items'); } const idx = await this.rng.nextInt(items.length - 1); return items[idx]; } if (items instanceof Set) { return this.choice(Array.from(items.values())); } return items[await this.choice(Array.from(Object.keys(items)))]; } public async choiceUsingWeight(items: Record): Promise { if(Object.keys(items).length === 0){ throw new Error('Empty items'); } let sum = 0; for (const value of Object.values(items)) { if (value <= 0) { continue; } sum += value; } let rd = await this.nextFloat1() * sum; for (const [item, value] of Object.entries(items)) { if (value <= 0) { if (rd <= 0) { return item; } continue; } if (rd <= value) { return item; } rd -= value; } throw new Error('Unreacheable'); } public async choiceUsingWeightPair(items: [T, number][]): Promise { if(items.length === 0){ throw new Error('Empty items'); } let sum = 0; for (const [, value] of items) { if (value <= 0) { continue; } sum += value; } let rd = await this.nextFloat1() * sum; for (const [item, value] of items) { if (value <= 0) { if (rd <= 0) { return item; } continue; } if (rd <= value) { return item; } rd -= value; } throw new Error('Unreacheable'); } }