Python课程设计之俄罗斯方块

Posted shangzh!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python课程设计之俄罗斯方块相关的知识,希望对你有一定的参考价值。

Python课程设计之俄罗斯方块

下载地址

点击下载
Python课程设计之俄罗斯方块
软件需求:Python环境
压缩包内含:源代码、打包好的可执行文件、文档报告

运行效果


基础页面

(1)、搭建基础窗体
使用tkinter实现基础窗体。

import tkinter as tk#实现tkinter基础窗体,导入tkinter包取别名tk  
win = tk.Tk()#tk库名,TK是tkinter库里面顶层窗体控件,用于表示用户主窗口  
win.mainloop()# 维持窗体 

运行代码生成窗口如下

接下来需要在窗体里面,添加一个画布容器用来“装”俄罗斯方块,就是让这个画布作为面板,俄罗斯方块的移动和绘制均在这个画板上实现。
这里我们设定一些俄罗斯方块游戏参数。行数为R(取20),列数为C(取12),俄罗斯方块的边长为cell_size(取30)
通过tkinger的画布类Canvas实现,Canvas是tkinter库里常用的画布对象,主要用于绘制一些东西

import tkinter as tk  #实现tkinter基础窗体,导入tkinter包取别名tk    
cell_size = 30 #每一个方格的边长  
C = 12 #列数  
R = 20 #行数  
height = R * cell_size #画布高度  
width = C * cell_size #画布宽度   
win = tk.Tk() #tk库名,TK是tkinter库里面顶层窗体控件,用于表示用户主窗口  
canvas = tk.Canvas(win, width=width, height=height)#参数为宽度与高度,win是父控件或者说母控件  
canvas.pack() #把所有的控件对象放置在指定位置,才能展示在界面窗体  
win.mainloop() # 维持窗体  
 此时界面的长宽就变成我们所需要的了

接下来在画板上绘制俄罗斯方块空白位,首先建立一个函数draw_cell_by_cr,用于在画板上绘制单个俄罗斯方块,然后建立一个函数draw_blank_board,用于在画板上绘制所有空白方块(也就是空白方块板,为了和背景区分,所以设置空白方块为轻灰色)

import tkinter as tk  #实现tkinter基础窗体,导入tkinter包取别名tk   
cell_size = 30 #每一个方格的边长  
C = 12 #列数  
R = 20 #行数  
height = R * cell_size #画布高度  
width = C * cell_size #画布宽度  
#在画板上绘制空白格子作为背景,先建立一个方法  
def draw_cell_by_cr(canvas, c, r, color="#CCCCCC"):  
    """ 
     canvas: 画板,用于绘制一个方块的Canvas对象 
     c: 方块所在列 
     r: 方块所在行 
     color: 方块颜色,默认为#CCCCCC,轻灰色 
    """  
    x0 = c * cell_size #列数*方块大小  
    y0 = r * cell_size#行数*方块大小  
    x1 = c * cell_size + cell_size#列数*方块大小+1  
    y1 = r * cell_size + cell_size#行数*方块大小+1  
    canvas.create_rectangle(x0, y0, x1, y1, fill=color, outline="white", width=2)  
  #用canvas画板对象在指定的行、列进行绘制矩形并填充,以及白色边框,宽度为2   
# 新建一个方法,绘制空白面板,也就是这些空白面板背景  
def draw_blank_board(canvas): #绘制空白面板  
    for ri in range(R): #遍历所有行  
       for ci in range(C): #遍历每一行的所有列  
                draw_cell_by_cr(canvas, ci, ri) #调用绘制函数    
win = tk.Tk() #tk库名,TK是tkinter库里面顶层窗体控件,用于表示用户主窗口  
canvas = tk.Canvas(win, width=width, height=height)#参数为宽度与高度,win是父控件或者说母控件  
canvas.pack() #把所有的控件对象放置在指定位置,才能展示在界面窗体  
draw_blank_board(canvas) #调用函数  
win.mainloop() # 维持窗体  

(2)、绘制O形俄罗斯方块
先从最简单的o型俄罗斯方块(即田字格)

先要记录每各形状所有格子的坐标,以格子所在行序号为纵坐标,所在列序号为横坐标,以田字格中点为原点,则o型俄罗斯方块的四个格子的坐标如下图所示

用列表和元组记录为

[  
    (-1, -1),  
    (0, -1),  
    (-1, 0),  
    (0, 0)  
]   

将俄罗斯方块形状字符串和坐标列表的映射关系存到字典SHAPES里(映射关系这里可以理解为一一对应的关系),同时建立一个字典SHAPESCOLOR,来记录俄罗斯方块形状字符串和颜色的一一对应关系。由于一个形状可以看成多个方格组成的,所以我们可以新建函数draw_cells用来绘制这个形状,最后选择一个地方来绘制这个形状。

1.# 定义形状  
2.SHAPES =   
3.    "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)],  
4.  
5.# 定义形状的颜色  
6.SHAPESCOLOR =   
7.    "O": "blue",  
8.  
9.def draw_cells(canvas, c, r, cell_list, color="#CCCCCC"):  
10.    """ 
11.    绘制指定形状指定颜色的俄罗斯方块 
12.     canvas: 画板 
13.     r: 该形状设定的原点所在的行 
14.     c: 该形状设定的原点所在的列 
15.     cell_list: 该形状各个方格相对自身所处位置 
16.     color: 该形状颜色 
17.    """  
18.    for cell in cell_list:  # 遍历这个形状的所有方格  
19.        cell_c, cell_r = cell  # 它的实际位置坐标为相对的这个位置加上原点的行列  
20.        ci = cell_c + c  
21.        ri = cell_r + r  
22.        # 判断该位置方格在画板内部(画板外部的方格不再绘制)  
23.        if 0 <= c < C and 0 <= r < R:  # 判断该方格在画板内部,再绘制  
24.            draw_cell_by_cr(canvas, ci, ri, color)  # 调用绘制函数  
25.# 下面这行代码放在draw_blank_board(canvas) 下面  
26.# 任取一个位置,如(3,3)绘制一个o型俄罗斯方块,用于展示  
27.draw_cells(canvas, 3, 3, SHAPES['O'], SHAPESCOLOR['O'])  
28.# 上面这行代码放在win.mainloop()上面  

(3)、绘制其他俄罗斯方块
俄罗斯方块主流分七种,除去上面的O型,其他六俄罗斯方块如图所示

对应的,要在SHAPES和SHAPESCOLOR中添加其他方块的坐标和颜色,添加后如下

1.# 定义各种形状,用列表与元组记录  
2.#用常量记录  
3.SHAPES =   
4.    "Z": [(-1, -1), (0, -1), (0, 0), (1, 0)],  
5.    "O": [(-1, -1), (0, -1), (-1, 0), (0, 0)],  
6.    "S": [(-1, 0), (0, 0), (0, -1), (1, -1)],  
7.    "T": [(-1, 0), (0, 0), (0, -1), (1, 0)],  
8.    "I": [(0, 1), (0, 0), (0, -1), (0, -2)],  
9.    "L": [(-1, 0), (0, 0), (-1, -1), (-1, -2)],  
10.    "J": [(-1, 0), (0, 0), (0, -1), (0, -2)]  
11.  
12.# 定义各种形状的颜色  
13.SHAPESCOLOR =   
14.    "O": "blue",  
15.    "Z": "Cyan",  
16.    "S": "red",  
17.    "T": "yellow",  
18.    "I": "green",  
19.    "L": "purple",  
20.    "J": "orange",  
21.  

将原来绘制O型俄罗斯方块处的代码改成如下代码,将这七种俄罗斯方块绘制出来

1.draw_cells(canvas, 3, 3, SHAPES['O'], SHAPESCOLOR['O'])  
2.draw_cells(canvas, 3, 8, SHAPES['S'], SHAPESCOLOR['S'])  
3.draw_cells(canvas, 3, 13, SHAPES['T'], SHAPESCOLOR['T'])  
4.draw_cells(canvas, 8, 3, SHAPES['I'], SHAPESCOLOR['I'])  
5.draw_cells(canvas, 8, 8, SHAPES['L'], SHAPESCOLOR['L'])  
6.draw_cells(canvas, 8, 13, SHAPES['J'], SHAPESCOLOR['J'])  
7.draw_cells(canvas, 5, 18, SHAPES['Z'], SHAPESCOLOR['Z'])  

界面动起来

要让游戏界面动起来,需要实现两点:1、定时刷新页面 2、移动页面内部的俄罗斯方块。光实现1的话理论上页面也算动起来了,但是如果界面内容不变,每次新的页面和旧的界面看起来是一样的,给人的观看感觉跟没有动没有区别,所以必须要实现2,来更改界面内容,给人“动”起来的感觉

(1)、定时刷新页面
实现定时刷新页面,写一个方法用于循环刷新,再定时调用,此部分代码写在原来代码最后的win.mainloop之前就好

1.FPS = 500  # 刷新页面的毫秒间隔  
2.def game_loop():  
3.    win.update()  
4.   
5.    # ===用于展示刷新,后续会删掉===  
6.    import time  
7.    print(time.ctime())  
8.    # ===========================  
9.   
10.    win.after(FPS, game_loop)  
11.   
12.win.update()  
13.win.after(FPS, game_loop) # 在FPS 毫秒后调用 game_loop方法 

此时运行发现命令行中会有如下输出,说明这个页面确实是每隔500ms刷新了一次

但是每次刷新后页面的内容是没有变化的,给人的感觉还是更没动一样,所以需要配合每次刷新,移动俄罗斯方块的位置
(2)、移动俄罗斯方块
对于某个俄罗斯方块,要绘制出来需要知道它的位置和类型,以及内部的各个方格。
这三个数据可以用字典存起来,这里规定格式如下

1.a_block =   
2.    'kind': 'O',  # 对应俄罗斯方块的类型  
3.    'cell_list': SHAPES['O'],  # 对应俄罗斯方块的各个方格  
4.    'cr': [3, 3]  # 对应横纵坐标,以左上角为原点,水平向右为横坐标轴正方向,竖直向下为纵坐标轴正方向  
5.  

此时一个这样的字典其实就可以看做一个俄罗斯方块对象,然后再建一个专门的方法draw_block_move,来绘制俄罗斯方块的移动,其实移动就是清掉旧位置已经绘制的俄罗斯方块,再在新位置绘制新的俄罗斯方块就好。然后修改下game_loop方法,更新修改部分如下

1.# 在draw_blank_board(canvas)语句下面添加如下语句  
2.def draw_block_move(canvas, block, direction=[0, 0]):  
3.    """ 
4.    绘制向指定方向移动后的俄罗斯方块 
5.    canvas: 画板 
6.    block: 俄罗斯方块对象 
7.    direction: 俄罗斯方块移动方向 
8.    ""#把方块里面所有信息取出来
9.    shape_type = block['kind']  
10.    c, r = block['cr']  
11.    cell_list = block['cell_list']  
12.   
13.    # 移动前,先清除原有位置绘制的俄罗斯方块,也就是用背景色绘制原有的俄罗斯方块  
14.    draw_cells(canvas, c, r, cell_list)  
15.     #移动,按照指定方向移动 
16.    dc, dr = direction
17.    new_c, new_r = c+dc, r+dr  
18.    block['cr'] = [new_c, new_r]  #把更新后的信息存在方块对象里面
19.    # 在新位置绘制新的俄罗斯方块就好  
20.    draw_cells(canvas, new_c, new_r, cell_list, SHAPESCOLOR[shape_type])  
21.   
22.   
23.a_block =   
24.    'kind': 'O',  # 对应俄罗斯方块的类型  
25.    'cell_list': SHAPES['O'],  # 对应俄罗斯方块的各个方格  
26.    'cr': [3, 3]  # 对应横纵坐标,以左上角为原点,水平向右为横坐标轴正方向,竖直向下为纵坐标轴正方向  
27.  
28.   
29.draw_block_move(canvas, a_block)  
30.   
31.   
32.# 修改原来的game_loop方法如下  
33.def game_loop():  
34.    win.update()  
35.   
36.    down = [0, 1]  
37.    draw_block_move(canvas, a_block, down)  
38.   
39.    win.after(FPS, game_loop)  


生成、移动、固定、变形

(1)、随机生成俄罗斯方块
这里需要导入python自带的随机库random,使用其中的choice方法,从俄罗斯方块形状列表中随机选出一个。
首先需要删除原来的位于game_loop方法上面的a_block = … 和draw_block_move(canvas, a_block)这2段代码(这两端代码俄罗斯方块是在固定位置,按照指定形状生成的,需要删掉这部分代码),然后再在原来的这2段代码所在位置,添加一个随机生成俄罗斯方块的方法generate_new_block

1.def generate_new_block():  
2.    # 随机生成新的俄罗斯方块  
3.   #使用choice方法在方块形状列表中随机选取一个
4.    kind = random.choice(list(SHAPES.keys()))  
5.    # 对应横纵坐标,以左上角为原点,水平向右为x轴正方向,  
6.    # 竖直向下为y轴正方向,x对应横坐标,y对应纵坐标  
7.    cr = [C // 2, 0]  #设置初始的行列,在第一行的正中间
8.    new_block =   
9.        'kind': kind,  # 对应俄罗斯方块的类型  
10.        'cell_list': SHAPES[kind],  
11.        'cr': cr  
12.      
13.   
14.    return new_block  

再将游戏运行方法game_loop修改成如下,其中,将当前的俄罗斯方块对象通过全局变量来存储,并在game_loop方法下面设置初始的俄罗斯方块为空(None),此时每次运行代码都会发现生成了随机的俄罗斯方块。

1.def game_loop():  
2.    win.update()  
3.    global current_block  #获取全局变量
4.    if current_block is None:  #如果它不存在,就生成,并原地绘制
5.        new_block = generate_new_block()  
6.        # 新生成的俄罗斯方块需要先在生成位置绘制出来  
7.        draw_block_move(canvas, new_block)  
8.        current_block = new_block  
9.    else:  #否则向下移动这个方块对象
10.        draw_block_move(canvas, current_block, [0, 1])  
11.   
12.    win.after(FPS, game_loop)  
13.   
14.current_block = None  

当然,此时还有问题没有解决,就是俄罗斯方块生成后,会一直往下移动,就算出了可视范围还是会往下移动(只是这时再也看不到了)。所以接下来要实现的是:当俄罗斯方块落地的时候,将它固定住,然后再去生成新的。
(2)、固定已经失效的俄罗斯方块
判断俄罗斯方块落地的原则是,该方块是否可以继续向下移动:这里首先需要的是一个方法check_move需要判断俄罗斯方块是否可以向指定位置移动,该generate_new_block方法的下面,添加check_move方法代码如下

1.def check_move(block, direction=[0, 0]):#当方块落地时进行固定,然后生成新的方块
2.    """ 
3.        判断俄罗斯方块是否可以朝制定方向移动 
4.        block: 俄罗斯方块对象 
5.        direction: 俄罗斯方块移动方向 
6.        boolean 是否可以朝制定方向移动 
7.        """  
8.    cc, cr = block['cr']  #先取出方块行列坐标
9.    cell_list = block['cell_list']  #内部各个格子相对自身的坐标行列
10.   
11.    for cell in cell_list:  
12.        cell_c, cell_r = cell  
13.        c = cell_c + cc + direction[0]  
14.        r = cell_r + cr + direction[1]  
15.        # 判断该位置是否超出左右边界,以及下边界  
16.        # 一般不判断上边界,因为俄罗斯方块生成的时候,可能有一部分在上边界之上还没有出来  
17.        if c < 0 or c >= C or r >= R:  
18.            return False  
19.   
20.    return True  

再将游戏运行方法game_loop修改成如下,便可以每次俄罗斯方块落地后都会生成新的

1.def game_loop():  
2.    win.update()  
3.    global current_block  #获取全局变量
4.   
5.    if current_block is None:  #如果它不存在,就生成,并原地绘制
6.        new_block = generate_new_block()  
7.        # 新生成的俄罗斯方块需要先在生成位置绘制出来  
8.        draw_block_move(canvas, new_block)  
9.        current_block = new_block  #并且设置当前的方块为新生成的对象
10.    else:  #否则向下移动这个方块对象
11.        if check_move(current_block, [0, 1]):#添加这个方法可以判断是否可以向下移动
12.            draw_block_move(canvas, current_block, [0, 1])  
13.        else:  
14.            current_block = None  
15.   
16.    win.after(FPS, game_loop)  

此时仍然有个问题就是老的俄罗斯方块会被新的落地的俄罗斯方块重合。

这个需要处理,当俄罗斯方块发现下面有已固定的俄罗斯方块时,也不能移动。首先,我们需要添加一个变量block_list来记录已固定的俄罗斯方块。在原代码draw_blank_board(canvas)的位置的下面,添加如下代码

1.block_list = []  #建立一个变量,计入已经固定的方块,使其不会重叠,先生成初始的
2.for i in range(R):  #由空

以上是关于Python课程设计之俄罗斯方块的主要内容,如果未能解决你的问题,请参考以下文章

JAVA课程设计 俄罗斯方块

ccf160402-俄罗斯方块

基于.NET的俄罗斯方块课程设计

CSP 俄罗斯方块(201604-2)

CCF 201604-2俄罗斯方块 (模拟)

JAVA课程设计——俄罗斯方块