@nekooftheabyss/fortuna
Version:
A Gacha-like system to roll random items with weights.
113 lines (110 loc) • 3.51 kB
text/typescript
/**
* Roll one item from a pool using linear search. Simple and great for one-time use pools.
* Use `GachaMachine` if you will be picking multiple items from the same pool.
* ```ts
* const items = [
* { result: "SSR cool character", chance: 1 },
* { result: "Kinda rare character", chance: 3 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* ]
* roll(items) // Rolls one item from the list of items
* ```
*
* ```ts
* const items = [
* { result: "SSR cool character", chance: 1 },
* { result: "Kinda rare character", chance: 3 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* ]
* roll(items, 19) // Rolls one item from the list of items, faster because the total chance is known.
* ```
* @module
*/
/** One item from a gacha pool */
export type GachaChoice<ItemType> = {
result: ItemType;
chance: number;
}
/**
* Roll one item from a pool using linear search. Simple and great for one-time use pools.
* Use `GachaMachine` if you will be picking multiple items from the same pool.
* @param items List of items to roll from, each having a `result` and `chance`.
* @param totalChance Total weight of the pool.
* ```ts
* const items = [
* { result: "SSR cool character", chance: 1 },
* { result: "Kinda rare character", chance: 3 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* ]
* roll(items) // Rolls one item from the list of items
* ```
*
* ```ts
* const items = [
* { result: "SSR cool character", chance: 1 },
* { result: "Kinda rare character", chance: 3 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* { result: "Mob character", chance: 5 },
* ]
* roll(items, 19) // Rolls one item from the list of items, faster because the total chance is known.
* ```
*/
export function roll<ItemType>(
choices: ItemType[],
chances: number[],
totalChance?: number
): ItemType;
export function roll<ItemType>(
choices: GachaChoice<ItemType>[],
totalChance?: number
): ItemType;
export function roll<ItemType>(
choices: ItemType[] | GachaChoice<ItemType>[],
chanceArrOrTotalChance: number | number[] = 0,
totalChance = 0
): ItemType {
const asArray = Array.isArray(chanceArrOrTotalChance);
let total = asArray ? totalChance : chanceArrOrTotalChance || 0;
if (typeof total !== "number") {
throw new TypeError(
`Invalid type for total chance. Expected 'undefined' or 'number', got '${typeof total}'`
);
}
let i = 0;
if (total === 0) {
while (i < choices.length) {
total += asArray
? chanceArrOrTotalChance[i]
: (choices[i] as GachaChoice<ItemType>).chance;
i += 1;
}
}
const result = Math.random() * total;
let going = 0.0;
i = 0;
while (i < choices.length) {
going += asArray
? chanceArrOrTotalChance[i]
: (choices[i] as GachaChoice<ItemType>).chance;
if (result < going) {
return asArray
? (choices[i] as ItemType)
: (choices[i] as GachaChoice<ItemType>).result;
}
i += 1;
}
return asArray
? (choices[Math.floor(Math.random() * choices.length)] as ItemType)
: (
choices[
Math.floor(Math.random() * choices.length)
] as GachaChoice<ItemType>
).result;
}