Skip to content

Commit

Permalink
Merge pull request #22 from retraigo/dev
Browse files Browse the repository at this point in the history
[feat] A few small improvements
  • Loading branch information
retraigo authored Feb 14, 2023
2 parents 85d6228 + eb5a01e commit faba217
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 253 deletions.
138 changes: 65 additions & 73 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,125 +1,117 @@
# fortuna v2

Weighted gacha system.

## Usage

Create an item using `GachaMachine.createItem`

More weight = more common
More weight = more common. Think of it as in terms of probability.

```js

import { GachaMachine } from "https://deno.land/x/fortuna/mod.ts"
// or
import { GachaMachine } from "https://deno.land/x/fortuna/dist/fortuna.js"

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 },
]
{ result: "Mob character #1", chance: 5 },
{ result: "Mob character #2", chance: 5 },
{ result: "Mob character #3", chance: 5 },


const machine = new GachaMachine(items)

machine.get(10) // Rolls 10x
machine.get(10) // Rolls 10x

/*
My result:
[
"Kinda rare character",
"Mob character",
"Mob character",
"Mob character",
"Mob character",
"Mob character #1",
"Mob character #3",
"Mob character #3",
"Mob character #1",
"Kinda rare character",
"Mob character",
"Mob character",
"Mob character",
"Mob character"
"Mob character #2",
"Mob character #2" ,
"Mob character #1",
"Mob character #2"
]
*/
```

### Plain weighted random selection

You probably don't need all complicated stuff. Here's a quick way to just create a simple weight-based gacha system:
(Only works on v1.1.0 and above)
(Only works on v3.0.1 and above)

```ts
import { GachaMachine } from 'https://deno.land/x/fortuna/mod.ts' // wherever you are importing from.
import { roll } from "https://deno.land/x/fortuna/src/roll.ts"; // wherever you are importing from.

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 },
]
{ result: "Mob character #1", chance: 5 },
{ result: "Mob character #2", chance: 5 },
{ result: "Mob character #3", chance: 5 },
];

GachaMachine.rollWithLinearSearch(items) // Rolls one item from the list of items using linear search.
roll(items); // Rolls one item from the list of items using linear search.
```

`GachaMachine#get()` works using Binary Search by default. Using the Binary Search method explicitly requires a different structure of data for input.
You can also provide two individual arrays for choices and weights.

```ts
import { GachaMachine } from 'https://deno.land/x/fortuna/mod.ts' // wherever you are importing from.
import { roll } from "https://deno.land/x/fortuna/src/roll.ts"; // wherever you are importing from.

const items = [
{ result: "SSR cool character", cumulativeChance: 1 },
{ result: "Kinda rare character", cumulativeChance: 4 },
{ result: "Mob character", cumulativeChance: 9 },
{ result: "Mob character", cumulativeChance: 14 },
{ result: "Mob character", cumulativeChance: 19 },
]

GachaMachine.rollWithBinarySearch(items) // Rolls one item from the list of items using linear search.
"SSR cool character",
"Kinda rare character",
"Mob character #1",
"Mob character #2",
"Mob character #3",
];
const chances = [1, 3, 5, 5, 5];

roll(items, chances); // Rolls one item from the list of items using linear search.
```

## How it works

Fortuna has two algorithms, one being a static method in the `GachaMachine` class and one
being a standalone function.

### Algorithm 1

Fortuna provides a simple function `roll` that generates a pseudo random number
and performs linear search on the provided data in order to find the proper item.
Providing the total chance may result in faster rolls.

```ts
// JS PSEUDOCODE

// Each choice is an object with
// chance: number
// result: T (type parameter)
//
// total chance can be supplied
// manually or computed from data
function roll(choices, total) {
// runs a loop to compute total
if(!total) total = sum_by_chance(data)

// generate random number for choosing
let rng = random_number(0, total)

// run a loop to find the choice
let current = 0.0;
for (choice in data) {
current = current + choice.chance;
if(rng < current) return choice.result;
}
}
```
import { roll } from "https://deno.land/x/fortuna/src/roll.ts"; // wherever you are importing from.

Fortuna's default `GachaMachine.get()` uses a more complex, but faster approach.

- When the `GachaMachine` class is instantiated, data is transformed into a form
suitable for binary search.
const items = [
{ result: "SSR cool character", chance: 1 },
{ result: "Kinda rare character", chance: 3 },
{ result: "Mob character #1", chance: 5 },
{ result: "Mob character #2", chance: 5 },
{ result: "Mob character #3", chance: 5 },
];

- When `GachaMachine.get()` is run, Fortuna performs binary search on this transformed
data.
roll(items, 19); // Rolls one item from the list of items using linear search.
```

The `roll` function is more suitable when the weighted data is used only once.
```ts
import { roll } from "https://deno.land/x/fortuna/src/roll.ts"; // wherever you are importing from.

The `get` method is suitable when the weighted data is reused. It comes at extra cost
during initialization but compensates for it with better performance when sampling.
const items = [
"SSR cool character",
"Kinda rare character",
"Mob character #1",
"Mob character #2",
"Mob character #3",
];
const chances = [1, 3, 5, 5, 5];

roll(items, chances, 19); // Rolls one item from the list of items using linear search.
```

## Documentation

Documentation for the latest version can be found in [https://doc.deno.land/https://deno.land/x/fortuna/mod.ts](https://doc.deno.land/https://deno.land/x/fortuna/mod.ts)

A guide for usage can be found in [docs.nekooftheabyss.moe](https://docs.nekooftheabyss.moe/fortuna) (not updated for v3 yet).

70 changes: 0 additions & 70 deletions benches/100k_rolls.ts

This file was deleted.

51 changes: 0 additions & 51 deletions benches/1_roll.ts

This file was deleted.

33 changes: 21 additions & 12 deletions benches/comparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
* Total number of items in pool: 905
*/

import { GachaMachine } from "../history/fortuna_v2.ts";
import { GachaMachine as GM } from "../mod.ts";
import { GachaMachine, roll } from "../mod.ts";

import pokemon from "../testdata/pokemon.json" assert { type: "json" };

Expand All @@ -29,7 +28,7 @@ const itemsForPicker = items.map((x) => ({
}));
const itemsForRWC = items.map((x) => ({ id: x.result, weight: x.chance }));

console.log(items.reduce((acc, a) => acc + a.chance, 0), items.length);
const totalChance = items.reduce((acc, a) => acc + a.chance, 0);

/**
* No op
Expand Down Expand Up @@ -76,16 +75,26 @@ Deno.bench("Balastrong/wrand", { group: "gacha" }, () => {
* Initializing class is done in the bench too
* to avoid bias.
*/
Deno.bench("retraigo/fortuna v2", { group: "gacha" }, () => {
Deno.bench("retraigo/fortuna", { baseline: true, group: "gacha" }, () => {
const machine = new GachaMachine(items);
machine.get(20);
machine.get(1e4);
});

/**
* Initializing class is done in the bench too
* to avoid bias.
/**
* Plain rolls without requiring any setup
*/
Deno.bench("retraigo/fortuna v3", { baseline: true, group: "gacha" }, () => {
const machine = new GM(items);
machine.get(20);
});
Deno.bench("retraigo/fortuna/roll", { group: "gacha" }, () => {
for (let i = 0; i < 1e4; ++i) {
roll(items);
}
});

/**
* Plain rolls without requiring any setup
* with totalChance
*/
Deno.bench("retraigo/fortuna/roll (with totalChance)", { group: "gacha" }, () => {
for (let i = 0; i < 1e4; ++i) {
roll(items, totalChance);
}
});
19 changes: 19 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit faba217

Please sign in to comment.