本文代码基于python
tkinter实现俄罗斯方块基础版——四、消除与得分
的最终代码,
建议先读懂基础版的代码。
对应的实现效果已投稿b站:BV1jt41157uR
对于基础很好的,可以简单阅读这里的核心代码梳理
本文第0部分是1,2,3,4部分的基础,强烈建议观看
本文第1,2,3,4部分相互独立,可跳跃观看
俄罗斯方块中的核心方法如下,该方法实现界面的刷新,可以认为是整个代码的核心(粗略比喻为控制中心)。后面的四种模式基本上或多或少是与该方法关联的。
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)
由于这个方法很重要,它是不断重复运行的(直到游戏退出),每一次(轮)运行的结果对应的就是一个帧,所以这里来逐行分析下:
TODO
TODO
TODO
隐藏模式就是俄罗斯方块落地之后,展示一瞬间再隐藏
那么我们首先要弄明白,
落地后的俄罗斯方块是怎么展示的?
在game_loop
方法中落地部分对应18-19行,而save_block_to_list
方法中并没有绘制相关的代码,
也就是落地时并没有进行俄罗斯方块的再次的绘制,那么落地后的俄罗斯方块是怎么展示的呢?
本轮落地的俄罗斯方块是通过上一轮的第15行draw_block_move(canvas, current_block, [0, 1])
绘制的,而本轮没有进行清除,所以上一轮的绘制留了下来。
进一步的,不考虑行满了被清除的情况。所有落地的俄罗斯方块都是通过第15行的绘制而展示出来的。
那么我们接下来应该怎么实现隐藏模式呢?
这里一步一步来个大家展示下这个摸索的过程,如果嫌啰嗦建议直接跳到本部分末尾,看最终代码
既然落地后的俄罗斯方块已经展示了,这里就先直接实现落地后的俄罗斯方块的隐藏,
在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行的绘制的结果(实际上使用新的绘制覆盖掉了)
那么我们下面要进一步的解决这个问题,就是要去实现先展示一瞬,再隐藏掉。
展示要能被看到,那么就不能和隐藏在同一帧里实现,要先展示,再在下一帧隐藏。
这里用一个全局变量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
里仍然看到的是俄罗斯方块落地后的效果,然后下一轮隐藏了落地后的效果。
也就是清除的过程和结果是没有展示的。
我们这里还是把这个解决一下
这个时候俄罗斯方块落地后,有三件事要做
先修改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