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

コンピュータパズルへの招待 5問目 ペグソリティア トライトライ

問題

最初の空所と終了時のペグ位置が同じ解を補償型の解といい、
そのうえ、最初の空所が盤の中央で補償型の解がある問題を中央補償問題といいます。

ペグソリティア トライトライの中央補償問題を解き、最短手数を求めます。

なお、ペグの移動手数を数えるときは、同一ペグによる連続跳びは同じ一手と数えます。

Fig.1 トライトライの中央補償問題


ソース

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

class Program
{
    const int UB = 24;

    struct JyoutaiDef
    {
        internal bool[] BanArr;
        internal int Level;
        internal List<uint> HashLog;
    }

    static void Main()
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();

        //ノード間のネットワーク図を作成
        DeriveNodeNetWork();

        var Stk = new Stack<JyoutaiDef>();
        JyoutaiDef WillPush;
        WillPush.BanArr = new bool[UB + 1];
        for (int I = 0; I <= UB; I++) {
            WillPush.BanArr[I] = true;
        }
        WillPush.BanArr[11] = false;
        WillPush.Level = 0;
        uint StaHash = GetHash(WillPush.BanArr);
        WillPush.HashLog = new List<uint>() { StaHash };
        Stk.Push(WillPush);

        var MinTesuuDict = new Dictionary<ulong, int>();
        MinTesuuDict.Add(StaHash, 0);

        List<uint> AnswerLog = null;
        int AnswerLevel = 12;
        bool FoundAnswer = false;

        while (Stk.Count > 0) {
            JyoutaiDef Popped = Stk.Pop();

            if (IsClear(Popped.BanArr)) {
                if (FoundAnswer == false && Popped.Level == AnswerLevel
                 || Popped.Level < AnswerLevel) {
                    Console.WriteLine("{0}手の解を発見。経過時間={1}", Popped.Level, sw.Elapsed);
                    FoundAnswer = true;
                    AnswerLog = Popped.HashLog;
                    AnswerLevel = Popped.Level;
                }
                continue;
            }

            //下限値枝切り
            int Kagen = Popped.Level + DeriveNeedMinTesuu(Popped.BanArr);
            if (Kagen > AnswerLevel) continue;
            if (FoundAnswer && Kagen == AnswerLevel) continue;

            var MovedArrList = new List<bool[]>();

            for (int I = 0; I <= UB; I++) {
                if (Popped.BanArr[I] == false) continue;

                //盤面とペグの位置を引数として、1手後の盤面のListを返す
                MovedArrList.AddRange(DeriveMovedArrList(Popped.BanArr, I));
            }

            foreach (bool[] EachMovedArr in MovedArrList) {
                uint CurrHash = GetHash(EachMovedArr);
                WillPush.BanArr = EachMovedArr;
                WillPush.Level = Popped.Level + 1;

                //ペグが存在必須のグループにペグがなければ枝切り
                if (HasLatestOnePeg(EachMovedArr) == false) continue;

                //メモ化探索
                bool IsOK = true;
                List<bool[]> KaitenArrList = CreateKaitenArr(EachMovedArr);
                foreach (bool[] EachKaitenArr in KaitenArrList) {
                    int MinTesuu;
                    uint KaitenHash = GetHash(EachKaitenArr);
                    if (MinTesuuDict.TryGetValue(KaitenHash, out MinTesuu)) {
                        if (MinTesuu <= WillPush.Level) {
                            IsOK = false;
                            break;
                        }
                    }
                }
                if (IsOK == false) continue;
                MinTesuuDict[CurrHash] = WillPush.Level;

                WillPush.HashLog = new List<uint>(Popped.HashLog) { CurrHash };
                Stk.Push(WillPush);
            }
        }
        for (int I = 0; I <= AnswerLog.Count - 1; I++) {
            Console.WriteLine("{0}手目", I);
            PrintBan(AnswerLog[I]);
        }
        Console.WriteLine("経過時間={0}", sw.Elapsed);
    }

    //移動のネットワーク図
    struct NodeNetWork
    {
        internal int FromInd;
        internal int JumpInd;
        internal int ToInd;
    }
    static NodeNetWork[] mNodeNetWorkArr;

    //移動のネットワーク図を作成
    static void DeriveNodeNetWork()
    {
        var NodeNetWorkList = new List<NodeNetWork>();

        Action<int, int, int> AddAct = (pFromInd, pJumpInd, pToInd) =>
        {
            NodeNetWorkList.Add(
                new NodeNetWork() { FromInd = pFromInd, JumpInd = pJumpInd, ToInd = pToInd });
            NodeNetWorkList.Add(
                new NodeNetWork() { FromInd = pToInd, JumpInd = pJumpInd, ToInd = pFromInd });
        };

        AddAct(0, 2, 5); AddAct(0, 3, 7);
        AddAct(1, 3, 6); AddAct(1, 4, 8);
        AddAct(2, 3, 4); AddAct(2, 5, 9); AddAct(2, 6, 11);
        AddAct(3, 6, 10); AddAct(3, 7, 12);
        AddAct(4, 7, 11); AddAct(4, 8, 13);
        AddAct(5, 6, 7); AddAct(5, 9, 14); AddAct(5, 10, 16);
        AddAct(6, 7, 8); AddAct(6, 10, 15); AddAct(6, 11, 17);
        AddAct(7, 11, 16); AddAct(7, 12, 18);
        AddAct(8, 12, 17); AddAct(8, 13, 19);
        AddAct(9, 10, 11); AddAct(9, 15, 21);
        AddAct(10, 11, 12); AddAct(10, 15, 20); AddAct(10, 16, 22);
        AddAct(11, 12, 13); AddAct(11, 16, 21); AddAct(11, 17, 23);
        AddAct(12, 17, 22); AddAct(12, 18, 24);
        AddAct(13, 18, 23);
        AddAct(14, 15, 16);
        AddAct(15, 16, 17);
        AddAct(16, 17, 18);
        AddAct(17, 18, 19);
        AddAct(20, 21, 22);
        AddAct(21, 22, 23);
        AddAct(22, 23, 24);

        mNodeNetWorkArr = NodeNetWorkList.ToArray();
    }

    //クリア判定
    static bool IsClear(bool[] pBanArr)
    {
        //中央にペグがなかったらNG
        if (pBanArr[11] == false) return false;

        //ペグが1個しかなければOK
        return pBanArr.Count(X => X) == 1;
    }

    //必要な最低手数を求める
    static int DeriveNeedMinTesuu(bool[] pBanArr)
    {
        int WillReturn = 0;

        if (pBanArr[0]) WillReturn++;
        if (pBanArr[1]) WillReturn++;
        if (pBanArr[14]) WillReturn++;
        if (pBanArr[20]) WillReturn++;
        if (pBanArr[19]) WillReturn++;
        if (pBanArr[24]) WillReturn++;

        Action<int, int, int> wkAct = (p1, p2, p3) =>
        {
            if (pBanArr[p1] && pBanArr[p2] && pBanArr[p3])
                WillReturn++;
        };

        wkAct(2, 5, 9);
        wkAct(4, 8, 13);
        wkAct(21, 22, 23);

        if (WillReturn == 0) WillReturn = 1;
        return WillReturn;
    }

    struct MoveInfoDef
    {
        internal bool[] BanArr;
        internal int FromInd;
    }

    //盤面とペグの位置を引数として、1手後の盤面のListを返す
    static List<bool[]> DeriveMovedArrList(bool[] pBanArr, int pFromInd)
    {
        var WillReturn = new List<bool[]>();

        var Stk = new Stack<MoveInfoDef>();
        MoveInfoDef WillPush;
        WillPush.BanArr = pBanArr;
        WillPush.FromInd = pFromInd;
        Stk.Push(WillPush);

        var VisitedSet = new HashSet<ulong>();
        while (Stk.Count > 0) {
            MoveInfoDef Popped = Stk.Pop();

            MoveInfoDef[] MovedInfoArr = DeriveMovedInfoArr(Popped.BanArr, Popped.FromInd);
            foreach (MoveInfoDef EachJyoutai in MovedInfoArr) {
                uint CurrHash = GetHash(EachJyoutai.BanArr);
                if (VisitedSet.Add(CurrHash) == false) continue;
                WillReturn.Add(EachJyoutai.BanArr);

                Stk.Push(EachJyoutai);
            }
        }
        return WillReturn;
    }

    //盤面とペグの位置を引数として、移動情報の配列を返す
    static MoveInfoDef[] DeriveMovedInfoArr(bool[] pBanArr, int pFromInd)
    {
        var WillReturn = new List<MoveInfoDef>();

        foreach (NodeNetWork EachNodeNetWork in mNodeNetWorkArr) {
            if (EachNodeNetWork.FromInd != pFromInd) continue;

            int JumpInd = EachNodeNetWork.JumpInd;
            int ToInd = EachNodeNetWork.ToInd;

            if (pBanArr[JumpInd] == false) continue;
            if (pBanArr[ToInd]) continue;

            //移動可能な場合
            MoveInfoDef WillAdd;
            WillAdd.BanArr = (bool[])pBanArr.Clone();
            WillAdd.BanArr[pFromInd] = false;
            WillAdd.BanArr[JumpInd] = false;
            WillAdd.BanArr[ToInd] = true;
            WillAdd.FromInd = ToInd;
            WillReturn.Add(WillAdd);
        }
        return WillReturn.ToArray();
    }

    //ペグが存在必須のグループにペグがあるかを返す
    static bool HasLatestOnePeg(bool[] pBanArr)
    {
        if (pBanArr[2]) return true;
        if (pBanArr[4]) return true;
        if (pBanArr[9]) return true;
        if (pBanArr[11]) return true;
        if (pBanArr[13]) return true;
        if (pBanArr[21]) return true;
        if (pBanArr[23]) return true;

        return false;
    }

    //配列を引数として、回転させた配列を返す
    static List<bool[]> CreateKaitenArr(bool[] pBanArr)
    {
        var WillReturn = new List<bool[]>();
        bool[] Kaiten060Do = DeriveKaiten60Do(pBanArr);
        bool[] Kaiten120Do = DeriveKaiten60Do(Kaiten060Do);
        bool[] Kaiten180Do = DeriveKaiten60Do(Kaiten120Do);
        bool[] Kaiten240Do = DeriveKaiten60Do(Kaiten180Do);
        bool[] Kaiten300Do = DeriveKaiten60Do(Kaiten240Do);
        bool[] wkHanten = SayuuHanten(pBanArr);
        bool[] wkHanten060Do = DeriveKaiten60Do(wkHanten);
        bool[] wkHanten120Do = DeriveKaiten60Do(wkHanten060Do);
        bool[] wkHanten180Do = DeriveKaiten60Do(wkHanten120Do);
        bool[] wkHanten240Do = DeriveKaiten60Do(wkHanten180Do);
        bool[] wkHanten300Do = DeriveKaiten60Do(wkHanten240Do);

        WillReturn.Add(pBanArr);
        WillReturn.Add(Kaiten060Do);
        WillReturn.Add(Kaiten120Do);
        WillReturn.Add(Kaiten180Do);
        WillReturn.Add(Kaiten240Do);
        WillReturn.Add(Kaiten300Do);

        WillReturn.Add(wkHanten);
        WillReturn.Add(wkHanten060Do);
        WillReturn.Add(wkHanten120Do);
        WillReturn.Add(wkHanten180Do);
        WillReturn.Add(wkHanten240Do);
        WillReturn.Add(wkHanten300Do);

        return WillReturn;
    }

    //60度回転させた盤面を返す
    static bool[] DeriveKaiten60Do(bool[] pBanArr)
    {
        bool[] WillReturn = new bool[UB + 1];
        WillReturn[0] = pBanArr[20];
        WillReturn[1] = pBanArr[14];
        WillReturn[2] = pBanArr[21];
        WillReturn[3] = pBanArr[15];
        WillReturn[4] = pBanArr[9];
        WillReturn[5] = pBanArr[22];
        WillReturn[6] = pBanArr[16];
        WillReturn[7] = pBanArr[10];
        WillReturn[8] = pBanArr[5];
        WillReturn[9] = pBanArr[23];
        WillReturn[10] = pBanArr[17];
        WillReturn[11] = pBanArr[11];
        WillReturn[12] = pBanArr[6];
        WillReturn[13] = pBanArr[2];
        WillReturn[14] = pBanArr[24];
        WillReturn[15] = pBanArr[18];
        WillReturn[16] = pBanArr[12];
        WillReturn[17] = pBanArr[7];
        WillReturn[18] = pBanArr[3];
        WillReturn[19] = pBanArr[0];
        WillReturn[20] = pBanArr[19];
        WillReturn[21] = pBanArr[13];
        WillReturn[22] = pBanArr[8];
        WillReturn[23] = pBanArr[4];
        WillReturn[24] = pBanArr[1];

        return WillReturn;
    }

    //左右反転させた盤面を返す
    static bool[] SayuuHanten(bool[] pBanArr)
    {
        bool[] WillReturn = new bool[UB + 1];

        Action<int, int> SetAct = (pFromInd, pToInd) =>
        {
            while (true) {
                WillReturn[pFromInd] = pBanArr[pToInd];
                WillReturn[pToInd] = pBanArr[pFromInd];

                pFromInd++; pToInd--;
                if (pFromInd > pToInd) break;
            }
        };
        SetAct(0, 1);
        SetAct(2, 4);
        SetAct(5, 8);
        SetAct(9, 13);
        SetAct(14, 19);
        SetAct(20, 24);

        return WillReturn;
    }

    //ハッシュ値を求める
    static uint GetHash(bool[] pBanArr)
    {
        var sb = new System.Text.StringBuilder();
        Array.ForEach(pBanArr, X => sb.Append(X ? "1" : "0"));
        return Convert.ToUInt32(sb.ToString(), 2);
    }

    //ハッシュ値から盤面を表示
    static void PrintBan(uint pHash)
    {
        bool[] wkBanArr = new bool[UB + 1];
        uint CopiedHash = pHash;
        for (int I = UB; 0 <= I; I--) {
            wkBanArr[I] = (CopiedHash % 2 == 1);
            CopiedHash /= 2;
        }
        PrintBan(wkBanArr);
    }

    //盤面を表示
    static void PrintBan(bool[] pBanArr)
    {
        var sb = new System.Text.StringBuilder();

        Action<int, int> AppendAct = (pStaInd, pEndInd) =>
        {
            for (int I = pStaInd; I <= pEndInd; I++)
                sb.Append(pBanArr[I] ? '●' : '□');
        };

        sb.Append("    "); AppendAct(0, 1); sb.AppendLine();

        sb.Append("   "); AppendAct(2, 4); sb.AppendLine();

        sb.Append("  "); AppendAct(5, 8); sb.AppendLine();

        sb.Append(" "); AppendAct(9, 13); sb.AppendLine();

        AppendAct(14, 19); sb.AppendLine();

        sb.Append(" "); AppendAct(20, 24); sb.AppendLine();

        Console.WriteLine(sb.ToString());
    }
}


実行結果

省略
11手目
    □□
   □□□
  □□□□
 □□□□□
□□●□□□
 □□●●□

12手目
    □□
   □□□
  □□□□
 □□●□□
□□□□□□
 □□□□□

経過時間=00:00:59.6883791


解説

深さ優先探索で12手以下の解を列挙してます。