目录
一. 游戏介绍
《俄罗斯方块》(Tetris,俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。
该游戏曾经被多家公司代理过。经过多轮诉讼后,该游戏的代理权最终被任天堂获得。任天堂对于俄罗斯方块来说意义重大,因为将它与GB搭配在一起后,获得了巨大的成功。
《俄罗斯方块》的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。
二. 代码重点
2.1 格子矩阵
程序中的一个核心变量是记录棋盘位置的numpy.ndarray对象grid,这里是用来记录每个格子的值,用以在前端画图的时候展示,四面的墙的值为-1,中间空间里面,空白的地方的值为0,有方块的位置的值为方块所对应的数值(大于0),程序通过判断grid里面的数值来对tkinter前端进行不同颜色的填充。
2.2 当前方块
另一个非常重要的变量是this_block,表示的是当前选中的方块(BLOCK_1~BLOCK_7),每次的选择都是随机性的,BLOCK_1~BLOCK_7都是简单的类,里面主要有三个变量:BLOCK_COLOR、BLOCK_NUM、BLOCK_LOCATION,其中,BLOCK_COLOR是方块的颜色,BLOCK_NUM是方块的号码。这里最重要的是BLOCK_LOCATION,列表类型,表示的是方块的初始位置,但是由于方块会有变形这个动作,因此这里列出来了所有方块变形后对应的初始位置。同时维护一个变量change_num,通过维护change_num来改变方块的初始位置。
self.BLOCK_1 = BLOCK_1()
self.BLOCK_2 = BLOCK_2()
self.BLOCK_3 = BLOCK_3()
self.BLOCK_4 = BLOCK_4()
self.BLOCK_5 = BLOCK_5()
self.BLOCK_6 = BLOCK_6()
self.BLOCK_7 = BLOCK_7()
self.block_list = [self.BLOCK_1, self.BLOCK_2, self.BLOCK_3, self.BLOCK_4, self.BLOCK_5, self.BLOCK_6, self.BLOCK_7]
self.this_block = self.block_list[random.randint(0,len(self.block_list)-1)]
2.3 方块位置
方块移动的重点是维护一个变量this_block_location,表示的是当前方块的位置,所有的移动操作(方块的生成、自动下落、加速下落、左右移动、方块变形)都是对这个this_block_location进行修改。
考虑到这里的方块会有变形,因此这里的this_block_location的维护方式是这样的:首先是有一个原始的位置this_block.BLOCK_LOCATION,以及对列和对行的偏移值变量block_col_offset和block_row_offset。在自动下落、加速下落中是对block_row_offset值的修改,在左右移动中是对block_col_offset的修改,在方块的生成、方块变形中,是对方块原始位置this_block.BLOCK_LOCATION的值的修改。
This_block_location的值的构成是由this_block.BLOCK_LOCATION和block_col_offset、block_row_offset构成,构成如下:self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
方块的初始位置图如下:
2.4 绑定键盘事件
另一个重点是绑定键盘事件,这里的绑定键盘事件主要有三种:
- 左右移动。这里主要是看方块的移动是否在空间内(即不能撞墙);
- 向下移动。这里是看方块最终停留的位置;
- 方块变形。将当前的方块进行指定方向变形。
三种都是涉及到方块当前位置的更改,其中主要要考虑的是一个碰撞检验,即移动/变形后的方块位置是否没有其他的方块相撞、或者是在内部空间内。
2.5 分数计算
另外还有一个重要的部分是分数的计算,在俄罗斯方块中,每次消除的得分和同时消除的行数相关
连续消除1行——100分
连续消除2行——200分
连续消除3行——400分
连续消除4行——800分
因此,这里需要判断连续消除的行数是多少。
三. 代码
from tkinter import *
from tkinter import messagebox
import time
import random
import numpy as np
class BLOCK_1():
'''
“田”字形方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [1, 0], [1, 1]]]
self.BLOCK_COLOR = 'blue'
self.BLOCK_NUM = 1
class BLOCK_2():
'''
“一”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [0, 2], [0, 3]], [[-1, 0], [0, 0], [1, 0], [2, 0]]]
self.BLOCK_COLOR = 'yellow'
self.BLOCK_NUM = 2
class BLOCK_3():
'''
反“Z”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [1, 0], [-1, 1]], [[0, 1], [0, 2], [-1, 0], [-1, 1]]]
self.BLOCK_COLOR = 'pink'
self.BLOCK_NUM = 3
class BLOCK_4():
'''
“L”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [0, 2], [1, 2]], [[-1, 0], [-1, 1], [0, 0], [1, 0]],
[[1, 0], [1, 1], [1, 2], [0, 0]], [[1, 0], [1, 1], [0, 1], [-1, 1]]]
self.BLOCK_COLOR = 'green'
self.BLOCK_NUM = 4
class BLOCK_5():
'''
“Z”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [1, 1], [-1, 0]], [[0, 0], [0, 1], [-1, 1], [-1, 2]]]
self.BLOCK_COLOR = 'purple'
self.BLOCK_NUM = 5
class BLOCK_6():
'''
反“L”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [0, 2], [-1, 2]], [[-1, 0], [-1, 1], [0, 1], [1, 1]],
[[-1, 0], [-1, 1], [-1, 2], [0, 0]], [[-1, 0], [0, 0], [1, 0], [1, 1]]]
self.BLOCK_COLOR = 'orange'
self.BLOCK_NUM = 6
class BLOCK_7():
'''
“T”字型方块
'''
def __init__(self):
self.BLOCK_LOCATION = [[[0, 0], [0, 1], [0, 2], [-1, 1]], [[0, 0], [0, 1], [1, 1], [-1, 1]],
[[0, 0], [0, 1], [1, 1], [0, 2]], [[-1, 1], [0, 1], [1, 1], [0, 2]]]
self.BLOCK_COLOR = 'white'
self.BLOCK_NUM = 7
class Tetris():
def __init__(self):
self.width = 360 # 界面宽度
self.height = 700 # 界面高度
self.grid_size = 30 # 单格子边长
self.row_num = 20 # 行数
self.col_num = 12 # 列数
self.speed = 500 # 格子下降速度
self.total_score = 0 # 总分数
self.inner_row_num = self.row_num - 2 # 内部格子空间行数
self.inner_col_num = self.col_num - 2 # 内部格子空间列数
self.grid = np.full((self.row_num, self.col_num), 0) # 记录格子空间的矩阵
self.grid_init()
self.BLOCK_1 = BLOCK_1()
self.BLOCK_2 = BLOCK_2()
self.BLOCK_3 = BLOCK_3()
self.BLOCK_4 = BLOCK_4()
self.BLOCK_5 = BLOCK_5()
self.BLOCK_6 = BLOCK_6()
self.BLOCK_7 = BLOCK_7()
self.block_list = [self.BLOCK_1, self.BLOCK_2, self.BLOCK_3, self.BLOCK_4, self.BLOCK_5, self.BLOCK_6, self.BLOCK_7]
self.color_dic = {1: self.BLOCK_1.BLOCK_COLOR, 2: self.BLOCK_2.BLOCK_COLOR, 3: self.BLOCK_3.BLOCK_COLOR,
4: self.BLOCK_4.BLOCK_COLOR, 5: self.BLOCK_5.BLOCK_COLOR, 6: self.BLOCK_6.BLOCK_COLOR,
7: self.BLOCK_7.BLOCK_COLOR, 0: 'black', -1: 'grey'} # 颜色对照表
self.tk = Tk()
self.tk.title('俄罗斯方块')
self.tk.geometry(f'{self.width}x{self.height}')
self.canvas = Canvas(self.tk, width = self.width, height = self.height, background='#FFFFFF')
self.canvas.pack()
self.canvas.focus_set() # 聚焦
self.canvas.bind("<KeyPress-Left>", self.move)
self.canvas.bind("<KeyPress-Right>", self.move)
self.canvas.bind("<KeyPress-Down>", self.move)
self.canvas.bind("<KeyPress-Up>", self.block_change)
self.draw_grid()
self.start_game()
self.tk.mainloop()
def grid_init(self):
# 墙体赋值
for i in range(0, self.row_num):
self.grid[i][0] = -1
self.grid[i][self.col_num - 1] = -1
for i in range(0, self.col_num):
self.grid[0][i] = -1
self.grid[self.row_num - 1][i] = -1
def draw_grid(self):
# 画格子
for i in range(0, self.row_num):
for j in range(0, self.col_num):
self.canvas.create_rectangle(j * self.grid_size + 2, i * self.grid_size + 2,
(j + 1) * self.grid_size - 2, (i + 1) * self.grid_size - 2,
fill=self.color_dic[self.grid[i][j]])
self.label_text = StringVar()
self.label_text.set('当前分数:' + str(self.total_score))
self.label_score = Label(self.tk, textvariable=self.label_text, font=('宋体', 20), foreground='#000000', background='#FFFFFF').place_configure(x=70, y=620)
def block_change(self, event):
# 方块变形
tmp_change_num = self.change_num + 1
tmp_choose_one = tmp_change_num % len(self.this_block.BLOCK_LOCATION)
tmp_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in
self.this_block.BLOCK_LOCATION[tmp_choose_one]]
for i in tmp_block_location:
if self.grid[i[1], i[0]] != 0 and i not in self.this_block_location:
return
self.change_num += 1
self.choose_one = self.change_num % len(self.this_block.BLOCK_LOCATION)
for i in self.this_block_location:
self.grid[i[1], i[0]] = 0
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in
self.this_block.BLOCK_LOCATION[self.choose_one]]
self.canvas.delete('all')
for i in self.this_block_location:
self.grid[i[1], i[0]] = self.this_block.BLOCK_NUM
self.draw_grid()
def get_random_block(self):
# 获取随机的格子,开始下落
self.this_block = self.block_list[random.randint(0,len(self.block_list)-1)]
self.block_col_offset = 5
self.block_row_offset = 1
self.choose_one = 0
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
self.start_judge = True
self.change_num = 0
if self.if_game_end():
messagebox.showinfo('提示', '游戏结束')
self.tk.destroy()
def move(self, event):
# 键盘事件,移动
if event.keysym == 'Left':
next_block_location = [[i[0] - 1, i[1]] for i in self.this_block_location]
for i in next_block_location:
if i not in self.this_block_location and self.grid[i[1], i[0]] != 0:
return
for i in self.this_block_location:
self.grid[i[1], i[0]] = 0
self.block_col_offset -= 1
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
elif event.keysym == 'Right':
next_block_location = [[i[0] + 1, i[1]] for i in self.this_block_location]
for i in next_block_location:
if i not in self.this_block_location and self.grid[i[1], i[0]] != 0:
return
for i in self.this_block_location:
self.grid[i[1], i[0]] = 0
self.block_col_offset += 1
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
elif event.keysym == 'Down':
for i in self.this_block_location:
self.grid[i[1], i[0]] = 0
while self.if_block_end() == False:
self.block_row_offset += 1
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
self.canvas.delete('all')
for i in self.this_block_location:
self.grid[i[1], i[0]] = self.this_block.BLOCK_NUM
self.draw_grid()
def start_game(self):
# 开始游戏
self.get_random_block()
self.block_move()
def block_eliminate(self):
# 方块清除
tmplist = self.grid.tolist()
continuous_row = 0
score = 0
for i in range(1, self.inner_row_num + 1):
tmpls = tmplist[i][1: self.inner_col_num + 1]
if min(tmpls) > 0:
continuous_row += 1
for j in range(i, 1, -1):
self.grid[j] = self.grid[j - 1]
else:
if continuous_row == 1:
score += 100
elif continuous_row == 2:
score += 200
elif continuous_row == 3:
score += 400
elif continuous_row >= 4:
score += 800
continuous_row = 0
if continuous_row == 1:
score += 100
elif continuous_row == 2:
score += 200
elif continuous_row == 3:
score += 400
elif continuous_row >= 4:
score += 800
continuous_row = 0
return score
def if_game_end(self):
# 判断游戏是否结束
for i in self.this_block_location:
if self.grid[i[1], i[0]] > 0:
return True
return False
def if_block_end(self):
# 判断方块是否下落完毕
next_block_location = [[i[0], i[1] + 1] for i in self.this_block_location]
for i in next_block_location:
next_block_x = i[1]
next_block_y = i[0]
if i not in self.this_block_location and self.grid[next_block_x, next_block_y] != 0:
return True
return False
def block_move(self):
# 方块的自动下落
if self.if_block_end() == True:
self.total_score += self.block_eliminate()
self.get_random_block()
for i in self.this_block_location:
self.grid[i[1], i[0]] = 0
if self.start_judge != True:
self.block_row_offset += 1
self.this_block_location = [[i[0] + self.block_col_offset, i[1] + self.block_row_offset] for i in self.this_block.BLOCK_LOCATION[self.choose_one]]
self.canvas.delete('all')
for i in self.this_block_location:
self.grid[i[1], i[0]] = self.this_block.BLOCK_NUM
self.draw_grid()
self.start_judge = False
if self.if_block_end() == False:
self.canvas.after(self.speed, self.block_move)
else:
self.get_random_block()
self.canvas.after(self.speed, self.block_move)
if __name__ == '__main__':
tetris = Tetris()