#object: 用tkinter绘制俄罗斯方块 #writer: mike #time: 2020,09,04 import tkinter as tk import random #import pygame from tkinter import messagebox import time window = tk.Tk() window.geometry("500x600") window.title("俄罗斯方块") #全局变量 rows = 17 coloms = 10 cell_size = 30 FPS = 500 SHAPES = { 'o': [(-1,-1),(0,-1),(-1,0),(0,0)], 's':[(-1,0),(0,0),(0,-1),(1,-1)], 't':[(-1,-1),(0,-1),(0,0,),(1,-1)], 'i':[(0,-2),(0,-1),(0,0),(0,1)], 'l':[(-1,-2),(0,0),(-1,-1),(-1,0)], 'j':[(-1,0),(0,0),(0,-1),(0,-2)], 'z':[(-1,-1),(0,-1),(0,0),(1,0)], } SHAPESCOLOR = { 'o': "blue", 's':"red", 't':"yellow", "i":"green", 'l':"purple", 'j':"orange", 'z':"Cyan", } CURRENT_BLOCK = None SCORE = 0 #记录已经生成的小方块 #初始化为空的二维数组 block_list = [] for i in range(rows): i_row = ["" for j in range(coloms)] block_list.append(i_row) #生成随机的俄罗斯方块 def generate_new_block(): kind = random.choice(list(SHAPES.keys())) new_block = { #注意这里应该用地板除,否则产生的方块坐标就乱了 'cell_position' : [(coloms//2), 0], 'cell_list' : SHAPES[kind], 'kind' : kind, } return new_block #在指定的行,指定的列,绘制指定颜色的一个方格 def draw_cell(canvas, x, y, color="#CCCCCC"): """ canvas: 画布 r: 行数 c: 列数 color:颜色 """ #左上角坐标 x_left = x*cell_size y_left = y*cell_size #右上角坐标 x_right = x_left + cell_size y_right = y_left + cell_size #绘制矩形 canvas.create_rectangle(x_left, y_left, x_right, y_right, fill = color, outline = "white", width = 2) #绘制整个背景 def draw_whole_background(canvas,block): for i in range(rows): for j in range(coloms): #取出当前的数组中的标记 cell_type = block[i][j] #如果当前数组中有值,就按当前颜色来绘制,如果没有值,就按背景色来绘制 if cell_type: draw_cell(canvas,j, i, SHAPESCOLOR[cell_type]) else: draw_cell(canvas, j, i ) #绘制特定的形状 def draw_special_cell(canvas, r, c, cell_list, color = "#CCCCCC"): """ canvas: 画布 r:行 c:列 cell_list: 关于(0,1)的具体的形状 color: 具体的颜色 """ for cell in cell_list: #得到相加之后的小方格的行列 x, y = cell cell_x = r + x cell_y = c + y #依靠知觉,将colums与rows互换了位置 if cell_x < coloms and cell_y < rows and cell_x>= 0 and cell_y >= 0: draw_cell(canvas, cell_x, cell_y, color) #绘制移动的带形状对的方块 #思路为:首先将原有方块绘制成背景色,移动坐标,绘制新颜色 #本函数position 的设置默认是不移动 def move_shape(canvas, block, position = [0,0]): """ canvas:画布 block:包括了一个特定形状的所有内容 position:移动的位置 """ shape_type = block['kind'] c, r = block['cell_position'] cell_list = block['cell_list'] #将原有形状填充为背景色 draw_special_cell(canvas, c, r, cell_list) #默认就是背景色 #绘制新背景色 d_x, d_y = position new_x = c + d_x new_y = r + d_y #更改原有的字典 block['cell_position'] = [new_x, new_y] draw_special_cell(canvas, new_x, new_y, cell_list, SHAPESCOLOR[shape_type]) # 刷新屏幕 def screen_loop(): #window.update() global CURRENT_BLOCK #如果当前没有形状,则生成一个形状 if CURRENT_BLOCK is None: new_block = generate_new_block() move_shape(canvas, new_block) CURRENT_BLOCK = new_block #如果数组中已经有标记了,也就是出生点已经被占用了,那么游戏结束 if not check_move(CURRENT_BLOCK): messagebox.showinfo("GAME OVER ", "you final score is %d"%SCORE) window.destroy() return else: #如果小方格通过了边界检查,就向下移动 if check_move(CURRENT_BLOCK, [0,1]): #如果已经有一个形状了,那么就向下移动这个小方块,直到这个小方块落地。 move_shape(canvas, CURRENT_BLOCK, [0,1]) #如果小方块超过了边界,就停止移动,并且在最初的位置生成新的小方块 else: #当小方块无法移动时, 先将小方块存进数组,表示这几个小方格已经被占用了 save_block(CURRENT_BLOCK) CURRENT_BLOCK = None clear_row() #生成一个显示分数的lable var = tk.StringVar() var.set("score is " + str(SCORE)) fen_label = tk.Label(window, textvariable = var, bg = '#FFBBBB', width = 20, height = 2) fen_label.place(x=330, y=150) #这条语句是不是有递归的意思,如果是,那就能解释越来越慢的原因 print(len(block_list[3])) window.after(FPS, screen_loop) #检查小方块是否会超出边界 #这里position的默认值是0 def check_move(block, direction = [0,0]): #得到圆点的坐标 x, y = block['cell_position'] #得到其他小格子的坐标 qita = block['cell_list'] for cell in qita: cell_x, cell_y = cell #通过循环计算出各个格子的新坐标 new_x = cell_x + x + direction[0] new_y = cell_y + y + direction[1] if new_x < 0 or new_x >= coloms or new_y >= rows: return False #如果小方格已经被标记了,那么也不能移动了 #如果不见 new_y 大于0 , 那么小方格会在上方不下来, 因为在列表中,如果行为负数,列表默认从导数第一个算起 #注意这里如果是 new_y > 0, 依然会有已经满行的,但是还在出生点生成小方格 if block_list[new_y][new_x] and new_y>=0: return False return True #记录已经有的小方块的函数 def save_block(block): block_type = block['kind'] hang, lie = block['cell_position'] c_list = block['cell_list'] #将每一个形状的小方格纪录进二维数组 for cell in c_list: xx, yy = cell new_xx = hang + xx new_yy = lie + yy block_list[new_yy][new_xx] = block_type #左右移动小方块 def left_right_move(event): direction = [0,0] #event 表示键盘上的事件 if event.keysym == "Left": direction = [-1,0] elif event.keysym == "Right": direction = [1,0] global CURRENT_BLOCK #如果当前block不为空,并且向下移动也没越界,没有标记,那么向下移动 if CURRENT_BLOCK is not None and check_move(CURRENT_BLOCK, direction): move_shape(canvas, CURRENT_BLOCK, direction) #旋转小方块 def rotate_move(event): global CURRENT_BLOCK if CURRENT_BLOCK is None: return #计算旋转的坐标 yuan_list = CURRENT_BLOCK['cell_list'] rotate_list = [] for cell in yuan_list: cu_x, cu_y = cell #依据正余旋函数公式,旋转90度相当于,(x,y )变为(y,-x) rotate_xy = [cu_y, -cu_x] rotate_list.append(rotate_xy) #建立旋转后的小方块字典 after_rotate = { 'kind':CURRENT_BLOCK['kind'], 'cell_list':rotate_list, 'cell_position':CURRENT_BLOCK['cell_position'], } #如果可以变换,那么清空原有的小方块,并绘制旋转后的小方块 if check_move(after_rotate): #获得当前的圆点坐标 xx , yy = CURRENT_BLOCK['cell_position'] #清空当前的小方块 draw_special_cell(canvas, xx, yy , CURRENT_BLOCK['cell_list']) #绘制旋转后的小方块 draw_special_cell(canvas, xx, yy, after_rotate['cell_list'], SHAPESCOLOR[CURRENT_BLOCK['kind']]) CURRENT_BLOCK = after_rotate #向下移动小方块 def speed_down(event): global CURRENT_BLOCK if CURRENT_BLOCK is None: return #得到当前的小方块的字典 cell_li = CURRENT_BLOCK['cell_list'] xxx, yyy = CURRENT_BLOCK['cell_position'] #初始化最小的深度 min_height = rows #得到每个形状的各个小方格的坐标 for cell in cell_li: cel_x, cel_y = cell new_x = xxx + cel_x new_y = yyy + cel_y #判断当前的小方块是否已经被占用 if block_list[new_y][new_x]: return h = 0 #从上到下遍历每一个小格子 for ri in range(new_y+1, rows): #如果下面的小格子已经被占用了,就退出 if block_list[ri][new_x]: break else: #如果下面的小格子没有冲突,那么就加一 h += 1 #如果当前的可移动行数比当前的最小值小,就更新当前的最小值 if h < min_height: min_height = h down = [0,min_height] #直接定位到最下面的位置 if check_move(CURRENT_BLOCK, down): move_shape(canvas, CURRENT_BLOCK, down) #判断指定行是否已经满了 def check_row_complete(row): for cell in row: if cell == '': return False else: return True #清除已经完成的行 def clear_row(): #下面的变量用于检查一行是否已经满了 whether_complete = False for ri in range(len(block_list)): if check_row_complete(block_list[ri]): whether_complete = True #如果已经满的行,大于第一行,那么所有的当前行都等于上一行的内容 if ri > 0: #遍历从当前行往上的所有行,当然除了第一行除外 for curr_r in range(ri,0,-1): block_list[curr_r] = block_list[curr_r-1] block_list[0] = ['' for j in range(coloms)] #对于第一行,直接赋值为空 else: block_list[ri] = ['' for j in range(coloms)] #如果发现有行已经被清除了,那么重新绘制一遍所有的小方格 if whether_complete: draw_whole_background(canvas, block_list) global SCORE SCORE += 100 # 加入音乐 # pygame.mixer.init() # pygame.mixer.music.load(r"C:usersmike1desktop123.mp3") # pygame.mixer.music.play(-1,0) #游戏界面的宽与高 width = coloms*cell_size height = rows*cell_size #绘制游戏界面 canvas = tk.Canvas(window, width = width, height = height) canvas.pack(side = 'left') #绘制基本的信息 tk.Label(window, text = "writer: mike time: 2020.09.04", bg = "#99FF33").place(x=350, y=50) draw_whole_background(canvas,block_list) #绑定键盘的左右键 canvas.focus_set() canvas.bind("<KeyPress-Left>", left_right_move) canvas.bind("<KeyPress-Right>", left_right_move) canvas.bind("<KeyPress-Up>", rotate_move) canvas.bind("<KeyPress-Down>", speed_down) #通过循环实现刷新屏幕,不知上面的写法是不是递归 # while 1: # screen_loop() # time.sleep(0.3) screen_loop() window.mainloop()
以上的俄罗斯方块,运行到一定时间之后会变的很慢,难道是因为,用到了递归吗,我并没有用到递归啊, 并不是很清楚?