python tkinter实现俄罗斯方块拓展——四种游戏模式

本文代码基于python
tkinter实现俄罗斯方块基础版——四、消除与得分
的最终代码,
建议先读懂基础版的代码。
对应的实现效果已投稿b站:BV1jt41157uR
对于基础很好的,可以简单阅读这里的核心代码梳理

本文第0部分是1,2,3,4部分的基础,强烈建议观看
本文第1,2,3,4部分相互独立,可跳跃观看

0、核心代码梳理

俄罗斯方块中的核心方法如下,该方法实现界面的刷新,可以认为是整个代码的核心(粗略比喻为控制中心)。后面的四种模式基本上或多或少是与该方法关联的。

def game_loop():
    win.update()
    global current_block
    if current_block is None:
        new_block = generate_new_block()
        # 新生成的俄罗斯方块需要先在生成位置绘制出来
        draw_block_move(canvas, new_block)
        current_block = new_block
        if not check_move(current_block, [0, 0]):
            messagebox.showinfo("Game Over!", "Your Score is %s" % score)
            win.destroy()
            return
    else:
        if check_move(current_block, [0, 1]):
            draw_block_move(canvas, current_block, [0, 1])
        else:
            # 无法移动,记入 block_list 中
            save_block_to_list(current_block)
            current_block = None

    check_and_clear()

    win.after(FPS, game_loop)

由于这个方法很重要,它是不断重复运行的(直到游戏退出),每一次(轮)运行的结果对应的就是一个帧,所以这里来逐行分析下:

  • 初步分析:
    第1行申明函数方法及名称,无需传参(实际是通过全局变量在获取参数,见第三行)
    第2行刷新win窗口
    第3行获取全局变量current_block,也就是当前下落中的俄罗斯方块
    第4行判断当前下落中的俄罗斯方块current_block是否为空None
    为空则执行第5-12行,主要是生成新的俄罗斯方块,并判断当前游戏是否已结束
    否则执行第14-19行,让当前的俄罗斯方块向下移动,无法下落就是落地,落地则设置current_block为None
    第21行检查是否有行可清除,有则清除并计算得分
    第23行设置刷新间隔,在FPS毫秒后刷新页面(重新调用方法本身)
  • 进一步分析
  • 第5-12行
    第5行生成一个新的俄罗斯方块
    第6行在原地绘制这个俄罗斯方块
    第7行将这个俄罗斯方块设置为current_block
    第8行判断当前位置能否放置当前的俄罗斯方块,即当前的俄罗斯方块是否会和已落地的相冲突,如果不能放置,就是冲突了,说明游戏结束,执行第10-12行弹框提示并退出游戏
  • 第14-19行:
    第14行判断当前俄罗斯方块是否能往下落,能则执行第15行,向下移动并绘制;不能则执行第18-19行
    第18行保存当前的俄罗斯方块,
    第19行current_block不再记录已经落地的俄罗斯方块,设置为None

1、残局模式

TODO

2、流动模式

TODO

3、上浮模式

TODO

4、隐藏模式

隐藏模式就是俄罗斯方块落地之后,展示一瞬间再隐藏
那么我们首先要弄明白,
落地后的俄罗斯方块是怎么展示的?
game_loop方法中落地部分对应18-19行,而save_block_to_list方法中并没有绘制相关的代码,
也就是落地时并没有进行俄罗斯方块的再次的绘制,那么落地后的俄罗斯方块是怎么展示的呢?
本轮落地的俄罗斯方块是通过上一轮的第15行draw_block_move(canvas, current_block, [0, 1])绘制的,而本轮没有进行清除,所以上一轮的绘制留了下来。
进一步的,不考虑行满了被清除的情况。所有落地的俄罗斯方块都是通过第15行的绘制而展示出来的。
那么我们接下来应该怎么实现隐藏模式呢?
这里一步一步来个大家展示下这个摸索的过程,如果嫌啰嗦建议直接跳到本部分末尾,看最终代码

1,隐藏落地后的俄罗斯方块

既然落地后的俄罗斯方块已经展示了,这里就先直接实现落地后的俄罗斯方块的隐藏,
game_loop方法代码之前,新增如下方法代码,实现落地后的俄罗斯方块的隐藏。

def hide_block_list():
    for ri in range(R):
        for ci in range(C):
            if block_list[ri][ci]:
                draw_cell_by_cr(canvas, ci, ri) # 默认状态下,绘制空白格的颜色

game_loop方法中的第18-19行之间添加代码hide_block_list()调用该方法。
此时隐藏已实现,但是有了新的问题:
那就是当前俄罗斯方块落地时,只展示了当前的俄罗斯方块,之前落地的俄罗斯方块没有展示。
这是因为新增的hide_block_list()清除了之前的第15行的绘制的结果(实际上使用新的绘制覆盖掉了)
那么我们下面要进一步的解决这个问题,就是要去实现先展示一瞬,再隐藏掉。

2,隐藏前先展示已落地的俄罗斯方块

展示要能被看到,那么就不能和隐藏在同一帧里实现,要先展示,再在下一帧隐藏。
这里用一个全局变量has_hiden来判断,默认为True,已隐藏,
game_loop方法代码之前,新增如下代码,实现落地后的俄罗斯方块的隐藏。

has_hiden = True
def show_block_list():
    for ri in range(R):
        for ci in range(C):
            if block_list[ri][ci]:
                draw_cell_by_cr(canvas, ci, ri, SHAPESCOLOR[block_list[ri][ci]])

再修改game_loop方法
将其中第3行改为

    global current_block, has_hiden

第18,19行改为

            if has_hiden: # has_hiden默认为True,所以先展示,展示后设置has_hiden为False
                save_block_to_list(current_block)
                show_block_list()
                has_hiden = False
            else: # 隐藏,隐藏后设置has_hiden为True
                hide_block_list()
                current_block = None
                has_hiden = True

到这里,隐藏模式是不是就算实现了呢?
可以算作实现了。
但是还有一个微小的问题,那就是关于行满清除的情况这里没有做处理。
目前的效果是,先展示俄罗斯方块落地后的效果。
展示之后,执行了check_and_clear(),进行了检查与清除,而这个清除的过程和结果并没有展示。
到下一轮game_loop里仍然看到的是俄罗斯方块落地后的效果,然后下一轮隐藏了落地后的效果。
也就是清除的过程和结果是没有展示的。

我们这里还是把这个解决一下

3,如果有清除行的情况,清除后展示下清除的结果再隐藏

这个时候俄罗斯方块落地后,有三件事要做

  • 1,先展示
  • 2,清除已满的行并展示
  • 3,隐藏
    而具体到每一帧的,进一步梳理如下
    俄罗斯方块落地后,
  • 第一帧,先展示落地的俄罗斯方块
  • 第二帧,判断是否有已满的行可清除
    如果有,在本帧(第二帧)展示清除的结果,在下一帧(第三帧)隐藏落地的俄罗斯方块
    否则,在本帧(第二帧)展示隐藏落地的俄罗斯方块

先修改check_and_clear()方法
在该方法中的末尾添加返回值,代码如下,用于判断是否有已满的行可清除(实际清除过程在该方法里已完成)

	return has_complete_row

修改game_loop方法如下

def game_loop():
    win.update()
    global current_block, has_hiden
    if current_block is None:
        new_block = generate_new_block()
        # 新生成的俄罗斯方块需要先在生成位置绘制出来
        draw_block_move(canvas, new_block)
        current_block = new_block
        if not check_move(current_block, [0, 0]):
            messagebox.showinfo("Game Over!", "Your Score is %s" % score)
            win.destroy()
            return
    else:
        if check_move(current_block, [0, 1]):
            draw_block_move(canvas, current_block, [0, 1])
        else:
            # 无法移动,记入 block_list 中
            if has_hiden:
                save_block_to_list(current_block)
                show_block_list()
                has_hiden = False
            else:
                if check_and_clear():
                    show_block_list()
                    has_hiden = False
                else:
                    hide_block_list()
                    current_block = None
                    has_hiden = True

    win.after(FPS, game_loop)

最后由于check_and_clear方法里已实现了清除后展示
所以上面的第24,25行其实是没必要的,上面的第22-29行课进一步优化为

			elif not check_and_clear():
                hide_block_list()
                current_block = None
                has_hiden = True

此时隐藏模式就算完全完成了
然后由于隐藏模式中如果展示时间太短,游戏体验会比较差,所以这里把展示时间搞长一些
第31代码修改如下

	if not has_hiden:
        win.after(2 * FPS, game_loop) # 展示的那一帧,设置时长为普通帧的两倍,也可自己进一步调整
    else:
        win.after(FPS, game_loop)

最终代码完整版本已上传到github:
https://github.com/BigShuang/Tetris/blob/master/2_MODE/04.py

你可能感兴趣的:(俄罗斯方块教程,python)