略笨的五子棋AI
继续之前的造轮子计划。本来只是打算做一个简单五子棋的和妹子一起玩,后来发现做个AI也挺有意思。就开始工作了。
本次的五子棋用纯HTML5实现。AI使用了简单的决策树和Alpha-Beta剪枝。
本来想使用ClojureScript作为AI的脚本,但是后来发现ClojureScript的性能差不多只有原生Javascript的1 / 100
。
于是放弃,使用了原生的JS。
Demo与Code
显示: HTML5 Canvas
显示部件使用了很简单的Canvas去画。关于悬停选框,使用了双层Canvas来实现。不得不说Canvas的性能确实相当不错,比起直接的DOM操作在移动端效果要好太多了。
人工智能:
AI部件实现了一个无状态的AI,付出了性能作为代价增加了开发速度哈。本来是想试试ClojureScript的,结果性能让我跪在地上了。即使是使用了advanced
的编译,依然慢的不得了。虽然总有人说clojure好好利用可以很快,但是我认为开发者不应当承担这种无意义的优化成本。
于是我就投奔了JS。本来以为用JS代码会变得很长,结果也就100+行。摊手。。。。
这里人工智能的原理非常简单。核心函数有两个。
- chance函数,根据当前棋盘状态和当前玩家推算出一个优势值。其基本原理就是对于不同的串算不同的分数。譬如我方活4子串对应x分,我方半活4子串对应y分,敌方活3子串对应z分。之类的。在分数设计中需要遵循以下几个原则。
- 分数应该分等级。及
p1, p2, p3...
- 因为等级存在优先关系,譬如一个活4子比数个活3子要珍贵的多,所以等级关系必须清晰。也就是,
p(i) >> p(i-1)
或者说p(i) > N * p(i-1)
,上级要远大于下级。 - 等级必须考虑先后手问题,同样的子数,先手(未下)比后手(已下)优先级要高。
- 分数应该分等级。及
-
think函数,能够递归的进行决策树推算,并且根据父子兄弟节点的chance来进行剪枝。这个函数的参数是
当前棋盘,当前玩家,递归层数
,返回值是[当前玩家最大胜率,对应步]
,其基本逻辑如下:(假定当前玩家为A,敌方为B) A 遍历所有步。 针对每一步,都更新棋盘后调用B的think,并传入减1的递归层数。 选取某一步,这一步使B的胜率最小。这一步就是答案。
除去这些之外,还有一些优化的要点:
- 减少遍历区域,只选择已下位置的相邻位子作为候选。
- Alpha-Beta剪枝。基本原理很简单:当A进行遍历时,例如遍历位置1得出了B的胜率为
c
,那么再遍历止位置2时,就告知B的think,如果计算过程中胜率大于c
,就剪枝。
总结
最后的结果是,这个AI可以在1秒的时间内递归3层,跟网上看到的一下数据差不多。
跟网上的一些别的AI下过,胜率不差。
跟自己做个AI对战有种奇异的快感有兴趣的童鞋也可以玩一玩哦