トップページに戻る    次のC#のサンプルへ    前のC#のサンプルへ

Cマガ電脳クラブ(第021回) 31ゲーム

問題

今回は、ふたりで遊ぶ31ゲームを紹介しよう。
2から6までの数を書いた駒それぞれ4個ずつを、Fig.1のように左右にだけスライドできるような盤の上に並べ、
すべての駒を左側に寄せてゲームを始める。ふたりが交互に、駒をひとつずつ右に移動する。

移動するごとに右側の数の合計を唱え、これがちょうど31になるようにした者が勝ちだ。
でなかったら、31にならなくても相手を32以上にしてしまってもよい。
もちろん、「パス」は許されない。

Fig.2は、あるゲームの終了の図で、ちょうど31ができている。

こんなこともある。Aが24を作ってBの番になった。見ると2の駒はすべて移動済みだ。
そこでBは6を動かして30にした。すると次の手でAは32以上しか作れない。そこでBの勝ちとなる。

実はこのゲーム、先手に必勝法がある。
そこで問題。1手目をコンピュータとし、人と対戦する必勝プログラムを作ってほしい。
対戦の表示方法はご自由にどうぞ。

          


ソース

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static List<int> LeftValList = new List<int>();
    static int RightValSum = 0;

    static bool HasSyouhai = false;

    static void Main()
    {
        for (int I = 1; I <= 6; I++) LeftValList.AddRange(Enumerable.Repeat(I, 4));
        BanHyouji();

        while (true) {
            ExecComItte();
            if (HasSyouhai) return;
            InputHitoItte(false);
            if (HasSyouhai) return;
        }
    }

    //人の1手を入力
    static void InputHitoItte(bool IsRetry)
    {
        if (IsRetry == false) Console.Write("動かす数字を入力して下さい ");
        string wkStr = Console.ReadLine();
        int wkInt;
        if (int.TryParse(wkStr, out wkInt) == false) {
            Console.Write("入力エラーです。再入力して下さい ");
            InputHitoItte(true); return;
        }
        if (LeftValList.Contains(wkInt) == false) {
            Console.Write("存在しない数字です。再入力して下さい ");
            InputHitoItte(true); return;
        }
        ExecItte(wkInt);
    }

    //手を指して、メッセージ表示
    static void ExecItte(int pVal)
    {
        LeftValList.Remove(pVal); RightValSum += pVal;

        Console.WriteLine("{0}を動かしました", pVal);
        Console.WriteLine();

        SyouhaiHantei();
        if (HasSyouhai == false) BanHyouji();
    }

    //COMが手を指す
    static void ExecComItte()
    {
        //優先順位1 合計を31にする手がある場合
        int NeedVal = 31 - RightValSum;
        if (LeftValList.Contains(NeedVal)) {
            ExecItte(NeedVal);
            return;
        }

        //優先順位2 定跡DBにマッチしている場合
        if (IsMatchJyouseki()) return;

        //優先順位3 合計を10,17,24にする手を指す
        if (RightValSum < 10) NeedVal = 10 - RightValSum;
        else if (RightValSum < 17) NeedVal = 17 - RightValSum;
        else if (RightValSum < 24) NeedVal = 24 - RightValSum;
        ExecItte(NeedVal);
    }

    //定跡DBにマッチしているかを判定し、マッチしたら1手指す
    static bool IsMatchJyouseki()
    {
        var JyousekiBanList = new List<int>();
        for (int I = 1; I <= 6; I++) JyousekiBanList.AddRange(Enumerable.Repeat(I, 4));

        var OrderedLeftValList = LeftValList.OrderBy(X => X);

        //定跡その1 初手は5を動かす
        if (JyousekiBanList.OrderBy(X => X).SequenceEqual(OrderedLeftValList)) {
            ExecItte(5); return true;
        }

        //定跡その2 55なら2を動かす
        JyousekiBanList.Remove(5); JyousekiBanList.Remove(5);
        if (JyousekiBanList.OrderBy(X => X).SequenceEqual(OrderedLeftValList)) {
            ExecItte(2); return true;
        }

        //定跡その3 5525なら2を動かす
        JyousekiBanList.Remove(2); JyousekiBanList.Remove(5);
        if (JyousekiBanList.OrderBy(X => X).SequenceEqual(OrderedLeftValList)) {
            ExecItte(2); return true;
        }

        //定跡その4 552525なら2を動かす
        JyousekiBanList.Remove(2); JyousekiBanList.Remove(5);
        if (JyousekiBanList.OrderBy(X => X).SequenceEqual(OrderedLeftValList)) {
            ExecItte(2); return true;
        }

        return false;
    }

    //盤を表示
    static void BanHyouji()
    {
        var sb = new System.Text.StringBuilder();
        if (LeftValList.Count % 2 == 0) {
            sb.AppendLine("□□□□□□□□□□□□□□□□□□□□□□□□□");
        }
        else {
            sb.AppendLine("■■■■■■■■■■■■■■■■■■■■■■■■■");
        }

        sb.AppendFormat("現在の手数={0}。", 4 * 6 - LeftValList.Count);
        sb.AppendFormat("現在の合計値={0,2}。", RightValSum);
        sb.Append((LeftValList.Count % 2 == 0) ? "コンピュータの手番。" : "人の手番。");
        sb.AppendLine();

        for (int I = 1; I <= 6; I++) {
            int Cnt = LeftValList.Count(X => X == I);
            for (int J = 1; J <= Cnt; J++) sb.Append(I);

            sb.Append("***");

            for (int J = 1; J <= 4 - Cnt; J++) sb.Append(I);
            sb.AppendLine();
        }
        Console.Write(sb.ToString());
    }

    //勝敗が決定しているかを判定
    static void SyouhaiHantei()
    {
        //場合1 コンピュータが31を取った場合
        if (RightValSum == 31 && LeftValList.Count % 2 == 1) {
            Console.WriteLine("コンピュータが合計値31を取ったので、コンピュータの勝ちです。");
            HasSyouhai = true;
            return;
        }

        //場合2 人が32以上をとった場合
        if (RightValSum >= 32 && LeftValList.Count % 2 == 0) {
            Console.WriteLine("人が32以上の合計値を取ったので、コンピュータの勝ちです。");
            HasSyouhai = true;
        }
    }
}


実行結果

省略

■■■■■■■■■■■■■■■■■■■■■■■■■
現在の手数=7。現在の合計値=26。人の手番。
1111***
2***222
3333***
4444***
***5555
6666***
動かす数字を入力して下さい 3
3を動かしました

□□□□□□□□□□□□□□□□□□□□□□□□□
現在の手数=8。現在の合計値=29。コンピュータの手番。
1111***
2***222
333***3
4444***
***5555
6666***
2を動かしました

コンピュータが合計値31を取ったので、コンピュータの勝ちです。


解説

もし、使用出来る数字に制限がないならば、
最初に先手が3を移動させる。
後手が何か移動させる。先手は(7-後手が移動させた数)を繰り返すと、
3+7*4 = 31 なため、先手必勝です。

しかし、使用出来る数字に制限があるため、
最初に5を取り、
コンピュータが10,17,24,31を狙う戦術を定跡として実装してます。
10,17,24,31を取れれば取り、
取れなかったら2を取って、再度10,17,24,31を狙います。

■コマネチ大学数学科110講:「31」: ガスコン研究所