作业内容

你的代码应该放在player.py中。player是一个class,它具有一个名为choose
action的函数。这个函数会在每次玩家需要一个动作时调用。你所写的选择动作函数应该根据棋盘的状态来决定采取什么行动。
•board.falling包含有关当前下落块的信息;最重要的是,它的形状(board.falling.shape)和单元格的坐标(board.falling.cells)。这个形状是enum
shape的一个实例(instance),它的定义可以在board.py中找到。
•board.next包含下一个要处理的块的信息。单元格还没有任何意义(这个俄罗斯方块还不在板上),但你可以检查形状。
•board.cell包含关于板上当前被占用的单元格的信息。更准确地说,这个是一组表示位置的元组(x,
y)。注:板子原点在左上角;x轴向右延伸,y轴向下延伸。 你可以遍历一个集合就像遍历一个列表一样;例如: for (x , y ) in
board . cells : xxxxxxxx
在上面的例子中,单元格的顺序是没有保证的。你可能按是从上到下,从左到右迭代已经占据的单元格。可以这样做: for y in range (
board . height ): for x in range ( board . width ): if (x , y ) in
board . cells :
#xxxxxxx 板子也有三种方法,可以用来尝试行动的效果: •Move()接受enum 方向的一个实例,其定义可以在board.py中找到。例如,要尝试向左移动方块,使用board.move(Direction.Left)。板的实例将随之更新该移动的效果。使用“Direction.Drop.”来放下方块。
•rota()接受一个enum旋转的实例;同样,请参考board.py获取定义。例如,要逆时针旋转一个块,使用board.rotate(Rotation.Anticlockwise).
•方法skip模拟跳过一个步骤的效果;它不接受任何argument
要尝试多个选项,得到一个板子的副本并在那里尝试你的行动的效果是有用的;对副本的操作不会影响原始副本 sandbox = board .
clone () sandbox . rotate ( Rotation . Anticlockwise ) sandbox . move
( Direction . Drop ) 一旦你的ai准备做出一个动作,你应当把动作返回给界面。例如,您可以使用return
Rotation.Clockwise告诉界面你想顺时针旋转方块。要跳过一个移动,使用特殊的值None,即return None。
如果你的AI有一个包含多个动作的计划,你可以将它们作为列表返回,也就是说, return [ Rotation .
Anticlockwise , Direction . Left , None , Direction . Drop ]
或者您可以使用yield语句返回每个单独的操作 yield Rotation . Anticlockwise yield Direction
. Left yield None yield Direction . Drop
后者也可以在循环中工作,并且可以是一种很好的方法,可以基于您早期的探索来综合一个计划,而不需要首先建立一个列表并返回它。
如果你的玩家返回多个动作,这些动作将只会执行直到当前掉落的方块落地。在那之后,系统会忽略剩下的动作,生成一个新的方块,并且你的函数choose_action将被再次调用,新的棋盘包含新的掉落的棋子(board.next)以及新的下一个棋子。另一方面,如果你的玩家提供的移动在方块到达之前就已经用完了,那么choose
action也会被再次调用。

查阅资料发现有一种名为Pierre Dellacherie的算法很好用,决定实现。

作业里面board\player类的分析

操作

在作业中你需要修改player类,传参的时候会把board对象传给你,你需要返回一种操作,可以是以下几种

Rotation.Clockwise
Rotation.AntiClockwise
Direction.Left
Direction.Right
上述四种操作的列表,也就是多步

Board

board对象主要包括以下几种属性和方法:
clone() 复制当前board
move(operation) 移动 operaion
rotate(operation) 旋转 operation
cells 二元元组形式的集合,存放当前图(已放置方块的点)
falling 当前下落块的对象
next 下一个下落块的对象
Height board高度(游戏高度)
Width board宽度(游戏宽度)

Block

falling是一种block对象,block类有这几种方法:
left 当前块最左侧小块的坐标
right 当前块最右侧小块的坐标
top 当前块最上侧小块的坐标
down 当前块最下侧小块的坐标
move(self, direction, board, count=1) 朝着direction移动当前块,默认移动一个单位
rotate(self, rotation, board): 按rotation旋转90°

Pierre Dellacherie算法原理

让机器去玩俄罗斯方块游戏,就是要遍历当前游戏中所有可以放置当前下落块的点(解空间),而在遍历的过程对于每一个放置点要通过一种评估函数来评价放在该点是否足够好。
评价结果的因素多方面,对于这些因素需要一个统一的考虑,选择一个合理的评估策略。
Pierre Dellacherie的评估参数解释:
当一块板块摆放之后,与这个板块接触的小方块的数量是一个需要考虑的参数。很显然,与值接触的小方块越多,说明这个板块摆放再改位置后产生的“空洞”的数量越少,如果一个“棋盘”局面中空的小方块或者“空洞”数量少则说明这个局面对玩家有利。
当一个板块摆放在某个位置之后,这个板块的最高点的高度是一个需要考虑的参数。这个高度会影响整体的高度,当有两个位置可选择摆放位置时,应该优先放置再板块最高点的高度比较低的位置上。
当一个板块摆放在某个位置之后能消除的行数是一个重要参数。毫无疑问,消除的行越多越好。
游戏区域中已经被下落板块填充的区域中空的小方格的数量也是评价游戏局面的一个重要参数。很显然,每一行中空的小方格数量越多,局面对玩家越不利。
游戏区域中已经下落板块填充的区域中“空洞”的数量也是一个重要参数。如果一个空的小方格上方被其他板块的小方格挡住,则这个小方格就形成了“空洞”,“空洞”是俄罗斯方块游戏中最难处理的情况,必须等上层的小方块都消除之后才有可能填充“空洞”,很显然,这是一个能恶化局面的参数。
简单地理解,摆放一个板块的策略是:板块放置的位置越靠下越好,方块之间越紧密越好,自身对消除行的方块贡献数量越多越好。

这里要注意的是不可为了追求消除行数,而去造成过多的空洞,这样也是不合理的。
Pierre Dellacherie算法将上述抽象的参数转化为6种具体的属性。如下:

landingHeight: 指当前板块放置之后,板块重心距离游戏区域底部的距离。(也就是小方块的海拔高度)
erodedPieceCellsMetric: 这是消除参数的体现,他代表的是消除的行数与当前摆放的板块中被消除的小方块的格数的成绩。

举个例子:下面这个例子就是说明红色的小方块下落之后会消除2行,而且自身贡献的小方格数是3个,所以返回值是3*2=6


boardRowTransitions:对于每一行小方格,从左往右看,从无小方格到有小方格是一种“变换”,从有小方格到无小方格也是一种“变换”,这个属性是各行中“变换”之和
何谓“变换”举个例子:

注意这里的变换是指放下当前方块后,行变换有多少。
上面这张图片中用黑色边框标注的即为一次“变换”,第一行为0次变换,第二行为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这个位置。)
实现
根据Pierre Dellacherie算法,实现了以下几个函数:

函数1 to_matrix 将给定棋盘转换成方便处理的矩阵形式

def to_matrix(self,board):matrix = [[None] * board.width for i in range(board.height)]for (x,y) in board.cells:#  print(x,y)matrix[y][x] = 1return matrix

函数2 Get_landing_Height 获取当前图最高高度

def Get_landing_Height(self,board):max_height = board.height + 1for cell in board.cells:if (cell[1] < max_height):max_height = cell[1]return max_height

函数3 消除的行数*方块自身被消格数(2)

def Get_eroded_Piece_cells_metirc(self,board):lines = 0usefulblock = 1matrix = self.to_matrix(board)flag = True# 按行遍历 找有方块的行数 如果有一行全不为空 lines ++ 为2 usefulblock++for i in range(len(matrix) - 1, 0, -1):  # 行count = 0for j in range(len(matrix[0])):  # 列if matrix[i][j] is not None:count += 1if count == len(matrix[0]):  # 有一行全满lines += 1for k in range(len(matrix[0])):if matrix[i][k] == 2:  # 如果有该方块usefulblock += 1# 整行未填充,则跳出循环if count == 0:break# graph = self.to_graph(matrix)return lines * usefulblock

函数4 获取行变换数

def Get_Row_trans(self,board):transition = 0matrix = self.to_matrix(board)for i in range(len(matrix)):for j in range(len(matrix[0])-1):if matrix[i][j] == None and matrix[i][j + 1] != None:transition += 1if matrix[i][j] != None and matrix[i][j + 1] == None:transiti

函数5 获取列变换数

def Get_Col_trans(self,board):transition = 0matrix = self.to_matrix(board)for j in range(len(matrix[0])):for i in  range(len(matrix)-1):if matrix[i][j] == None and matrix[i+1][j] !=None:transition += 1if matrix[i][j] !=None and matrix[i+1][j] == None:transition+=1return transition

函数7 获取空洞方格之和

def Get_Buried_holes(self,board):holes = 0matrix = self.to_matrix(board)for j in range(len(matrix[0])):  # 列flag = 0colHoles = 0for i in range(len(matrix)):  # 行if flag == 0 and matrix[i][j] != None:flag = 1if flag ==1  and matrix[i][j] == None:colHoles += 1holes += colHolesreturn holes

函数8 获取井深分数

   def Get_board_wells(self,board):sum_n = [0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231, 253, 276,300]  # 根据井深计算的分数数组wells = 0sum = 0matrix = self.to_matrix(board)#  for i in matrix:#     print(i)for j in range(len(matrix[0])):  # 列for i in range(len(matrix) - 1, 0, -1):  # 行if matrix[i][j] == None:if (j - 1 < 0 or matrix[i][j - 1] != None) and (j + 1 >= board.width or matrix[i][j + 1] != None):wells += 1else:sum += sum_n[wells]wells = 0if (wells != 0):sum += sum_n[wells]return sum

评估分数函数

def score_board(self,cloneboard):lh = self.Get_landing_Height(cloneboard)epcm = self.Get_eroded_Piece_cells_metirc(cloneboard)rt = self.Get_Row_trans(cloneboard)ct = self.Get_Col_trans(cloneboard)bh = self.Get_Buried_holes(cloneboard)bw = self.Get_board_wells(cloneboard)return -45 * lh + 34 * epcm - 32 * rt - 93 * ct - 79 * bh - 34 * bw+150*min([y for x,y in cloneboard.cells])+150*sum([y for x,y in cloneboard.cells])/len(cloneboard.cells)

尝试移动函数(遍历解空间,计算分数)

def try_move(self,board,xtarget,rtarget):cloneboard  = board.clone()times_rotate = 0move_ans=[]res = Falsetrymove = Nonewhile True:if times_rotate < rtarget :trymove = Rotation.Anticlockwisetimes_rotate+= 1elif cloneboard.falling.left < xtarget:trymove = Direction.Right        # trymove = [Direction.Right for i in range(xtarget-cloneboard.falling.left)]elif cloneboard.falling.left >xtarget:trymove = Direction.Left         #  trymove = [Direction.Left for i in range(cloneboard.falling.left-xtarget)]else:trymove = Direction.Dropif isinstance(trymove, Direction):res = cloneboard.move(trymove)elif isinstance(trymove,Rotation):res = cloneboard.rotate(trymove)move_ans.append(trymove)if res:#这时会把cell放到board里面score = self.score_board(cloneboard)return score,move_ans

选择函数

def choose_action(self, board):best_score = 0bestmove = Nonefor xtarget in range(10):for rtarget in range(4):score ,move = self.try_move(board,xtarget,rtarget)if score > best_score:best_score = scorebestmove =  move#  print( "ans", bestmove)return bestmove

最终效果:

Tips:

1.由于这个board里面存的都是坐标,所以如果我想要获取landing_height是比较麻烦的,因为falling块drop之后falling就会变成next块?,所以我只有对比棋盘的变化才能得到landing_height,那么就要对比前后两个棋盘,这个实现起来有点复杂,所以我就取当前棋盘的最高点作为landing_height了。

2.choose_action里面遍历所有的可能的情况,然后进入trymove帮我跑,计算score,注意rtarget是旋转次数,有些块有四种旋转状态,有些是两种(z),还有的就一种(方形),这里可以优化一下。

3.一开始怕麻烦没有写rotate,只放方块,结果效果很不好,写了rotate之后就好很多。

4.move函数只能移动,不能旋转!!!这是一个大坑,一开始都是写move(…,Rotation.clockwise)结果总是不转,一度卡了很长时间,后来去board类里面仔细看才发现,move函数写的输入啥都不报错。

5.消除的行数*方块自身被消格数这个是有问题的,还是因为那个辣鸡的用元组存储棋盘的结构的原因,导致我很难去找“方块自身被消除格数”,因为这要遍历前后两种棋盘,又要多加一倍的空间和时间开销,就没写了。就这效果都还可以。

6.遍历的时候无需考虑碰撞,board类里面的移动当出现边界碰撞就会取消该移动。

6.constant里面可以修改棋盘大小参数。

知乎同:https://zhuanlan.zhihu.com/p/307674120
参考博客:https://blog.csdn.net/qq_41882147/article/details/80005763

让AI玩俄罗斯方块 UCL ENGF2 CA4.1 作业相关推荐

  1. AI玩俄罗斯方块(Python实现)

    目录 1.环境 2.实现机制(Pierre Dellacherie算法) 3.代码实现 人工智能大火的今天,如果还是自己玩俄罗斯方块未免显得太LOW,为什么不对游戏升级,让机器自己去玩俄罗斯方块呢?有 ...

  2. 如何让AI教机器自己玩俄罗斯方块?

    作者 | Ahab 转载自公众号Ahab杂货铺(ID:PythonLearningCamp) 人工智能大火的今天,如果还是自己玩俄罗斯方块未免显得太 LOW,为什么不对游戏升级,让机器自己去玩俄罗斯方 ...

  3. 让 AI 教机器自己玩俄罗斯方块

    作者 | Ahab 责编 | 仲培艺 人工智能大火的今天,如果还是自己玩俄罗斯方块未免显得太 LOW,为什么不对游戏升级,让机器自己去玩俄罗斯方块呢?有了这个想法之后,我用了两天时间去搜集了大量资料, ...

  4. 实现一款俄罗斯方块小游戏非常简单!但是要实现AI自动俄罗斯方块才算牛逼!

    前言 最近刷抖音,看到一个玩俄罗斯方块的直播,居然玩到九万多分,也是个奇人,关键还有大几千人在那里看直播!这游戏有那么难吗?我试着玩了一下,最高也就玩到一千多分就玩不下去了!后面就图像想到,我是不是可 ...

  5. DeepMind训练AI玩足球,风骚走位比中国男足都强(狗头)

    来源:AI科技评论本文约4200字,建议阅读9分钟本文带你了解DeepMind训练的 AI 玩足球. AI踢足球可以有多燃? 不好,对方攻到底线了!看我一脚精准拦截.抢球! 想抢回去?没门! 差点被进 ...

  6. 音速索尼克 怪人_如何使用AI玩刺猬索尼克。 真干净!

    音速索尼克 怪人 by Vedant Gupta 由Vedant Gupta 如何使用AI玩刺猬索尼克. 真干净! (How to use AI to play Sonic the Hedgehog. ...

  7. 教ai玩游戏_简单解释:DeepMind如何教AI玩视频游戏

    教ai玩游戏 by Aman Agarwal 通过阿曼·阿加瓦尔(Aman Agarwal) 简单解释:DeepMind如何教AI玩视频游戏 (Explained Simply: How DeepMi ...

  8. 5月14日社区技术直播【Analytics Zoo上的分布式TensorFlow训练AI玩FIFA足球游戏】

    主题: Analytics Zoo上的分布式TensorFlow训练AI玩FIFA足球游戏 时间: 2020.5.14 19:00 参与方式: 扫描下方海报二维码加入钉钉群 或者 届时点击直播间直接观 ...

  9. 机器学习与游戏,不只让AI玩星际争霸那么简单!

    玩游戏这件事,似乎已经成为了我们生活中的日常,但仔细回想过去十几年间的游戏经历,其中的变化用"翻天覆地"来形容恐怕也不为过. 十几年前,风靡朋友圈的还是小霸王和超级玛丽,如今,PC ...

最新文章

  1. each(callback)与each(object[,callback])的区别
  2. Oracle导出空表解决办法
  3. Azure负载均衡器Standard Load Balancer介绍
  4. 2.5e2.0是合法的c语言常量,二级考试C语言程序设计.ppt
  5. hdu 4940 数据太水...
  6. 关于盘符里某些文件夹删除不了的解决方案研究
  7. java递归统计一个文件夹含子文件夹里文件不同后缀的出现次数
  8. ORACLE 多表关联 UPDATE 语句
  9. AcWing1082. 数字游戏
  10. ICLR最佳论文“彩票假设”:如何通过彩票假设构建轻量化模型(上)
  11. 写shell工具类,一个常用实例
  12. 用PHP写距离圣诞节还有多久,距离圣诞节还有多少天
  13. ps柔光在哪的相关介绍:图层面板和工具属性栏的柔光模式
  14. oppo手机文件共享媒体服务器,网件:ReadyNAS特色功能之媒体服务器
  15. Python坦克大战小游戏(三):背景
  16. 存款利息计算器html代码,html+jQuery简单的利息计算器
  17. Vue笔记-尚硅谷-Alex
  18. 【javascript输入一个数判断这个数是否为素数】判断素数
  19. 设计模式之责任链模式
  20. 评测 osmo_易用能折叠的稳定器 大疆OSMO Mobile3评测

热门文章

  1. python无需下载就可使用的标准模块
  2. Android实现版本更新和自动安装
  3. 图像的二维傅里叶变换频谱图特点研究
  4. 我的前端之路:从切图到放弃
  5. 小学学计算机学flash动画,flash动画教学计划
  6. MSN登陆不了怎么办
  7. 014-HTML-区块(不是区块链)
  8. 计算机为什么自己放音乐,电脑开机后为什么自动放音乐?在哪里头可以解决?
  9. 2020年焊工(高级)模拟试题及焊工(高级)模拟考试
  10. Android Studio利用时钟控件AnalogClock显示模拟时钟以及TextClock显示数字时钟