小游戏 | Python自动玩俄罗斯方块

来源:互联网 编辑:wan玩得好手游小编更新:2024-11-01 06:14:26 人气:
小笨聪前面的文章总是用 Python 来进行爬虫,今天就换一下口味,体验一下
  Python  
  自动耍  
  俄罗斯方块  
俄罗斯方块(Tetris) 规则:由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。
这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。(来自百度百科
俄罗斯方块游戏界面

我们此次的任务就是在这条规则的基础上,利用 Python 和简单的 AI 算法,实现自动寻找最优位置和调整方块形态,达到快速高效得分的目的。(最终效果见文末视频


代码分三部分:

  1. utils.py:基础设置和界面设计

  2. ai.py:AI算法和具体方式

  3. AITetris.py:实现游戏主循环


一.基础设置和界面设计


1.定义一个俄罗斯方块

方块共有7种形状,每块包含四个人小方块,分别写出小方块的相对坐标、定义获取小方块旋转角度、绝对坐标和相对坐标边界的函数。

1class tetrisShape():
2    def __init__(selfshape
=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                                         [[00], [00], [00], [00]],
22                                         [[0-1], [00], [01], [02]],
23                                         [[0-1], [00], [01], [11]],
24                                         [[0-1], [00], [01], [-11]],
25                                         [[0-1], [00], [01], [10]],
26                                         [[00], [0-1], [10], [1-1]],
27                                         [[00], [0-1], [-10], [1-1]],
28                                         [[00], [0-1], [10], [-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__(selfwidth
=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()-10, 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__(selfparentgrid_sizeinner_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(00, -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()   


2.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[212                    action = [d_now, x_now, score]
13return action

未来场景优劣评定考虑的因素有:

    1)可消除的行数;

    2)堆积后的俄罗斯方块内的虚洞数量;

    3)堆积后的俄罗斯方块内的小方块数量;

    4)堆积后的俄罗斯方块的最高点;

    5)堆积后的俄罗斯方块的高度(每一列都有一个高度)标准差;

    6)堆积后的俄罗斯方块的高度一阶前向差分;

    7)堆积后的俄罗斯方块的高度一阶前向差分的标准差;

    9)堆积后的俄罗斯方块的最高点和最低点之差。


 1# 下个俄罗斯方块以某种方式模拟到达底部
2board = self.imitateDropDown(board, self.inner_board.next_tetris, d_next, x_next, distances[x_next])
3width, height = self.inner_board.width, self.inner_board.height
4# 下一个俄罗斯方块以某方案行动到达底部后的得分(可消除的行数)
5removed_lines = 0
6# 空位统计
7hole_statistic_0 = [0] * width
8hole_statistic_1 = [0] * width
9# 方块数量
10num_blocks = 0
11# 空位数量
12num_holes = 0
13# 每个x位置堆积俄罗斯方块的最高点
14roof_y = [0] * width
15for 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......


3.实现游戏主循环


定义俄罗斯方块游戏类初始化(包含块大小、下落速度、水平布局、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([])



  最终效果视频展示  



注:本次代码参考资料

https://github.com/LoveDaisy/tetris_game


以上就是本次Python自动耍俄罗斯方块的过程,后台回复“ 俄罗斯方块 ”即可获取源码。【完】


等等......今天公众号开通了流量主功能,大家随手点点底部广告呗,谢谢啦!



往期推荐




  原创不易,如有帮忙,点个在看或赞赏呗


欢迎玩家到【wan玩得好手游】查看最新变态版手游攻略,只需要在百度输入【wan玩得好手游】就可以浏览最新上线送满vip的变态手游攻略了,更多有关BT手游的攻略和资讯,敬请关注玩得好手游!

更多...

热门推荐

更多...

相关文章