[JavaScript]なんでこんなにガチャガチャしたくなるのか。。。

# 基本ゲームは無課金勢
ゲームは好きで課金さえすれば無双できるのはわかっているけど、
いつサ終するかわからないデータにお金かけるくらいなら、
ドッグラン行ったり・水槽買ったり・餌用コオロギ買ったりするほうが、
有意義と思ってしまうくらいゲームへの優先度が年々下がってきてる。。。

# やっぱりガチャ回したくね？
無課金でもアホほどガチャ回したい（ガチャガチャきゅ～と･ふぃぎゅ@メイト
そんな気持ちにお答えして、自分の好きな条件でいっぱい回せるガチャを作ってみた。
過去に[ガチャの話](https://qiita.com/AsilHatake/items/67bb3a78304214546129)してたので、ソレの簡単実装って位置付け

そう言えば、この業界入って死ぬほど残業させられていた頃、
すもももももも（アニメ）で知った　MOSAIC.WAV　の
【キミは何テラバイト？】と【Magical Hacker ☆ くるくるリスク】を、
頭おかしくなるほど聞いてたお陰で立派なプログラマーになれました！
みんなも頭おかしくなるくらいレッツ、リッスン！！

## とりあえず簡易ガチャ（キャラ設定がアホほど長いやつ）
```javascript:gacha1.js
// キャラ一覧
/**
 * ■排出率
 * SSR：なんかとっても凄いキャラ1 → 0.30%（リスト内の出現数 / リストの総数）
 * SSR：なんかとっても凄いキャラ2 → 0.30%
 * SSR：なんかとっても凄いキャラ3 → 0.30%
 * SSR：なんかとっても凄いキャラ4 → 0.30%
 * SSR：なんかとっても凄いキャラ5 → 0.30%
 * SR：とっても凄いキャラ1        → 1.52%
 * SR：とっても凄いキャラ2        → 1.52%
 * SR：とっても凄いキャラ3        → 1.52%
 * SR：とっても凄いキャラ4        → 1.52%
 * SR：とっても凄いキャラ5        → 1.52%
 * R：凄いキャラ1                 → 3.03%
 * R：凄いキャラ2                 → 3.03%
 * R：凄いキャラ3                 → 3.03%
 * R：凄いキャラ4                 → 3.03%
 * R：凄いキャラ5                 → 3.03%
 * R：凄いキャラ6                 → 3.03%
 * R：凄いキャラ7                 → 3.03%
 * R：凄いキャラ8                 → 3.03%
 * R：凄いキャラ9                 → 3.03%
 * R：凄いキャラ10                → 3.03%
 * N：ただのキャラ1               → 6.06%
 * N：ただのキャラ2               → 6.06%
 * N：ただのキャラ3               → 6.06%
 * N：ただのキャラ4               → 6.06%
 * N：ただのキャラ5               → 6.06%
 * N：ただのキャラ6               → 6.06%
 * N：ただのキャラ7               → 6.06%
 * N：ただのキャラ8               → 6.06%
 * N：ただのキャラ9               → 6.06%
 * N：ただのキャラ10              → 6.06%
 */
const charList = ["SSR：なんかとっても凄いキャラ1"
  , "SSR：なんかとっても凄いキャラ2"
  , "SSR：なんかとっても凄いキャラ3"
  , "SSR：なんかとっても凄いキャラ4"
  , "SSR：なんかとっても凄いキャラ5"
  , "SR：とっても凄いキャラ1"
  , "SR：とっても凄いキャラ1"
  , "SR：とっても凄いキャラ1"
  , "SR：とっても凄いキャラ1"
  , "SR：とっても凄いキャラ1"
  , "SR：とっても凄いキャラ2"
  , "SR：とっても凄いキャラ2"
  , "SR：とっても凄いキャラ2"
  , "SR：とっても凄いキャラ2"
  , "SR：とっても凄いキャラ2"
  , "SR：とっても凄いキャラ3"
  , "SR：とっても凄いキャラ3"
  , "SR：とっても凄いキャラ3"
  , "SR：とっても凄いキャラ3"
  , "SR：とっても凄いキャラ3"
  , "SR：とっても凄いキャラ4"
  , "SR：とっても凄いキャラ4"
  , "SR：とっても凄いキャラ4"
  , "SR：とっても凄いキャラ4"
  , "SR：とっても凄いキャラ4"
  , "SR：とっても凄いキャラ5"
  , "SR：とっても凄いキャラ5"
  , "SR：とっても凄いキャラ5"
  , "SR：とっても凄いキャラ5"
  , "SR：とっても凄いキャラ5"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ1"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ2"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ3"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ4"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ5"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ6"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ7"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ8"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ9"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "R：凄いキャラ10"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ1"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ2"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ3"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ4"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ5"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ6"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ7"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ8"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ9"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
  , "N：ただのキャラ10"
];
function gacha() {
  console.log(charList[Math.floor(Math.random() * charList.length)]);
}

module.exports = {
  gacha
};
```
## 試しに10連を3回、回してみた
![スクリーンショット 2024-08-20 152705.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/996378/ce432a36-80fd-2ff0-227b-676cc29eda65.png)

### 結果的には以下になりました。

```text:結果
■4回出た
N：ただのキャラ1

■2回出た
N：ただのキャラ2
N：ただのキャラ7

■3回出た
N：ただのキャラ3
N：ただのキャラ4
N：ただのキャラ6

■1回出た
N：ただのキャラ8
N：ただのキャラ9
R：凄いキャラ1
R：凄いキャラ3
R：凄いキャラ5
R：凄いキャラ8
R：凄いキャラ9
R：凄いキャラ10
SR：とっても凄いキャラ2
SR：とっても凄いキャラ3
SR：とっても凄いキャラ4
SR：とっても凄いキャラ5
SSR：なんかとっても凄いキャラ3
```

大体10回のうちの半分がNで、2～3がR、1～2がSRで、SSRが20回転しないうちに出たので、
割と良心的なガチャなんじゃないかなと（ほぼ運だけど

ただコレだと、ガチャで出てくるキャラのデータがアホほど多くなって可読性に欠けるため、もう少し仕組みを考えてみる。

# ランク毎のキャラ一覧生成してから割り出すやつ
```javascript:gacha2.js
// ランク一覧
const rankList = [
  {
    rank: "SSR",
    weight: 1,
  },
  {
    rank: "SR",
    weight: 5,
  },
  {
    rank: "R",
    weight: 10,
  },
  {
    rank: "N",
    weight: 20,
  }
];
// キャラ一覧
const charList = {
  "SSR": ["なんかとっても凄いキャラ1"
    , "なんかとっても凄いキャラ2"
    , "なんかとっても凄いキャラ3"
    , "なんかとっても凄いキャラ4"
    , "なんかとっても凄いキャラ5"],
  "SR": ["とっても凄いキャラ1"
    , "とっても凄いキャラ2"
    , "とっても凄いキャラ3"
    , "とっても凄いキャラ4"
    , "とっても凄いキャラ5"],
  "R": ["凄いキャラ1"
    , "凄いキャラ2"
    , "凄いキャラ3"
    , "凄いキャラ4"
    , "凄いキャラ5"
    , "凄いキャラ6"
    , "凄いキャラ7"
    , "凄いキャラ8"
    , "凄いキャラ9"
    , "凄いキャラ10"],
  "N": ["ただのキャラ1"
    , "ただのキャラ2"
    , "ただのキャラ3"
    , "ただのキャラ4"
    , "ただのキャラ5"
    , "ただのキャラ6"
    , "ただのキャラ7"
    , "ただのキャラ8"
    , "ただのキャラ9"
    , "ただのキャラ10"
  ],
};

function gacha() {
  const chList = rankList.map(rank=> {
    const cList = [];
    for(let i = 0; i < rank.weight; i++)
      cList.push(...charList[rank.rank].map(ch => `${rank.rank}：${ch}`));
    return cList;
  }).flat();
  console.log(chList[Math.floor(Math.random() * chList.length)]);
}

module.exports = {
  gacha
};
```
## かいせつ
ランク情報に重みを付けて、
ランクに紐づくキャラリストを検索用のListに重み分追加していき、
ソレを最後に抽出してるって感じです。
→SSR：なんかとっても凄いキャラ1はList上に1しか無いけど、N：ただのキャラ1はListに20いる感じ

ロジック内でデータを生成しているためキャラ情報分のコード量はだいぶ減ったかと。
使ってるデータ的には最初に書いたロジックと同じ【ような】データとなっている。
※キャラのインデックスが違うだけで、中身をソートしたら同じデータとなる

## おまけ
SSR/SRのキャラをもっと絞りたいってなったら、ソレ以外のランクの重みを増やせば全体数が上がって、ジャブジャブ課金させることが出来るね（ﾆｯｺﾘ
※クソ運営という批判がバンバン来て低評価の嵐になりそうだけど。。。

ちなみに排出率を調べたい用の関数も残しておきますね
```javascript:排出率チェック（ラング毎に1キャラ値の排出量を算出）
function rate() {
  const totalChar = rankList.map(rank => charList[rank.rank].length * rank.weight).reduce((total, val) => total + val, 0);
  console.log(`総キャラ数：${totalChar}`);
  rankList.forEach(rank => console.log(`${rank.rank}：${(rank.weight / totalChar * 100).toFixed(2)}%`));
}
// こんな感じで出力
// 総キャラ数：330
// SSR：0.30%
// SR：1.52%
// R：3.03%
// N：6.06%
```