diff --git a/.vscode/settings.json b/.vscode/settings.json index b943dbc..8675ad5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "deno.enable": true + "deno.enable": true, + "deno.unstable": true } \ No newline at end of file diff --git a/failures/roll1.ts b/algorithms/v1.ts similarity index 94% rename from failures/roll1.ts rename to algorithms/v1.ts index d213697..b3f6750 100644 --- a/failures/roll1.ts +++ b/algorithms/v1.ts @@ -8,7 +8,7 @@ import type { GachaChoice } from "../mod.ts"; export function roll( choices: GachaChoice[], ): GachaChoice { - let filteredChoices = []; + const filteredChoices = []; for (let i = 0; i < choices.length; ++i) { for (let chance = choices[i].chance; chance > 0.0; --chance) { filteredChoices.push(i); diff --git a/failures/roll2.ts b/algorithms/v2.ts similarity index 91% rename from failures/roll2.ts rename to algorithms/v2.ts index f53a4b5..e8dfae9 100644 --- a/failures/roll2.ts +++ b/algorithms/v2.ts @@ -8,7 +8,7 @@ import type { GachaChoice } from "../mod.ts"; export function roll( choices: GachaChoice[], ): GachaChoice { - let filteredChoices = choices; + const filteredChoices = []; let total = 0.0; for (let i = 0; i < choices.length; ++i) { if (choices[i].chance > 0.0) { @@ -16,7 +16,7 @@ export function roll( total += choices[i].chance; } } - let result = Math.random() * total; + const result = Math.random() * total; let going = 0.0; for (let i = 0; i < filteredChoices.length; ++i) { going += filteredChoices[i].chance; diff --git a/algorithms/v3.ts b/algorithms/v3.ts new file mode 100644 index 0000000..53813ec --- /dev/null +++ b/algorithms/v3.ts @@ -0,0 +1,24 @@ +import type { GachaChoice } from "../mod.ts"; + +/** + * Roll one from an array of gacha choices. + * @param {GachaChoice[]} choices - Choices to roll from. + * @returns {GachaChoice} Items rolled. + */ +export function roll( + choices: GachaChoice[], +): GachaChoice { + const total = choices.reduce( + (acc: number, val: GachaChoice) => acc + val.chance, + 0, + ); + const 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)]; +} diff --git a/algorithms/v4.ts b/algorithms/v4.ts new file mode 100644 index 0000000..a255000 --- /dev/null +++ b/algorithms/v4.ts @@ -0,0 +1,28 @@ +import type { GachaChoice } from "../mod.ts"; + +/** + * Roll one from an array of gacha choices. + * @param {GachaChoice[]} choices - Choices to roll from. + * @returns {GachaChoice} Items rolled. + */ +export function roll( + choices: GachaChoice[], +): GachaChoice { + let total = 0; + let i = 0; + while (i < choices.length) { + total += choices[i].chance; + i += 1; + } + const result = Math.random() * total; + let going = 0.0; + i = 0; + while (i < choices.length) { + going += choices[i].chance; + if (result < going) { + return choices[i]; + } + i += 1; + } + return choices[Math.floor(Math.random() * choices.length)]; +} diff --git a/algorithms/v4_sub.ts b/algorithms/v4_sub.ts new file mode 100644 index 0000000..671450d --- /dev/null +++ b/algorithms/v4_sub.ts @@ -0,0 +1,29 @@ +import type { GachaChoice } from "../mod.ts"; + +// Dropped before release since it was slightly faster than v4. + +/** + * Roll one from an array of gacha choices. + * @param {GachaChoice[]} choices - Choices to roll from. + * @returns {GachaChoice} Items rolled. + */ +export function roll( + choices: GachaChoice[], +): GachaChoice { + let total = 0; + let i = 0; + while (i < choices.length) { + total += choices[i].chance; + i += 1; + } + const result = Math.random() * total; + i -= 1; + while (i > -1) { + total -= choices[i].chance; + if (result > total) { + return choices[i]; + } + i -= 1; + } + return choices[Math.floor(Math.random() * choices.length)]; +} diff --git a/benches/100k_rolls.ts b/benches/100k_rolls.ts new file mode 100644 index 0000000..6f0755d --- /dev/null +++ b/benches/100k_rolls.ts @@ -0,0 +1,51 @@ +import { roll as roll1 } from "../algorithms/v1.ts"; +import { roll as roll2 } from "../algorithms/v2.ts"; +import { roll as roll3 } from "../algorithms/v3.ts"; +import { roll as roll4 } from "../algorithms/v4.ts"; +import { roll as roll4_sub } from "../algorithms/v4_sub.ts"; +import { GachaMachine } from "../mod.ts"; + +import pokemon from "../testdata/pokemon.json" assert { type: "json" }; + +const items = pokemon.slice(0, 151).map((x) => ({ + result: x.id, + chance: x.tier === "legendary" ? 11 : x.tier === "mythic" ? 1 : 25, +})); + +Deno.bench("nop", () => {}); + +Deno.bench("Algorithm V1", () => { + for (let i = 0; i < 1e6; ++i) { + roll1(items); + } +}); + +Deno.bench("Algorithm V2", () => { + for (let i = 0; i < 1e6; ++i) { + roll2(items); + } +}); + +Deno.bench("Algorithm V3", () => { + for (let i = 0; i < 1e6; ++i) { + roll3(items); + } +}); + +Deno.bench("Algorithm V4 _ Sub", () => { + for (let i = 0; i < 1e6; ++i) { + roll4_sub(items); + } +}); + +Deno.bench("Algorithm V4", () => { + for (let i = 0; i < 1e6; ++i) { + roll4(items); + } +}); + +Deno.bench("Algorithm V4 in Fortuna", () => { + for (let i = 0; i < 1e6; ++i) { + GachaMachine.roll(items); + } +}); diff --git a/benches/1_roll.ts b/benches/1_roll.ts new file mode 100644 index 0000000..f17b0a6 --- /dev/null +++ b/benches/1_roll.ts @@ -0,0 +1,51 @@ +import { roll as roll1 } from "../algorithms/v1.ts"; +import { roll as roll2 } from "../algorithms/v2.ts"; +import { roll as roll3 } from "../algorithms/v3.ts"; +import { roll as roll4 } from "../algorithms/v4.ts"; +import { roll as roll4_sub } from "../algorithms/v4_sub.ts"; +import { GachaMachine } from "../mod.ts"; + +import pokemon from "../testdata/pokemon.json" assert { type: "json" }; + +const items = pokemon.slice(0, 151).map((x) => ({ + result: x.id, + chance: x.tier === "legendary" ? 11 : x.tier === "mythic" ? 1 : 25, +})); + +Deno.bench("nop", () => {}); + +Deno.bench("Algorithm V1", () => { + for (let i = 0; i < 1e2; ++i) { + roll1(items); + } +}); + +Deno.bench("Algorithm V2", () => { + for (let i = 0; i < 1e2; ++i) { + roll2(items); + } +}); + +Deno.bench("Algorithm V3", () => { + for (let i = 0; i < 1e2; ++i) { + roll3(items); + } +}); + +Deno.bench("Algorithm V4 _ Sub", () => { + for (let i = 0; i < 1e2; ++i) { + roll4_sub(items); + } +}); + +Deno.bench("Algorithm V4", () => { + for (let i = 0; i < 1e2; ++i) { + roll4(items); + } +}); + +Deno.bench("Algorithm V4 in Fortuna", () => { + for (let i = 0; i < 1e2; ++i) { + GachaMachine.roll(items); + } +}); diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..97d361e --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "tasks": { + "bench": "deno bench --unstable benches/100k_rolls.ts", + "test": "deno run --allow-hrtime test.ts" + } +} \ No newline at end of file diff --git a/mod.js b/mod.js index ab5ea87..9196e44 100644 --- a/mod.js +++ b/mod.js @@ -15,7 +15,7 @@ class GachaMachine { return Array.from(this.items.reduce((acc, val)=>acc.add(val.tier), new Set()).entries()).map((x)=>x[0]); } configItems(items) { - let newItems = items = items.sort((a, b)=>a.tier - b.tier).map((x)=>({ + const newItems = items = items.sort((a, b)=>a.tier - b.tier).map((x)=>({ chance: x.chance, result: x.result, tier: x.tier @@ -23,7 +23,7 @@ class GachaMachine { this.items = newItems; } configTiers(items) { - let tiers = []; + const tiers = []; const pool = this.pool; for(let i = 0; i < pool.length; ++i){ tiers[pool[i]] = { @@ -37,21 +37,17 @@ class GachaMachine { tiers[items[i1 - 1].tier].items += 1; tiers[items[i1 - 1].tier].chance += items[i1 - 1].chance; } - let tierList = []; - for(let i2 in tiers){ - tierList.push(tiers[i2]); - } - this.tiers = tierList; + this.tiers = tiers; } get(num = 1, detailed = false, pool = this.pool) { if (detailed) { - let result = []; + const result = []; for(let i = num; i > 0; --i){ result.push(this.choose(pool, detailed)); } return result; } else { - let result1 = []; + const result1 = []; for(let i1 = num; i1 > 0; --i1){ result1.push(this.choose(pool)); } @@ -59,22 +55,29 @@ class GachaMachine { } } choose(pool = this.pool, detailed) { - let tier = GachaMachine.roll(this.tiers.filter((x)=>pool.includes(x.tier)).map((x)=>({ + const tier = GachaMachine.roll(this.tiers.filter((x)=>pool.includes(x.tier)).map((x)=>({ chance: x.chance, result: x.tier }))); const result = GachaMachine.roll(this.items.filter((x)=>x.tier == tier.result)); return detailed ? result : result.result; } - static roll(choices) { - const total = choices.reduce((acc, val)=>acc + val.chance, 0); - let result = Math.random() * total; + static roll(choices, totalChance) { + let total = totalChance || 0; + let i = 0; + while(i < choices.length){ + total += choices[i].chance; + i += 1; + } + const result = Math.random() * total; let going = 0.0; - for(let i = 0; i < choices.length; ++i){ + i = 0; + while(i < choices.length){ going += choices[i].chance; if (result < going) { return choices[i]; } + i += 1; } return choices[Math.floor(Math.random() * choices.length)]; } diff --git a/mod.ts b/mod.ts index f06bb43..f71b74e 100644 --- a/mod.ts +++ b/mod.ts @@ -141,17 +141,21 @@ export class GachaMachine { choices: GachaChoice[], totalChance?: number, ): GachaChoice { - const total = totalChance || choices.reduce( - (acc: number, val: GachaChoice) => acc + val.chance, - 0, - ); + let total = totalChance || 0; + let i = 0; + while (i < choices.length) { + total += choices[i].chance; + i += 1; + } const result = Math.random() * total; let going = 0.0; - for (let i = 0; i < choices.length; ++i) { + i = 0; + while (i < choices.length) { going += choices[i].chance; if (result < going) { return choices[i]; } + i += 1; } return choices[Math.floor(Math.random() * choices.length)]; } diff --git a/test.ts b/test.ts index 0fa6806..53bfb20 100644 --- a/test.ts +++ b/test.ts @@ -24,6 +24,7 @@ const items = pokemon.slice(0, 151).map((x) => ({ weight: x.tier === "legendary" ? 10 : x.tier === "mythic" ? 1 : 25, })); + const timeConfig = performance.now(); const machine = new GachaMachine(items); @@ -91,3 +92,4 @@ console.log( ["s", "ms", "us", "ns"], ), ); +// console.log(items.map(x => `GachaChoice {${JSON.stringify(x)}}`).join(",\n"))