【ポケポケ】ラルトスの有無を考慮し、最速でサーナイトにさせるための方法は？


## 前の記事

https://qiita.com/mogamoga1337/items/42bf4c86b1fbcfbab681

# 本文

前回は状況を考慮せずに画一的にカードを使用していたが、
今回はラルトスの有無でカードの使用順序が変わるのかどうかを調べたいと思う

## 先に結論

ラルトスの有無によらず常に「博士 → モンボ → 石板」がよい

## 想定するデッキ構成

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

## ラルトスがいる場合

### ソースコード

<details><summary>ソースコード</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 index = 山札.findIndex(カード => カード !== "ラルトス");
    const カード = 山札.splice(index, 1)[0];

    シャッフル(山札);

    return カード;
}

function 初手ラルトス取得(山札) {
    const タネポケ = "ラルトス";
    const index = 山札.findIndex(カード => カード === タネポケ);
    山札.splice(index, 1);

    シャッフル(山札);

    return タネポケ;
}

function 初手ラルトス以外のタネポケ取得(山札) {
    const 対象のタネポケList = ["ミュウツー", "自由枠"];
    const 山札のタネポケList = 山札.filter(カード => 対象のタネポケList.includes(カード));

    const タネポケ = 山札のタネポケList[Math.floor(Math.random() * 山札のタネポケList.length)];
    const index = 山札.findIndex(カード => カード === タネポケ);
    山札.splice(index, 1);

    シャッフル(山札);

    return タネポケ;
}

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.25869
標準偏差：1.7246296657266873

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

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

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

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

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

## ラルトスがいない場合

### ソースコード

`function 手札生成(山札)`の箇所をいじっただけ

<details><summary>ソースコード</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 index = 山札.findIndex(カード => カード !== "ラルトス");
    const カード = 山札.splice(index, 1)[0];

    シャッフル(山札);

    return カード;
}

function 初手ラルトス取得(山札) {
    const タネポケ = "ラルトス";
    const index = 山札.findIndex(カード => カード === タネポケ);
    山札.splice(index, 1);

    シャッフル(山札);

    return タネポケ;
}

function 初手ラルトス以外のタネポケ取得(山札) {
    const 対象のタネポケList = ["ミュウツー", "自由枠"];
    const 山札のタネポケList = 山札.filter(カード => 対象のタネポケList.includes(カード));

    const タネポケ = 山札のタネポケList[Math.floor(Math.random() * 山札のタネポケList.length)];
    const index = 山札.findIndex(カード => カード === タネポケ);
    山札.splice(index, 1);

    シャッフル(山札);

    return タネポケ;
}

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.696233
標準偏差：1.7600467635113153

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

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

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

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

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

## チラ裏

ラルトスがいる場合も、博士がモンボより優先なのか…

バグってんのかな？
分からん