An open API service indexing awesome lists of open source software.

https://github.com/allemandi/gacha-engine

Practical, type-safe toolkit for simulating and understanding gacha rates and rate-ups.
https://github.com/allemandi/gacha-engine

drop-rate gacha gacha-engine gacha-simulator game-probability gaming lootbox probability random-drop rate-up rolls simulation type-safe typescript

Last synced: 4 months ago
JSON representation

Practical, type-safe toolkit for simulating and understanding gacha rates and rate-ups.

Awesome Lists containing this project

README

          

# ๐Ÿ“– @allemandi/gacha-engine

[![NPM Version](https://img.shields.io/npm/v/@allemandi/gacha-engine)](https://www.npmjs.com/package/@allemandi/gacha-engine)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/allemandi/gacha-engine/blob/main/LICENSE)

> **Practical, type-safe toolkit for simulating and understanding gacha rates and rate-ups.**
> Supports `"weighted"` and `"flatRate"` modes for different gacha strategies.
> Works in Node.js and browsers โ€“ supports ESM, CommonJS, and UMD.

---

## ๐Ÿ”– Table of Contents

- [โœจ Features](#-features)
- [๐Ÿ› ๏ธ Installation](#๏ธ-installation)
- [๐Ÿš€ Quick Usage Examples](#-quick-usage-examples)
- [๐Ÿ“˜ API](#-api)
- [๐Ÿงช Tests](#-tests)
- [๐Ÿ”— Related Projects](#-related-projects)
- [๐Ÿค Contributing](#-contributing)

---

## โœจ Features

- ๐ŸŽฒ **Roll simulation** โ€“ Perform gacha rolls with weighted or flat-rate logic
- ๐Ÿ” **Probability analysis** โ€“ Drop rates, cumulative probabilities, target probabilities
- ๐Ÿ“ **Multi-rarity support** โ€“ Flexible rarity-based or item-based probability distributions
- โšก **Performance optimized** โ€“ Efficient with cached calculations
- ๐Ÿ›ก๏ธ **Type-safe** โ€“ Written in TypeScript with strict configuration validation

---

## ๐Ÿ› ๏ธ Installation
```bash
# Yarn
yarn add @allemandi/gacha-engine

# NPM
npm install @allemandi/gacha-engine
```

## ๐Ÿš€ Quick Usage Examples

**ESM (Weighted Mode)**
```js
import { GachaEngine } from '@allemandi/gacha-engine';

const pools = [
{
rarity: 'SSR',
items: [
{ name: 'Super Hobo', weight: 0.8, rateUp: true },
{ name: 'Broke King', weight: 0.4 },
{ name: 'Cardboard Hero', weight: 0.4 }
]
},
{
rarity: 'SR',
items: [
{ name: 'Cold Salaryman', weight: 1.5, rateUp: true },
{ name: 'Numb Artist', weight: 1.8 },
{ name: 'Crying Cook', weight: 1.8 }
]
},
{
rarity: 'R',
items: [
{ name: 'Regular Joe', weight: 5.0 },
{ name: 'Normal Person', weight: 5.0 }
]
}
];

const rarityRates = {
SSR: 0.01,
SR: 0.05,
R: 0.94
};

const engine = new GachaEngine({ mode: 'weighted', pools, rarityRates });

console.log('10 rolls:', engine.roll(10).join(', '));

const rate = engine.getItemDropRate('Super Hobo');
console.log('Drop rate for Super Hobo:', (rate * 100) + '%');
// ~0.4% โ†’ (0.8 / 1.6) * 0.01 = 0.005 โ†’ 0.5%

const cumulative = engine.getCumulativeProbabilityForItem('Super Hobo', 300);
console.log('Probability in 300 rolls:', (cumulative * 100) + '%');
// ~77.7%

console.log('Rolls for 50% chance:', engine.getRollsForTargetProbability('Super Hobo', 0.5));
// 139

console.log('Rate-up items:', engine.getRateUpItems().join(', '));
// Super Hobo, Cold Salaryman
```

**CommonJS (Flat Rate Mode)**
```js
const { GachaEngine } = require('@allemandi/gacha-engine');

const pools = [
{
rarity: 'SSR',
items: [
{ name: 'Superior Rat', weight: 0.003, rateUp: true },
{ name: 'Dumpster King', weight: 0.002 }
]
},
{
rarity: 'SR',
items: [
{ name: 'Sleepy Chef', weight: 0.015 }
]
},
{
rarity: 'R',
items: [
{ name: 'Unknown Student', weight: 0.1 }
]
}
];

const engine = new GachaEngine({ mode: 'flatRate', pools });

console.log('Roll x5:', engine.roll(5).join(', '));

const dropRate = engine.getItemDropRate('Superior Rat');
console.log('Drop rate for Superior Rat:', (dropRate * 100) + '%');
// 0.3%

const cumulative = engine.getCumulativeProbabilityForItem('Superior Rat', 500);
console.log('Chance after 500 rolls:', (cumulative * 100).toFixed(1) + '%');
// ~78.5%

const rollsFor50 = engine.getRollsForTargetProbability('Superior Rat', 0.5);
console.log('Rolls for 50% chance:', rollsFor50);
// ~231

console.log('Rate-up items:', engine.getRateUpItems().join(', '));
// Superior Rat
```

**UMD (Browser, Weighted Mode)**
```html

const { GachaEngine } = AllemandiGachaEngine;

const engine = new GachaEngine({
mode: 'weighted',
rarityRates: {
SSR: 0.02,
SR: 0.08,
R: 0.90
},
pools: [
{
rarity: 'SSR',
items: [
{ name: 'Trash Wizard', weight: 1.0 },
{ name: 'Park Master', weight: 1.0, rateUp: true }
]
},
{
rarity: 'SR',
items: [
{ name: 'Street Sweeper', weight: 2.0 },
{ name: 'Bench Philosopher', weight: 1.0 }
]
},
{
rarity: 'R',
items: [
{ name: 'Bus Stop Ghost', weight: 5.0 }
]
}
]
});

const rate = engine.getItemDropRate('Park Master');
const rolls = engine.getRollsForTargetProbability('Park Master', 0.75);
const cumulative = engine.getCumulativeProbabilityForItem('Park Master', 200);

console.log('1x Roll:', engine.roll());
console.log('Drop rate for Park Master:', (rate * 100).toFixed(2) + '%');
// 1.0 / 2.0 * 0.02 = 0.01 โ†’ 1.00%

console.log('Cumulative 200 rolls:', (cumulative * 100).toFixed(1) + '%');
// ~86.6%

console.log('Rolls for 75% chance:', rolls);
// ~138

console.log('Rate-up items:', engine.getRateUpItems().join(', '));
// Park Master

console.log('All items:', engine.getAllItemDropRates().map(i => i.name));
// ["Trash Wizard", "Park Master", "Street Sweeper", "Bench Philosopher", "Bus Stop Ghost"]

```

## ๐Ÿ“˜ API

### Constructor
`new GachaEngine(config: GachaEngineConfig)`

Creates a new GachaEngine instance with validation.

**Config Options:**

- Weighted Mode
```ts
{
mode: 'weighted'; // (default)
rarityRates: Record; // Required: must sum to 1.0
pools: Array<{
rarity: string; // Must match a key in `rarityRates`
items: Array<{
name: string;
weight: number;
rateUp?: boolean;
}>
}>
}
```
- Flat Rate Mode

```ts
{
mode: 'flatRate';
pools: Array<{
rarity: string; // Used only for categorization
items: Array<{
name: string;
weight: number; // Interpreted as direct probability (must sum to 1.0 across all items)
rateUp?: boolean;
}>
}>
}
```

### Methods

#### Rolling
`roll(count?: number): string[]`
- Simulate gacha rolls and returns item names
- `count`: Number of rolls to perform (default: 1)
- Returns array of item names

#### Analysis
`getItemDropRate(name: string): number`
- Returns the effective drop rate for a specific item
- In weighted mode:
- Computed as `dropRate = (item.weight / totalPoolWeight) ร— rarityBaseRate`
- In flat rate mode:
- `Returns the item's defined probability.
- Throws if the item does not exist.

`getRarityProbability(rarity: string): number`
- Returns the base probability for a given rarity tier
- Only in "weighted" mode.
- Throws in flatRate mode.

`getCumulativeProbabilityForItem(name: string, rolls: number): number`
- Calculates probability of getting the item at least once in N rolls
- Uses formula: `1 - (1 - dropRate)^rolls`

`getRollsForTargetProbability(name: string, targetProbability: number): number`
- Calculates the minimum number of rolls needed to reach a specific probability of pulling a given item.
- Returns `Infinity` if item has zero drop rate
- Returns 1 if target probability โ‰ฅ 1.0

#### Utility
`getRateUpItems(): string[]`
- Returns names of all items marked with `rateUp: true`

`getAllItemDropRates(): Array<{name: string, dropRate: number, rarity: string}>`
- Returns a list of all items with:
- name: Item name
- dropRate: Calculated drop probability
- rarity: Associated rarity (or "flatRate" in flat mode)

## ๐Ÿงช Tests

> Available in the GitHub repo only.

```bash
# Run the test suite with Vitest
yarn test
# or
npm test
```

## ๐Ÿ”— Related Projects
Check out these related projects that might interest you:

**[@allemandi/embed-utils](https://github.com/allemandi/embed-utils)**
- Fast, type-safe utilities for vector embedding comparison and search.

**[Embed Classify CLI](https://github.com/allemandi/embed-classify-cli)**
- Node.js CLI tool for local text classification using word embeddings.

## ๐Ÿค Contributing
If you have ideas, improvements, or new features:

1. Fork the project
2. Create your feature branch (git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add some amazing feature')
4. Push to the branch (git push origin feature/amazing-feature)
5. Open a Pull Request