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

Cマガ電脳クラブ(第009回) ピースチェンジ

問題

今回はスライディングブロックパズルだ。Fig.1のようにピースが配置されている。
この状態からピースをすべらせて移動し、Fig.2の状態にしたい。

「NO」を「ON」にするのだ。何手で完成できるだろうか。
ピースの動きの途中経過もしっかり書いて、最少手数でお願いします。

注記 ■■は、上下および左右に分断できないです。

Fig.1


Fig.2


ソース

using System;
using System.Collections.Generic;

class Program
{
    struct JyoutaiDef
    {
        internal char[,] Ban;
        internal List<char[,]> BanList;
        internal HashSet<string> ExistBanSet;
        internal List<char> MoveLogList;
    }

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

        var wkBan = new char[,]{{'■','■','N','O'},
                                {'O','F','F','□'}};

        //X座標とY座標を変換してセット
        WillPush.Ban = new char[wkBan.GetLength(1), wkBan.GetLength(0)];
        for (int X = 0; X <= WillPush.Ban.GetUpperBound(0); X++) {
            for (int Y = 0; Y <= WillPush.Ban.GetUpperBound(1); Y++) {
                WillPush.Ban[X, Y] = wkBan[Y, X];
            }
        }

        WillPush.BanList = new List<char[,]>() { WillPush.Ban };
        WillPush.ExistBanSet = new HashSet<string>();
        IsAlReadyExistBan(WillPush.ExistBanSet, WillPush.Ban);
        WillPush.MoveLogList = new List<char>();

        var stk = new Stack<JyoutaiDef>();
        stk.Push(WillPush);

        int AnswerLevel = int.MaxValue;
        var AnswerBanList = new List<char[,]>();
        var AnswerMoveLogList = new List<char>();

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

            if (AnswerLevel < Popped.MoveLogList.Count) continue;

            if (IsOK(Popped.Ban)) {
                AnswerLevel = Popped.MoveLogList.Count;
                Console.WriteLine("解候補を発見しました。手数={0}", AnswerLevel);
                AnswerBanList = Popped.BanList;
                AnswerMoveLogList = Popped.MoveLogList;
                continue;
            }

            int SpaceX = 0, SpaceY = 0;
            for (int X = 0; X <= Popped.Ban.GetUpperBound(0); X++) {
                for (int Y = 0; Y <= Popped.Ban.GetUpperBound(1); Y++) {
                    if (Popped.Ban[X, Y] == '□') {
                        SpaceX = X;
                        SpaceY = Y;
                    }
                }
            }

            Action<int, int, char> PushSyori = (pFromX, pFromY, pMoveLog) =>
            {
                //■は上下に分断できない
                if (pMoveLog == '↑' || pMoveLog == '↓') {
                    if (Popped.Ban[pFromX, pFromY] == '■') return;
                }

                WillPush.Ban = (char[,])Popped.Ban.Clone();
                //■は左右に分断できない
                if (Popped.Ban[pFromX, pFromY] == '■') {
                    if (pMoveLog == '→') pFromX--;
                    if (pMoveLog == '←') pFromX++;
                }

                WillPush.Ban[SpaceX, SpaceY] = WillPush.Ban[pFromX, pFromY];
                WillPush.Ban[pFromX, pFromY] = '□';
                WillPush.ExistBanSet = new HashSet<string>(Popped.ExistBanSet);
                if (IsAlReadyExistBan(WillPush.ExistBanSet, WillPush.Ban)) return;

                WillPush.BanList = new List<char[,]>(Popped.BanList) { WillPush.Ban };
                WillPush.MoveLogList = new List<char>(Popped.MoveLogList) { pMoveLog };
                stk.Push(WillPush);
            };

            if (SpaceY > 0) //空白の上の文字を下に移動
                PushSyori(SpaceX, SpaceY - 1, '↓');

            if (SpaceY < Popped.Ban.GetUpperBound(1)) //空白の下の文字を上に移動
                PushSyori(SpaceX, SpaceY + 1, '↑');

            if (SpaceX > 0) //空白の左の文字を右に移動
                PushSyori(SpaceX - 1, SpaceY, '→');

            if (SpaceX < Popped.Ban.GetUpperBound(0)) //空白の右の文字を左に移動
                PushSyori(SpaceX + 1, SpaceY, '←');
        }
        Console.WriteLine("解を発見しました。手数={0}", AnswerLevel);
        foreach (var AnyMoveLog in AnswerMoveLogList) Console.Write(AnyMoveLog);
        Console.WriteLine(); Console.WriteLine();
        Console.WriteLine("盤面の遷移は、");
        PrintAnswer(AnswerBanList, AnswerMoveLogList);
    }

    static bool IsOK(char[,] pBan)
    {
        var wkBan = new char[,]{{'■','■','O','N'},
                                {'O','F','F','□'}};

        //X座標とY座標を変換してチェック
        for (int X = 0; X <= pBan.GetUpperBound(0); X++) {
            for (int Y = 0; Y <= pBan.GetUpperBound(1); Y++) {
                if (pBan[X, Y] != wkBan[Y, X]) return false;
            }
        }
        return true;
    }

    static bool IsAlReadyExistBan(HashSet<string> pExistBanSet, char[,] pBan)
    {
        var sb = new System.Text.StringBuilder();
        for (int X = 0; X <= pBan.GetUpperBound(0); X++) {
            for (int Y = 0; Y <= pBan.GetUpperBound(1); Y++) {
                sb.Append(pBan[X, Y]);
            }
        }
        return pExistBanSet.Add(sb.ToString()) == false;
    }

    static void PrintAnswer(List<char[,]> pBanList, List<char> pMoveLogList)
    {
        for (int I = 0; I <= pBanList.Count - 1; I++) {
            for (int Y = 0; Y <= pBanList[I].GetUpperBound(1); Y++) {
                for (int X = 0; X <= pBanList[I].GetUpperBound(0); X++) {
                    Console.Write(pBanList[I][X, Y]);
                }
                Console.WriteLine();
            }
            if (I < pBanList.Count - 1) Console.WriteLine(pMoveLogList[I]);
        }
        Console.WriteLine();
    }
}


実行結果

省略
解候補を発見しました。手数=48
解候補を発見しました。手数=48
解候補を発見しました。手数=48
解候補を発見しました。手数=44
解を発見しました。手数=44
↓→→↑←←←↓→→↑←↓←↑→→→↓←↑←↓→→↑←←←↓→↑→↓←←↑→→→↓←←↑

盤面の遷移は、
■■NO
OFF□
↓
■■N□
OFFO
→
■■□N
OFFO
→
□■■N
OFFO
↑
O■■N
□FFO
←
O■■N
F□FO
←
O■■N
FF□O
←
O■■N
FFO□
↓
O■■□
FFON
→
O□■■
FFON
→
□O■■
FFON
↑
FO■■
□FON
←
FO■■
F□ON
↓
F□■■
FOON
←
F■■□
FOON
↑
F■■N
FOO□
→
F■■N
FO□O
→
F■■N
F□OO
→
F■■N
□FOO
↓
□■■N
FFOO
←
■■□N
FFOO
↑
■■ON
FF□O
←
■■ON
FFO□
↓
■■O□
FFON
→
■■□O
FFON
→
□■■O
FFON
↑
F■■O
□FON
←
F■■O
F□ON
←
F■■O
FO□N
←
F■■O
FON□
↓
F■■□
FONO
→
F□■■
FONO
↑
FO■■
F□NO
→
FO■■
□FNO
↓
□O■■
FFNO
←
O□■■
FFNO
←
O■■□
FFNO
↑
O■■O
FFN□
→
O■■O
FF□N
→
O■■O
F□FN
→
O■■O
□FFN
↓
□■■O
OFFN
←
■■□O
OFFN
←
■■O□
OFFN
↑
■■ON
OFF□


解説

ラムダ式でPush処理をまとめてます。