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

Cマガ電脳クラブ(第067回) 矢な八つ

問題

各面に矢印を描いた立方体が8個ある。
それぞれの立方体の展開図をFig.1に示した。

これらを並べて2×2×2の立方体にして、
各面ごとの4本の矢印の向きをすべて揃えてほしい。

解答集計の便宜のため、完成した大きな立方体を展開して、Fig.2のように表記するようお願いする。
ちなみにFig.2は,Fの立方体のところだけが間違っている惜しい誤答である。

Fig.1 矢な八つ (8個の立方体の展開図)


Fig.2 矢な八つ (解答表記例・誤答)


ソース

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

class Program
{
    //ダイス番号(1〜8)と、6面の配置候補の配列のList
    static Dictionary<int, List<char[]>> Use6MenArrListDict =
        new Dictionary<int, List<char[]>>();

    struct JyoutaiDef
    {
        internal int Level;
        internal List<int> SetDiceNo; //配置したダイス番号
        internal List<char[]> Set6MenArrList; //配置したダイスの展開図のList
    }

    static void Main()
    {
        DeriveUse6MenArrListDict();

        //デバッグ用の6面の配置候補の出力
        //DebugPrintUse6MenArrListDict();

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

        WillPush.Level = 0;
        WillPush.SetDiceNo = new List<int>();
        WillPush.Set6MenArrList = new List<char[]>();
        stk.Push(WillPush);

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

            if (Popped.Level == 8) {
                Console.WriteLine("解を発見");
                PrintAnswer(Popped);
                return;
            }

            WillPush.Level = Popped.Level + 1;
            foreach (var EachPair in Use6MenArrListDict) {
                //最初に配置するダイスは、固定で1番とする
                if (WillPush.Level == 1 && EachPair.Key != 1) continue;

                //回転解の除外で
                //上段右後のダイス番号 < 上段左前のダイス番号
                if (WillPush.Level == 3)
                    if (Popped.SetDiceNo[1] < EachPair.Key == false)
                        continue;

                if (Popped.SetDiceNo.Contains(EachPair.Key)) continue;

                foreach (char[] EachKouhoArr in EachPair.Value) {
                    WillPush.SetDiceNo = new List<int>(Popped.SetDiceNo) { EachPair.Key };
                    WillPush.Set6MenArrList = new List<char[]>(Popped.Set6MenArrList);
                    WillPush.Set6MenArrList.Add(EachKouhoArr);
                    if (IsValid(WillPush.Set6MenArrList) == false) continue;
                    stk.Push(WillPush);
                }
            }
        }
    }

    //8つのダイスの、使用する6面の候補を求める
    static void DeriveUse6MenArrListDict()
    {
        //この展開図と配列の添字を対応させて、8つのダイスの展開図を定義
        // 0
        //1234
        // 5

        //展開図とダイスとの対応は、下記の1対1対応とする
        // 上
        //左前右後
        // 下

        var Base6MenArrList = new List<char[]>();
        Base6MenArrList.Add("↑↑→←→→".ToCharArray());
        Base6MenArrList.Add("↑↑↓↑↑→".ToCharArray());
        Base6MenArrList.Add("↑↑↓↑→→".ToCharArray());
        Base6MenArrList.Add("↑↑↓→↑→".ToCharArray());
        Base6MenArrList.Add("↑↑↓↓↑↑".ToCharArray());
        Base6MenArrList.Add("↑↑↓↓↑→".ToCharArray());
        Base6MenArrList.Add("↑↑←↓↑→".ToCharArray());
        Base6MenArrList.Add("↑→→→↓↑".ToCharArray());

        for (int I = 0; I <= Base6MenArrList.Count - 1; I++) {
            Use6MenArrListDict[I + 1] = DeriveUse6MenArrList(Base6MenArrList[I]);
        }
    }

    //1つのダイスの、配置の候補を求める(ダイス1つにつき24通り)
    static List<char[]> DeriveUse6MenArrList(char[] pBase6MenArrList)
    {
        var WillReturn = new List<char[]>();

        Action<char[]> wkAct = (pKaiten6MenArr) =>
            WillReturn.AddRange(DeriveUse4MenArrList(pKaiten6MenArr));

        //0が上面、5が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 0, 1, 2, 3, 4, 5 },
                                              new int[] { 0, 0, 0, 0, 0, 0 },
                                              new int[] { }, new int[] { }));

        //5が上面、0が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 5, 3, 2, 1, 4, 0 },
                                              new int[] { 2, 2, 2, 2, 2, 2 },
                                              new int[] { }, new int[] { }));

        //1が上面、3が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 1, 5, 2, 0, 4, 3 },
                                              new int[] { 1, 1, 1, 1, 1, 1 },
                                              new int[] { 4 }, new int[] { 4 }));

        //3が上面、1が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 3, 0, 2, 5, 4, 1 },
                                              new int[] { -1, -1, -1, -1, -1, -1 },
                                              new int[] { 4 }, new int[] { 4 }));

        //2が上面、4が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 2, 5, 3, 0, 1, 4 },
                                              new int[] { 1, 0, 1, 2, 1, 1 },
                                              new int[] { 4 }, new int[] { 4 }));

        //4が上面、2が下面
        wkAct(Kaiten6MenArr(pBase6MenArrList, new int[] { 4, 0, 3, 5, 1, 2 },
                                              new int[] { -1, 0, -1, -2, -1, -1 },
                                              new int[] { 4 }, new int[] { 4 }));

        //重複データの削除
        for (int I = WillReturn.Count - 1; 0 <= I; I--) {
            bool WillRemove = false;
            for (int J = 0; J <= I - 1; J++) {
                if (WillReturn[J].SequenceEqual(WillReturn[I])) {
                    WillRemove = true;
                    break;
                }
            }
            if (WillRemove) WillReturn.RemoveAt(I);
        }

        return WillReturn;
    }

    //元の展開図の配列、元になる添字の配列と、90度回転の回数の配列
    //上下鏡像になる元になる添字の配列
    //左右鏡像になる元になる添字の配列
    //を引数として、ダイスと対応した展開図の各矢印を求める
    static char[] Kaiten6MenArr(char[] pBase6MenArr, int[] pBaseIndArr, int[] pKaitenArr,
                                int[] JyougeKyouzouIndArr, int[] SayuuKyouzouIndArr)
    {
        var WillReturn = new List<char>();

        for (int I = 0; I <= pBaseIndArr.GetUpperBound(0); I++) {
            char WillAdd = Kaiten90do(pBase6MenArr[pBaseIndArr[I]], pKaitenArr[I]);
            if (JyougeKyouzouIndArr.Contains(I))
                WillAdd = DeriveKyouzouArrow(WillAdd, true);
            if (SayuuKyouzouIndArr.Contains(I))
                WillAdd = DeriveKyouzouArrow(WillAdd, false);

            WillReturn.Add(WillAdd);
        }
        return WillReturn.ToArray();
    }

    //Nが正数なら、右に90度回転をN回行った矢印を返す
    //Nが負数なら、左に90度回転をN回行った矢印を返す
    static char Kaiten90do(char pArrow, int pN)
    {
        if (pN < 0) pN += 4;

        for (int I = 1; I <= pN; I++) {
            if (pArrow == '↑') pArrow = '→';
            else if (pArrow == '→') pArrow = '↓';
            else if (pArrow == '↓') pArrow = '←';
            else if (pArrow == '←') pArrow = '↑';
        }
        return pArrow;
    }

    //上下鏡像もしくは左右鏡像の矢印を返す
    static char DeriveKyouzouArrow(char pArrow, bool pIsJyougeKyouzou)
    {
        if (pIsJyougeKyouzou) {
            if (pArrow == '↑') return '↓';
            if (pArrow == '↓') return '↑';
            return pArrow;
        }
        else {
            if (pArrow == '→') return '←';
            if (pArrow == '←') return '→';
            return pArrow;
        }
    }

    //使用する4面の並び順を求める(4通り)
    static List<char[]> DeriveUse4MenArrList(char[] pKaiten6MenArr)
    {
        var WillReturn = new List<char[]>();

        //正順での共通処理
        Action<int[], int> CommonAct = (pBaseIndArr, pN) =>
        {
            char[] WillAddArr = new char[6];
            for (int I = 0; I <= pBaseIndArr.GetUpperBound(0); I++) {
                WillAddArr[I] = pKaiten6MenArr[pBaseIndArr[I]];
            }
            //上面と下面は90度回転を行う
            WillAddArr[0] = Kaiten90do(WillAddArr[0], pN);
            WillAddArr[5] = Kaiten90do(WillAddArr[5], -pN);

            WillReturn.Add(WillAddArr);
        };

        //正順の4つ
        CommonAct(new int[] { 0, 1, 2, 3, 4, 5 }, 0);
        CommonAct(new int[] { 0, 2, 3, 4, 1, 5 }, 1);
        CommonAct(new int[] { 0, 3, 4, 1, 2, 5 }, 2);
        CommonAct(new int[] { 0, 4, 1, 2, 3, 5 }, 3);

        return WillReturn;
    }

    //デバッグ用の6面の配置候補の出力
    static void DebugPrintUse6MenArrListDict()
    {
        foreach (var EachPair in Use6MenArrListDict) {
            Console.WriteLine("ダイス{0}の配置リスト", EachPair.Key);
            for (int I = 0; I <= EachPair.Value.Count - 1; I++) {
                Console.Write("配置{0,2} ", I + 1);
                Array.ForEach(EachPair.Value[I], A => Console.Write(A));
                Console.WriteLine();
            }
        }
        Console.WriteLine();
    }

    //上段左後  上段右後
    //上段左前  上段右前
    //下段左後  下段右後
    //下段左前  下段右前
    //の順番でダイスを配置するので、有効な状態かをチェックする
    static bool IsValid(List<char[]> pSet6MenArrList)
    {
        //上段右後の配置
        if (pSet6MenArrList.Count == 2) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[1][4] != pSet6MenArrList[0][4]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[1][0] != pSet6MenArrList[0][0]) return false;
        }
        //上段左前の配置
        if (pSet6MenArrList.Count == 3) {
            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[2][1] != pSet6MenArrList[0][1]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[2][0] != pSet6MenArrList[0][0]) return false;
        }
        //上段右前の配置
        if (pSet6MenArrList.Count == 4) {
            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[3][3] != pSet6MenArrList[1][3]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[3][2] != pSet6MenArrList[2][2]) return false;

            //上面 = 上段左後のダイスの上面
            if (pSet6MenArrList[3][0] != pSet6MenArrList[0][0]) return false;
        }
        //下段左後の配置
        if (pSet6MenArrList.Count == 5) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[4][4] != pSet6MenArrList[0][4]) return false;

            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[4][1] != pSet6MenArrList[0][1]) return false;
        }
        //下段右後の配置
        if (pSet6MenArrList.Count == 6) {
            //後面 = 上段左後のダイスの後面
            if (pSet6MenArrList[5][4] != pSet6MenArrList[0][4]) return false;

            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[5][5] != pSet6MenArrList[4][5]) return false;

            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[5][3] != pSet6MenArrList[1][3]) return false;
        }
        //下段左前の配置
        if (pSet6MenArrList.Count == 7) {
            //左面 = 上段左後のダイスの左面
            if (pSet6MenArrList[6][1] != pSet6MenArrList[0][1]) return false;

            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[6][5] != pSet6MenArrList[4][5]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[6][2] != pSet6MenArrList[2][2]) return false;
        }
        //下段右前の配置
        if (pSet6MenArrList.Count == 8) {
            //下面 = 下段左後のダイスの下面
            if (pSet6MenArrList[7][5] != pSet6MenArrList[4][5]) return false;

            //右面 = 上段右後のダイスの右面
            if (pSet6MenArrList[7][3] != pSet6MenArrList[1][3]) return false;

            //前面 = 上段左前のダイスの前面
            if (pSet6MenArrList[7][2] != pSet6MenArrList[2][2]) return false;
        }

        return true;
    }

    //配置したダイスを展開図で表示する
    //上段左後  上段右後
    //上段左前  上段右前
    //下段左後  下段右後
    //下段左前  下段右前
    //の順番で展開図を表示する
    static void PrintAnswer(JyoutaiDef pJyoutaiDef)
    {
        Action<int> PrintPairTenkaizu = pBaseSoeji =>
        {
            var sb = new System.Text.StringBuilder();
            sb.AppendFormat("Dice{0}      Dice{1}",
                pJyoutaiDef.SetDiceNo[pBaseSoeji], pJyoutaiDef.SetDiceNo[pBaseSoeji + 1]);
            sb.AppendLine();

            Action<int> CommonSyori = (pTargetMen) =>
            {
                sb.AppendFormat(" {0}    {1}",
                    pJyoutaiDef.Set6MenArrList[pBaseSoeji][pTargetMen],
                    pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][pTargetMen]);
                sb.AppendLine();
            };

            CommonSyori(0);

            sb.AppendFormat("{0}{1}{2}{3} {4}{5}{6}{7}",
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][1],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][2],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][3],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji][4],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][1],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][2],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][3],
                pJyoutaiDef.Set6MenArrList[pBaseSoeji + 1][4]);
            sb.AppendLine();

            CommonSyori(5);

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

        for (int I = 0; I <= pJyoutaiDef.SetDiceNo.Count - 1; I += 2) {
            if (I == 0) Console.WriteLine("上段左後と上段右後の展開図");
            if (I == 2) Console.WriteLine("上段左前と上段右前の展開図");
            if (I == 4) Console.WriteLine("下段左後と下段右後の展開図");
            if (I == 6) Console.WriteLine("下段左前と下段右前の展開図");
            PrintPairTenkaizu(I);
        }

        //完成した大きな立方体の展開図を出力
        PrintTenkaizu(pJyoutaiDef.Set6MenArrList);
    }

    //完成した大きな立方体の展開図を出力
    static void PrintTenkaizu(List<char[]> pSet6MenArrList)
    {
        Console.WriteLine("完成した大きな立方体の展開図");
        var sb = new System.Text.StringBuilder();

        Action<char, char> Out2CharAct = (pChar1, pChar2) =>
        {
            sb.AppendFormat("  {0}{1}", pChar1, pChar2);
            sb.AppendLine();
        };

        Action<char[]> Out8CharAct = (pCharArr) =>
        {
            Array.ForEach(pCharArr, A => sb.Append(A));
            sb.AppendLine();
        };

        Out2CharAct(pSet6MenArrList[0][0], pSet6MenArrList[1][0]);
        Out2CharAct(pSet6MenArrList[2][0], pSet6MenArrList[3][0]);

        Out8CharAct(new char[] {pSet6MenArrList[0][1], pSet6MenArrList[2][1],
                                pSet6MenArrList[2][2], pSet6MenArrList[3][2],
                                pSet6MenArrList[3][3], pSet6MenArrList[1][3],
                                pSet6MenArrList[1][4], pSet6MenArrList[0][4]});

        Out8CharAct(new char[] {pSet6MenArrList[4][1], pSet6MenArrList[6][1],
                                pSet6MenArrList[6][2], pSet6MenArrList[7][2],
                                pSet6MenArrList[7][3], pSet6MenArrList[5][3],
                                pSet6MenArrList[5][4], pSet6MenArrList[4][4]});

        Out2CharAct(pSet6MenArrList[4][5], pSet6MenArrList[5][5]);
        Out2CharAct(pSet6MenArrList[6][5], pSet6MenArrList[7][5]);

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


実行結果

解を発見
上段左後と上段右後の展開図
Dice1      Dice3
 ←    ←
→↑↓↓ ←→↑↓
 ↓    ←

上段左前と上段右前の展開図
Dice7      Dice8
 ←    ←
→←↓↑ ←←↑←
 →    →

下段左後と下段右後の展開図
Dice4      Dice6
 ↓    ↓
→↑↑↓ ↓↑↑↓
 ←    ←

下段左前と下段右前の展開図
Dice2      Dice5
 ←    →
→←↓← ↓←↑←
 ←    ←

完成した大きな立方体の展開図
  ←←
  ←←
→→←←↑↑↓↓
→→←←↑↑↓↓
  ←←
  ←←


解説

ダイスの配置は24通りですが、
ダイスの展開図から(展開図の反転を許可して)立方体を作成して配置するのは、48通りあります。
本問では、展開図は両面を考える必要はなしと解釈しました。

なお、Cマガ電脳クラブ(第037回) カラーボックスのように、
問題で、この展開図の立方体があると説明されてる場合は、
展開図の両面を許可するとして、ダイスの配置は48通りです。

解の検証は、
各ダイスが展開図から作成可能な立方体であること。
各ダイスの6面が条件を満たすこと。
の2つをチェックしてます。

実機検証として、ティッシュの箱8個を使って検証しました。

立体パズルのページ --- 矢な八つ