基于Pierre Dellacherie算法实现俄罗斯方块的人工智能(python实现)《三》
本章主要讲述人工智能的实现。先讲解Pierre Dellacherie算法的基本知识,再讲解我是怎么实现的。
一些有趣的背景知识
- 为啥叫俄罗斯方块?:首先俄罗斯方块是一个俄罗斯人做的,这个无聊的人就是前苏联科学家阿列克谢·帕基特诺夫(铭记前辈)
- 游戏规则就不说了,不懂的人都是没有童年的
- 俄罗斯方块会结束吗?在1997年Heidi Burgiel根据极大极小值算法证明了完全随机的俄罗斯方块游戏最终一定会结束。于是大家的热情转向了怎样才能让俄罗斯方块得到更高的分数,在2009年的时候,世界最优的俄罗斯方块的智能算法(by Thiery & Scherrer)是可以平均消除3500万行的(很牛嘟有没有!)
- 俄罗斯方块的智能算法的类型,基本上有
one-piece
和two-piece
两种,就是仅考虑当前方块和把下一个方块也纳入考虑范围两种算法。但实际上,one-piece
算法相对简单一些,而且不比two-piece
差,甚至还要强。
Pierre Dellacherie算法原理
- 所谓让机器自己去玩俄罗斯方块,就是让机器计算当前方块的所有形态可放置的所有位置,然后根据统一的评价标准,计算出最优的位置进行放置。
- 但是评价结果的因素是多方面,对于这些因素需要一个统一的考虑,选择一个合理的评估策略。
根据整理大概有一下几个相关的参数
- 当一块板块摆放之后,与这个板块接触的小方块的数量是一个需要考虑的参数。很显然,与值接触的小方块越多,说明这个板块摆放再改位置后产生的“空洞”的数量越少,如果一个“棋盘”局面中空的小方块或者“空洞”数量少则说明这个局面对玩家有利。
- 当一个板块摆放在某个位置之后,这个板块的最高点的高度是一个需要考虑的参数。这个高度会影响整体的高度,当有两个位置可选择摆放位置时,应该优先放置再板块最高点的高度比较低的位置上。
- 当一个板块摆放在某个位置之后能消除的行数是一个重要参数。毫无疑问,消除的行越多越好。
- 游戏区域中已经被下落板块填充的区域中空的小方格的数量也是评价游戏局面的一个重要参数。很显然,每一行中空的小方格数量越多,局面对玩家越不利。
- 游戏区域中已经下落板块填充的区域中“空洞”的数量也是一个重要参数。如果一个空的小方格上方被其他板块的小方格挡住,则这个小方格就形成了“空洞”,“空洞”是俄罗斯方块游戏中最难处理的情况,必须等上层的小方块都消除之后才有可能填充“空洞”,很显然,这是一个能恶化局面的参数。
简单地理解,摆放一个板块的策略是:板块放置的位置越靠下越好,方块之间越紧密越好,自身对消除行的方块贡献数量越多越好。
这里要注意的是不可为了追求消除行数,而去造成过多的空洞,这样也是不合理的。
Pierre Dellacherie算法将上述抽象的参数转化为6种具体的属性。如下:
- landingHeight:指当前板块放置之后,板块重心距离游戏区域底部的距离。(也就是小方块的海拔高度)
- erodedPieceCellsMetric:这是消除参数的体现,他代表的是消除的行数与当前摆放的板块中被消除的小方块的格数的成绩。
- 举个例子:下面这个例子就是说明红色的小方块下落之后会消除2行,而且自身贡献的小方格数是3个,所以返回值是3*2=6
- 举个例子:下面这个例子就是说明红色的小方块下落之后会消除2行,而且自身贡献的小方格数是3个,所以返回值是3*2=6
boardRowTransitions:对于每一行小方格,从左往右看,从无小方格到有小方格是一种“变换”,从有小方格到无小方格也是一种“变换”,这个属性是各行中“变换”之和
- 何谓“变换”举个例子:
- 上面这张图片中用红色边框标注的即为一次“变换”,第一行为7次变换,第二行为6次变换
boardColTransitions:这是每一列的变换次数之和
- boardBuriedHoles:各列中的“空洞的小方格数之和”
- 举例说明:
- 如图所示,空洞数为6
- boardWells:各列中“井”的深度的连加和
- “井”的定义是,两边(包括边界)都有方块填充的空列。
- 举例说明:
- 如图所示,以两边的最低边为“井”的开始,图中一共有两个“井”,深度分别为2和3,
- (ps:不要怀疑为什么有些既是洞又是“井”)
- 所以这个图返回的boardWells值应该是
(1+2)+(1+2+3)= 9
接下来介绍Pierre Dellacherie算法的评估函数了。
value = -landingHeight + erodedPieceCellsMetric - boardRowTransitions - boardColTransitions - (4 * boardBuriedHoles) - boardWells
- 根据各指标的权重的经验值修改评估函数为:
value = -45 × landingHeight + 34 × erodedPieceCellsMetric - 32 × boardRowTransitions - 93 × boardColTransitions - (79 × boardBuriedHoles) - 34 × boardWells
- value值大的为最优位置,你没看错就是大的是最优,即使所有数都会是负数
- 有点同学可能会问,要出现两个局面评分相同那怎么办呢?问得非常好,这个时候需要加入一个计算优先度的函数,这个也很简单。公式如下:
priority=100 * 板块需要水平移动移动的次数 + 板块需要选择的次数
- (ps:可能PD算法的设计是 如果板块摆放再游戏区域的左侧优先度要加上10,那是因为他的那个游戏横向的小方格数量是10个,是一个偶数,而他的中心点在6这个位置。)
- priority值小的为最优位置
- 以上就是Pierre Dellacherie算法的全部内容,建议各位老铁根据理解自己写代码,这样不会有桎梏。下面是我的代码的解释
代码解释:
根据上面的理解我直接写了一个
robotWorker
类,看懂了上面的概念写代码并不难,难在第一步,获取某方块的所有形态可以放置的所有位置这个才是难点。
基本的实现步骤是:
- 1. 获取某方块的所有形态可以放置的所有位置
- 2. 计算所有位置的value值和priority值
- 3. 比较值的大小找到最优位置
- 下面代码可能有很多可以优化的地方,我写得比较急(一天实现),没有优化,各位轻喷
- 新建一个robotWorker对象时,需要把
center(中心点),shape(方块名),station(状态 or 形态),color(颜色,其实没什么用),matrix(游戏界面中存储颜色的矩阵)
class RobotWorker():
SHAPES = ['I', 'J', 'L',