系列文章入口

《Python3编程实战Tetris机器人》

Tetris类

组合Block类,实现俄罗斯方块的绘制及移动、旋转等所有操作。这是Tetris游戏的业务核心,第一步先实现手动玩的需求,以后AI自动玩时,还会改造这个类。在所有的逻辑里面,特别注意旋转(rotate)操作,后面解决的不少的bug被证明都是由于rotate操作考虑不全面所引起的。

设计思路

Tetris类通过组合Block类来实现屏幕的绘制,并与tkinter库进行解耦。因为tkinter库的设计,我们的界面使用了两个Canvas来分别实现游戏空间和下一方块的显示,因此一个方块有可能显示在不同的Canvas中。当一个方块放置后,要从nextCanvas中取出下一个方块,放置到游戏空间的上方。这个操作我没有找到简单的方法来实施跨Canvas移动元件。我的实现方法是,重新在游戏空间生成一个与nextCanvas中一样的方块,因为我每一个方块的初始形态是固定的,只是让它实现了几次随机的旋转,因此我只需查询next Tetris中的形状和旋转次数就可以复制了。

相关常数

GameRoom = [[0 for i in range(12)] for i in range(22)]   # 游戏空间定义 10x20

TETRISAHPES = (                   # 方块的形态定义
    (1, 1, 1, 1),                 # 方块一共有六种形态
    (0, 1, 1, 1, 0, 1),           # 其它形态都可以通过几次旋转来得到
    (1, 1, 1, 0, 0, 0, 1),        # 我定义旋转为顺时针旋转
    (0, 1, 1, 0, 0, 1, 1),        # 每一个方块的形态最多旋转四次就回到初始形态
    (1, 1, 0, 0, 0, 1, 1),
    (0, 1, 1, 0, 1, 1),
    (0, 1, 0, 0, 1, 1, 1)
)

TETRISCOLORS = (                  # 方块形态与颜色的绑定
    "red",
    "magenta",
    "darkMagenta",
    "gray",
    "darkGreen",
    "darkCyan",
    "darkBlue"
)

具体实现

构造函数

def __init__(self, canvas, x, y, shape):
    self.x = x                            # 方块在游戏空间的横坐标位置 1-10
    self.y = y                            # 方块在游戏空间的纵坐标位置 1-20
    self.canvas = canvas                  # 方块绘制的空间
    self.objs = []                        # 组合Block类对象
    self.rotateCount = 0                  # 方块旋转次数
    self.shape = shape                    # 方块初始形态
    self.color = TETRISCOLORS[shape]      # 方块颜色
    self.data = [                         # 方块形态数据
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [0, 0, 0, 0]
    ]
    curShape = TETRISAHPES[shape % len(TETRISAHPES)]
    for i, b in enumerate(curShape):      # 绘制方块初始形态
        if b:
            self.data[1 + i // TETRISDIMENSION][i % TETRISDIMENSION] = 1     # 形态数据初始化        
            self.objs.append(Block(canvas, self.x + i % TETRISDIMENSION, \   # 组合Block并绘制
                 self.y + 1 + i // TETRISDIMENSION, self.color))

判断游戏空间某个位置是否有Block

这个函数很重要,判断是否越界、方块是否能移动都需要它。游戏空间比实际空间大一圈,最外围数据都初始化为1,作为越界哨兵。

def hasBlock(self, x, y):
    if x < 1 or x > 10 or y > 20:
        return True
    if GameRoom[y][x] == 1:
        return True
    else:
        return False

判断一个方块(Tetris)是否能放置

移动、旋转以及游戏是否结束的判断都要用到

def canPlace(self, x, y):
    for i in range(TETRISDIMENSION):
        for j in range(TETRISDIMENSION):
            if self.data[i][j] and GameRoom[y + i][x + j]:
                return False
    return True

清除方块

清除操作由组合的Block类自行完成。

def clean(self):
    for block in self.objs:
        block.clean()
    self.objs.clear()

方块重绘

旋转的时候使用,因为Block类的relocate使用是tkinter.move来实现的,而它的参数是相对距离。因此旋转的重绘比较麻烦,我采用了相对简单粗暴的方法来实现。

def redraw(self):
    self.clean()                           # 整体清除
    for i in range(TETRISDIMENSION):       # 生成全新Tetris
        for j in range(TETRISDIMENSION):   # 方法很简单、有效
            if self.data[i][j]:            # 但因为tkinter的问题,后来发现它加剧了内存泄漏问题
                self.objs.append(Block(self.canvas, self.x + j, self.y + i, self.color))

内容预告

移动和旋转放到下一篇中。
因为tkinter和Timer的问题,后面在AI实现后,发现程序有严重的内存泄漏问题,这个问题把我好一阵折磨,欲后事如何,请持续关注,谢谢!

项目地址

https://gitee.com/zhoutk/ptetris
或
https://github.com/zhoutk/ptetris

运行方法

1. install python3, git
2. git clone https://gitee.com/zhoutk/ptetris (or download and unzip source code)
3. cd ptetris
4. python3 tetris

This project surpport windows, linux, macOs

on linux, you must install tkinter first, use this command:  
sudo apt install python3-tk

相关项目

已经实现了C++版,项目地址:

https://gitee.com/zhoutk/qtetris

zhoutk
2.6k 声望1.2k 粉丝

自由程序员,技术路线c,delphi,c++,c#,java,php,node.js,python,golang,typescript;超喜欢react.js的设计思路,全栈开发成为我的终极目标。开发机器macbook pro,或装ubuntu、fedora的机器,编程用vim...