前些时日研究出华容道的计算机求解,后来得知华容道、独立钻石、魔法并称三大不可思议的智力游戏,于是下手玩起了独立钻石,没想到拿不到“天才”的称号,于是又只能跟当初解决不了华容道问题一样,动手写程序求解了。
写完后发现自己还是菜了一些,需要7分钟才能找到“天才”解法。不过已经可以满足我的虚荣心了,优化的工作就留给有缘看到这篇博文的朋友吧。
求解开始
第1步==4,6,LEFT
第2步==4,3,RIGHT
第3步==4,1,RIGHT
第4步==6,4,UP
第5步==4,4,LEFT
第6步==5,6,LEFT
第7步==6,3,UP
第8步==5,1,RIGHT
第9步==5,4,LEFT
第10步==7,5,UP
第11步==7,3,RIGHT
第12步==4,5,DOWN
第13步==7,5,UP
第14步==2,5,DOWN
第15步==3,7,LEFT
第16步==3,4,RIGHT
第17步==3,2,RIGHT
第18步==5,2,UP
第19步==3,1,RIGHT
第20步==3,4,LEFT
第21步==5,7,UP
第22步==3,7,LEFT
第23步==1,3,DOWN
第24步==1,5,LEFT
第25步==4,3,UP
第26步==1,3,DOWN
第27步==3,2,RIGHT
第28步==3,4,RIGHT
第29步==5,5,UP
第30步==3,6,LEFT
第31步==2,4,DOWN
求解结束
耗时:416.4852297秒
解空间:3765429
如果朋友只是来找答案的,那么看到这里就可以了,因为接下来的内容,可能会稍微有点吃力。
首先使用vs2019【可以大于等于2019版本】新建C#控制台项目,取名“独立钻石”,接着将以下【两个】cs文件拷贝到项目中即可运行。
注意事项:【编译器:VS>=2019,运行环境:C#.NET5,项目类型:控制台应用程序,电脑内存:>=16G】
请特别注意对电脑内存的要求,因为我怕你的电脑会卡死,啊哈哈。
1.Aspect.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 独立钻石
{
public class Aspect
{
public bool[,] map;
public Aspect parent = null;
public int th = 0;
public Tuple step = null;
public bool success = false;
public Aspect()
{
map = new bool[9, 9];
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
map[i, j] = true;
map[j, i] = true;
}
}
map[4, 4] = false;
}
public Aspect(Aspect parent, Tuple step)
{
this.map = (bool[,])parent.map.Clone();
this.parent = parent;
this.step = step;
this.th = parent.th + 1;
int i = step.Item1, j = step.Item2;
this.map[i, j] = !this.map[i, j];
switch (step.Item3)
{
case Turn.LEFT:
this.map[i, j - 1] = !this.map[i, j - 1];
this.map[i, j - 2] = !this.map[i, j - 2];
break;
case Turn.RIGHT:
this.map[i, j + 1] = !this.map[i, j + 1];
this.map[i, j + 2] = !this.map[i, j + 2];
break;
case Turn.UP:
this.map[i - 1, j] = !this.map[i - 1, j];
this.map[i - 2, j] = !this.map[i - 2, j];
break;
case Turn.DOWN:
this.map[i + 1, j] = !this.map[i + 1, j];
this.map[i + 2, j] = !this.map[i + 2, j];
break;
}
this.success = th == 31 && map[4, 4];
}
public List> Steps()
{
List> tuples = new();
#region LEFT
if (map[1, 3] == false && map[1, 4] && map[1, 5])
{
tuples.Add(new(1, 5, Turn.LEFT));
}
if (map[2, 3] == false && map[2, 4] && map[2, 5])
{
tuples.Add(new(2, 5, Turn.LEFT));
}
if (map[6, 3] == false && map[6, 4] && map[6, 5])
{
tuples.Add(new(6, 5, Turn.LEFT));
}
if (map[7, 3] == false && map[7, 4] && map[7, 5])
{
tuples.Add(new(7, 5, Turn.LEFT));
}
for (int i = 3; i <= 5; i++)
{
for (int j = 1; j <= 5; j++)
{
if (map[i, j] == false && map[i, j + 1] && map[i, j + 2])
{
tuples.Add(new(i, j + 2, Turn.LEFT));
}
}
}
#endregion
#region RIGHT
if (map[1, 5] == false && map[1, 4] && map[1, 3])
{
tuples.Add(new(1, 3, Turn.RIGHT));
}
if (map[2, 5] == false && map[2, 4] && map[2, 3])
{
tuples.Add(new(2, 3, Turn.RIGHT));
}
if (map[6, 5] == false && map[6, 4] && map[6, 3])
{
tuples.Add(new(6, 3, Turn.RIGHT));
}
if (map[7, 5] == false && map[7, 4] && map[7, 3])
{
tuples.Add(new(7, 3, Turn.RIGHT));
}
for (int i = 3; i <= 5; i++)
{
for (int j = 7; j >= 3; j--)
{
if (map[i, j] == false && map[i, j - 1] && map[i, j - 2])
{
tuples.Add(new(i, j - 2, Turn.RIGHT));
}
}
}
#endregion
#region UP
if (map[3, 1] == false && map[4, 1] && map[5, 1])
{
tuples.Add(new(5, 1, Turn.UP));
}
if (map[3, 2] == false && map[4, 2] && map[5, 2])
{
tuples.Add(new(5, 2, Turn.UP));
}
if (map[3, 6] == false && map[4, 6] && map[5, 6])
{
tuples.Add(new(5, 6, Turn.UP));
}
if (map[3, 7] == false && map[4, 7] && map[5, 7])
{
tuples.Add(new(5, 7, Turn.UP));
}
for (int i = 1; i <= 5; i++)
{
for (int j = 3; j <= 5; j++)
{
if (map[i, j] == false && map[i + 1, j] && map[i + 2, j])
{
tuples.Add(new(i + 2, j, Turn.UP));
}
}
}
#endregion
#region DOWN
if (map[5, 1] == false && map[4, 1] && map[3, 1])
{
tuples.Add(new(3, 1, Turn.DOWN));
}
if (map[5, 2] == false && map[4, 2] && map[3, 2])
{
tuples.Add(new(3, 2, Turn.DOWN));
}
if (map[5, 6] == false && map[4, 6] && map[3, 6])
{
tuples.Add(new(3, 6, Turn.DOWN));
}
if (map[5, 7] == false && map[4, 7] && map[3, 7])
{
tuples.Add(new(3, 7, Turn.DOWN));
}
for (int i = 7; i >= 3; i--)
{
for (int j = 3; j <= 5; j++)
{
if (map[i, j] == false && map[i - 1, j] && map[i - 2, j])
{
tuples.Add(new(i - 2, j, Turn.DOWN));
}
}
}
#endregion
return tuples;
}
}
public enum Turn
{
LEFT,
RIGHT,
UP,
DOWN
}
}
2.Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace 独立钻石
{
class Program
{
static void Main()
{
DateTime start = DateTime.Now;
int count = 0;
Console.WriteLine("求解开始");
Aspect aspect = new();
Queue queue = new();
queue.Enqueue(aspect);
HashSet set = new();
while (queue.Any())
{
Aspect now = queue.Dequeue();
List> steps = now.Steps();
foreach (var step in steps)
{
Aspect next = new(now, step);
string self = ToString(next.map);
if (!set.Contains(self))
{
List mays = 各种变换(next.map);
foreach (string may in mays)
{
try
{
set.Add(may);
}
catch { }
}
queue.Enqueue(next);
//stack.Push(next);
count = Math.Max(queue.Count, count);
if (next.success)
{
Print(next);
break;
}
}
}
}
Console.WriteLine("求解结束");
DateTime end = DateTime.Now;
Console.WriteLine($"耗时:{(end - start).TotalSeconds}秒");
Console.WriteLine($"解空间:{count}");
}
public static void Print(Aspect aspect)
{
if (aspect.parent != null)
{
Print(aspect.parent);
}
if (aspect.step != null)
{
Console.WriteLine($"第{aspect.th}步=={aspect.step.Item1},{aspect.step.Item2},{aspect.step.Item3}==>{ToString(aspect.map)}");
}
}
public static string ToString(bool[,] map)
{
StringBuilder sb = new();
for (int i = 1; i <= 7; i++)
{
int line = 0;
for (int j = 1; j <= 7; j++)
{
if (map[i, j])
{
line |= (1 << (j - 1));
}
}
sb.Append((char)line);
}
return sb.ToString();
}
public static List 各种变换(bool[,] map)
{
List result = new();
//垂直轴对称
//水平轴对称
//撇轴对称
//捺轴对称
//顺时针90度
//逆时针90度
//中心对称
StringBuilder[] sbs = new StringBuilder[8];
for(int i = 0; i < sbs.Length; i++)
{
sbs[i] = new();
}
int[] line = new int[8];
for (int i = 1; i <= 7; i++)
{
for(int j = 0; j < 8; j++)
{
line[j] = 0;
}
for (int j = 1; j <= 7; j++)
{
if(map[i, 8 - j])
{
line[0] |= (1 << (j - 1));
}
if(map[8 - i, j])
{
line[1] |= (1 << (j - 1));
}
if(map[j, i])
{
line[2] |= (1 << (j - 1));
}
if(map[8 - j, 8 - i])
{
line[3] |= (1 << (j - 1));
}
if(map[8 - i, 8 - j])
{
line[4] |= (1 << (j - 1));
}
if (map[j, 8 - i])
{
line[5] |= (1 << (j - 1));
}
if(map[8 - j, i])
{
line[6] |= (1 << (j - 1));
}
if (map[i, j])
{
line[7] |= (1 << (j - 1));
}
}
for(int j = 0; j < 8; j++)
{
sbs[j].Append((char)line[j]);
}
}
for(int i = 0; i < 8; i++)
{
result.Add(sbs[i].ToString());
}
return result;
}
}
}
把棋盘假想成9X9的二维平面,为啥不是7X7,因为周边扩充后,我们方便边沿判断,具体方案如下:
1.把棋盘设置为9X9的布尔矩阵map[9,9]
2.除了中间的点map[4,4]为false,其余均为true
3.那么我们在计算当前局面的可行步时,便可以大大方便边沿判断
广度优先,毋庸置疑。
但是,难点在于剪枝,去重。
注意到每个局面最终都是7X7的,而且棋子只有两种可能,有和无,非常适合使用0和1来代替
我们看初始局面,就像是
0 0 1 1 1 0 0
0 0 1 1 1 0 0
1 1 1 1 1 1 1
1 1 1 0 1 1 1
1 1 1 1 1 1 1
0 0 1 1 1 0 0
0 0 1 1 1 0 0
把每一行看作一个二进制的数字,可以转换成
第一行 30
第二行 30
第三行 127
第四行 119
第五行 127
第六行 30
第七行 30
由于每个数字即使最大也才127,因此我们可以把它们转换成ASCII字符 ,并串起来形成长度仅7个字符的字符串,这样就可以轻松表示一个局面,而且可以确保不可能重复,这个不需要证明吧。
解决了存储局面的问题,还要剪枝,我们知道,一个正方形,你可以对它进行7种变换都属于类似的自己,分别是【水平竖直轴对称,撇捺轴对称,中心对称=旋转180度,顺逆时针旋转90度】,再加上本身的话,就是每次移动最多可以确定8个局面算是重复的,注意最多,因为有可能其进行一些变换后与原来相同。
把以上两个问题想通透之后,具体代码相信大家就可以自己写出来了。
当然,如果不嫌在下代码的性能问题,也可以稍作参考。