UNPKG

@allemandi/gacha-engine

Version:

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

315 lines (255 loc) โ€ข 8.5 kB
# ๐Ÿ“– @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. --- <!-- omit from toc --> ## ๐Ÿ”– 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 <script src="https://unpkg.com/@allemandi/gacha-engine"></script> <script> 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"] </script> ``` ## ๐Ÿ“˜ API ### Constructor `new GachaEngine(config: GachaEngineConfig)` Creates a new GachaEngine instance with validation. **Config Options:** - Weighted Mode ```ts { mode: 'weighted'; // (default) rarityRates: Record<string, number>; // 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