![6f543dcb23639ef6114c3764c5ffef6e.png](https://i-blog.csdnimg.cn/blog_migrate/8a282ab3b646bd2a8481a580c215af93.jpeg)
![d77ce2b32a35705487f80d60044c5de8.gif](https://i-blog.csdnimg.cn/blog_migrate/f0e1ac1ee8ed66770026700e1b814734.gif)
小笨聪前面的文章总是用 Python 来进行爬虫,今天就换一下口味,体验一下 Python 自动耍俄罗斯方块。
小游戏 | Python自动玩俄罗斯方块mp.weixin.qq.com![abc89b89eae458d9634f370ffac1a579.png](https://i-blog.csdnimg.cn/blog_migrate/fa68da75e57255f3fa84a0a550adb66b.jpeg)
俄罗斯方块(Tetris) 规则:由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。(来自百度百科)
![af8ebcff9caa241d30be081df8b3cd85.png](https://i-blog.csdnimg.cn/blog_migrate/78eb534c415e1cbc71777352d67417d8.jpeg)
我们此次的任务就是在这条规则的基础上,利用 Python 和简单的 AI 算法,实现自动寻找最优位置和调整方块形态,达到快速高效得分的目的。(最终效果见文末视频)
代码分三部分:
- utils.py:基础设置和界面设计
- ai.py:AI算法和具体方式
- AITetris.py:实现游戏主循环
一.基础设置和界面设计
1.定义一个俄罗斯方块
方块共有7种形状,每块包含四个人小方块,分别写出小方块的相对坐标、定义获取小方块旋转角度、绝对坐标和相对坐标边界的函数。
1class tetrisShape():
2 def __init__(self, shape=0):
3 # 空块
4 self.shape_empty = 0
5 # 一字型块
6 self.shape_I = 1
7 # L型块
8 self.shape_L = 2
9 # 向左的L型块
10 self.shape_J = 3
11 # T型块
12 self.shape_T = 4
13 # 田字型块
14 self.shape_O = 5
15 # 反向Z型块
16 self.shape_S = 6
17 # Z型块
18 self.shape_Z = 7
19 # 每种块包含的四个小方块相对坐标分布
20 self.shapes_relative_coords = [
21 [[0, 0], [0, 0], [0, 0], [0, 0]],
22 [[0, -1], [0, 0], [0, 1], [0, 2]],
23 [[0, -1], [0, 0], [0, 1], [1, 1]],
24 [[0, -1], [0, 0], [0, 1], [-1, 1]],
25 [[0, -1], [0, 0], [0, 1], [1, 0]],
26 [[0, 0], [0, -1], [1, 0], [1, -1]],
27 [[0, 0], [0, -1], [-1, 0], [1, -1]],
28 [[0, 0], [0, -1], [1, 0], [-1, -1]]
29 ]
30 self.shape = shape
31 self.relative_coords = self.shapes_relative_coords[self.shape]
32 '''获得该形状当前旋转状态的四个小方块的相对坐标分布'''
33 def getRotatedRelativeCoords(self, direction):
34 # 初始分布
35 if direction == 0 or self.shape == self.shape_O:
36 return self.relative_coords
37 # 逆时针旋转90度
38 if direction == 1:
39 return [[-y, x] for x, y in self.relative_coords]
40 # 逆时针旋转180度
41 if direction == 2:
42 if self.shape in [self.shape_I, self.shape_Z, self.shape_S]:
43 return self.relative_coords
44 else:
45 return [[-x, -y] for x, y in self.relative_coords]
46 # 逆时针旋转270度
47 if direction == 3:
48 if self.shape in [self.shape_I, self.shape_Z, self.shape_S]:
49 return [[-y, x] for x, y in self.relative_coords]
50 else:
51 return [[y, -x] for x, y in self.relative_coords]
2.内部板块
游戏主界面分为内部版块和外部板块,将游戏进行中的数据记录与数据处理部分定义在内部板块类中,将游戏数据可视化定义在外部板块类中。在内部版块类中,我们可以操作方块向左、向右、顺时针、逆时针、向下和坠落等运动。
在方块移动时,也要实时判断方块是否越界或落地并及时处理,如果落地则将方块合并并整行消除,再创建新的方块,以此循环。
1class InnerBoard():
2 def __init__(self, width=10, height=22):
3 # 宽和长, 单位长度为小方块边长
4 self.width = width
5 self.height = height
6 self.reset()
7 '''判断当前俄罗斯方块是否可以移动到某位置'''
8 def ableMove(self, coord, direction=None):
9 assert len(coord) == 2
10 if direction is None:
11 direction = self.current_direction
12 for x, y in self.current_tetris.getAbsoluteCoords(direction, coord[0], coord[1]):
13 # 超出边界
14 if x >= self.width or x < 0 or y >= self.height or y < 0:
15 return False
16 # 该位置有俄罗斯方块了
17 if self.getCoordValue([x, y]) > 0:
18 return False
19 return True
20 '''向右移动'''
21 def moveRight(self):
22 if self.ableMove([self.current_coord[0]+1, self.current_coord[1]]):
23 self.current_coord[0] += 1
24 '''向左移动'''
25 def moveLeft(self):
26 if self.ableMove([self.current_coord[0]-1, self.current_coord[1]]):
27 self.current_coord[0] -= 1
28 '''顺时针转'''
29 def rotateClockwise(self):
30 if self.ableMove(self.current_coord, (self.current_direction-1) % 4):
31 self.current_direction = (self.current_direction-1) % 4
32 '''逆时针转'''
33 def rotateAnticlockwise(self):
34 if self.ableMove(self.current_coord, (self.current_direction+1) % 4):
35 self.current_direction = (self.current_direction+1) % 4
36 '''向下移动'''
37 def moveDown(self):
38 removed_lines = 0
39 if self.ableMove([self.current_coord[0], self.current_coord[1]+1]):
40 self.current_coord[1] += 1
41 else:
42 x_min, x_max, y_min, y_max = self.current_tetris.getRelativeBoundary(self.current_direction)
43 # 简单起见, 有超出屏幕就判定游戏结束
44 if self.current_coord[1] + y_min < 0:
45 self.is_gameover = True
46 return removed_lines
47 self.mergeTetris()
48 removed_lines = self.removeFullLines()
49 self.createNewTetris()
50 return removed_lines
3.外部板块和侧面板
1class ExternalBoard(QFrame):
2 score_signal = pyqtSignal(str)
3 def __init__(self, parent, grid_size, inner_board):
4 super().__init__(parent)
5 self.grid_size = grid_size
6 self.inner_board = inner_board
7 self.setFixedSize(grid_size * inner_board.width, grid_size * inner_board.height)
8 self.initExternalBoard()
9 '''外部板块初始化'''
10 def initExternalBoard(self):
11 self.score = 0
12 '''把内部板块结构画出来'''
13 def paintEvent(self, event):
14 painter = QPainter(self)
15 for x in range(self.inner_board.width):
16 for y in range(self.inner_board.height):
17 shape = self.inner_board.getCoordValue([x, y])
18 drawCell(painter, x * self.grid_size, y * self.grid_size, shape, self.grid_size)
19 for x, y in self.inner_board.getCurrentTetrisCoords():
20 shape = self.inner_board.current_tetris.shape
21 drawCell(painter, x * self.grid_size, y * self.grid_size, shape, self.grid_size)
22 painter.setPen(QColor(0x777777))
23 painter.drawLine(0, self.height()-1, self.width(), self.height()-1)
24 painter.drawLine(self.width()-1, 0, self.width()-1, self.height())
25 painter.setPen(QColor(0xCCCCCC))
26 painter.drawLine(self.width(), 0, self.width(), self.height())
27 painter.drawLine(0, self.height(), self.width(), self.height())
28 '''数据更新'''
29 def updateData(self):
30 self.score_signal.emit(str(self.score))
31 self.update()
32
33
34 '''
35 侧面板, 右边显示下一个俄罗斯方块的形状
36 '''
37class SidePanel(QFrame):
38 def __init__(self, parent, grid_size, inner_board):
39 super().__init__(parent)
40 self.grid_size = grid_size
41 self.inner_board = inner_board
42 self.setFixedSize(grid_size * 5, grid_size * inner_board.height)
43 self.move(grid_size * inner_board.width, 0)
44 '''画侧面板'''
45 def paintEvent(self, event):
46 painter = QPainter(self)
47 x_min, x_max, y_min, y_max = self.inner_board.next_tetris.getRelativeBoundary(0)
48 dy = 3 * self.grid_size
49 dx = (self.width() - (x_max - x_min) * self.grid_size) / 2
50 shape = self.inner_board.next_tetris.shape
51 for x, y in self.inner_board.next_tetris.getAbsoluteCoords(0, 0, -y_min):
52 drawCell(painter, x * self.grid_size + dx, y * self.grid_size + dy, shape, self.grid_size)
53 '''更新数据'''
54 def updateData(self):
55 self.update()
二、AI算法和具体方式
此处AI 算法基本思想就是,遍历当前可操作的俄罗斯方块和下一个可操作的俄罗斯方块(根据不同的策略,即选择不同的位置和旋转角度)下落到底部后组成的所有可能的未来场景,从这些未来场景中选择一个最优的,其对应的当前可操作的俄罗斯方块的行动策略即为当前解,具体的代码实现如下:
1# 简单的AI算法
2for d_now in current_direction_range:
3 x_now_min, x_now_max, y_now_min, y_now_max = self.inner_board.current_tetris.getRelativeBoundary(d_now)
4 for x_now in range(-x_now_min, self.inner_board.width - x_now_max):
5 board = self.getFinalBoardData(d_now, x_now)
6 for d_next in next_direction_range:
7 x_next_min, x_next_max, y_next_min, y_next_max = self.inner_board.next_tetris.getRelativeBoundary(d_next)
8 distances = self.getDropDistances(board, d_next, range(-x_next_min, self.inner_board.width-x_next_max))
9 for x_next in range(-x_next_min, self.inner_board.width-x_next_max):
10 score = self.calcScore(copy.deepcopy(board), d_next, x_next, distances)
11 if not action or action[2] < score:
12 action = [d_now, x_now, score]
13return action
未来场景优劣评定考虑的因素有:
1)可消除的行数;
2)堆积后的俄罗斯方块内的虚洞数量;
3)堆积后的俄罗斯方块内的小方块数量;
4)堆积后的俄罗斯方块的最高点;
5)堆积后的俄罗斯方块的高度(每一列都有一个高度)标准差;
6)堆积后的俄罗斯方块的高度一阶前向差分;
7)堆积后的俄罗斯方块的高度一阶前向差分的标准差;
9)堆积后的俄罗斯方块的最高点和最低点之差。
代码实现如下:
1 # 下个俄罗斯方块以某种方式模拟到达底部
2 board = self.imitateDropDown(board, self.inner_board.next_tetris, d_next, x_next, distances[x_next])
3 width, height = self.inner_board.width, self.inner_board.height
4 # 下一个俄罗斯方块以某方案行动到达底部后的得分(可消除的行数)
5 removed_lines = 0
6# 空位统计
7 hole_statistic_0 = [0] * width
8 hole_statistic_1 = [0] * width
9 # 方块数量
10 num_blocks = 0
11 # 空位数量
12 num_holes = 0
13 # 每个x位置堆积俄罗斯方块的最高点
14 roof_y = [0] * width
15 for y in range(height-1, -1, -1):
16 # 是否有空位
17 has_hole = False
18 # 是否有方块
19 has_block = False
20 for x in range(width):
21 if board[x + y * width] == tetrisShape().shape_empty:
22 has_hole = True
23 hole_statistic_0[x] += 1
24 else:
25 has_block = True
26 roof_y[x] = height - y
27 if hole_statistic_0[x] > 0:
28 hole_statistic_1[x] += hole_statistic_0[x]
29 hole_statistic_0[x] = 0
30 if hole_statistic_1[x] > 0:
31 num_blocks += 1
32 if not has_block:
33 break
34 if not has_hole and has_block:
35 removed_lines += 1
36 ......
三、实现游戏主循环
定义俄罗斯方块游戏类初始化(包括块大小、下落速度、水平布局、AI控制等),并含有开始、暂停、界面更新等事件。
1class TetrisGame(QMainWindow):
2 def __init__(self):
3 super().__init__()
4 # 是否暂停ing
5 self.is_paused = False
6 # 是否开始ing
7 self.is_started = False
8 self.initUI()
9 '''界面初始化'''
10 def initUI(self):
11 # 块大小
12 self.grid_size = 22
13 # 游戏帧率
14 self.fps = 100
15 self.timer = QBasicTimer()
16 # 水平布局
17 layout_horizontal = QHBoxLayout()
18 self.inner_board = InnerBoard()
19 self.external_board = ExternalBoard(self, self.grid_size, self.inner_board)
20
21 .
22 .
23 .
24
25 '''按键事件'''
26 def keyPressEvent(self, event):
27 if not self.is_started or self.inner_board.current_tetris == tetrisShape().shape_empty:
28 super(TetrisGame, self).keyPressEvent(event)
29 return
30 key = event.key()
31 # P键暂停
32 if key == Qt.Key_P:
33 self.pause()
34 return
35 if self.is_paused:
36 return
37 else:
38 super(TetrisGame, self).keyPressEvent(event)
39 self.updateWindow()
40
41
42if __name__ == '__main__':
43 app = QApplication([])
最终效果视频展示
![25dedabf05d3f39d088d9ef94a631d16.png](https://i-blog.csdnimg.cn/blog_migrate/f74a110f11e41ed29de978ba92f183dc.png)
以上就是本次Python自动耍俄罗斯方块的过程,微信公众号“学编程的金融客”后台回复“ 俄罗斯方块 ”即可获取源码。【完】
小游戏 | Python自动玩俄罗斯方块mp.weixin.qq.com![abc89b89eae458d9634f370ffac1a579.png](https://i-blog.csdnimg.cn/blog_migrate/fa68da75e57255f3fa84a0a550adb66b.jpeg)
往期推荐
1.爬取流浪地球影评
2.爬取《明日之子》评论和弹幕
3.北上广深租房图鉴
4.爬取抖音视频
5.母亲节祝福代码
你的点赞和关注就是对我最大的支持!
![1cd3400fe574f6202b246238dc893fb6.png](https://i-blog.csdnimg.cn/blog_migrate/686a3ac1eecdc9d4477031c7346d6fc4.jpeg)