系列文章入口
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