@allemandi/gacha-engine 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gacha-engine.d.ts +16 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.module.js +2 -0
- package/dist/index.module.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/{src/types.ts → dist/types.d.ts} +0 -3
- package/package.json +16 -1
- package/.github/workflows/ci.yml +0 -42
- package/.github/workflows/publish.yml +0 -26
- package/eslint.config.mjs +0 -34
- package/src/gacha-engine.ts +0 -58
- package/src/index.ts +0 -1
- package/test/index.test.ts +0 -111
- package/tsconfig.json +0 -15
- package/vitest.config.ts +0 -8
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { GachaEngineConfig } from './types';
|
|
2
|
+
export declare class GachaEngine {
|
|
3
|
+
private pools;
|
|
4
|
+
private rarityRates;
|
|
5
|
+
constructor({ rarityRates, pools }: GachaEngineConfig);
|
|
6
|
+
getItemDropRate(name: string): number;
|
|
7
|
+
getRarityProbability(rarity: string): number;
|
|
8
|
+
getCumulativeProbabilityForItem(name: string, rolls: number): number;
|
|
9
|
+
getRollsForTargetProbability(name: string, targetProbability: number): number;
|
|
10
|
+
getRateUpItems(): string[];
|
|
11
|
+
getAllItemDropRates(): {
|
|
12
|
+
name: string;
|
|
13
|
+
dropRate: number;
|
|
14
|
+
rarity: string;
|
|
15
|
+
}[];
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n\n constructor({ rarityRates = {}, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n }\n\n getItemDropRate(name: string): number {\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRarityRate = this.rarityRates[pool.rarity] ?? totalPoolProb;\n return (item.probability / totalPoolProb) * baseRarityRate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n const pool = this.pools.find(p => p.rarity === rarity);\n if (!pool) throw new Error(`Rarity \"${rarity}\" not found`);\n\n const totalProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRate = this.rarityRates[rarity] ?? totalProb;\n return baseRate;\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n}\n"],"names":["GachaEngine","_ref","_ref$rarityRates","rarityRates","pools","this","_proto","prototype","getItemDropRate","name","_step","_iterator","_createForOfIteratorHelperLoose","done","pool","value","item","items","find","i","_this$rarityRates$poo","totalPoolProb","reduce","sum","probability","baseRarityRate","rarity","Error","getRarityProbability","_this$rarityRates$rar","p","totalProb","getCumulativeProbabilityForItem","rolls","rate","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","filter","rateUp","map","getAllItemDropRates","_this","dropRate"],"mappings":"uIACwB,WAIpB,SAAAA,EAAAC,GAA0DC,IAAAA,EAAAD,EAA5CE,YAAAA,OAAW,IAAAD,EAAG,CAAE,EAAAA,EAAEE,EAAKH,EAALG,WAHxBA,WAAK,EAAAC,KACLF,iBAAW,EAGfE,KAAKD,MAAQA,EACbC,KAAKF,YAAcA,CACvB,CAAC,IAAAG,EAAAN,EAAAO,UAgDA,OAhDAD,EAEDE,gBAAA,SAAgBC,GACZ,IAAA,IAA6BC,EAA7BC,2pBAAAC,CAAmBP,KAAKD,SAAKM,EAAAC,KAAAE,MAAE,CAAA,IAApBC,EAAIJ,EAAAK,MACLC,EAAOF,EAAKG,MAAMC,KAAK,SAAAC,GAAK,OAAAA,EAAEV,OAASA,CAAI,GACjD,GAAIO,EAAM,CAAA,IAAAI,EACAC,EAAgBP,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAC,OAAKI,EAAMJ,EAAEK,WAAW,EAAE,GACnEC,EAA8CL,OAAhCA,EAAGf,KAAKF,YAAYW,EAAKY,SAAON,EAAIC,EACxD,OAAQL,EAAKQ,YAAcH,EAAiBI,CAChD,CACJ,CACA,MAAU,IAAAE,MAAK,SAAUlB,EAAI,cACjC,EAACH,EAEDsB,qBAAA,SAAqBF,GAAc,IAAAG,EACzBf,EAAOT,KAAKD,MAAMc,KAAK,SAAAY,GAAC,OAAIA,EAAEJ,SAAWA,CAAM,GACrD,IAAKZ,EAAM,MAAU,IAAAa,MAAK,WAAYD,EAAM,eAE5C,IAAMK,EAAYjB,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAM,OAAAI,EAAMJ,EAAEK,WAAW,EAAE,GAErE,OADyCK,OAA3BA,EAAGxB,KAAKF,YAAYuB,IAAOG,EAAIE,CAEjD,EAACzB,EAED0B,gCAAA,SAAgCvB,EAAcwB,GAC1C,IAAMC,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAQ,EAAG0B,KAAKC,IAAI,EAAIF,EAAMD,EAClC,EAAC3B,EAED+B,6BAAA,SAA6B5B,EAAc6B,GACvC,IAAMJ,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAIyB,GAAQ,EAAUK,SACfJ,KAAKK,KAAKL,KAAKM,IAAI,EAAIH,GAAqBH,KAAKM,IAAI,EAAIP,GACpE,EAAC5B,EAEDoC,eAAA,WACI,OAAWrC,KAACD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM2B,OAAO,SAAAzB,GAAC,OAAIA,EAAE0B,MAAM,GAAEC,IAAI,SAAA3B,GAAC,OAAIA,EAAEV,IAAI,EAAC,EAEtD,EAACH,EAEDyC,oBAAA,WAAmB,IAAAC,EAAA3C,KACf,OAAOA,KAAKD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM6B,IAAI,SAAA3B,GAAM,MAAA,CACdV,KAAMU,EAAEV,KACRwC,SAAUD,EAAKxC,gBAAgBW,EAAEV,MACjCiB,OAAQI,EAAEJ,OACb,EAAE,EAEX,EAAC1B,CAAA,CAvDmB"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { GachaEngine } from "./gacha-engine";
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function t(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}var r=/*#__PURE__*/function(){function r(t){var r=t.rarityRates,e=void 0===r?{}:r,n=t.pools;this.pools=void 0,this.rarityRates=void 0,this.pools=n,this.rarityRates=e}var e=r.prototype;return e.getItemDropRate=function(r){for(var e,n=function(r){var e="undefined"!=typeof Symbol&&r[Symbol.iterator]||r["@@iterator"];if(e)return(e=e.call(r)).next.bind(e);if(Array.isArray(r)||(e=function(r,e){if(r){if("string"==typeof r)return t(r,e);var n={}.toString.call(r).slice(8,-1);return"Object"===n&&r.constructor&&(n=r.constructor.name),"Map"===n||"Set"===n?Array.from(r):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?t(r,e):void 0}}(r))){e&&(r=e);var n=0;return function(){return n>=r.length?{done:!0}:{done:!1,value:r[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(this.pools);!(e=n()).done;){var o=e.value,i=o.items.find(function(t){return t.name===r});if(i){var a,u=o.items.reduce(function(t,r){return t+r.probability},0),l=null!=(a=this.rarityRates[o.rarity])?a:u;return i.probability/u*l}}throw new Error('Item "'+r+'" not found')},e.getRarityProbability=function(t){var r,e=this.pools.find(function(r){return r.rarity===t});if(!e)throw new Error('Rarity "'+t+'" not found');var n=e.items.reduce(function(t,r){return t+r.probability},0);return null!=(r=this.rarityRates[t])?r:n},e.getCumulativeProbabilityForItem=function(t,r){var e=this.getItemDropRate(t);return 1-Math.pow(1-e,r)},e.getRollsForTargetProbability=function(t,r){var e=this.getItemDropRate(t);return e<=0?Infinity:Math.ceil(Math.log(1-r)/Math.log(1-e))},e.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},e.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},r}();export{r as GachaEngine};
|
|
2
|
+
//# sourceMappingURL=index.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.module.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n\n constructor({ rarityRates = {}, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n }\n\n getItemDropRate(name: string): number {\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRarityRate = this.rarityRates[pool.rarity] ?? totalPoolProb;\n return (item.probability / totalPoolProb) * baseRarityRate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n const pool = this.pools.find(p => p.rarity === rarity);\n if (!pool) throw new Error(`Rarity \"${rarity}\" not found`);\n\n const totalProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRate = this.rarityRates[rarity] ?? totalProb;\n return baseRate;\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n}\n"],"names":["GachaEngine","_ref","_ref$rarityRates","rarityRates","pools","this","_proto","prototype","getItemDropRate","name","_step","_iterator","_createForOfIteratorHelperLoose","done","pool","value","item","items","find","i","_this$rarityRates$poo","totalPoolProb","reduce","sum","probability","baseRarityRate","rarity","Error","getRarityProbability","_this$rarityRates$rar","p","totalProb","getCumulativeProbabilityForItem","rolls","rate","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","filter","rateUp","map","getAllItemDropRates","_this","dropRate"],"mappings":"sGACa,IAAAA,eAAW,WAIpB,SAAAA,EAAAC,GAA0DC,IAAAA,EAAAD,EAA5CE,YAAAA,OAAW,IAAAD,EAAG,CAAE,EAAAA,EAAEE,EAAKH,EAALG,WAHxBA,WAAK,EAAAC,KACLF,iBAAW,EAGfE,KAAKD,MAAQA,EACbC,KAAKF,YAAcA,CACvB,CAAC,IAAAG,EAAAN,EAAAO,UAgDA,OAhDAD,EAEDE,gBAAA,SAAgBC,GACZ,IAAA,IAA6BC,EAA7BC,2pBAAAC,CAAmBP,KAAKD,SAAKM,EAAAC,KAAAE,MAAE,CAAA,IAApBC,EAAIJ,EAAAK,MACLC,EAAOF,EAAKG,MAAMC,KAAK,SAAAC,GAAK,OAAAA,EAAEV,OAASA,CAAI,GACjD,GAAIO,EAAM,CAAA,IAAAI,EACAC,EAAgBP,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAC,OAAKI,EAAMJ,EAAEK,WAAW,EAAE,GACnEC,EAA8CL,OAAhCA,EAAGf,KAAKF,YAAYW,EAAKY,SAAON,EAAIC,EACxD,OAAQL,EAAKQ,YAAcH,EAAiBI,CAChD,CACJ,CACA,MAAU,IAAAE,MAAK,SAAUlB,EAAI,cACjC,EAACH,EAEDsB,qBAAA,SAAqBF,GAAc,IAAAG,EACzBf,EAAOT,KAAKD,MAAMc,KAAK,SAAAY,GAAC,OAAIA,EAAEJ,SAAWA,CAAM,GACrD,IAAKZ,EAAM,MAAU,IAAAa,MAAK,WAAYD,EAAM,eAE5C,IAAMK,EAAYjB,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAM,OAAAI,EAAMJ,EAAEK,WAAW,EAAE,GAErE,OADyCK,OAA3BA,EAAGxB,KAAKF,YAAYuB,IAAOG,EAAIE,CAEjD,EAACzB,EAED0B,gCAAA,SAAgCvB,EAAcwB,GAC1C,IAAMC,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAQ,EAAG0B,KAAKC,IAAI,EAAIF,EAAMD,EAClC,EAAC3B,EAED+B,6BAAA,SAA6B5B,EAAc6B,GACvC,IAAMJ,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAIyB,GAAQ,EAAUK,SACfJ,KAAKK,KAAKL,KAAKM,IAAI,EAAIH,GAAqBH,KAAKM,IAAI,EAAIP,GACpE,EAAC5B,EAEDoC,eAAA,WACI,OAAWrC,KAACD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM2B,OAAO,SAAAzB,GAAC,OAAIA,EAAE0B,MAAM,GAAEC,IAAI,SAAA3B,GAAC,OAAIA,EAAEV,IAAI,EAAC,EAEtD,EAACH,EAEDyC,oBAAA,WAAmB,IAAAC,EAAA3C,KACf,OAAOA,KAAKD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM6B,IAAI,SAAA3B,GAAM,MAAA,CACdV,KAAMU,EAAEV,KACRwC,SAAUD,EAAKxC,gBAAgBW,EAAEV,MACjCiB,OAAQI,EAAEJ,OACb,EAAE,EAEX,EAAC1B,CAAA,CAvDmB"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
!function(t,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports):"function"==typeof define&&define.amd?define(["exports"],r):r((t||self).AllemandiGachaEngine={})}(this,function(t){function r(t,r){(null==r||r>t.length)&&(r=t.length);for(var e=0,n=Array(r);e<r;e++)n[e]=t[e];return n}t.GachaEngine=/*#__PURE__*/function(){function t(t){var r=t.rarityRates,e=void 0===r?{}:r,n=t.pools;this.pools=void 0,this.rarityRates=void 0,this.pools=n,this.rarityRates=e}var e=t.prototype;return e.getItemDropRate=function(t){for(var e,n=function(t){var e="undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(e)return(e=e.call(t)).next.bind(e);if(Array.isArray(t)||(e=function(t,e){if(t){if("string"==typeof t)return r(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):void 0}}(t))){e&&(t=e);var n=0;return function(){return n>=t.length?{done:!0}:{done:!1,value:t[n++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(this.pools);!(e=n()).done;){var o=e.value,i=o.items.find(function(r){return r.name===t});if(i){var a,u=o.items.reduce(function(t,r){return t+r.probability},0),l=null!=(a=this.rarityRates[o.rarity])?a:u;return i.probability/u*l}}throw new Error('Item "'+t+'" not found')},e.getRarityProbability=function(t){var r,e=this.pools.find(function(r){return r.rarity===t});if(!e)throw new Error('Rarity "'+t+'" not found');var n=e.items.reduce(function(t,r){return t+r.probability},0);return null!=(r=this.rarityRates[t])?r:n},e.getCumulativeProbabilityForItem=function(t,r){var e=this.getItemDropRate(t);return 1-Math.pow(1-e,r)},e.getRollsForTargetProbability=function(t,r){var e=this.getItemDropRate(t);return e<=0?Infinity:Math.ceil(Math.log(1-r)/Math.log(1-e))},e.getRateUpItems=function(){return this.pools.flatMap(function(t){return t.items.filter(function(t){return t.rateUp}).map(function(t){return t.name})})},e.getAllItemDropRates=function(){var t=this;return this.pools.flatMap(function(r){return r.items.map(function(e){return{name:e.name,dropRate:t.getItemDropRate(e.name),rarity:r.rarity}})})},t}()});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/gacha-engine.ts"],"sourcesContent":["import { RarityInput, GachaEngineConfig } from './types';\nexport class GachaEngine {\n private pools: RarityInput[];\n private rarityRates: Record<string, number>;\n\n constructor({ rarityRates = {}, pools }: GachaEngineConfig) {\n this.pools = pools;\n this.rarityRates = rarityRates;\n }\n\n getItemDropRate(name: string): number {\n for (const pool of this.pools) {\n const item = pool.items.find(i => i.name === name);\n if (item) {\n const totalPoolProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRarityRate = this.rarityRates[pool.rarity] ?? totalPoolProb;\n return (item.probability / totalPoolProb) * baseRarityRate;\n }\n }\n throw new Error(`Item \"${name}\" not found`);\n }\n\n getRarityProbability(rarity: string): number {\n const pool = this.pools.find(p => p.rarity === rarity);\n if (!pool) throw new Error(`Rarity \"${rarity}\" not found`);\n\n const totalProb = pool.items.reduce((sum, i) => sum + i.probability, 0);\n const baseRate = this.rarityRates[rarity] ?? totalProb;\n return baseRate;\n }\n\n getCumulativeProbabilityForItem(name: string, rolls: number): number {\n const rate = this.getItemDropRate(name);\n return 1 - Math.pow(1 - rate, rolls);\n }\n\n getRollsForTargetProbability(name: string, targetProbability: number): number {\n const rate = this.getItemDropRate(name);\n if (rate <= 0) return Infinity;\n return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));\n }\n\n getRateUpItems(): string[] {\n return this.pools.flatMap(p =>\n p.items.filter(i => i.rateUp).map(i => i.name)\n );\n }\n\n getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {\n return this.pools.flatMap(p =>\n p.items.map(i => ({\n name: i.name,\n dropRate: this.getItemDropRate(i.name),\n rarity: p.rarity\n }))\n );\n }\n}\n"],"names":["GachaEngine","_ref","_ref$rarityRates","rarityRates","pools","this","_proto","prototype","getItemDropRate","name","_step","_iterator","_createForOfIteratorHelperLoose","done","pool","value","item","items","find","i","_this$rarityRates$poo","totalPoolProb","reduce","sum","probability","baseRarityRate","rarity","Error","getRarityProbability","_this$rarityRates$rar","p","totalProb","getCumulativeProbabilityForItem","rolls","rate","Math","pow","getRollsForTargetProbability","targetProbability","Infinity","ceil","log","getRateUpItems","flatMap","filter","rateUp","map","getAllItemDropRates","_this","dropRate"],"mappings":"+WACwB,WAIpB,SAAAA,EAAAC,GAA0DC,IAAAA,EAAAD,EAA5CE,YAAAA,OAAW,IAAAD,EAAG,CAAE,EAAAA,EAAEE,EAAKH,EAALG,WAHxBA,WAAK,EAAAC,KACLF,iBAAW,EAGfE,KAAKD,MAAQA,EACbC,KAAKF,YAAcA,CACvB,CAAC,IAAAG,EAAAN,EAAAO,UAgDA,OAhDAD,EAEDE,gBAAA,SAAgBC,GACZ,IAAA,IAA6BC,EAA7BC,2pBAAAC,CAAmBP,KAAKD,SAAKM,EAAAC,KAAAE,MAAE,CAAA,IAApBC,EAAIJ,EAAAK,MACLC,EAAOF,EAAKG,MAAMC,KAAK,SAAAC,GAAK,OAAAA,EAAEV,OAASA,CAAI,GACjD,GAAIO,EAAM,CAAA,IAAAI,EACAC,EAAgBP,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAC,OAAKI,EAAMJ,EAAEK,WAAW,EAAE,GACnEC,EAA8CL,OAAhCA,EAAGf,KAAKF,YAAYW,EAAKY,SAAON,EAAIC,EACxD,OAAQL,EAAKQ,YAAcH,EAAiBI,CAChD,CACJ,CACA,MAAU,IAAAE,MAAK,SAAUlB,EAAI,cACjC,EAACH,EAEDsB,qBAAA,SAAqBF,GAAc,IAAAG,EACzBf,EAAOT,KAAKD,MAAMc,KAAK,SAAAY,GAAC,OAAIA,EAAEJ,SAAWA,CAAM,GACrD,IAAKZ,EAAM,MAAU,IAAAa,MAAK,WAAYD,EAAM,eAE5C,IAAMK,EAAYjB,EAAKG,MAAMK,OAAO,SAACC,EAAKJ,GAAM,OAAAI,EAAMJ,EAAEK,WAAW,EAAE,GAErE,OADyCK,OAA3BA,EAAGxB,KAAKF,YAAYuB,IAAOG,EAAIE,CAEjD,EAACzB,EAED0B,gCAAA,SAAgCvB,EAAcwB,GAC1C,IAAMC,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAQ,EAAG0B,KAAKC,IAAI,EAAIF,EAAMD,EAClC,EAAC3B,EAED+B,6BAAA,SAA6B5B,EAAc6B,GACvC,IAAMJ,EAAO7B,KAAKG,gBAAgBC,GAClC,OAAIyB,GAAQ,EAAUK,SACfJ,KAAKK,KAAKL,KAAKM,IAAI,EAAIH,GAAqBH,KAAKM,IAAI,EAAIP,GACpE,EAAC5B,EAEDoC,eAAA,WACI,OAAWrC,KAACD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM2B,OAAO,SAAAzB,GAAC,OAAIA,EAAE0B,MAAM,GAAEC,IAAI,SAAA3B,GAAC,OAAIA,EAAEV,IAAI,EAAC,EAEtD,EAACH,EAEDyC,oBAAA,WAAmB,IAAAC,EAAA3C,KACf,OAAOA,KAAKD,MAAMuC,QAAQ,SAAAb,GACtB,OAAAA,EAAEb,MAAM6B,IAAI,SAAA3B,GAAM,MAAA,CACdV,KAAMU,EAAEV,KACRwC,SAAUD,EAAKxC,gBAAgBW,EAAEV,MACjCiB,OAAQI,EAAEJ,OACb,EAAE,EAEX,EAAC1B,CAAA,CAvDmB"}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
|
|
2
1
|
export interface RarityInput {
|
|
3
2
|
rarity: string;
|
|
4
3
|
items: GachaItem[];
|
|
5
4
|
}
|
|
6
|
-
|
|
7
5
|
export interface GachaItem {
|
|
8
6
|
name: string;
|
|
9
7
|
probability: number;
|
|
10
8
|
rateUp?: boolean;
|
|
11
9
|
}
|
|
12
|
-
|
|
13
10
|
export interface GachaEngineConfig {
|
|
14
11
|
rarityRates?: Record<string, number>;
|
|
15
12
|
pools: RarityInput[];
|
package/package.json
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@allemandi/gacha-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Practical, type-safe toolkit for simulating and understanding gacha rates and rate-ups.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.module.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"unpkg": "dist/index.umd.js",
|
|
9
9
|
"source": "src/index.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.module.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE"
|
|
24
|
+
],
|
|
10
25
|
"repository": "https://github.com/allemandi/gacha-engine.git",
|
|
11
26
|
"author": "allemandi <69766017+allemandi@users.noreply.github.com>",
|
|
12
27
|
"license": "MIT",
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
lint:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/checkout@v3
|
|
13
|
-
- uses: actions/setup-node@v3
|
|
14
|
-
with:
|
|
15
|
-
node-version: 18
|
|
16
|
-
cache: 'yarn'
|
|
17
|
-
- run: yarn install --frozen-lockfile
|
|
18
|
-
- run: yarn lint
|
|
19
|
-
|
|
20
|
-
test:
|
|
21
|
-
needs: lint
|
|
22
|
-
runs-on: ubuntu-latest
|
|
23
|
-
steps:
|
|
24
|
-
- uses: actions/checkout@v3
|
|
25
|
-
- uses: actions/setup-node@v3
|
|
26
|
-
with:
|
|
27
|
-
node-version: 18
|
|
28
|
-
cache: 'yarn'
|
|
29
|
-
- run: yarn install --frozen-lockfile
|
|
30
|
-
- run: yarn test
|
|
31
|
-
|
|
32
|
-
build:
|
|
33
|
-
needs: test
|
|
34
|
-
runs-on: ubuntu-latest
|
|
35
|
-
steps:
|
|
36
|
-
- uses: actions/checkout@v3
|
|
37
|
-
- uses: actions/setup-node@v3
|
|
38
|
-
with:
|
|
39
|
-
node-version: 18
|
|
40
|
-
cache: 'yarn'
|
|
41
|
-
- run: yarn install --frozen-lockfile
|
|
42
|
-
- run: yarn build
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
name: Publish to NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*.*.*'
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
publish:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: '18'
|
|
18
|
-
registry-url: 'https://registry.npmjs.org/'
|
|
19
|
-
cache: 'yarn'
|
|
20
|
-
|
|
21
|
-
- run: yarn install --frozen-lockfile
|
|
22
|
-
- run: yarn build
|
|
23
|
-
|
|
24
|
-
- run: yarn publish --non-interactive --access public
|
|
25
|
-
env:
|
|
26
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/eslint.config.mjs
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from "eslint/config";
|
|
2
|
-
import typescriptEslint from "@typescript-eslint/eslint-plugin";
|
|
3
|
-
import tsParser from "@typescript-eslint/parser";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import js from "@eslint/js";
|
|
7
|
-
import { FlatCompat } from "@eslint/eslintrc";
|
|
8
|
-
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = path.dirname(__filename);
|
|
11
|
-
const compat = new FlatCompat({
|
|
12
|
-
baseDirectory: __dirname,
|
|
13
|
-
recommendedConfig: js.configs.recommended,
|
|
14
|
-
allConfig: js.configs.all
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export default defineConfig([{
|
|
18
|
-
|
|
19
|
-
ignores: ["dist/**"],
|
|
20
|
-
|
|
21
|
-
extends: compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended"),
|
|
22
|
-
|
|
23
|
-
plugins: {
|
|
24
|
-
"@typescript-eslint": typescriptEslint,
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
languageOptions: {
|
|
28
|
-
parser: tsParser,
|
|
29
|
-
ecmaVersion: 2020,
|
|
30
|
-
sourceType: "module",
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
rules: {},
|
|
34
|
-
}]);
|
package/src/gacha-engine.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { RarityInput, GachaEngineConfig } from './types';
|
|
2
|
-
export class GachaEngine {
|
|
3
|
-
private pools: RarityInput[];
|
|
4
|
-
private rarityRates: Record<string, number>;
|
|
5
|
-
|
|
6
|
-
constructor({ rarityRates = {}, pools }: GachaEngineConfig) {
|
|
7
|
-
this.pools = pools;
|
|
8
|
-
this.rarityRates = rarityRates;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
getItemDropRate(name: string): number {
|
|
12
|
-
for (const pool of this.pools) {
|
|
13
|
-
const item = pool.items.find(i => i.name === name);
|
|
14
|
-
if (item) {
|
|
15
|
-
const totalPoolProb = pool.items.reduce((sum, i) => sum + i.probability, 0);
|
|
16
|
-
const baseRarityRate = this.rarityRates[pool.rarity] ?? totalPoolProb;
|
|
17
|
-
return (item.probability / totalPoolProb) * baseRarityRate;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
throw new Error(`Item "${name}" not found`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
getRarityProbability(rarity: string): number {
|
|
24
|
-
const pool = this.pools.find(p => p.rarity === rarity);
|
|
25
|
-
if (!pool) throw new Error(`Rarity "${rarity}" not found`);
|
|
26
|
-
|
|
27
|
-
const totalProb = pool.items.reduce((sum, i) => sum + i.probability, 0);
|
|
28
|
-
const baseRate = this.rarityRates[rarity] ?? totalProb;
|
|
29
|
-
return baseRate;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
getCumulativeProbabilityForItem(name: string, rolls: number): number {
|
|
33
|
-
const rate = this.getItemDropRate(name);
|
|
34
|
-
return 1 - Math.pow(1 - rate, rolls);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
getRollsForTargetProbability(name: string, targetProbability: number): number {
|
|
38
|
-
const rate = this.getItemDropRate(name);
|
|
39
|
-
if (rate <= 0) return Infinity;
|
|
40
|
-
return Math.ceil(Math.log(1 - targetProbability) / Math.log(1 - rate));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getRateUpItems(): string[] {
|
|
44
|
-
return this.pools.flatMap(p =>
|
|
45
|
-
p.items.filter(i => i.rateUp).map(i => i.name)
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
getAllItemDropRates(): { name: string; dropRate: number; rarity: string }[] {
|
|
50
|
-
return this.pools.flatMap(p =>
|
|
51
|
-
p.items.map(i => ({
|
|
52
|
-
name: i.name,
|
|
53
|
-
dropRate: this.getItemDropRate(i.name),
|
|
54
|
-
rarity: p.rarity
|
|
55
|
-
}))
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { GachaEngine } from "./gacha-engine";
|
package/test/index.test.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { GachaEngine } from '../src/gacha-engine';
|
|
3
|
-
import type { RarityInput, GachaEngineConfig } from '../src/types';
|
|
4
|
-
|
|
5
|
-
const mockPools: RarityInput[] = [
|
|
6
|
-
{
|
|
7
|
-
rarity: 'common',
|
|
8
|
-
items: [
|
|
9
|
-
{ name: 'ItemA', probability: 0.5 },
|
|
10
|
-
{ name: 'ItemB', probability: 0.5 },
|
|
11
|
-
],
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
rarity: 'rare',
|
|
15
|
-
items: [
|
|
16
|
-
{ name: 'ItemC', probability: 0.7 },
|
|
17
|
-
{ name: 'ItemD', probability: 0.3, rateUp: true },
|
|
18
|
-
],
|
|
19
|
-
},
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const config: GachaEngineConfig = {
|
|
23
|
-
rarityRates: {
|
|
24
|
-
common: 0.8,
|
|
25
|
-
rare: 0.2,
|
|
26
|
-
},
|
|
27
|
-
pools: mockPools,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
describe('GachaEngine', () => {
|
|
31
|
-
const engine = new GachaEngine(config);
|
|
32
|
-
|
|
33
|
-
describe('getItemDropRate', () => {
|
|
34
|
-
it('should return correct drop rate using rarityRates', () => {
|
|
35
|
-
expect(engine.getItemDropRate('ItemA')).toBeCloseTo(0.4); // 0.5 * 0.8
|
|
36
|
-
expect(engine.getItemDropRate('ItemD')).toBeCloseTo(0.06); // 0.3 * 0.2
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('throws if item does not exist', () => {
|
|
40
|
-
expect(() => engine.getItemDropRate('Unknown')).toThrow('Item "Unknown" not found');
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('getRarityProbability', () => {
|
|
45
|
-
it('should return correct base rarity rate', () => {
|
|
46
|
-
expect(engine.getRarityProbability('common')).toBe(0.8);
|
|
47
|
-
expect(engine.getRarityProbability('rare')).toBe(0.2);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('throws if rarity not found', () => {
|
|
51
|
-
expect(() => engine.getRarityProbability('epic')).toThrow('Rarity "epic" not found');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('getCumulativeProbabilityForItem', () => {
|
|
56
|
-
it('should calculate cumulative probability correctly', () => {
|
|
57
|
-
const dropRate = engine.getItemDropRate('ItemA'); // 0.4
|
|
58
|
-
const rolls = 3;
|
|
59
|
-
const expected = 1 - Math.pow(1 - dropRate, rolls);
|
|
60
|
-
expect(engine.getCumulativeProbabilityForItem('ItemA', rolls)).toBeCloseTo(expected);
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('getRollsForTargetProbability', () => {
|
|
65
|
-
it('should calculate rolls to reach target probability', () => {
|
|
66
|
-
const target = 0.9;
|
|
67
|
-
const rate = engine.getItemDropRate('ItemA'); // 0.4
|
|
68
|
-
const expected = Math.ceil(Math.log(1 - target) / Math.log(1 - rate));
|
|
69
|
-
expect(engine.getRollsForTargetProbability('ItemA', target)).toBe(expected);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('returns Infinity if drop rate is zero', () => {
|
|
73
|
-
const zeroRateEngine = new GachaEngine({
|
|
74
|
-
pools: [{
|
|
75
|
-
rarity: 'none',
|
|
76
|
-
items: [
|
|
77
|
-
{ name: 'NeverDrops', probability: 0 },
|
|
78
|
-
{ name: 'Other', probability: 1 },
|
|
79
|
-
],
|
|
80
|
-
}],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(zeroRateEngine.getRollsForTargetProbability('NeverDrops', 0.5)).toBe(Infinity);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('getRateUpItems', () => {
|
|
88
|
-
it('returns only rate-up item names', () => {
|
|
89
|
-
expect(engine.getRateUpItems()).toEqual(['ItemD']);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('returns empty array if no rate-up items', () => {
|
|
93
|
-
const noRateUpEngine = new GachaEngine({
|
|
94
|
-
pools: [{
|
|
95
|
-
rarity: 'common',
|
|
96
|
-
items: [{ name: 'NoRateUp', probability: 1 }],
|
|
97
|
-
}],
|
|
98
|
-
});
|
|
99
|
-
expect(noRateUpEngine.getRateUpItems()).toEqual([]);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe('getAllItemDropRates', () => {
|
|
104
|
-
it('returns all items with correct drop rates and rarities', () => {
|
|
105
|
-
const results = engine.getAllItemDropRates();
|
|
106
|
-
expect(results).toContainEqual({ name: 'ItemA', dropRate: 0.4, rarity: 'common' });
|
|
107
|
-
expect(results).toContainEqual({ name: 'ItemD', dropRate: 0.06, rarity: 'rare' });
|
|
108
|
-
expect(results).toHaveLength(4);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ESNext",
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"moduleResolution": "Node",
|
|
6
|
-
"declaration": true,
|
|
7
|
-
"outDir": "dist",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["src"],
|
|
14
|
-
"exclude": ["node_modules", "dist", "test"]
|
|
15
|
-
}
|