上回分享了博文 用python自带的tkinter做游戏(一)—— 贪吃蛇 篇
  今天继续,尝试用tkinter来制作一个更经典的游戏 —— 俄罗斯方块。
  
  俄罗斯方块相信大家都玩过,一共有七个方块组,每个方块组由四个小方格组成,有四种旋转的状态。
  
  关于这七个方块组,构建的方法主要有二种,绝对构建和相对构建。什么意思呢?绝对构建就是直接用点阵图画出所需要的图形,比如[[1,1],[1,1]],就是由四个1组成的一个大方块,[[1,1,0],[0,1,1]]就是Z字型的构件,以此类推。
  
  而我选择用的是相对构建。在这里我也解释一下,每个方块组由四个小方格组成,选择其中的一个为定位格,然后以这个定位格的坐标来决定其它三个格子的位置。也就是说这三个格子是随着定位格的移动而移动,所以我就把这三个格子命名为随从格。

如果还没明白相对构建的概念,那我图解一下吧。
  
  比如这个列表: [[0, 0],[-1, 0],[-1,-1],[-1,-2]]
  [0, 0] 代表定位格的坐标,就是原点,图中的黄格子就是定位格。
  [-1, 0]为1号随从格的相对坐标,-1为Y轴,0是X轴,也就是说紧贴着定位格的上方的那个格子。
  2号随从格相对于定位格的XY轴都小1,所以是[-1,-1]。
  以此类推,3号定位格的相对坐标就是[-1,-2]。
  
  别忘了还有九十度旋转的功能,顺时针旋转九十度后图片就成了这样。
  
  列表的描述就成了[[ 0,-2],[ 0, 1],[-1, 1],[-2, 1]]
  解释一下,定位格的坐标成了[ 0,-2],这是因为旋转后的定位格相对于之前的位置向左移动了两格,也就是X轴减去2,所以是[ 0,-2]。
  那我们接下来继续旋转。
  
列表描述为: [ 0, 0],[ 0, 1],[ 0, 2],[-1, 0]]

再旋转。

[[0, 0],[-1, 0],[-2, 0],[-2, 1]]
最后一种状态再旋转一次的话就回到了第一种状态。
因为定位格产生了位移,所以第一种状态的列表描述改为[[0, 2],[-1, 0],[-1,-1],[-1,-2]]
这样这个组件就可以不断的旋转而不会产生位移。

至此,四种旋转的状态齐了。由于四种状态下,四个定位格的选择都是在同一Y轴上,所以产生的位移都是发生在X轴上。
为了缩短列表长度,我就省略了Y轴坐标,用逆时针旋转时的位移(也只发生在X轴上)来替代。

最终七个组件的代码如下:

seven_shapes =   [[[[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]], [[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]],[[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]], [[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]]],[[[-1,-1],[ 0, 1],[ 0, 2],[ 0, 3]], [[ 1, 1],[-1, 0],[-2, 0],[-3, 0]],[[-1,-1],[ 0, 1],[ 0, 2],[ 0, 3]], [[ 1, 1],[-1, 0],[-2, 0],[-3, 0]]],[[[ 0, 0],[-1,-1],[-1, 0],[-1, 1]], [[ 1, 0],[-1, 0],[-2, 0],[-1,-1]],[[-1,-1],[ 0, 1],[ 0, 2],[-1, 1]], [[ 0, 1],[-1, 0],[-2, 0],[-1, 1]]],[[[-1, 0],[-1, 0],[-1, 1],[-1, 2]], [[ 1, 1],[-2,-1],[-2, 0],[-1, 0]],[[ 0,-1],[ 0, 1],[ 0, 2],[-1, 2]], [[ 0, 0],[ 0, 1],[-1, 0],[-2, 0]]],[[[ 2, 2],[-1, 0],[-1,-1],[-1,-2]], [[ 0,-2],[ 0, 1],[-1, 1],[-2, 1]],[[ 0, 0],[ 0, 1],[ 0, 2],[-1, 0]], [[-2, 0],[-1, 0],[-2, 0],[-2, 1]]],[[[ 0, 0],[ 0, 1],[-1,-1],[-1, 0]], [[ 0, 0],[-1, 0],[-1, 1],[-2, 1]],[[ 0, 0],[ 0, 1],[-1,-1],[-1, 0]], [[ 0, 0],[-1, 0],[-1, 1],[-2, 1]]],[ [[-1,-1],[ 0, 1],[-1, 2],[-1, 1]], [[ 1, 1],[-1, 0],[-1,-1],[-2,-1]],[[-1,-1],[ 0, 1],[-1, 2],[-1, 1]], [[ 1, 1],[-1, 0],[-1,-1],[-2,-1]]]]# seven_shapes[x]为七个组件,分别是O,I,T,L,J,Z,S七种形态。每个组件含有四种转向。
# seven_shapes[x][0]为初始转向状态,seven_shapes[x][1]为下一个顺时针90度转向状态,以此类推,一共四个。
# seven_shapes[x][0][1]为1号随从格的相对坐标,以此类推,随从格一共有三个。
# seven_shapes[x][0][0]为旋转偏差值。
# seven_shapes[x][0][0][0]为逆时针偏差值。
# seven_shapes[x][0][0][1]为顺时针偏差值。

其实俄罗斯方块和贪吃蛇游戏类似,都属于方格类游戏,原理都差不多,部分代码甚至可以通用。

class Tetris():""" 俄罗斯方块游戏  """def __init__(self):""" 游戏参数设置 """self.FPS        = 200                 # 降落速度 1000 = 1秒self.col_cells  = 12                  # 一行多少个单元格(含边框)self.row_cells  = 24                  # 一共多少行单元格(含边框)self.canvas_bg  = 'white'             # 游戏背景色self.cell_gap   = 1                   # 方格间距self.frame_x    = 5                   # 左右边距self.frame_y    = 5                   # 上下边距self.win_w_plus = 280                 # 窗口右边额外多出的宽度self.color_dict = {0:  '#e0e0e0',     # 0表示空白1:  '#8f8f8f',     # 1为已落地的方块色2:    'green',     # 2为定位格3:    'green',     # 3为组件中除了定位格剩下的三个单元格4:  '#b3b3b3'}     # 4代表边框

开场还是游戏的参数的设置。颜色分了五种,但定位格和随从格的颜色相同,确保整体性。

单元格(cell_size)的大小没有定义,这次我打算根据显示器的高度来自动匹配。

        screenHeight = window.winfo_screenheight()  # 获取显示区域的高度cell_size = screenHeight / 30

然后创建游戏地图。

    def create_map(self,col,row):""" 创建地图列表 """global game_mapgame_map = []for y in range(0,col):game_map.append([])for y in range(0,col):for x in range(0,row):game_map[y].append(x)   game_map[y][x] = 0 # 生成一个全是0的空数列def create_wall(self):""" 绘制边框 """ # 除了顶部,三个边全部是边框for i in range(0,self.col_cells-1):game_map[self.row_cells-1][i] = 4for i in range(0,self.row_cells-1):game_map[i][0] = 4game_map[i][self.col_cells-1] = 4game_map[-1][-1] = 4

创建好了地图,并绘制边框。与贪吃蛇不同,这次就不封顶了。

接下来创建画布。

    def create_canvas(self):""" 创建画布 """global canvas,windowcanvas_h = cell_size * self.row_cells + self.frame_y*2canvas_w = cell_size * self.col_cells + self.frame_x*2canvas = tk.Canvas(window, bg = self.canvas_bg, height =       canvas_h,width =       canvas_w,highlightthickness =              0)def fresh_cells(self): """ 刷新单元格 """for y in range(0,self.row_cells):for x in range(0,self.col_cells):a = self.frame_x + cell_size*xb = self.frame_y + cell_size*yc = self.frame_x + cell_size*(x+1)d = self.frame_y + cell_size*(y+1)e = self.canvas_bgf = self.cell_gapg = self.color_dict[game_map[y][x]]canvas.itemconfig(canvas.create_rectangle(a,b,c,d,outline = e, width = f, fill = g),fill = g)canvas.place(x=0,y=0)

这次将create_canvas单独做成一个函数,因为我发现在loop中反复刷新创建画布的话,时间久了会造成BUG。

俄罗斯方块游戏进行的时候,会有下一个shape的提示,所以需要随机生成2个shape

now_and_next_shape = [] # 存放当前和之后的2个shapedef random_shape(self):""" 生成随机的shape """global turn_timesturn_times = 0 # 翻转的次数x_shape = random.randint(0,6)  # 七个组件随机出现now_and_next_shape.append(x_shape)

生成了shape,就可以抓取定位格的坐标了。

    def shape_xy(self):""" 获取定位格坐标 """  # 每个shape由4个单元格组成,一个定位格和三个随从单元格global shape_x, shape_yxy = []for i in range(0,self.row_cells):try: # 查找数值为2的坐标,没有就返回0。为防止在0列,先加上1,最后再减去。x = game_map[i].index(2) + 1 except:x = 0xy.append(x)shape_x = max(xy)shape_y = xy.index(shape_x)shape_x = shape_x - 1 # 之前加1,现在减回

定位格有了,就可以定义随从格了。

    def other_shapes(self,a,b):  # 每个shape由4个单元格组成,一个定位格和三个随从单元格""" 三个随从单元格的坐标。a为当前或是下一个shape,b是旋转次数 """global y1,x1,y2,x2,y3,x3y1 = seven_shapes[now_and_next_shape[a]][b][1][0]x1 = seven_shapes[now_and_next_shape[a]][b][1][1]y2 = seven_shapes[now_and_next_shape[a]][b][2][0]x2 = seven_shapes[now_and_next_shape[a]][b][2][1]y3 = seven_shapes[now_and_next_shape[a]][b][3][0]x3 = seven_shapes[now_and_next_shape[a]][b][3][1]

有了这个函数,再加上定位格的坐标,就可以在任意地点显示所生成的shape了。

在正式游戏之前,先把下一个shape的预览做好。
我们之前只创建了主游戏的map,现在需要再建立一个mini map,用来存放下一个shape。

    def creat_mini_canvas(self):""" 创建预览图用的mini——canvas """global canvas2canvas2 = tk.Canvas(window,bg = self.canvas_bg,height = self.cell_size * 2 + self.frame_y*2,width = self.cell_size * 4 + self.frame_x*2,highlightthickness = 0)def creat_mini_map(self):""" 创建预览图 """mini_map = [] for y in range(0,2):mini_map.append([])for y in range(0,2):for x in range(0,4):mini_map[y].append(x)mini_map[y][x] = 0        # 生成一个全是0的空数列l0 = [1,0,1,0,2,1,0]            # 七个组件每个初始出现的X坐标值y0 = 1                          # 初始的Y坐标值x0 = l0[now_and_next_shape[1]]  # 初始的X坐标值# 其余三个shape的坐标Tetris().other_shapes(1, 0)     # 1是下一个shape,0是旋转的次数mini_map[y0     ][x0     ] = 4mini_map[y0 + y1][x0 + x1] = 4mini_map[y0 + y2][x0 + x2] = 4mini_map[y0 + y3][x0 + x3] = 4for y in range(0,2):for x in range(0,4):canvas2.itemconfig(canvas2.create_rectangle(self.frame_x + cell_size*x,self.frame_y + cell_size*y,self.frame_x + cell_size*(x+1),self.frame_y + cell_size*(y+1),outline      = self.canvas_bg,width        = self.cell_gap),fill         = self.color_dict[mini_map[y][x]])canvas2.place(x= cell_size * self.col_cells + cell_size*2, y = 0)

说明一下,因为七个shape长短不一,所以初始出现的X坐标需要定义一下。l0 = [1,0,1,0,2,1,0] 这里面的七个数字就是七个shape初始出现的X坐标。

预览图搞定了,继续回到主画面,开始定义当前的shape。

    def shape_follow(self,x,a): # 三个随从格绑定与定位格的坐标""" x为turn_times的翻转值,a为单元格的颜色代码 """Tetris().shape_xy()l0 = [5,4,5,5,6,5,5]            # 七个组件每个初始出现的X坐标值y0 = 1                          # 初始的Y坐标值x0 = l0[now_and_next_shape[0]]  # 初始的X坐标值Tetris().other_shapes(0, x)if  shape_x != -1: # 等于-1的话就代表没有shape,不是刚开始就是刚落地if game_map[shape_y + y1][shape_x + x1] in [0,2,3] and \game_map[shape_y + y2][shape_x + x2] in [0,2,3] and \game_map[shape_y + y3][shape_x + x3] in [0,2,3]:game_map[shape_y + y1][shape_x + x1] = agame_map[shape_y + y2][shape_x + x2] = agame_map[shape_y + y3][shape_x + x3] = aelse: # 出现在画面顶端game_map[y0     ][x0     ] = 2game_map[y0 + y1][x0 + x1] = 3game_map[y0 + y2][x0 + x2] = 3game_map[y0 + y3][x0 + x3] = 3Tetris().shape_xy()

再定义一个移动中的shape

    def shape_move(self,x,y,z):""" shape移动。x,y为XY轴的偏移,z为颜色代码(当前shape或是已落地的shape,1或3) """Tetris().shape_follow(turn_times,0)     # 把三个随从格颜色变成0,即删除game_map[shape_y + 0][shape_x + 0] = 0  # 把定位格颜色变为0,即删除game_map[shape_y + y][shape_x + x] = 2  # X轴或Y轴移动一格后生成新的定位格Tetris().shape_follow(turn_times,z)     # 根据新的定位格坐标生成随从格,颜色是1或者是3(当前shape或已落地的shape)

移动shape的代码和贪吃蛇的差不多,就不详细说明了。直接看最下方的完整代码或者看上一篇博文吧,在这里我就省点精力了。

因为shape还能翻转,这里的代码我说明一下:

        def clockwise_key(key):""" 顺时针转向按键 """global turn_timesdirection = event.keysymif(direction == key):turn_times = turn_times + 1 # 旋转次数,每旋转一次数值加1if turn_times > 3: # 总共4个转向,大于3就变回0turn_times = 0x4 = seven_shapes[now_and_next_shape[0]][turn_times][0][1]Tetris().shape_follow(turn_times - 1, 0)Tetris().shape_move(x4,0,3)def clockwise_key_estimate():""" 顺时针转向判断 """ # 如果遇上墙或者已完成的方块,阻止其转向x = turn_times + 1if x > 3:x = 0x0 = seven_shapes[now_and_next_shape[0]][x][0][1]Tetris().other_shapes(0, x)if shape_y + y1 > 0 or shape_y + y2 > 0 or shape_y + y3 > 0:if    game_map[shape_y +  0][shape_x + x0 +  0] in [1,4] or \game_map[shape_y + y1][shape_x + x0 + x1] in [1,4] or \game_map[shape_y + y2][shape_x + x0 + x2] in [1,4] or \game_map[shape_y + y3][shape_x + x0 + x3] in [1,4]:Tetris().shape_move(0,0,3)else:clockwise_key('j')

逆时针旋转的差不多,我也略过省时间了。
FC红白机里的俄罗斯方块还有减速和加速功能,我这里也复刻了,代码比较简单,我也就不详细解读了,请自行阅读代码。

根据游戏设计,满行即消除。这消除的代码如下:

    def full_del(self):""" 满行清除并新增一行 """global r1,r2,r3,r4r = 0 # 一次消除的行数for i in range(1,self.row_cells-1): # 某行若出现10个1,就删除该行,并在第2行之后再插入一行空的if game_map[i].count(1) == self.col_cells - 2:del game_map[i]r = r + 1new_row = [] # 准备要插入的新行for x in range(0,self.col_cells):new_row.append(0)new_row[ 0] = 4 # 第一格和最后一格是边框new_row[-1] = 4game_map.insert(2,new_row)if   r == 1:       # 本次一共消除了一行r1 = r1 + 1elif r == 2:       # 本次一共消除了二行r2 = r2 + 1elif r == 3:       # 本次一共消除了三行r3 = r3 + 1elif r == 4:       # 本次一共消除了四行r4 = r4 + 1

原理很简单,满行就删除,然后再插入一行空白的。
再加入一个统计,记录下每次一共消除了几行,供计分用。

    def scoring_loop(self):""" 计分更新 """global r5,r6,scoring_labler5 = r1 + r2*2 + r3*3 + r4*4 # 消除的总数r6 = r1 + r2*2*2 + r3*3*3 + r4*4*4 # 计分。消的越多,奖励越多scoring_lable['text'] = "\n" \+ "\n单消: " + str(r1) \+ "\n双消: " + str(r2) \+ "\n三消: " + str(r3) \+ "\n四消: " + str(r4) \+ "\n" \+ "\n总消: " + str(r5) \+ "\n总分: " + str(r6)

上回贪吃蛇没有计分系统,这次弥补上。
本人设计的计分系统比较变态,大大鼓励一次性消除四行。
本来想设计成只消除一行的话就扣分,后来仔细想想自己都觉得变态,还是算了吧。。。

是游戏总得有个尽头。

    def game_over(self):""" 游戏结束 """global r1,r2,r3,r4,r5,r6,scoring_lableif game_map[2].count(1) > 0: # 有1出现了就算失败showinfo('Game Over','再来一局')scoring_lable['text'] = ''Tetris().game_start()

只要在地图的第三行出现了数值1就算游戏失败。
胜利条件?那是不存在的!

至此整个俄罗斯方块算是完成了,UI是简陋了些,也就够自娱自乐。
懒得做起始页面了,就用暂停的方式来做初始界面。
所以游戏开始前需要按空格键,确认后方可进行游戏。

此外游戏中所有的按键必须要小写,不能设置成和贪吃蛇一样,大小写均可。
具体原因我还没分析,应该和按键释放有关,若有高人能指点一二,本人将感激不尽!

晒一下游戏画面:
(欢迎加本人WX一同交流:znix1116)

最终附上完整代码:
(剧透:下一期的主题——用python自带的tkinter做游戏(三)—— 推箱子 篇)
2022.03.25 更新了代码,修复了些BUG。

# -*- coding: utf-8 -*-
"""
Created on Sun Apr  4 10:12:46 2021@author: Juni Zhu
"""import tkinter as tk
from tkinter.messagebox import showinfo
import randomseven_shapes  =  [[[[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]], [[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]],[[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]], [[ 0, 0],[ 0, 1],[-1, 0],[-1, 1]]],[[[-1,-1],[ 0, 1],[ 0, 2],[ 0, 3]], [[ 1, 1],[-1, 0],[-2, 0],[-3, 0]],[[-1,-1],[ 0, 1],[ 0, 2],[ 0, 3]], [[ 1, 1],[-1, 0],[-2, 0],[-3, 0]]],[[[ 0, 0],[-1,-1],[-1, 0],[-1, 1]], [[ 1, 0],[-1, 0],[-2, 0],[-1,-1]],[[-1,-1],[ 0, 1],[ 0, 2],[-1, 1]], [[ 0, 1],[-1, 0],[-2, 0],[-1, 1]]],[[[-1, 0],[-1, 0],[-1, 1],[-1, 2]], [[ 1, 1],[-2,-1],[-2, 0],[-1, 0]],[[ 0,-1],[ 0, 1],[ 0, 2],[-1, 2]], [[ 0, 0],[ 0, 1],[-1, 0],[-2, 0]]],[[[ 2, 2],[-1, 0],[-1,-1],[-1,-2]], [[ 0,-2],[ 0, 1],[-1, 1],[-2, 1]],[[ 0, 0],[ 0, 1],[ 0, 2],[-1, 0]], [[-2, 0],[-1, 0],[-2, 0],[-2, 1]]],[[[ 0, 0],[ 0, 1],[-1,-1],[-1, 0]], [[ 0, 0],[-1, 0],[-1, 1],[-2, 1]],[[ 0, 0],[ 0, 1],[-1,-1],[-1, 0]], [[ 0, 0],[-1, 0],[-1, 1],[-2, 1]]],[ [[-1,-1],[ 0, 1],[-1, 2],[-1, 1]], [[ 1, 1],[-1, 0],[-1,-1],[-2,-1]],[[-1,-1],[ 0, 1],[-1, 2],[-1, 1]], [[ 1, 1],[-1, 0],[-1,-1],[-2,-1]]]]# seven_shapes[x]为七个组件,分别是O,I,T,L,J,Z,S七种形态。每个组件含有四种转向。
# seven_shapes[x][0]为初始转向状态,seven_shapes[x][1]为下一个顺时针90度转向状态,以此类推,一共四个。
# seven_shapes[x][0][1]为1号随从格的相对坐标,以此类推,随从格一共有三个。
# seven_shapes[x][0][0]为旋转偏差值。
# seven_shapes[x][0][0][0]为逆时针偏差值。
# seven_shapes[x][0][0][1]为顺时针偏差值。now_and_next_shape = [] # 存放当前和之后的2个shapeclass Tetris():""" 俄罗斯方块游戏  """def __init__(self):""" 游戏参数设置 """self.FPS        = 150                 # 降落速度 1000 = 1秒self.col_cells  = 12                  # 一行多少个单元格(含边框)self.row_cells  = 24                  # 一共多少行单元格(含边框)self.canvas_bg  = 'white'             # 游戏背景色self.cell_gap   = 1                   # 方格间距self.frame_x    = 5                   # 左右边距self.frame_y    = 5                   # 上下边距self.win_w_plus = 280                 # 窗口右边额外多出的宽度self.color_dict = {0:  '#e0e0e0',     # 0表示空白1:  '#8f8f8f',     # 1为已落地的方块色2:    'green',     # 2为定位格3:    'green',     # 3为组件中除了定位格剩下的三个单元格4:  '#b3b3b3'}     # 4代表边框self.run_game()def window_center(self,window,w_size,h_size):""" 窗口居中 """screenWidth  =  window.winfo_screenwidth()  # 获取显示区域的宽度screenHeight = window.winfo_screenheight()  # 获取显示区域的高度left         =  (screenWidth - w_size) // 2top          = (screenHeight - h_size) // 2 - 10window.geometry("%dx%d+%d+%d" % (w_size, h_size, left, top))def create_map(self,col,row):""" 创建地图列表 """global game_mapgame_map = []for y in range(0,col):game_map.append([])for y in range(0,col):for x in range(0,row):game_map[y].append(x)   game_map[y][x] = 0 # 生成一个全是0的空数列def create_wall(self):""" 绘制边框 """ # 除了顶部,三个边全部是边框for i in range(0,self.col_cells-1):game_map[self.row_cells-1][i] = 4for i in range(0,self.row_cells-1):game_map[i][0] = 4game_map[i][self.col_cells-1] = 4game_map[-1][-1] = 4def create_canvas(self):""" 创建画布 """global canvas,windowcanvas_h = cell_size * self.row_cells + self.frame_y*2canvas_w = cell_size * self.col_cells + self.frame_x*2canvas = tk.Canvas(window, bg = self.canvas_bg,height =       canvas_h,width =       canvas_w,highlightthickness =              0)def fresh_cells(self): """ 刷新单元格 """for y in range(0,self.row_cells):for x in range(0,self.col_cells):a = self.frame_x + cell_size*xb = self.frame_y + cell_size*yc = self.frame_x + cell_size*(x+1)d = self.frame_y + cell_size*(y+1)e = self.canvas_bgf = self.cell_gapg = self.color_dict[game_map[y][x]]canvas.itemconfig(canvas.create_rectangle(a,b,c,d,outline = e, width = f, fill = g),fill = g)canvas.place(x=0,y=0)def random_shape(self):""" 生成随机的shape """global turn_timesturn_times = 0 # 翻转的次数x_shape = random.randint(0,6)  # 七个组件随机出现now_and_next_shape.append(x_shape)def get_locator_cell_pos(self):""" 获取定位格坐标 """  # 每个shape由4个单元格组成,一个定位格和三个随从单元格global shape_x, shape_yxy = []for i in range(0,self.row_cells):try: # 查找数值为2的坐标,没有就返回0。为防止在0列,先加上1,最后再减去。x = game_map[i].index(2) + 1 except:x = 0xy.append(x)shape_x = max(xy)shape_y = xy.index(shape_x)shape_x = shape_x - 1 # 之前加1,现在减回def get_follow_cells_pos(self,a,b):  # 每个shape由4个单元格组成,一个定位格和三个随从单元格""" 三个随从单元格的坐标。a为当前或是下一个shape,b是旋转次数 """global y1,x1,y2,x2,y3,x3y1 = seven_shapes[now_and_next_shape[a]][b][1][0]x1 = seven_shapes[now_and_next_shape[a]][b][1][1]y2 = seven_shapes[now_and_next_shape[a]][b][2][0]x2 = seven_shapes[now_and_next_shape[a]][b][2][1]y3 = seven_shapes[now_and_next_shape[a]][b][3][0]x3 = seven_shapes[now_and_next_shape[a]][b][3][1]def creat_mini_canvas(self):""" 创建预览图用的mini canvas """global canvas2canvas2 = tk.Canvas(window,bg = self.canvas_bg,height = cell_size * 2 + self.frame_y*2,width = cell_size * 4 + self.frame_x*2,highlightthickness = 0)def creat_mini_map(self):""" 创建预览图 """mini_map = [] for y in range(0,2):mini_map.append([])for y in range(0,2):for x in range(0,4):mini_map[y].append(x)mini_map[y][x] = 0        # 生成一个全是0的空数列l0 = [1,0,1,0,2,1,0]            # 七个组件每个初始出现的X坐标值y0 = 1                          # 初始的Y坐标值x0 = l0[now_and_next_shape[1]]  # 初始的X坐标值# 其余三个shape的坐标self.get_follow_cells_pos(1, 0)     # 1是下一个shape,0是旋转的次数mini_map[y0     ][x0     ] = 4mini_map[y0 + y1][x0 + x1] = 4mini_map[y0 + y2][x0 + x2] = 4mini_map[y0 + y3][x0 + x3] = 4for y in range(0,2):for x in range(0,4):canvas2.itemconfig(canvas2.create_rectangle(self.frame_x + cell_size*x,self.frame_y + cell_size*y,self.frame_x + cell_size*(x+1),self.frame_y + cell_size*(y+1),outline      = self.canvas_bg,width        = self.cell_gap),fill         = self.color_dict[mini_map[y][x]])canvas2.place(x= cell_size * self.col_cells + cell_size*2, y = 0)def follow_cells_bind_to_locator(self,x,a): # 三个随从格绑定与定位格的坐标""" x为turn_times的翻转值,a为单元格的颜色代码 """self.get_locator_cell_pos()l0 = [5,4,5,5,6,5,5]            # 七个组件每个初始出现的X坐标值y0 = 1                          # 初始的Y坐标值x0 = l0[now_and_next_shape[0]]  # 初始的X坐标值self.get_follow_cells_pos(0, x)if  shape_x != -1: # 等于-1的话就代表没有shape,不是刚开始就是刚落地if game_map[shape_y + y1][shape_x + x1] in [0,2,3] and \game_map[shape_y + y2][shape_x + x2] in [0,2,3] and \game_map[shape_y + y3][shape_x + x3] in [0,2,3]:game_map[shape_y + y1][shape_x + x1] = agame_map[shape_y + y2][shape_x + x2] = agame_map[shape_y + y3][shape_x + x3] = aelse: # 出现在画面顶端game_map[y0     ][x0     ] = 2game_map[y0 + y1][x0 + x1] = 3game_map[y0 + y2][x0 + x2] = 3game_map[y0 + y3][x0 + x3] = 3self.get_locator_cell_pos()def Release_speed(self,event):""" 上下键释放 """ # 弹起上键或下键,触发速度的改变def speed_Release(up,down):""" 上下键释放 """global speeddirection = event.keysymif(direction == up):speed[0] = 0if(direction == down):speed[0] = 0speed_Release('w','s')def shape_move(self,x,y,z):""" shape移动。x,y为XY轴的偏移,z为颜色代码(当前shape或是已落地的shape,1或3) """self.follow_cells_bind_to_locator(turn_times,0)     # 把三个随从格颜色变成0,即删除game_map[shape_y + 0][shape_x + 0] = 0                  # 把定位格颜色变为0,即删除game_map[shape_y + y][shape_x + x] = 2                  # X轴或Y轴移动一格后生成新的定位格self.follow_cells_bind_to_locator(turn_times,z)     # 根据新的定位格坐标生成随从格,颜色是1或者是3(当前shape或已落地的shape)def control_shape(self,event):""" 操控shape """def speed_key(up,down):""" 上下键控制速度 """global speeddirection = event.keysymif(direction == up):speed[0] = 1elif(direction == down):speed[0] = 2else:speed[0] = 0def move_key(key,x):""" 左右移动按键,x为左右移动数 """global turn_timesdirection = event.keysymif(direction == key): self.get_follow_cells_pos(0, turn_times)# 如果遇上墙或者已固定的方块,则不做移动if    game_map[shape_y +  0][shape_x +  0 + x] in [1,4] or \game_map[shape_y + y1][shape_x + x1 + x] in [1,4] or \game_map[shape_y + y2][shape_x + x2 + x] in [1,4] or \game_map[shape_y + y3][shape_x + x3 + x] in [1,4]:passelse:self.shape_move(x,0,3)def clockwise_key(key):""" 顺时针转向按键 """global turn_timesdirection = event.keysymif(direction == key):turn_times = turn_times + 1 # 旋转次数,每旋转一次数值加1if turn_times > 3: # 总共4个转向,大于3就变回0turn_times = 0x4 = seven_shapes[now_and_next_shape[0]][turn_times][0][1]self.follow_cells_bind_to_locator(turn_times - 1, 0)self.shape_move(x4,0,3)def counterclockwise_key(key):""" 逆时针转向按键 """global turn_timesdirection = event.keysymif(direction == key):turn_times = turn_times - 1if turn_times < 0: # 和顺时针相反,小于0就变成3turn_times = 3x4 = seven_shapes[now_and_next_shape[0]][turn_times][0][0]if turn_times < 3:self.follow_cells_bind_to_locator(turn_times + 1, 0)else:self.follow_cells_bind_to_locator(0, 0)self.shape_move(x4,0,3)def pause_key(key):""" 暂停键 """global loop,gloopdirection = event.keysymif(direction == key):loop = 0showinfo('暂停','按确定键继续')loop = 1gloop = window.after(FPS, self.game_loop)def clockwise_key_estimate():""" 顺时针转向判断 """ # 如果遇上墙或者已完成的方块,阻止其转向x = turn_times + 1if x > 3:x = 0x0 = seven_shapes[now_and_next_shape[0]][x][0][1]self.get_follow_cells_pos(0, x)if shape_y + y1 > 0 or shape_y + y2 > 0 or shape_y + y3 > 0:if    game_map[shape_y +  0][shape_x + x0 +  0] in [1,4] or \game_map[shape_y + y1][shape_x + x0 + x1] in [1,4] or \game_map[shape_y + y2][shape_x + x0 + x2] in [1,4] or \game_map[shape_y + y3][shape_x + x0 + x3] in [1,4]:self.shape_move(0,0,3)else:clockwise_key('j')def counterclockwise_estimate():""" 逆时针转向判断 """ # 如果遇上墙或者已完成的方块,阻止其转向x = turn_times - 1if x < 0:x = 3x0 = seven_shapes[now_and_next_shape[0]][x][0][0]self.get_follow_cells_pos(0, x)if shape_y + y1 > 0 or shape_y + y2 > 0 or shape_y + y3 > 0:if          game_map[shape_y +  0][shape_x + x0 +  0] in [1,4] or \game_map[shape_y + y1][shape_x + x0 + x1] in [1,4] or \game_map[shape_y + y2][shape_x + x0 + x2] in [1,4] or \game_map[shape_y + y3][shape_x + x0 + x3] in [1,4]:self.shape_move(0,0,3)else:counterclockwise_key('k')move_key('a', -1)move_key('d',  1)speed_key('w','s')pause_key('space')clockwise_key_estimate()counterclockwise_estimate()self.fresh_cells()def auto_down(self):""" 组件自动下降 """global turn_times,FPSself.get_follow_cells_pos(0, turn_times)# 当前shape下方是墙或者是已完成的shape,那当前shape变成已完成的状态,即值等于1if    game_map[shape_y +  0 + 1][shape_x +  0] in [1,4] or \game_map[shape_y + y1 + 1][shape_x + x1] in [1,4] or \game_map[shape_y + y2 + 1][shape_x + x2] in [1,4] or \game_map[shape_y + y3 + 1][shape_x + x3] in [1,4]:self.shape_move(0,0,1)     # 三个随从格变成1game_map[shape_y][shape_x] = 1 # 定位格也变成1del now_and_next_shape[0]  # 删除当前shape,准备出现下一个self.random_shape()    # 再随机创建一个新的shapecanvas2.delete('all')      # 清除预览图的canvas,创建过多会有BUGself.creat_mini_map()self.follow_cells_bind_to_locator(0,3)speed[0] = 0FPS = self.FPSelse:self.shape_move(0,1,3)def full_del(self):""" 满行清除并新增一行 """global r1,r2,r3,r4r = 0 # 一次消除的行数for i in range(1,self.row_cells-1): # 某行若出现10个1,就删除该行,并在第2行之后再插入一行空的if game_map[i].count(1) == self.col_cells - 2:del game_map[i]r = r + 1new_row = [] # 准备要插入的新行for x in range(0,self.col_cells):new_row.append(0)new_row[ 0] = 4 # 第一格和最后一格是边框new_row[-1] = 4game_map.insert(2,new_row)if   r == 1:       # 本次一共消除了一行r1 = r1 + 1elif r == 2:       # 本次一共消除了二行r2 = r2 + 1elif r == 3:       # 本次一共消除了三行r3 = r3 + 1elif r == 4:       # 本次一共消除了四行r4 = r4 + 1def scoring(self):""" 计分牌 """global scoring_lablescoring_lable = tk.Label(window, text="",font=('Yahei', 12),anchor="ne", justify="left")scoring_lable.place(x= cell_size * self.col_cells + cell_size*2, y = cell_size*16)        def scoring_loop(self):""" 计分更新 """global r5,r6,scoring_labler5 = r1 + r2*2 + r3*3 + r4*4 # 消除的总数r6 = r1 + r2*2*2 + r3*3*3 + r4*4*4 # 计分。消的越多,奖励越多scoring_lable['text'] = "\n" \+ "\n单消: " + str(r1) \+ "\n双消: " + str(r2) \+ "\n三消: " + str(r3) \+ "\n四消: " + str(r4) \+ "\n" \+ "\n总消: " + str(r5) \+ "\n总分: " + str(r6)def game_over(self):""" 游戏结束 """global r1,r2,r3,r4,r5,r6,scoring_lableif game_map[2].count(1) > 0: # 有1出现了就算失败showinfo('Game Over','再来一局')scoring_lable['text'] = ''self.game_start()def game_loop(self):""" 游戏循环刷新 """global window, FPS, gloopcanvas.delete('all') # 清除canvas,不清除的话时间久了有BUGself.fresh_cells()self.auto_down()self.full_del()self.scoring_loop()if   speed[0] == 1:  # 值为1时,速度减慢FPS = self.FPS * 5   elif speed[0] == 2:  # 值为2时,速度加快FPS = 20else:FPS = self.FPSself.game_over()if loop == 1: # 暂停开关gloop = window.after(FPS, self.game_loop)def game_start(self):"""  """global window,speed,FPS,loop,r1,r2,r3,r4,r5,r6r1 = 0 # 消除一行的次数r2 = 0 # 消除二行的次数r3 = 0 # 消除三行的次数r4 = 0 # 消除四行的次数r5 = 0 # 消除的总数r6 = 0 # 计分loop = 0    # 暂停开关。1为开启,0为暂停speed = [0] # 速度调节参数self.create_map(self.row_cells,self.col_cells)self.create_wall()self.random_shape()self.random_shape() # 执行2次,生成2个随机的shapeself.creat_mini_canvas()self.creat_mini_map()self.follow_cells_bind_to_locator(0,3)window.bind('<KeyPress>',   self.control_shape)window.bind('<KeyRelease>', self.Release_speed)FPS = self.FPSself.create_canvas()self.scoring()self.game_loop()def close_w():if loop == 1:window.after_cancel(gloop)window.destroy()window.protocol('WM_DELETE_WINDOW', close_w)window.mainloop()def run_game(self):""" 开启游戏 """global window,cell_size,gloopwindow = tk.Tk()window.focus_force()window.title('Tetris')gloop = NonescreenHeight = window.winfo_screenheight()  # 获取显示区域的高度cell_size = screenHeight / (self.row_cells + 5)win_w_size = self.col_cells * cell_size + self.frame_x*2 + self.win_w_plus win_h_size = self.row_cells * cell_size + self.frame_y*2self.window_center(window,win_w_size,win_h_size)txt_lable = tk.Label(window, text="按 空格键 开始游戏"+"\n"+"\n字母键AD左右移动"+"\n字母键W减速"+"\n字母键S加速下降"+"\n"+"\n字母键J顺时针旋转"+"\n字母键K逆时针旋转"+"\n"+"\n以上所有的字母"+"\n均为小写状态"+"\n"+"\n空格键暂停"+"\n"+"\n"+"\n版本:1.1"+"\n作者:Juni Zhu"+"\n微信:znix1116"font=('Yahei', 12),anchor="ne", justify="left")txt_lable.place(x= cell_size * self.col_cells + cell_size*2, y = cell_size*4)self.game_start()if __name__ == '__main__':Tetris()

用python自带的tkinter做游戏(二)—— 俄罗斯方块 篇相关推荐

  1. 用python自带的tkinter做游戏(一)—— 贪吃蛇 篇

    用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 本人新手,刚自学python满半年,现分享下心得,希望各位老手能指点一二,也希望和我一样的新手能共勉,谢谢~ 大家都知道用python做 ...

  2. 用python的tkinter做游戏(八)—— 实现图片在tkinter中自适应大小(自动匹配窗口)

    用python的tkinter做游戏 系列: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用python自带的tkinter做游戏(二)-- 俄罗斯方块 篇 用python自带的t ...

  3. 用python的tkinter做游戏(七)—— 双人射击游戏Demo(类的应用) 篇

    不知不觉这已经是第七篇文章了,今天来谈谈python中类(class)在游戏中的应用. 老规矩,先展现一下之前的几篇博文: 用python自带的tkinter做游戏(一)-- 贪吃蛇 篇 用pytho ...

  4. 用python的tkinter做游戏(五)—— 魔塔 篇

    好久没更新了,今天继续:用python自带的tkinter做游戏系列的第五弹,魔塔 篇 之前的四篇博文介绍的分别是贪食蛇和俄罗斯方块,推箱子的简易版和推箱子的重制版. 用python自带的tkinte ...

  5. python tkinter计算器实例_使用Python自带GUI tkinter编写一个期权价格计算器

    0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...

  6. python 布莱克舒尔斯_使用Python自带GUI tkinter编写一个期权价格计算器

    0 准备工作 首先,确认环境中有numpy.scipy.stats和tkinter三个功能包.前两个功能包可用于Python的数学计算,比如使用numpy来生成随机数用于Monte Carlo模拟,以 ...

  7. 【Python】如何用pyth做游戏脚本(太简单了吧)

    文章目录 前言 一.开发前景 二.开发流程 3.1.获取窗口句柄,把窗口置顶 3. 2.截取游戏界面,分割图标,图片比较 二.程序核心-图标连接算法(路径寻找) 四.开发总结 五.源码 总结 前言 简 ...

  8. python和cc++哪个适合做游戏_分享一个C++与Python开发的中小型通用游戏服务端框架(跨平台,开源,适合MMORPG游戏)...

    在开发一款游戏项目时,在立项时我们往往会考虑或者纠结很多,比如: 1,对于开发来说:服务端和客户端应该选择什么语言?用什么协议通信才更效率?协议后期如何维护?socket是用长连接还是短连接?tcp还 ...

  9. 用Python自带的tkinter制作一款简易音乐播放器(附工程文件)

    tkinter GUI 界面: 基于tkinter库实现简易可视化界面,并调用pygame等第三方库实现播放歌曲.切换歌曲.歌曲进度调整.批量导入曲库等基本功能:程序设置为单曲循环模式,需要手动切换歌 ...

最新文章

  1. TCP的粘包和拆包及Netty中的解决方案
  2. 框架升级后某个类型所在程序集发生转移,应用还能正常运行吗?
  3. 厉害了!《流浪地球》《疯狂的外星人》票房均破10亿元大关
  4. C#实现鼠标进入按键范围后按键自动窗体内位置移动
  5. SVG 图标和sketch 模版免费网站
  6. 买哪个股票稳赚,三类股票,可以买了
  7. 服务器安全,服务器密码遭篡改
  8. 小米路由器4a千兆版修改sn和关闭电源led灯
  9. Matlab 实现串口助手
  10. 特网云 DirectAdmin 安装SSL
  11. 怎么在服务器解压文件,云服务器怎么解压文件
  12. Tao Admin免费开源后台管理系统
  13. 简介 IndexedDB 及详解 IndexedDB 在实际项目中可能存在的问题与解决方案
  14. 思科认证入门级课程介绍(一)
  15. 6.824:FaRM笔记
  16. tracking里面几种常见图的画法
  17. Qt Quick 如何入门?
  18. Zabbix接口测试文档
  19. python代码批量处理图片resize
  20. 多x多y的origin图_孖记士多 X 阿婆牛杂!广州的两大本土老字号,搞在一起了

热门文章

  1. 总工会招聘计算机及答案,2019 年事业单位工会系统招聘考试《工会基础知识》 真题库及答案【2019版】.pdf...
  2. 火牛单片机rtc时钟配置_RTC 实时时钟驱动 - Linux内核之我的天下 - CSDN博客
  3. lol提示游戏环境异常重启计算机,lol游戏环境异常请重启机器,小编教你lol游戏环境异常请重启机器怎么解决...
  4. 微信纵剑仙界服务器选不了吗,纵剑仙界微信登录版
  5. Python超复古小小小小小游戏项目(终)
  6. python实现汉诺塔游戏
  7. 随笔(四)—2018-2-1-ANU暑期学校-我的矛盾
  8. 【转载】我的算法图书
  9. 以三字游戏为例的模拟项目工程的实现与布局
  10. 信息化的社会,服装行业面临转型压力