石を取り合うゲーム:最後の一つを取った方が負け
遊びをつくる執筆にClaudeを使用しています。

石の山から交互に1個以上を取り、最後の一つを取った方が負けになるゲームを作る場面を考えてみます。ルールは数行で書けて、画面も石の山と二つのボタンだけで足ります。ところが完成したCPUを相手に遊ぶと、特定の石の数のときだけ、どう取っても必ず負けると感じる瞬間が来ます。その手触りの正体は、ゲームの木を端から塗り分けていくと見えてきます。 この記事は、その塗り分けを自分の手とコードで行う「実験の設計書」です。確かめる問いは二つです。一つは、石がn個で自分の番のとき、最善を尽くして勝てる局面と負ける局面はどう並ぶか。もう一つは、「最後を取ると負け」という一行のルール変更が、その並びをどう変えるかです。結果はここには書きません。表を作り、並びを読み、理由まで自分で言えるようになるのがゴールです。
この記事の目次
問いを立てる:ルールと変数を固定する
まず遊びの定義を固定します。結果を比べるには、何を動かして何を動かさないかを先に決めておく必要があるからです。
- 山に石がn個あります。二人が交互に番を持ちます。
- 自分の番では、1個以上K個以下を取ります。Kは「1回に取れる最大数」です。
- 山の石が足りないときは、残り全部までしか取れません。パスはできません。
- 通常版は最後の石を取った方が勝ちです。ミゼール版(題名のルール)は最後の石を取った方が負けです。ミゼールは「わざと負けに向かう」という意味の言葉で、この種の逆ルール全般に使われます。
動かす変数は三つです。1回に取れる最大数K(2、3、4)、最初の石の数n(1〜20)、ルール(通常版かミゼール版)です。決定的なゲームで偶然が入らないため、同じ条件の繰り返し計測は要りません。回数を重ねるのは、手作業とコードの照合のほうです。
ここで使う言葉を一つ決めます。石がn個で自分の番のとき、相手がどう打っても最終的に勝てるなら、その局面を「勝ち位置」と呼びます。どう打っても負けるなら「負け位置」と呼びます。以下ではこの二つだけで考えます。
手順1:紙で勝敗表を作る
最初はコードを使わず、K=2、n=1〜10の表を紙で作ります。ミゼール版からいきます。手順は次のとおりです。
- n=1の行を書きます。残りが1個で自分の番のときは、取る手が「1個取る」しかありません。取ると最後の石を取るので負けです。したがってn=1は負け位置です。
- n=2の行を書きます。取れるのは1個か2個です。それぞれの手のあとに、相手に渡す石の数を並べます。相手に渡した局面が負け位置になる手が一つでもあれば、n=2は勝ち位置です。
- n=3以降も同じ手順で進めます。各行の判定は、すでに書いた行だけを見れば決まります。この「小さい数から順に埋める」やり方が、あとのコードの骨格になります。
記録は「石の数、取れる手、各手で相手に渡す数、判定」の4列で書きます。手が1〜Kの最大K個なので、K=2なら1行が2手で収まります。
次に通常版で同じ表を作ります。違いは1行だけです。n=1の行で、1個取ると石が0個になり、取った自分が勝ちます。この一点が出発点で、そこから下の行すべての判定がずれていきます。
手順2:コードで全探索して、手作業と照合する
手作業で表の作り方がつかめたら、同じ計算をブラウザに任せます。下のコードを index.html として保存し、ブラウザで開くと、石の数ごとの勝ち・負けが一覧で出ます。外部ライブラリもサーバーも使いません。
<!doctype html>
<meta charset="utf-8">
<pre id="out"></pre>
<script>
const K = 3; // 1回に取れる最大数
const N = 20; // 調べる石の数の上限
const MISERE = true; // true: 最後を取ると負け / false: 最後を取ると勝ち
// win[n]: 石がn個で自分の番のとき、最善を尽くして勝てるか
// win[0]: 石が0個で自分の番。直前の相手が最後を取った状態
const win = [MISERE];
for (let n = 1; n <= N; n++) {
win[n] = false;
for (let t = 1; t <= Math.min(K, n); t++) {
// t個取って n-t 個を相手に渡す。相手が負け位置なら自分は勝てる
if (!win[n - t]) win[n] = true;
}
}
let s = "";
for (let n = 1; n <= N; n++) s += n + "個: " + (win[n] ? "勝ち" : "負け") + "\n";
document.getElementById("out").textContent = s;
</script>ルールの違いは win[0] の一行にだけ入っています。ミゼール版では、石が0個になった時点で番が回ってくるのは「最後を取らされなかった側」です。その人の勝ちなので true を入れます。通常版では、0個で番が回ってくる側は最後を取られた側で負けなので false にします。
記録は次の手順で行います。
- K=2、MISERE = true で実行し、n=1〜10の結果を手作業の表と1行ずつ照合します。ずれた行があれば、手作業とコードのどちらかに誤りがあります。ずれた行が最初に現れたnを控えると、原因を探しやすくなります。
- K=2、3、4と、MISERE の true と false の6通りを実行します。
- 各組について、負けになる石の数をnの小さい順に書き出します。
書き出した結果は、次の形式の表にまとめると比べやすくなります。
- 2、通常版で負けのn:(記入)、ミゼール版で負けのn:(記入)
- 3、通常版で負けのn:(記入)、ミゼール版で負けのn:(記入)
- 4、通常版で負けのn:(記入)、ミゼール版で負けのn:(記入)
結果の読み方:必勝法の構造を言葉にする
表が埋まったら、負けのnの並びを見てください。次の仮説を立てて照合します。
- 仮説A:負けのnは、一定の間隔で繰り返し現れる。
- 仮説B:その間隔はK+1と関係している。
- 仮説C:通常版とミゼール版では、負けのnの位置が一つ分ずれる。
三つとも表で成り立っていれば、その理由を次の確認で言葉にできます。勝敗表が正しいための条件は二つです。
- 負け位置から取れるどの手も、勝ち位置に着く。
- 勝ち位置からは、負け位置に着く手が少なくとも一つある。
ミゼール版で、帰納法に近い形でたどってみます。n=1は負け位置です。n=2からn=K+1までは、1個だけ残す手があります。取る数はn−1個で、1以上K以下に収まるので打てます。相手が負け位置のn=1に置かれるため、これらは勝ち位置です。n=K+2になると、1〜K個のどれを取っても残りはK+1〜2個で、すべて勝ち位置になります。したがってn=K+2は負け位置です。
ここから先は同じことの繰り返しです。ある負け位置Lの次のK個の局面は、差の分を取ればLに戻せるので勝ち位置になります。その次の局面L+K+1は、どの手でも勝ち位置に着くので負け位置です。負け位置は(K+1)ごとに現れ、先頭が1になります。
この構造から、ゲームとしての必勝法が読めます。
- 自分が勝ち位置にいるときは、相手に渡す石を「(K+1)で割って1余る数」にします。
- 相手がx個取ったら、(K+1−x)個取り返します。これで毎回、一巡の合計がK+1個になります。
- 最後は相手が1個の山を渡され、取らされて負けます。
通常版では、狙う数が「割り切れる数」になります。ミゼール版はそこから1個ぶん手前で止めるだけで、構造そのものは同じです。表のずれが一つ分だったなら、仮説Cが成り立っていたことになります。
仮説が崩れた場合は、次の順に点検します。K+1と無関係な間隔になったなら、Kの設定か Math.min(K, n) の上限を見直します。通常版とミゼール版で一致する行があるなら、win[0] の初期値が切り替わっているかを確かめます。
人間の対局で確かめる:気づきの手数を記録する
表が読めたら、次は人間のプレイで確かめます。必勝法を知らない人が、どの時点でこの構造に気づくかを観察する実験です。
- 準備:K=3、n=21で、石を数えられるおもちゃのゲームを一つ用意します。紙の上の丸印でも構いません。
- 記録:1局ごとに、各手番の取った数と、残りの石の数を書きます。単位は1局、同じ相手と5局ずつ行います。先手と後手は局ごとに交代します。
- 見る点:相手の取り方に「合計がK+1になる返し方」が現れ始めたのはいつか。同じ人が先手と後手で、その傾向が変わるか。
この実験で結論を急ぎすぎないことが大切です。5局では、気づいたのか偶然だったのかを分けられません。「返し方が現れたか」ではなく「その局面で、他の手の選択肢があったか」を併せて見ると、偶然の返しと意図的な返しを区別しやすくなります。
起こりやすい誤り
- 「自分の番」と「直前」を混ぜる:win[n] は「石がn個で自分の番」と定義しています。0個の行が「直前に誰が取ったか」の話になることを意識しないと、真偽が逆になります。
- 最後の石の扱いを取り違える:ミゼール版で「1個残す」ことと「0個残す」ことを混同すると、狙う数が1つずれます。
- 石の数を超えて取らせる:n<Kのとき、K個取れるとして判定すると、存在しない手を数えてしまいます。コードの Math.min(K, n) はその防止です。
- 表だけで満足する:勝敗表が正しくても、遊びとして面白いとは限りません。どちらかが必ず勝つ局面は、初手の石の数で決まってしまいます。n=21のようにあらかじめ勝ち位置を決めて渡すと、後手は何をしても勝てないことになります。
次の行動
まずK=2、n=1〜10の勝敗表を紙で作り、コードの出力と1行ずつ照合してください。次に、ゲームに仕立てるなら、CPUの強さを3段階に分ける設計が考えられます。たとえば「いつも必勝手を打つ」「一定の確率でわざと外す」「ランダムに打つ」のような分け方です。
作ったものを公開するときは、ZIPかHTMLで投稿できます。アップロードの入口は直下の index.html です。投稿前には、石の数とKを変えた画面で、表と同じ勝敗になるかをもう一度確かめておくと安心です。

