npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@codianz/gacha

v0.0.4

Published

lottery algorithm.

Downloads

3

Readme

抽選ライブラリ

特徴

  • 抽選処理はサービスの利益率に絡む重要箇所なので、効率よりも型の安全性コードのメンテナンス性を重視しています。
  • デジスロやデジバチのようなギャンブル機とは異なり、抽選結果が分散されているため、確率を重視して抽選を行います。(射幸心を煽る仕掛けは含まれていません)

景品定義

景品には WINSLOSES に分類され、それぞれ、当選落選 に対応します。 具体的には下記のように定義してください。(トランスパイルでエラーになるからといって、血迷っても anyas なんかは使わないでください。)

const wins = ["ハワイ旅行", "松", "竹", "梅"] as const;
const loses = ["ドリンクバー無料", "次回100円無料"] as const;

WINSLOSES の違い

  • WINS は在庫管理を行います。確率により当選可能商品だとしても、該当商品の在庫が存在しない場合には除外されます。
  • LOSES は在庫管理を行いません。(Repository<WINS, LOSES>を参照)

抽選の設定

  • Configure<WINS, LOSES>当選商品落選商品確率などを保持します。
  • WINSLOSES を定義します。(内部ではストリングリテラル型とその配列で保持します)
  • 各確率を定義します。(百分率ではないので、割合で定義して構いません)
  • gain は内部で当選確率を算出するためのオフセット値になります。0 以上を指定してください。
  • ヒストグラムの取得では、ratioの合計数値 × gain 分のデータを取得します。したがって、gainを上げすぎると抽選処理のパフォーマンスに影響が出てきます。
import * as gacha from "@codianz/gacha";

const wins = ["gold", "silver", "bronze"] as const;
const loses = ["none1", "none2"] as const;

const config = new gacha.Configure(
  wins,
  loses,
  {
    gold: 1,
    silver: 20,
    bronze: 30,
    none1: 20,
    none2: 50
  },
  1000
);

データアクセス

  • Repository<WINS, LOSES> に、過去の抽選結果現在の在庫数 の取得要求に対して応答するクラスです。
  • Repository<WINS, LOSES>interface なので、実装が必要です。
  • 実際にはデータベースやストレージなどと連携することになります。

getPastResultHistogram(n: number): Promise<{ [_ in WINS | LOSES]: number; }>

直近 n 個のデータからヒストグラムを返却します。

getStocks(): Promise<{ [_ in WINS]: number; }>

  • 現在の在庫数を返却します。
  • これはWINSのみです。
  • LOSES は在庫管理対象外です。
  • 在庫管理を行わず、確率だけで管理する場合は、全て1以上の値を固定値として返却することで実現可能です。
import * as gacha from "@codianz/gacha";

class SampleRepo implements gacha.Repository<wins_t, loses_t> {
  private m_data: Array<wins_t | loses_t> = [];

  getPastResultHistogram(n: number): Promise<{ [_ in wins_t | loses_t]: number; }> {
    const data = this.m_data.slice(-n);
    return Promise.resolve({
        gold: data.filter((v) => v === "gold").length,
        silver: data.filter((v) => v === "silver").length,
        bronze: data.filter((v) => v === "bronze").length,
        none1: data.filter((v) => v === "none1").length,
        none2: data.filter((v) => v === "none2").length,
    });
  }

  getStocks(): Promise<{ [_ in wins_t]: number; }> {
    const stocks = this.m_stocks;
    return Promise.resolve(stocks ? { ...stocks } : {
      gold: 1,
      silver: 1,
      bronze: 1,
    });
  }
};

抽選の実行

  • Engine<WINS, LOSES>を生成して、execute()を実行します。
  • この result に応じて在庫数や、抽選記録を残すのはこのライブラリでは行いません。
  • 抽選実施が同時に発生し在庫確保ができなかった場合は、再度、execute()を実行します。
  • 非同期関数なので、リトライ処理は気をつけてね。
import * as gacha from "@codianz/gacha";

const engine = new gacha.Engine(config, repo);
engine.execute().then(result => {
  /** result に結果が入ります */
});

実行結果サンプル

在庫数未指定・試行回数連続1,000,000 x 10回

| 商品 | 種別 | 当選割合 | 在庫数 | | - | - | - | - | |gold | WINS | 1 | 未指定 | |silver | WINS | 20 | 未指定 | |bronze | WINS | 30 | 未指定 | |none1 | LOSES | 20 | 未指定 | |none2 | LOSES | 50 | 未指定 |

stock unmanaged
ideal {gold: 0.008264462809917356, silver: 0.1652892561983471, bronze: 0.24793388429752067, none1: 0.1652892561983471, none2: 0.4132231404958678}
count#1 {gold: 8281, silver: 165296, bronze: 247926, none1: 165292, none2: 413205}
ratio#1 {gold: 0.008281, silver: 0.165296, bronze: 0.247926, none1: 0.165292, none2: 0.413205}
count#2 {gold: 8279, silver: 165289, bronze: 247931, none1: 165292, none2: 413209}
ratio#2 {gold: 0.008279, silver: 0.165289, bronze: 0.247931, none1: 0.165292, none2: 0.413209}
count#3 {gold: 8279, silver: 165294, bronze: 247928, none1: 165291, none2: 413208}
ratio#3 {gold: 0.008279, silver: 0.165294, bronze: 0.247928, none1: 0.165291, none2: 0.413208}
count#4 {gold: 8278, silver: 165290, bronze: 247929, none1: 165293, none2: 413210}
ratio#4 {gold: 0.008278, silver: 0.16529, bronze: 0.247929, none1: 0.165293, none2: 0.41321}
count#5 {gold: 8283, silver: 165291, bronze: 247930, none1: 165292, none2: 413204}
ratio#5 {gold: 0.008283, silver: 0.165291, bronze: 0.24793, none1: 0.165292, none2: 0.413204}
count#6 {gold: 8281, silver: 165295, bronze: 247930, none1: 165290, none2: 413204}
ratio#6 {gold: 0.008281, silver: 0.165295, bronze: 0.24793, none1: 0.16529, none2: 0.413204}
count#7 {gold: 8278, silver: 165290, bronze: 247932, none1: 165295, none2: 413205}
ratio#7 {gold: 0.008278, silver: 0.16529, bronze: 0.247932, none1: 0.165295, none2: 0.413205}
count#8 {gold: 8279, silver: 165291, bronze: 247932, none1: 165293, none2: 413205}
ratio#8 {gold: 0.008279, silver: 0.165291, bronze: 0.247932, none1: 0.165293, none2: 0.413205}
count#9 {gold: 8282, silver: 165290, bronze: 247933, none1: 165290, none2: 413205}
ratio#9 {gold: 0.008282, silver: 0.16529, bronze: 0.247933, none1: 0.16529, none2: 0.413205}
count#10 {gold: 8278, silver: 165294, bronze: 247928, none1: 165291, none2: 413209}
ratio#10 {gold: 0.008278, silver: 0.165294, bronze: 0.247928, none1: 0.165291, none2: 0.413209}

在庫数指定・試行回数連続1,000,000 x 10回

| 商品 | 種別 | 当選割合 | 在庫数 | | - | - | - | - | |gold | WINS | 1 | 1 | |silver | WINS | 20 | 20 | |bronze | WINS | 30 | 30 | |none1 | LOSES | 20 | 未指定 | |none2 | LOSES | 50 | 未指定 |

stock managed
ideal {gold: 0.008264462809917356, silver: 0.1652892561983471, bronze: 0.24793388429752067, none1: 0.1652892561983471, none2: 0.4132231404958678}
count#1 {gold: 1, silver: 20, bronze: 30, none1: 165318, none2: 412908}
ratio#1 {gold: 0.0000017292750705976547, silver: 0.0000345855014119531, bronze: 0.00005187825211792964, none1: 0.2858802961210631, none2: 0.7140315108503364}
count#2 {gold: 1, silver: 20, bronze: 30, none1: 165402, none2: 413542}
ratio#2 {gold: 0.0000017271306315253154, silver: 0.00003454261263050631, bronze: 0.00005181391894575946, none1: 0.2856708607155502, none2: 0.7142410556222419}
count#3 {gold: 1, silver: 20, bronze: 30, none1: 166016, none2: 412474}
ratio#3 {gold: 0.0000017284859672866746, silver: 0.00003456971934573349, bronze: 0.00005185457901860024, none1: 0.28695632634506457, none2: 0.7129555208706038}
count#4 {gold: 1, silver: 20, bronze: 30, none1: 165577, none2: 413455}
ratio#4 {gold: 0.0000017268681691570983, silver: 0.00003453736338314197, bronze: 0.00005180604507471295, none1: 0.2859296508445249, none2: 0.7139822788788481}
count#5 {gold: 1, silver: 20, bronze: 30, none1: 165870, none2: 413538}
ratio#5 {gold: 0.000001725747637020048, silver: 0.00003451495274040096, bronze: 0.00005177242911060144, none1: 0.2862497605525154, none2: 0.7136622263179966}
count#6 {gold: 1, silver: 20, bronze: 30, none1: 165140, none2: 413264}
ratio#6 {gold: 0.0000017287429445678574, silver: 0.00003457485889135715, bronze: 0.000051862288337035725, none1: 0.285484609865936, none2: 0.714427224243891}
count#7 {gold: 1, silver: 20, bronze: 30, none1: 165262, none2: 412960}
ratio#7 {gold: 0.000001729287032249474, silver: 0.00003458574064498948, bronze: 0.00005187861096748421, none1: 0.2857854335236126, none2: 0.7141263728377427}
count#8 {gold: 1, silver: 20, bronze: 30, none1: 165044, none2: 413559}
ratio#8 {gold: 0.0000017281484272121164, silver: 0.000034562968544242325, bronze: 0.000051844452816363494, none1: 0.28522052902079653, none2: 0.7146913354094157}
count#9 {gold: 1, silver: 20, bronze: 30, none1: 166007, none2: 412511}
ratio#9 {gold: 0.0000017284023167504654, silver: 0.00003456804633500931, bronze: 0.000051852069502513964, none1: 0.2869268833967945, none2: 0.7129849680850512}
count#10 {gold: 1, silver: 20, bronze: 30, none1: 165327, none2: 413936}
ratio#10 {gold: 0.0000017261795848192862, silver: 0.00003452359169638572, bronze: 0.00005178538754457859, none1: 0.28538409221941813, none2: 0.7145278726217561}