之前写了几篇关于墨盘游戏的求解程序,但是由于对盘面的点的编码方式使用了行,列表示,使得代码逻辑复杂,又不能很好的去除对称的状态,以及该游戏不容易构造有效的估值函数,导致搜索空间太大,有些关卡内存溢出都不能找到解
当我看到六边形网格这篇译文后,大有所益,决定对之前的墨盘游戏求解程序重写一遍,鉴于之前的程序找到了少于官方提示步数的解法,新的程序不再依赖官方提示步数,既然没有好的估值函数,又要找到最优解,那就使用双向广度优先搜索吧,比起广度优先搜索,可以显著减少搜索空间,时间
盘面表示
不多说,看这篇译文六边形网格
有效的点击点
for (int x = -(this.layer - 1); x < this.layer; x++) {
for (int y = -(this.layer - 1); y < this.layer; y++) {
for (int z = -(this.layer - 1); z < this.layer; z++) {
if (x + y + z == 0) {
......
}
}
}
}
循环次数: ( l a y e r ∗ 2 − 1 ) 3 (layer * 2 - 1) ^ {3} (layer∗2−1)3,实际有效点击点: 3 ∗ l a y e r ∗ ( l a y e r − 1 ) + 1 3 * layer * (layer - 1) + 1 3∗layer∗(layer−1)+1,先将可点击的点坐标保存到数组,减少大量的无效循环次数
去除对称状态
看过译文就知道,使用立体坐标对盘面的点进行编码,使得整个六边形具有完美优雅的对称性
这样就可以找到六条对称轴,一个对称中心(六条对称轴交点)
这样就可以将起始状态和终止状态相同的对称性的对称节点剪枝,减少搜索量
代码
测试结果一
对比墨盘游戏 算法求解-测试结果,墨盘游戏 算法求解 改进版-测试结果,搜索时间,空间都有显著的减小
int[] startStatus = {
0,0,0,0,
1,0,0,0,1,
0,0,0,0,0,0,
0,0,0,1,0,0,0,
0,1,0,0,1,0,
0,0,1,0,0,
0,0,0,0
};
int[] endStatus = {
0,0,0,0,
0,0,0,0,0,
0,0,1,1,0,0,
0,0,0,1,0,0,0,
0,0,1,0,0,0,
0,1,1,0,0,
0,0,0,0
};
开始队列长度854 结束队列长度1237 开始树节点2725 结束树节点1284
搜索时间: 91
step:1 x:-1 y:2 z:-1 action:C
○ ○ ○ ○
○ ○ ○ ○ ●
○ ○ ○ ○ ○ ○
○ ○ ● ● ○ ○ ○
○ ● ○ ○ ● ○
○ ○ ● ○ ○
○ ○ ○ ○
step:2 x:2 y:-1 z:-1 action:C
○ ○ ○ ○
○ ○ ○ ○ ○
○ ○ ○ ○ ○ ○
○ ○ ● ● ● ○ ○
○ ● ○ ○ ● ○
○ ○ ● ○ ○
○ ○ ○ ○
step:3 x:1 y:-1 z:0 action:L
○ ○ ○ ○
○ ○ ○ ○ ○
○ ○ ○ ● ○ ○
○ ○ ● ○ ● ○ ○
○ ● ○ ● ○ ○
○ ○ ● ○ ○
○ ○ ○ ○
step:4 x:-1 y:0 z:1 action:L
○ ○ ○ ○
○ ○ ○ ○ ○
○ ○ ○ ● ○ ○
○ ○ ● ○ ● ○ ○
○ ● ○ ● ○ ○
○ ○ ● ○ ○
○ ○ ○ ○
step:5 x:0 y:0 z:0 action:R
○ ○ ○ ○
○ ○ ○ ○ ○
○ ○ ○ ● ○ ○
○ ○ ● ● ● ○ ○
○ ○ ○ ○ ○ ○
○ ● ● ○ ○
○ ○ ○ ○
测试结果二
以下10关解法均比官方提示步数快一步
关卡 | 关卡 | 关卡 | 关卡 | 关卡 |
---|---|---|---|---|
VII-3 | VII-6 | VII-7 | X-5 | XI-11 |
XIII-8 | XIV-6 | XIV-9 | XV-2 | XVI-2 |