export interface GachaData<ItemType> { tier: number; result: ItemType; chance: number;}
export interface GachaChoice<ItemType> { result: ItemType; chance: number;}
export interface GachaTier { items: number; chance: number; tier: number;}
export class GachaMachine<ItemType> { items: GachaData<ItemType>[]; tiers: GachaTier[]; constructor(items: GachaData<ItemType>[]) { this.items = []; this.tiers = []; this.configItems(items); this.configTiers(items); } get pool(): number[] { return Array.from( this.items.reduce( (acc: Set<number>, val: GachaData<ItemType>) => acc.add(val.tier), new Set<number>(), ).entries(), ).map((x) => x[0]); } configItems(items: GachaData<ItemType>[]): void { let newItems: GachaData<ItemType>[] = items = items.sort((a, b) => a.tier - b.tier ) .map((x) => ({ chance: x.chance, result: x.result, tier: x.tier, })); this.items = newItems; } configTiers(items: GachaData<ItemType>[]): void { let tiers: GachaTier[] = []; const pool = this.pool; for (let i = 0; i < pool.length; ++i) { tiers[pool[i]] = { items: 0, chance: 0, tier: pool[i] }; } for (let i = items.length; i > 0; --i) { if (!pool.includes(items[i - 1].tier)) continue; tiers[items[i - 1].tier].items += 1; tiers[items[i - 1].tier].chance += items[i - 1].chance; } let tierList = []; for (let i in tiers) { tierList.push(tiers[i]); } this.tiers = tierList; } get( num = 1, detailed = false, pool: number[] = this.pool, ): GachaChoice<ItemType>[] | ItemType[] { if (detailed) { let result: GachaChoice<ItemType>[] = []; for (let i = num; i > 0; --i) { result.push(this.choose(pool, detailed)); } return result; } else { let result: ItemType[] = [];
for (let i = num; i > 0; --i) { result.push(this.choose(pool)); } return result; } } choose(pool: number[], detailed: boolean): GachaChoice<ItemType>; choose(pool: number[]): ItemType; choose( pool: number[] = this.pool, detailed?: boolean, ): GachaChoice<ItemType> | ItemType { let tier = GachaMachine.roll<number>( this.tiers.filter((x) => pool.includes(x.tier)).map((x) => ({ chance: x.chance, result: x.tier, })), ); const result = GachaMachine.roll<ItemType>( this.items.filter((x) => x.tier == tier.result), ); return detailed ? result : result.result; } static roll<ItemType>( choices: GachaChoice<ItemType>[], ): GachaChoice<ItemType> { const total = choices.reduce( (acc: number, val: GachaChoice<ItemType>) => acc + val.chance, 0, ); let result = Math.random() * total; let going = 0.0; for (let i = 0; i < choices.length; ++i) { going += choices[i].chance; if (result < going) { return choices[i]; } } return choices[Math.floor(Math.random() * choices.length)]; } static createItem<ItemType>( result: ItemType, chance = 1, tier = 1, ): GachaData<ItemType> { return { result, chance, tier }; } static createRollChoice<ItemType>( result: ItemType, chance = 1, ): GachaChoice<ItemType> { return { result, chance }; }}
export { GachaMachine as Fortuna };export { GachaMachine as default };