1、Alpha-Beta剪枝
Alpha-Beta 同“MinMax”非常相似, 区别主要在于 MinMax 运行时要检查整个博弈树,然后尽可能选择最好的线路。 Alpha-Beta 则是在MinMax的基础上把一些不必要的分支剪去,加快搜索速度。
1.1 Alpha-Beta 是在搜索中传递两个值:
- Alpha:搜索到的最好值。
- Beta:对于对手来说最坏的值。
1.2搜索过程中Alpha,Beta有以下三种关系情况:
- 评估值小于Alpha:剪枝。因为对我方来说,当前局面的评估值小于Alpha将不可能产生更好的情况,对局面不可能有提升,因此对剩余的合法着法没有必要再搜索。
- 评估值大于Beta:j剪枝。在对手看来,他总是会找到一个对策比 Beta对应的情况更好。如果当前局面的评估值大于 Beta,那么整个结点就作废了, 因为对手不希望走到这个局面,而它有别的着法可以避免到达这个局面,因此剩下的合理着法没有必要再搜索。
- 评估值大于 Alpha 但小于 Beta:保留该着法,可以考虑该着法。
2、置换表 + Zobrist 键值 + 迭代加深
2.1 使用“Zobrist 键值”来唯一记录棋盘的信息。
这个可以利用 python-chess 来完成,用这个键值来代表某一个确定的局面。从搜索中得到结果后,要保存到置换表中,主要为了避免重复工作。
2.2 使用迭代加深技术来搭配置换表。
有一开始只搜索一层,如果搜索的时间比分配的时间少,那么搜索两层,然后再搜索三层,等等,直到用完时间为止。这足以保证很好地运用时间了。如果可以很快搜索到一个深度,那么在接下来的时间可以搜索得更深,或许可以完成。
如果局面比想象的复杂,那么不必搜索得太深,但是至少有合理的着法可以走了,因为不太可能连 1 层搜索也完不成。
在置换表的基础上进行迭代加深搜索,很多局面都可以直接从置换表中得到,从而加快搜索速度。
3、空着向前裁剪
空着向前裁剪(Null-Move Forward Pruning),运用可能忽视重要路线的冒险策略,使得国际象棋的分枝因子锐减,它导致搜索深度的显著提高,因为大多数情况下它明显降低了搜索的数量。它的工作原理是裁剪大量无用着法而只保留好的。
在搜索着法以前(事实上在你生成着法以前),做一个减少深度的搜索,让对手先走,如果这个搜索的结果大于或等于 Beta,那么简单地返回 Beta而不需要搜索任何着法。这个思想就给了对手出击的机会,如果你的局面仍然好到超过 Beta 的程度,就假设如果你搜索了所有的着法也会超过 Beta。
这个方法能节省时间的原因是,开始时用了减少深度的搜索。深度减少因子称为 R,因此跟用深度 D 搜索所有的着法相比,现在你是先以 D - R 搜索对手的着法。一个比较好 R 是 2,如果你要对所有的着法搜索 6 层,你最终只对对手所有的着法搜索了 4 层
4、静态搜索
国际象棋中会有很多强制的应对。如果有人用马吃掉我们的象,那么我们最好吃还他的马。Alpha-Beta 搜索不是特别针对这种情况的。 我们把深度参数传递给函数,当深度到达零就做完了,即使一方的后被捉。
静态搜索就是一个应对的方法。当 Alpha-Beta 用尽深度后,通过调用静态搜索来代替调用Evaluate() 这个函数也对局面作评价,只是避免了在明显有对策的情况下看错局势。
简而言之,静态搜索就是应对可能的动态局面的搜索。
典型的静态搜索只搜索吃子着法。这会引发一个问题,因为在国际象棋中吃子通常不是强制的。如果局势很平静,而且我们面对的吃子只有 QxP(后吃兵,导致丢后),我们不会强迫后去吃兵的,所以静态搜索不应该强迫吃子。因此,走子一方可以选择是吃子还是不吃子。
如果评价好得足以截断而不需要试图吃子时,就马上截断(返回 Beta)。
如果评价不足以产生截断,但是比 Alpha 好,那么就更新 Alpha 来反映静态评价。然后尝试吃子着法,如果其中任何一个产生截断,搜索就中止。可能它们没有一个是好的,这也没问题。这个函数有几个可能的结果:
- 可能评价函数会返回足够高的数值,使得函数通过 Beta 截断马上返回
- 可能某个吃子产生 Beta 截断
- 可能静态评价比较坏,而任何吃子着法也不会更好
- 可能任何吃子都不好,但是静态评价只比 Alpha 高一点点