import type { RNG } from './RNG.js'; // RNG 유틸리티 모음 export class RandUtil { constructor(protected rng: RNG) {} public nextFloat1(): number { return this.rng.nextFloat1(); } public nextRange(min: number, max: number): number { const range = max - min; return this.nextFloat1() * range + min; } public nextRangeInt(minInclusive: number, maxInclusive: number): number { const range = maxInclusive - minInclusive; return this.rng.nextInt(range) + minInclusive; } public nextInt(minInclusive: number, maxExclusive: number): number { const span = maxExclusive - minInclusive; if (span <= 1) { return minInclusive; } return minInclusive + this.rng.nextInt(span - 1); } public nextBit(): boolean { const bits = this.rng.nextBits(1); return bits[0]! != 0; } public nextBool(prob = 0.5): boolean { if (prob >= 1) { return true; } if (prob === 0.5) { return this.nextBit(); } if (prob <= 0) { return false; } return this.nextFloat1() < prob; } public shuffle(srcArray: T[]): T[] { 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 = this.rng.nextInt(cnt - srcIdx - 1) + srcIdx; if (srcIdx === destIdx) { continue; } const srcValue = result[srcIdx]!; const destValue = result[destIdx]!; result[srcIdx] = destValue; result[destIdx] = srcValue; } return result; } //Object는 integer key에 예외가 있어 shuffleAssoc은 없음 public choice(items: T[] | Record | Set): T { if (items instanceof Array) { if (items.length === 0) { throw new Error('Empty items'); } const idx = this.rng.nextInt(items.length - 1); return items[idx]!; } if (items instanceof Set) { return this.choice(Array.from(items.values())); } const key = this.choice(Array.from(Object.keys(items))) as keyof typeof items; return items[key]!; } public choiceUsingWeight(items: Record): string | number { 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 = 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 choiceUsingWeightPair(items: [T, number][]): T { 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 = 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'); } }