写的第一个俄罗斯方块

#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()

以上的俄罗斯方块,运行到一定时间之后会变的很慢,难道是因为,用到了递归吗,我并没有用到递归啊, 并不是很清楚?

原文地址:https://www.cnblogs.com/zijidefengge/p/13625704.html