using System;
using System.Collections.Generic;
class Program
{
const int LevelLimit = 73;
const int UB_X = 4;
const int UB_Y = 3;
struct PointDef
{
internal int X;
internal int Y;
}
struct JyoutaiDef
{
internal char[,] BanArr;
internal int Level;
internal List<string> Log;
}
static void Main()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
string[] InitArr ={"牛牛止□□",
"牛牛止横横",
"木木縦コ輪",
"小屋縦ミ木"};
var Que = new Queue<JyoutaiDef>();
JyoutaiDef WillEnqueue;
WillEnqueue.BanArr = new char[UB_X + 1, UB_Y + 1];
for (int X = 0; X <= UB_X; X++) {
for (int Y = 0; Y <= UB_Y; Y++) {
WillEnqueue.BanArr[X, Y] = InitArr[Y][X];
}
}
WillEnqueue.Level = 0;
WillEnqueue.Log = new List<string>();
WillEnqueue.Log.Add(BanToStr(WillEnqueue.BanArr));
Que.Enqueue(WillEnqueue);
var VisitedSet = new HashSet<ulong>();
VisitedSet.Add(GetHash(WillEnqueue.BanArr));
while (Que.Count > 0) {
JyoutaiDef Dequeued = Que.Dequeue();
if (IsClear(Dequeued.BanArr)) {
for (int I = 0; I <= Dequeued.Log.Count - 1; I++) {
Console.WriteLine("{0}手目", I);
Console.WriteLine(Dequeued.Log[I]);
}
break;
}
//下限値枝切り
if (Dequeued.Level + DeriveNeedMinTesuu(Dequeued.BanArr) > LevelLimit) continue;
foreach (char EachPiece in "牛止横縦小木コ輪ミ") {
//盤面とピースを引数として、1手後の盤面のListを返す
List<char[,]> MovedArrList = DeriveMovedArrList(Dequeued.BanArr, EachPiece);
foreach (char[,] EachMovedArr in MovedArrList) {
WillEnqueue.BanArr = EachMovedArr;
WillEnqueue.Level = Dequeued.Level + 1;
//再訪防止
if (VisitedSet.Add(GetHash(EachMovedArr)) == false) continue;
WillEnqueue.Log = new List<string>(Dequeued.Log);
WillEnqueue.Log.Add(BanToStr(WillEnqueue.BanArr));
Que.Enqueue(WillEnqueue);
}
}
}
Console.WriteLine("経過時間={0}", sw.Elapsed);
}
//クリア判定
static bool IsClear(char[,] pBanArr)
{
if (pBanArr[3, 2] != '牛') return false;
if (pBanArr[4, 2] != '牛') return false;
if (pBanArr[3, 3] != '牛') return false;
if (pBanArr[4, 3] != '牛') return false;
if (pBanArr[2, 0] != '止') return false;
if (pBanArr[2, 1] != '止') return false;
if (pBanArr[3, 1] != '横') return false;
if (pBanArr[4, 1] != '横') return false;
if (pBanArr[2, 2] != '縦') return false;
if (pBanArr[2, 3] != '縦') return false;
if (pBanArr[1, 2] != '□') return false;
if (pBanArr[1, 3] != '□') return false;
return true;
}
//最小の残り手数を返す(下限値枝切り用)
static int DeriveNeedMinTesuu(char[,] pBanArr)
{
PointDef wkPos = DerivePiecePosArr(pBanArr, '牛')[0];
int X = wkPos.X;
int Y = wkPos.Y;
if (X == 0 && Y == 0) return 18;
if (X == 1 && Y == 0) return 14;
if (X == 2 && Y == 0) return 10;
if (X == 3 && Y == 0) return 6;
if (X == 0 && Y == 1) return 14;
if (X == 1 && Y == 1) return 10;
if (X == 2 && Y == 1) return 6;
if (X == 3 && Y == 1) return 2;
if (X == 0 && Y == 2) return 10;
if (X == 1 && Y == 2) return 6;
if (X == 2 && Y == 2) return 2;
//牛が[3,2]にいたら、柵の座標を見る
int WillReturn = 0;
wkPos = DerivePiecePosArr(pBanArr, '止')[0];
if (wkPos.Y != 0) WillReturn++;
WillReturn += Math.Abs(2 - wkPos.X);
wkPos = DerivePiecePosArr(pBanArr, '横')[0];
if (wkPos.X != 3) WillReturn++;
WillReturn += Math.Abs(1 - wkPos.Y);
wkPos = DerivePiecePosArr(pBanArr, '縦')[0];
if (wkPos.Y != 2) WillReturn++;
WillReturn += Math.Abs(2 - wkPos.X);
return WillReturn;
}
struct MoveInfoDef
{
internal char[,] BanArr;
internal PointDef FromPos;
}
//盤面とピースを引数として、1手後の盤面のListを返す
static List<char[,]> DeriveMovedArrList(char[,] pBanArr, char pPiece)
{
var WillReturn = new List<char[,]>();
PointDef[] PiecePosArr = DerivePiecePosArr(pBanArr, pPiece);
var Stk = new Stack<MoveInfoDef>();
MoveInfoDef WillPush;
foreach (PointDef EachPos in PiecePosArr) {
WillPush.BanArr = pBanArr;
WillPush.FromPos = EachPos;
Stk.Push(WillPush);
}
var VisitedSet = new HashSet<ulong>();
while (Stk.Count > 0) {
MoveInfoDef Popped = Stk.Pop();
MoveInfoDef[] MovedInfoArr = DeriveMovedInfoArr(Popped.BanArr, pPiece, Popped.FromPos);
foreach (MoveInfoDef EachJyoutai in MovedInfoArr) {
ulong CurrHash = GetHash(EachJyoutai.BanArr);
if (CurrHash == GetHash(pBanArr)) continue;
if (VisitedSet.Add(CurrHash) == false) continue;
WillReturn.Add(EachJyoutai.BanArr);
//1手で複数マス動けるピースの場合
if (pPiece != '牛') Stk.Push(EachJyoutai);
}
}
return WillReturn;
}
//盤面とピースを引数として、移動情報の配列を返す
static MoveInfoDef[] DeriveMovedInfoArr(char[,] pBanArr, char pPiece, PointDef pFromPos)
{
var WillReturn = new List<MoveInfoDef>();
int FromX = pFromPos.X, FromY = pFromPos.Y;
Action<char[,], int, int> AddAct1 = (pArr, pToX, pToY) =>
{
MoveInfoDef WillAdd;
WillAdd.BanArr = pArr;
WillAdd.FromPos = new PointDef() { X = pToX, Y = pToY };
WillReturn.Add(WillAdd);
};
if (pPiece == '牛') {
//上に移動
if (IsSpace(pBanArr, FromX, FromY - 1)
&& IsSpace(pBanArr, FromX + 1, FromY - 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY - 1] = wkArr[FromX + 1, FromY - 1] = '牛';
wkArr[FromX, FromY] = wkArr[FromX + 1, FromY] = '牛';
wkArr[FromX, FromY + 1] = wkArr[FromX + 1, FromY + 1] = '□';
AddAct1(wkArr, FromX, FromY - 1);
}
//下に移動
if (IsSpace(pBanArr, FromX, FromY + 2)
&& IsSpace(pBanArr, FromX + 1, FromY + 2)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY] = wkArr[FromX + 1, FromY] = '□';
wkArr[FromX, FromY + 1] = wkArr[FromX + 1, FromY + 1] = '牛';
wkArr[FromX, FromY + 2] = wkArr[FromX + 1, FromY + 2] = '牛';
AddAct1(wkArr, FromX, FromY + 1);
}
//左に移動
if (IsSpace(pBanArr, FromX - 1, FromY)
&& IsSpace(pBanArr, FromX - 1, FromY + 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX - 1, FromY] = wkArr[FromX - 1, FromY + 1] = '牛';
wkArr[FromX, FromY] = wkArr[FromX, FromY + 1] = '牛';
wkArr[FromX + 1, FromY] = wkArr[FromX + 1, FromY + 1] = '□';
AddAct1(wkArr, FromX - 1, FromY);
}
//右に移動
if (IsSpace(pBanArr, FromX + 2, FromY)
&& IsSpace(pBanArr, FromX + 2, FromY + 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX + 2, FromY] = wkArr[FromX + 2, FromY + 1] = '牛';
wkArr[FromX + 1, FromY] = wkArr[FromX + 1, FromY + 1] = '牛';
wkArr[FromX, FromY] = wkArr[FromX, FromY + 1] = '□';
AddAct1(wkArr, FromX + 1, FromY);
}
}
if (pPiece == '止' || pPiece == '縦') {
//上に移動
if (IsSpace(pBanArr, FromX, FromY - 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY - 1] = pPiece;
wkArr[FromX, FromY] = pPiece;
wkArr[FromX, FromY + 1] = '□';
AddAct1(wkArr, FromX, FromY - 1);
}
//下に移動
if (IsSpace(pBanArr, FromX, FromY + 2)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY] = '□';
wkArr[FromX, FromY + 1] = pPiece;
wkArr[FromX, FromY + 2] = pPiece;
AddAct1(wkArr, FromX, FromY + 1);
}
//左に移動
if (IsSpace(pBanArr, FromX - 1, FromY)
&& IsSpace(pBanArr, FromX - 1, FromY + 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX - 1, FromY] = pPiece;
wkArr[FromX - 1, FromY + 1] = pPiece;
wkArr[FromX, FromY] = wkArr[FromX, FromY + 1] = '□';
AddAct1(wkArr, FromX - 1, FromY);
}
//右に移動
if (IsSpace(pBanArr, FromX + 1, FromY)
&& IsSpace(pBanArr, FromX + 1, FromY + 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX + 1, FromY] = pPiece;
wkArr[FromX + 1, FromY + 1] = pPiece;
wkArr[FromX, FromY] = wkArr[FromX, FromY + 1] = '□';
AddAct1(wkArr, FromX + 1, FromY);
}
}
if (pPiece == '横' || pPiece == '小') {
Func<char> Derive2Mojime = () => pPiece == '横' ? '横' : '屋';
//上に移動
if (IsSpace(pBanArr, FromX, FromY - 1)
&& IsSpace(pBanArr, FromX + 1, FromY - 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY - 1] = pPiece;
wkArr[FromX + 1, FromY - 1] = Derive2Mojime();
wkArr[FromX, FromY] = wkArr[FromX + 1, FromY] = '□';
AddAct1(wkArr, FromX, FromY - 1);
}
//下に移動
if (IsSpace(pBanArr, FromX, FromY + 1)
&& IsSpace(pBanArr, FromX + 1, FromY + 1)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY] = wkArr[FromX + 1, FromY] = '□';
wkArr[FromX, FromY + 1] = pPiece;
wkArr[FromX + 1, FromY + 1] = Derive2Mojime();
AddAct1(wkArr, FromX, FromY + 1);
}
//左に移動
if (IsSpace(pBanArr, FromX - 1, FromY)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX - 1, FromY] = pPiece;
wkArr[FromX, FromY] = Derive2Mojime();
wkArr[FromX + 1, FromY] = '□';
AddAct1(wkArr, FromX - 1, FromY);
}
//右に移動
if (IsSpace(pBanArr, FromX + 2, FromY)) {
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[FromX, FromY] = '□';
wkArr[FromX + 1, FromY] = pPiece;
wkArr[FromX + 2, FromY] = Derive2Mojime();
AddAct1(wkArr, FromX + 1, FromY);
}
}
if (pPiece == '木' || pPiece == 'コ' || pPiece == '輪' || pPiece == 'ミ') {
Action<int, int> AddAct2 = (pToX, pToY) =>
{
if (IsSpace(pBanArr, pToX, pToY) == false) return;
char[,] wkArr = (char[,])pBanArr.Clone();
wkArr[pToX, pToY] = pPiece; wkArr[FromX, FromY] = '□';
MoveInfoDef WillAdd;
WillAdd.BanArr = wkArr;
WillAdd.FromPos = new PointDef() { X = pToX, Y = pToY };
WillReturn.Add(WillAdd);
};
//上に移動
AddAct2(FromX, FromY - 1);
//下に移動
AddAct2(FromX, FromY + 1);
//左に移動
AddAct2(FromX - 1, FromY);
//右に移動
AddAct2(FromX + 1, FromY);
}
return WillReturn.ToArray();
}
//盤面とピースを引数として、ピースの左上の座標の配列を返す
static PointDef[] DerivePiecePosArr(char[,] pBanArr, char pPiece)
{
var WillReturn = new List<PointDef>();
for (int LoopY = 0; LoopY <= UB_Y; LoopY++) {
for (int LoopX = 0; LoopX <= UB_X; LoopX++) {
if (pBanArr[LoopX, LoopY] == pPiece) {
PointDef WillAdd;
WillAdd.X = LoopX;
WillAdd.Y = LoopY;
WillReturn.Add(WillAdd);
//木以外は1座標のみ
if (pPiece != '木') return WillReturn.ToArray();
}
}
}
return WillReturn.ToArray();
}
//座標が空白かを判定
static bool IsSpace(char[,] pBanArr, int pX, int pY)
{
if (pX < 0 || UB_X < pX) return false;
if (pY < 0 || UB_Y < pY) return false;
return pBanArr[pX, pY] == '□';
}
//ハッシュ値を求める
static ulong GetHash(char[,] pBanArr)
{
var sb = new System.Text.StringBuilder();
for (int X = 0; X <= UB_X; X++) {
for (int Y = 0; Y <= UB_Y; Y++) {
char wkChar = pBanArr[X, Y];
if (wkChar == '屋') continue;
if (wkChar == '牛') sb.Append('6');
else if (wkChar == '止') sb.Append('5');
else if (wkChar == '横') sb.Append('4');
else if (wkChar == '縦') sb.Append('3');
else if (wkChar == '小') sb.Append('2');
else if (wkChar == '木') sb.Append('1');
else if (wkChar == 'コ') sb.Append('1');
else if (wkChar == '輪') sb.Append('1');
else if (wkChar == 'ミ') sb.Append('1');
else sb.Append('0');
}
}
//8の21乗=9223372036854780000なので8進数ならオーバーフローしない
return Convert.ToUInt64(sb.ToString(), 8);
}
//盤面をString型で表現
static string BanToStr(char[,] pBanArr)
{
var sb = new System.Text.StringBuilder();
for (int Y = 0; Y <= UB_Y; Y++) {
for (int X = 0; X <= UB_X; X++) {
sb.Append(pBanArr[X, Y]);
}
sb.AppendLine();
}
return sb.ToString();
}
}