【ポケポケ】幻の石板、博士の研究、モンスターボールはどの順番で使うべきか


## 先に結論から

かなり微差だが、「博士 → モンボ → 石板」が一番効果的

## 目的

最速で2進化させるための順番を知りたい

## 気にしないこと

* 時と場合によるだろーみたいな意見
* ソースコードの可読性、実行速度
* ターン上限

## 仕様

* カードは合計20枚
* 初期手札は5枚。うち1枚は必ず種ポケ
* 初ターンは進化不可
* 進化は一段階ずつしかできない
* 博士は山札から2枚ドローする。ただし1ターンに1回のみ
* モンボは山札から種ポケをドローし、山札をシャッフルする
* 石板はトップがエスパータイプならばドローする。それ以外はトップを山札の下に送る

## 想定するデッキ構成

| ミュウツー | サーナイト系 | 自由枠 | 石板 | 博士 | モンボ | その他 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 2 | 6 | 0 | 2 | 2 | 2 | 6 |
<!--| 2 | 6 | 0 | 1 | 2 | 2 | 7 |
| 2 | 6 | 0 | 0 | 2 | 2 | 8 |
| 2 | 6 | 1 | 2 | 2 | 2 | 5 |
| 2 | 6 | 1 | 1 | 2 | 2 | 6 |
| 2 | 6 | 1 | 0 | 2 | 2 | 7 |-->

サーナイト系 ・・・ ラルトス、キルリア、サーナイトのこと
自由枠 ・・・ エスパータイプの種ポケモンの自由枠のこと
その他 ・・・ グッズ、サポートのこと

:::note info
いろんなパターンで調べるつもりでしたが、なんか怠くなりました
:::

## ソースコード

ろくにテストしてないからバグってるかも…

<details><summary>ソースコード（JS）</summary>

```js
// ### パラメータ ###

const ミュウツー_総数 = 2;
const サナ系_グループ数 = 2;
const 自由枠_総数 = 0;
const 石板_総数 = 2;
const 博士_総数 = 2;
const モンボ_総数 = 2;
const その他_総数 = 6;

const 優先度List = [
    ["石板", "博士", "モンボ"],
    ["石板", "モンボ", "博士"],
    ["博士", "石板", "モンボ"],
    ["博士", "モンボ", "石板"],
    ["モンボ", "石板", "博士"],
    ["モンボ", "博士", "石板"],
];

// ### メインロジック ###

const 試行回数 = 200_0000;

for (const 優先度 of 優先度List) {
    const ターン数List = [];
    let ターン数_合計 = 0;
    for (let i = 0; i < 試行回数; i++) {
        const ターン数 = サーナイトが完成したターン(優先度);
        ターン数List.push(ターン数);
        ターン数_合計 += ターン数;
    }
    const 平均 = ターン数_合計 / 試行回数;
    const 分散 = ターン数List.reduce((合計, ターン数) => 合計 + Math.pow(ターン数 - 平均, 2), 0) / 試行回数;
    console.log(`優先度：${優先度.join(" → ")}`)
    console.log(`平均：${平均}`);
    console.log(`標準偏差：${Math.sqrt(分散)}`);
    console.log("");
}

// ### 関数群 ###

function 山札生成() {
    const 山札 = [];
    for (let i = 0; i < ミュウツー_総数; i++) {
        山札.push("ミュウツー");
    }
    for (let i = 0; i < サナ系_グループ数; i++) {
        山札.push("ラルトス");
        山札.push("キルリア");
        山札.push("サーナイト");
    }
    for (let i = 0; i < 自由枠_総数; i++) {
        山札.push("自由枠");
    }
    for (let i = 0; i < 石板_総数; i++) {
        山札.push("石板");
    }
    for (let i = 0; i < 博士_総数; i++) {
        山札.push("博士");
    }
    for (let i = 0; i < モンボ_総数; i++) {
        山札.push("モンボ");
    }
    for (let i = 0; i < その他_総数; i++) {
        山札.push("その他");
    }

    if (山札.length !== 20) {
        throw new Error(`山札の総数がおかしい：${山札.length}`);
    }

    return シャッフル(山札);
}

function 手札生成(山札) {
    const 手札 = [];
    手札.push(モンボ使用(山札));
    手札.push(ドロー(山札));
    手札.push(ドロー(山札));
    手札.push(ドロー(山札));
    手札.push(ドロー(山札));
    return 手札;
}

function シャッフル(山札) {
    for (let i = 0; i < 山札.length; i++) {
        const j = Math.floor(Math.random() * 山札.length);
        const tmp = 山札[i];
        山札[i] = 山札[j];
        山札[j] = tmp;
    }
    return 山札;
}

function ドロー(山札) {
    return 山札.shift();
}

function モンボ使用(山札) {
    const 対象のタネポケList = ["ミュウツー", "ラルトス", "自由枠"];
    const 山札のタネポケList = 山札.filter(カード => 対象のタネポケList.includes(カード));

    let タネポケ = "";
    if (山札のタネポケList.length > 0) {
        タネポケ = 山札のタネポケList[Math.floor(Math.random() * 山札のタネポケList.length)];

        const index = 山札.findIndex(カード => カード === タネポケ);
        山札.splice(index, 1);
    }

    シャッフル(山札);

    return タネポケ;
}

function 博士使用(山札) {
    return 山札.splice(0, 2);
}

function 石板使用(山札) {
    if (山札.length === 0) {
        return "";
    }

    const 対象のタネポケList = ["ミュウツー", "ラルトス", "キルリア", "サーナイト", "自由枠"];

    if (対象のタネポケList.includes(山札[0])) {
        return ドロー(山札);
    }
    else {
        const カード = ドロー(山札);
        山札.push(カード);
        return "";
    }
}

function サーナイトが完成したターン(優先度) {
    const 山札 = 山札生成();
    const 手札 = 手札生成(山札);

    let ターン数 = 0;
    let 場のサナ系のカード = "";

    while (場のサナ系のカード !== "サーナイト") {
        ターン数++;
        手札.push(ドロー(山札));

        for (const 使うカード of 優先度) {
            while (手札.includes(使うカード)) {
                カードを捨てる(手札, 使うカード);
            
                if (使うカード === "石板") {
                    const 引いたカード = 石板使用(山札);
                    if (引いたカード !== "") {
                        手札.push(引いたカード);
                    }
                }
                else if (使うカード === "博士") {
                    const 引いたカードList = 博士使用(山札);
                    手札.push(...引いたカードList);
                    break; // 博士は1ターンに1回しか使えないため
                }
                else if (使うカード === "モンボ") {
                    const 引いたカード = モンボ使用(山札);
                    if (引いたカード !== "") {
                        手札.push(引いたカード);
                    }
                }
            }
        }

        if (ターン数 === 1) {
            if (手札.includes("ラルトス")) {
                場のサナ系のカード = "ラルトス";
            }
        }
        else {
            if (場のサナ系のカード === "" && 手札.includes("ラルトス")) {
                場のサナ系のカード = "ラルトス";
            }
            else if (場のサナ系のカード === "ラルトス" && 手札.includes("キルリア")) {
                場のサナ系のカード = "キルリア";
            }
            else if (場のサナ系のカード === "キルリア" && 手札.includes("サーナイト")) {
                場のサナ系のカード = "サーナイト";
            }
        }
    }

    return ターン数;
}

function カードを捨てる(手札, カード) {
    const index = 手札.indexOf(カード);
    if (index !== -1) {
        手札.splice(index, 1);
    }
}
```

</details>


## 結果

```
優先度：石板 → 博士 → モンボ
平均：4.428111
標準偏差：1.7632362211533645

優先度：石板 → モンボ → 博士
平均：4.438606
標準偏差：1.7631445705705244

優先度：博士 → 石板 → モンボ
平均：4.4184055
標準偏差：1.7608493228022017

優先度：博士 → モンボ → 石板
平均：4.3877305
標準偏差：1.7397504302015587

優先度：モンボ → 石板 → 博士
平均：4.420216
標準偏差：1.7480236020463444

優先度：モンボ → 博士 → 石板
平均：4.405268
標準偏差：1.7444970186864621
```

## おまけ（幻の石板なしの場合）

| ミュウツー | サーナイト系 | 自由枠 | 石板 | 博士 | モンボ | その他 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 2 | 6 | 0 | 0 | 2 | 2 | 8 |

実質的に優先度は

* 博士 → モンボ
* モンボ → 博士

の2種類

```
優先度：石板 → 博士 → モンボ
平均：4.8770865
標準偏差：2.0485914115526525

優先度：石板 → モンボ → 博士
平均：4.8855755
標準偏差：2.0464104020857365

優先度：博士 → 石板 → モンボ
平均：4.8739515
標準偏差：2.0472115854723487

優先度：博士 → モンボ → 石板
平均：4.8775615
標準偏差：2.0476418909675287

優先度：モンボ → 石板 → 博士
平均：4.888345
標準偏差：2.048001992419984

優先度：モンボ → 博士 → 石板
平均：4.886103
標準偏差：2.045020898012578
```

幻の石板2枚はない場合と比べて、約0.5ターン程度の短縮に貢献している

## んにゃぴ

ググって見つけたこの記事によると

https://note.com/23571113171923/n/nac4d300e6566

「モンボ → 博士 → 石板」
がよいらしい

条件を同じにして実行してみたところ

| ミュウツー | サーナイト系 | 自由枠 | 石板 | 博士 | モンボ | その他 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 2 | 6 | 1 | 2 | 2 | 2 | 5 |

```
優先度：石板 → 博士 → モンボ
平均：4.483367
標準偏差：1.7592473798026116

優先度：石板 → モンボ → 博士
平均：4.502988
標準偏差：1.761319412204814

優先度：博士 → 石板 → モンボ
平均：4.470773
標準偏差：1.7558495899229465

優先度：博士 → モンボ → 石板
平均：4.449545
標準偏差：1.7411700930714427

優先度：モンボ → 石板 → 博士
平均：4.4841845
標準偏差：1.7484209647599276

優先度：モンボ → 博士 → 石板
平均：4.4722875
標準偏差：1.7474630231580857
```

やはり、「博士 → モンボ → 石板」が最適となった

Why！

## チラ裏

今思えば初期手札に石盤博士モンボを固定して入れた方がハッキリとした値が出そうだ

ラルトスが引けていない場合は「博士 → モンボ → 石板」がよく、
ラルトスが引けている場合は「モンボ → 博士 → 石板」がよいのでは？

ミュウツー&サーナイトが出来上がるまでの速さは？

石板で博士がボトムになったときに、モンボでシャッフルするようなロジックにしたらどうなるだろうか？

博士が来るまで石盤を使わないみたいなこともあるか？

ポケモン図鑑も考慮したら面白いかも？

<!--

## チラ裏（ポケポケ運営へ）

先攻が**圧倒的に**不利すぎるから、

* 先攻はレッドカードで飛ばない傷薬を初期手札に加える
* 先攻で勝った場合は経験値N倍

くらいしておくれ。

後者はゲームバランスに全く関与しないし、実装も楽でしょ。

倍率は $ユーザー全体の後攻の勝率 \div ユーザー全体の先攻の勝率$ にすればいいんでない？

-->