Python3 编程实战 Tetris 机器人(game 类)

SegmentFault

共 14408字,需浏览 29分钟

 ·

2021-06-25 05:17

作者:zhoutk

来源:SegmentFault 思否社区

系列文章地址

https://segmentfault.com/a/1190000040121026

game类

游戏逻辑控制类,是界面与Tetris类之间的粘合者,接受界面的鼠标及键盘事件,操作Tetris类,实现游戏逻辑。单个方块的操作,在Tetris中已经实现,game类主要是实现消行算法、新方块的产生、游戏速度控制等。

设计思路

消层算法简单的处理就是发现一行,消除一行。本项目使用一点技巧,先找出所有可消除行,把行号存入数组中,一次性消除。游戏速度的控制,使用定时器来实现,但发现python的定时器与其它语言有些差别,会不断产生新的定时器对象,开始感觉有些不对劲,但也没有重视。后来确认,这会产生内存泄漏,后使用tkinter.after替换了。

相关常数

SCORES = (0,1,3,7,10)      # 消层分值设定

STEPUPSCORE = 50           # 每增长50分,速度加快一个等级
STEPUPINTERVAL = 100       # 每增长一个等级,定时器间隔时间减少100毫秒

具体实现

游戏状态变量

game.gameRunningStatus

  • 0 : 游戏未开始
  • 1 : 手动游戏
  • 2 : 游戏回放
  • 5 : 游戏暂停

开始游戏

def start(self):
    self.gameRunningStatus = 1
    self.gameSpeedInterval = 1000        # 初始游戏速度
    self.gameSpeed = 1                   # 游戏速度等级
    self.gameLevels = 0                  # 消层数
    self.gameScores = 0                  # 总得分
    self.app.updateGameInfo(1,0,0)       # 初始化界面信息
    self.canvas.delete(ALL)              # 清空游戏空间
    self.nextCanvas.delete(ALL)          # 下一方块空间清空
    initGameRoom()                       # 初始化游戏空间数据

    self.tetris = Tetris(self.canvas, 4, 0, random.randint(0,6))             # 随机生成第一个方块
    for i in range(random.randint(0,4)):                                     # 旋转随机次数,方块出场式
        self.tetris.rotate()
    self.nextTetris = Tetris(self.nextCanvas, 1, 1, random.randint(0,6))     # 随机生成下一方块
    for i in range(random.randint(0,4)):                                     # 下一方块初始形态(随机)
        self.nextTetris.rotate()

    self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)           # 控制游戏速度定时器
    self.tick.start()

生成下一方块

游戏控制主要函数,在方块下落到底部后,进行消层、统计得分、速度等级判定、游戏是否结束判定以及将下一方块移入游戏空间并再生成一个方块显示在下一方块显示空间中。
def generateNext(self):
    cleanLevels = self.clearRows()                               # 统计可消除层数
    if cleanLevels > 0:                                          # 有可消层,计算分值
        self.gameLevels += cleanLevels
        self.gameScores += SCORES[cleanLevels]
        if self.gameScores / STEPUPSCORE >= self.gameSpeed:
            self.gameSpeed += 1
            self.gameSpeedInterval -= STEPUPINTERVAL
        self.app.updateGameInfo(self.gameSpeed, self.gameLevels, self.gameScores)
    self.tetris = Tetris(self.canvas, 4, 0, self.nextTetris.getTetrisShape())    # 复制nexTetris到游戏空间
    for i in range(self.nextTetris.getRotateCount()):
        if not self.tetris.rotate():
            break
    if self.tetris.canPlace(4, 0):                   # 判定游戏是否结束
        self.nextCanvas.delete(ALL)                  # 游戏未结束,生成新的方块放入下一方块空间
        self.nextTetris = Tetris(self.nextCanvas, 1, 1, random.randint(0,6))
        for i in range(random.randint(0,4)):
            self.nextTetris.rotate()
    else:                                            # 游戏结束
        self.gameRunningStatus = 0
        self.canvas.create_text(150, 200, text = "Game is over!", fill="white", font = "Times 28 italic bold")
        self.app.setStartButtonText("Start")
        print("game is over!")

统计可消除层

clearRows函数查找能消除的层,将其消除,返回可消除层总数。
def clearRows(self):
    occupyLines = []                  # 存储可消除层行号
    h = 20
    while h > 0:
        allOccupy = 0
        for i in range(1, 11):
            if GameRoom[h][i]:
                allOccupy += 1        # block统计
        if allOccupy == 10:           # 行满
            occupyLines.append(h)     # 存储行号
        elif allOccupy == 0:          # 有一个空位,跳过些行
            break
        h -= 1
    if len(occupyLines) > 0:          # 有可消层
        self.doCleanRows(occupyLines) # 消除可消层
    return len(occupyLines)

消层函数

消层函数,根据clearRows函数统计的可消层行号,消除游戏空间的满行。算法的难点在于要同时控制两个变量,一个是从下到上遍历游戏空间,另一方面要将满行以上的空间数据下移,下移的步长为已经消除的行数。
def doCleanRows(self, lines):
    index = 0                                 # 存储已经消除了多少行
    h = lines[index]                          # 满行行号数据
    while h >= 0:                             # 只需要从最下面一满行开始即可
        if index < len(lines) and h == lines[index]:         # 找到一可消行
            index += 1                        # 已消行总数加1
            for j in range(1, 11):
                GameRoom[h][j] = 0            # 游戏空间数据消行
                for b in self.canvas.find_closest(\         # Canvas元件消除
                    j * BLOCKSIDEWIDTH - HALFBLOCKWIDTH, \
                    h  * BLOCKSIDEWIDTH - HALFBLOCKWIDTH):
                    self.canvas.delete(b)
        else:                                 # 移动游戏空间数据
            count = 0                         # 空位统计,全空,可以提前结束循环
            for j in range(1, 11):
                if GameRoom[h][j] == 1:
                    count += 1
                    GameRoom[h + index][j] = GameRoom[h][j]     # 注意index变量,这是移动步长,与已经消除行数有关
                    GameRoom[h][j] = 0
                    for b in self.canvas.find_closest(j * BLOCKSIDEWIDTH - HALFBLOCKWIDTH, h  * BLOCKSIDEWIDTH - HALFBLOCKWIDTH):
                        self.canvas.move(b, 0, index * BLOCKSIDEWIDTH)
            if count == 0:                   # 发现整行位全空,提前退出
                break
        h -= 1

方块控制

方块控制已经在Tetris类实现,在game类中,只是转发事件到当前方块即可。唯一多了一个moveDownEnd - 方块直落函数。
def moveDownEnd(self):
    while self.moveDown():      # 循环下落,直到不能再落
        pass

游戏速度控制

游戏速度控制实现很容易,只需要定时触发一次down函数就可以了。
def tickoff(self):
    if self.gameRunningStatus == 1:
        self.moveDown()
        self.tick = Timer(self.gameSpeedInterval / 1000, self.tickoff)
        self.tick.start()

内容预告

定时器的使用会引入新线程,会出现资源冲突问题,下单将解决线程冲突。

项目地址

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


点击左下角阅读原文,到 SegmentFault 思否社区 和文章作者展开更多互动和交流,扫描下方”二维码“或在“公众号后台回复“ 入群 ”即可加入我们的技术交流群,收获更多的技术文章~

- END -



浏览 26
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报