基于python的俄罗斯方块小游戏
目录
1.概述
1.1 摘要
本文介绍了一个基于Python语言开发的简单俄罗斯方块小游戏。该游戏使用了pygame库来实现图形界面和声音效果,并通过面向对象的设计方法来组织代码。游戏的主要功能包括方块的随机生成、移动、旋转、消除行以及得分计算等。游戏界面简洁直观,音效增强了游戏体验。此外,游戏还提供了音乐开关和游戏暂停的功能,以增加游戏的可玩性和用户友好性。
1.2 开发背景
俄罗斯方块是一款经典的益智游戏,自上世纪80年代推出以来一直备受欢迎。随着计算机技术的不断发展,越来越多的开发者尝试将这款游戏移植到各种平台上。Python作为一种易学易用且功能强大的编程语言,非常适合用于开发小游戏。同时,pygame
库提供了丰富的图形、声音和事件处理功能,使得开发过程更加便捷。
1.3 开发环境
Windows 11系统
pycharm 2021
python 3.9
1.4 实现功能
1.方块生成与显示:游戏会根据预设的方块类型随机生成一个新的方块,并在游戏界面的顶部显示。
2.方块移动与旋转:玩家可以通过键盘控制方块的左右移动和顺时针/逆时针旋转。
3.消除行与得分:当玩家将一行方块填满时,该行会消除并给玩家加分。消除的行数越多,得分越高。
4.游戏结束条件:当方块堆积到游戏界面的顶部时,游戏结束。
5.音效与音乐:游戏中有方块下落、消除行等音效,同时提供了开启/关闭背景音乐的选项。
6.游戏暂停与恢复:玩家可以随时暂停游戏并在之后恢复,方便玩家在需要时暂时离开。
7.等级与速度:随着游戏的进行,方块的生成速度会逐渐加快,增加游戏难度。
2.代码描述
2.1 模块导入
import pygame from pygame.locals import * from sys import exit import random
分析:
1.pygame
: 一个用于制作游戏的Python库。
2.pygame.locals
: 从pygame
库中导入的常量集合。
3.sys
: Python标准库中的模块,用于与Python解释器交互。
4.random
: 用于生成随机数。
2.2 初始化变量
# 使用pygame.init()初始化pygame库 pygame.init() # 常量 MAX_I = 34 MAX_J = 15 # 定义地图的最大行数和列数 SIZE = 15 # 方块的大小 COLORS = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 255, 255)] # 一个颜色列表,用于表示不同的方块 gameMap = [[0 for j in range(MAX_J + 3)] for i in range(MAX_I + 3)] # 一个二维列表,代表游戏地图。初始化为全0,表示没有方块。 tetrisHeight = 0 # 记录当前塔的高度,即已放下的方块行数 tetrises = [ [ [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] ], [ [1, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0] ], [ [0, 1, 0, 0], [1, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [1, 0, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 0, 1, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] ] # 一个列表,包含7种不同的基本方块形状。每个形状都是一个二维列表,表示方块的各个部分。 # 分数 score = 0 # 等级 level = 1 # 音乐开关 isMusic = True # 游戏暂停 isPause = False
分析:
此代码段只是游戏的初始化部分,没有包含游戏的主循环、方块的移动、旋转、消除行以及得分等逻辑。但从这段代码中,我们可以看出这是一个俄罗斯方块游戏的基础框架,接下来的代码会包含游戏的主体逻辑和界面渲染等部分。
2.3 播放音乐
def playMyMusic(src): if isMusic: sound = pygame.mixer.Sound(src) sound.play()
分析:
这个函数接收一个参数src
,这个参数应该是音乐文件的路径。如果isMusic
变量为True
(意味着音乐功能是开启的),那么函数会加载音乐文件并播放它。这里使用了pygame.mixer.Sound
来加载声音文件,并调用play
方法来播放声音。
2.4 创建方块类
2.4.1 定义私有属性
class Tetris: __i = 0 __j = 0 __color = 0 __nextColor = 0 __nextType = 0
分析:
1.__i
: 方块的行位置。
2.__j
: 方块的列位置。
3.__color
: 方块的颜色。
4.__nextColor
: 下一个方块的颜色。
5.__nextType
: 下一个方块的类型。
2.4.2 构造函数__init__
这个构造函数在创建Tetris
类的实例时被调用。它初始化下一个方块的颜色和类型,并调用createTetris
方法来创建当前的方块。
def __init__(self): self.__nextColor = random.randint(0, 3) + 1 # 一共四种颜色 self.__nextType = random.randint(0, 6) # 一共七种类型 self.createTetris()
2.4.3 createTetris
方法
def createTetris(self): # 根据类型调整一下快出现的位置 if self.__nextType == 0: self.__i = 1 self.__j = 7 else: self.__i = 2 self.__j = 6 self.__color = self.__nextColor # 根据方块模板,放置整个到地图 for i in range(4): for j in range(4): if tetrises[self.__nextType][i][j] == 1: if gameMap[self.__i + i][self.__j + j] == 0: gameMap[self.__i + i][self.__j + j] = self.__color else: print('游戏失败!') exit() return -1 # 产生下一种类型和颜色 self.__nextColor = random.randint(0, 3) + 1 # 一共四种颜色 self.__nextType = random.randint(0, 6) # 一共七种类型 playMyMusic('music/get.wav')
分析:
该段代码的主要功能是创建一个新的俄罗斯方块(Tetris)并将其放置在游戏地图上。以下是代码的主要功能概述:
确定方块的初始位置:根据方块的类型(self.__nextType),代码确定了方块在游戏地图上的初始位置(self.__i 和 self.__j)。不同的方块类型可能需要不同的起始位置。
设置方块的颜色:将方块的颜色设置为下一个可用的颜色(self.__nextColor)。颜色是通过一个随机数生成的,确保每种颜色都有可能被选中。
放置方块到游戏地图:通过两个嵌套的循环,代码遍历方块的每个格子。如果方块模板中的某个格子值为1(表示该格子被填充),则检查游戏地图中对应的位置是否为空(值为0)。如果为空,则在该位置放置方块(即将该位置的值设置为方块的颜色)。如果对应位置已经被其他方块占据,则输出“游戏失败!”并退出游戏。
生成下一种方块类型和颜色:在成功放置当前方块后,代码生成下一种方块的类型和颜色。这是通过随机数生成器实现的,确保每次生成的方块类型和颜色都是随机的。
播放音效:放置方块后,播放一个音效文件('music/get.wav'),为玩家提供反馈。
总的来说,这段代码是俄罗斯方块游戏中生成和放置新方块的关键部分,它负责处理方块的初始位置、颜色、放置逻辑以及生成下一种方块。
def moveDown(self): global gameMap # 判断是否可以下移 for j in range(4): for i in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: # 判断是否到底 if self.__i + i + 1 > MAX_I: return 1 # 判断前面是否有东西 if gameMap[self.__i + i + 1][self.__j + j] != 0: return 1 break # 下移 for j in range(4): for i in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i + 1][self.__j + j] = \ gameMap[self.__i + i + 1][self.__j + j], gameMap[self.__i + i][self.__j + j] self.__i += 1
分析:
该段代码的主要功能是处理俄罗斯方块游戏中方块的下移操作。以下是代码的主要功能概述:
判断是否可以下移:通过两个嵌套的循环遍历方块的每个格子,检查当前方块是否可以下移。首先,它检查方块是否已经到达地图的底部(即是否超过了最大行数MAX_I),如果是,则不能下移,函数返回1。其次,它检查方块的下方是否有其他方块占据,如果有,则不能下移,函数同样返回1。
执行下移操作:如果上述检查都通过,说明方块可以下移,代码将执行下移操作。通过另一个两个嵌套的循环遍历方块的每个格子,找到当前方块的每个格子,并将其与其下方的对应格子交换位置。这实际上是将整个方块向下移动了一行。
更新方块位置:在成功执行下移操作后,代码更新方块的__i属性值,将其增加1,以反映方块在游戏地图中的新位置。
这个函数的目的是处理方块的下移逻辑,包括检查是否可以下移、执行下移操作以及更新方块的位置。它是俄罗斯方块游戏中控制方块移动的重要部分。
def stopTetris(self): global tetrisHeight flag = True for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j] += 10 # 找到第一个颜色方块 if flag: tetrisHeight = MAX_I - self.__i if tetrisHeight < MAX_I - self.__i else tetrisHeight flag = False self.deleteRow()
分析:
该段代码的主要功能是停止当前正在下落的俄罗斯方块,并对其进行处理。以下是代码的主要功能概述:
标记方块停止状态:首先,代码通过遍历当前方块的每个格子,将其颜色值增加10,以标记这些格子属于已经停止下落的方块。这样做通常是为了在游戏地图上区分正在下落和已经停止的方块。
确定方块高度:在标记方块的过程中,代码还查找并记录了当前方块的最高位置(即方块中最顶部的行索引)。这是通过比较当前方块最高行与全局变量tetrisHeight
的值来完成的,确保tetrisHeight
始终存储当前游戏地图中最高的方块高度。
删除完整行:在标记完方块并确定了其高度后,代码调用deleteRow
函数来检查并删除游戏地图中的完整行。这是俄罗斯方块游戏中的一个重要机制,当玩家堆叠的方块形成完整的一行时,该行会被消除,并为玩家带来分数和可能的等级提升。
总的来说,这段代码负责在方块停止下落时对其进行处理,包括标记方块状态、确定方块高度以及触发行消除机制。这是俄罗斯方块游戏中管理方块状态和控制游戏逻辑的关键部分。
def moveLeft(self): # 判断是否能够左移 for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: if self.__j + j - 1 < 0: return 1 if gameMap[self.__i + i][self.__j + j - 1] != 0: return 1 break # 左移 for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i][self.__j + j - 1] = \ gameMap[self.__i + i][self.__j + j - 1], gameMap[self.__i + i][self.__j + j] self.__j -= 1
分析:
该段代码的主要功能是处理俄罗斯方块游戏中方块的左移操作。以下是代码的主要功能概述:
判断是否可以左移:代码首先通过两个嵌套的循环遍历方块的每个格子,检查当前方块是否可以向左移动。它首先检查方块的左侧是否有足够的空间(即是否超出了地图的左边界),如果是,则不能左移,函数返回1。其次,它检查方块的左侧是否有其他方块占据,如果有,则不能左移,函数同样返回1。
执行左移操作:如果上述检查都通过,说明方块可以左移,代码将执行左移操作。通过另一个两个嵌套的循环遍历方块的每个格子,找到当前方块的每个格子,并将其与其左侧的对应格子交换位置。这实际上是将整个方块向左移动了一列。
更新方块位置:在成功执行左移操作后,代码更新方块的__j
属性值,将其减少1,以反映方块在游戏地图中的新位置。
这个函数的目的是处理方块的左移逻辑,包括检查是否可以左移、执行左移操作以及更新方块的位置。它是俄罗斯方块游戏中控制方块移动的重要部分。
def moveRight(self): # 判断是否能右移 for i in range(4): for j in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: if self.__j + j + 1 >= MAX_J: return 1 if gameMap[self.__i + i][self.__j + j + 1] != 0: return 1 break # 右移 for i in range(4): for j in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i][self.__j + j + 1] = \ gameMap[self.__i + i][self.__j + j + 1], gameMap[self.__i + i][self.__j + j] self.__j += 1
分析:
该段代码的主要功能是处理俄罗斯方块游戏中方块的右移操作。以下是代码的主要功能概述:
判断是否可以右移:代码首先通过两个嵌套的循环遍历方块的每个格子,检查当前方块是否可以向右移动。它首先检查方块的右侧是否有足够的空间(即是否超出了地图的右边界),如果是,则不能右移,函数返回1。其次,它检查方块的右侧是否有其他方块占据,如果有,则不能右移,函数同样返回1。
执行右移操作:如果上述检查都通过,说明方块可以右移,代码将执行右移操作。通过另一个两个嵌套的循环遍历方块的每个格子,找到当前方块的每个格子,并将其与其右侧的对应格子交换位置。这实际上是将整个方块向右移动了一列。
更新方块位置:在成功执行右移操作后,代码更新方块的__j
属性值,将其增加1,以反映方块在游戏地图中的新位置。
这个函数的目的是处理方块的右移逻辑,包括检查是否可以右移、执行右移操作以及更新方块的位置。它是俄罗斯方块游戏中控制方块移动的重要部分。与左移操作类似,但方向相反。
def change(self): tMap = [[0 for j in range(4)] for i in range(4)] # 将所有方块顺时针旋转90度赋值到 tMap 中 i = 0 k = 3 while i < 4: for j in range(4): if MAX_I > self.__i + j >= 0 and MAX_J > self.__j + k >= 0 and gameMap[self.__i + j][ self.__j + k] == 0 or \ gameMap[self.__i + j][self.__j + k] == self.__color: tMap[j][k] = gameMap[self.__i + i][self.__j + j] else: return i += 1 k -= 1 # 赋值 for i in range(4): for j in range(4): gameMap[self.__i + i][self.__j + j] = tMap[i][j] playMyMusic('music/change.wav')
分析:
该段代码的主要功能是改变(旋转)当前方块的状态,具体来说是将方块顺时针旋转90度。以下是代码的主要功能概述:
创建临时地图:首先,代码创建了一个名为tMap
的4x4二维列表,用于存储旋转后的方块状态。这个列表初始时所有元素都为0。
顺时针旋转方块:接下来,代码通过两个嵌套的循环来实现方块的顺时针旋转。外部循环控制旋转的行,内部循环控制旋转的列。在循环中,代码检查原始方块(在gameMap
中)的每个格子,如果该格子为空(值为0)或者与当前方块的颜色相同,则将其值复制到tMap
中相应的位置。如果遇到任何障碍物(即非空且颜色不同的格子),旋转操作将立即停止,并返回(不执行任何旋转)。
更新游戏地图:如果旋转操作成功完成(即没有遇到障碍物),代码将tMap
中的新状态复制回gameMap
,从而更新游戏地图中方块的位置和状态。
播放音效:最后,代码调用playMyMusic
函数播放一个名为'change.wav'的音效文件,为玩家提供旋转操作的反馈。
总的来说,这个函数的作用是改变当前方块的方向,将其顺时针旋转90度,并更新游戏地图以反映这一变化。如果旋转过程中遇到障碍物,则不会进行任何更改。
def deleteRow(self): # 找到有方块的最后一行 lastRow = 0 t = False for i in range(3, -1, -1): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color + 10: lastRow = self.__i + i t = True break if t: break for i in range(lastRow, MAX_I - tetrisHeight - 1, -1): for j in range(MAX_J): if gameMap[i][j] == 0: break else: global score score += 10 playMyMusic('music/delete.wav') # 删除行 gameMap.pop(i) gameMap.insert(0, [0 for j in range(MAX_J + 3)]) # 增加等级 global level level += score // 1000 # 再次调用删除行函数操作删行 self.deleteRow()
分析:
该段代码的主要功能是删除俄罗斯方块游戏中由玩家堆叠形成的完整行,并处理相应的得分和等级提升。以下是代码的主要功能概述:
找到有方块的最后一行:代码首先通过两个嵌套的循环遍历当前方块及其下方的行,查找最下方有方块的行(即最顶层的完整行)。它检查每个格子的值是否等于当前方块的颜色值加10(这是标记已停止方块的方式)。一旦找到这样的格子,就记录下其所在的行号,并跳出循环。
删除完整行:接下来,代码从最后一行开始向上遍历游戏地图。对于每一行,它检查该行是否所有格子都非空(即该行是一个完整行)。如果找到一个完整行,它将执行以下操作:
- 增加玩家得分(通常增加10分)。
- 播放一个音效文件('delete.wav'),为玩家提供反馈。
- 从游戏地图中移除该完整行。这是通过将该行从列表中移除(gameMap.pop(i))并在列表的开头插入一个新行(所有格子都为空)来实现的。
- 更新玩家的等级。这是通过将玩家的得分除以1000并向上取整来实现的,每1000分提升一个等级。
递归调用:在删除一行后,代码会再次调用自身(self.deleteRow()
),以检查是否由于删除行而形成了新的完整行。这是必要的,因为在某些情况下,删除一行可能会导致上方的多行也成为完整行。递归调用将继续进行,直到没有新的完整行可以删除为止。
这个函数的目的是处理玩家堆叠形成的完整行,给予玩家相应的得分,并更新游戏状态,包括游戏地图、玩家得分和等级。它是俄罗斯方块游戏中的重要机制之一,用于管理游戏进程和玩家表现。
def nextTetris(self): return self.__nextType, self.__nextColor # 全局变量 screen = '' # 屏幕 gameTetris = Tetris()
分析:
该方法用于返回下一个方块的类型和颜色。它返回两个私有成员变量:self.__nextType
和 self.__nextColor
。这两个变量通常在类的其他部分被设置,表示下一个将要出现的方块的类型(形状)和颜色。
此外,代码还定义了一个全局变量 screen
,它被初始化为空字符串。这个变量可能用于表示游戏的屏幕或显示界面,但在这段代码中并没有进一步的使用或说明。
另一个全局变量 gameTetris
是 Tetris
类的一个实例。Tetris
类可能包含了俄罗斯方块游戏的所有逻辑和状态信息,但由于代码中并没有给出 Tetris
类的具体实现,所以无法确定其具体的功能和结构。通常,这样的类会包含游戏的状态(如当前方块、下一个方块、游戏地图、得分等),以及控制游戏进程的方法(如开始新游戏、移动方块、旋转方块、删除行等)。
2.5 绘制游戏地图
# 绘制游戏地图 def drawMap(): # 画上边框 for i in range(MAX_I - 4): # 画右边 myRect(screen, COLORS[2], [0, i * SIZE, SIZE, SIZE]) # 画左边 myRect(screen, COLORS[2], [(MAX_J + 1) * SIZE, i * SIZE, SIZE, SIZE]) # 画下面 for i in range(MAX_J + 2): myRect(screen, COLORS[2], [i * SIZE, (MAX_I - 4) * SIZE, SIZE, SIZE]) # 给地图涂色 for i in range(4, MAX_I): for j in range(MAX_J): t = gameMap[i][j] - 10 if gameMap[i][j] > 10 else gameMap[i][j] myRect(screen, COLORS[t], [(j + 1) * SIZE, (i - 4) * SIZE, SIZE, SIZE]) # 文字内容,下一个方块 drawMyText(screen, "下一块:", 305, 18, 15, (255, 255, 255)) # 绘制下一块方块 startX = 270 startY = 30 nextType, nextColor = gameTetris.nextTetris() # 显示下一个方块的背景 pygame.draw.rect(screen, COLORS[5], [startX, startY, SIZE * 4, SIZE * 4], 1) mySize = SIZE * 0.8 # 缩小下一个方块大小 # 根据形状,修改方块的位置(居中) if nextType == 0: startX += (SIZE * 4 - mySize) / 2 startY += (SIZE * 4 - mySize * 4) / 2 elif nextType == 2 or nextType == 3: startX += (SIZE * 4 - mySize * 2) / 2 startY += (SIZE * 4 - mySize * 3) / 2 elif nextType == 4 or nextType == 5 or nextType == 6: startX += (SIZE * 4 - mySize * 3) / 2 startY += (SIZE * 4 - mySize * 2) / 2 elif nextType == 1: startX += (SIZE * 4 - mySize * 4) / 2 startY += (SIZE * 4 - mySize * 4) / 2 # 绘制下一个方块 for i in range(4): for j in range(4): # 绘制有图形地方 if tetrises[nextType][i][j] == 1: # 根据不同的 myRect(screen, COLORS[nextColor], [startX + j * mySize, startY + i * mySize, mySize, mySize]) color = (255, 0, 0) # 绘制分数 scoreText = "分数:{}".format(score) drawMyText(screen, scoreText, 300, 120, 15, color) # 等级 drawMyText(screen, "等级:{}".format(level), 300, 140, 15, color) color = (255, 0, 255) # 绘制音乐 textMusic = "音乐:" if isMusic: textMusic += "开" else: textMusic += '关' drawMyText(screen, textMusic, 300, 220, 15, color) # 绘制游戏暂停 textPause = "暂停:" if isPause: textPause += "开" else: textPause += '关' drawMyText(screen, textPause, 300, 240, 15, color) # 绘制键值 color = (130, 205, 255) drawMyText(screen, "↑ 旋转90度", 300, 300, 15, color) drawMyText(screen, "← 向左移动", 300, 320, 15, color) drawMyText(screen, "→ 向右移动", 300, 340, 15, color) drawMyText(screen, "↓ 快速向下", 300, 360, 15, color) # 绘制背景 startY = 410 color = (255, 255, 0) flower = "★" drawMyText(screen, flower, 300, startY + 0, 15, color) flower = "★★" drawMyText(screen, flower, 300, startY + 20, 15, color) flower = "★★★" drawMyText(screen, flower, 300, startY + 40, 15, color)
分析:
该段代码的主要功能是绘制俄罗斯方块游戏的游戏地图和界面上的其他元素。具体来说,它执行以下操作:
绘制游戏地图边框:首先,代码通过循环绘制了游戏地图的上、下、左、右四个边框。这些边框使用了一个特定的颜色(COLORS[2]
),并且根据游戏地图的尺寸(MAX_I
和 MAX_J
)来确定位置和大小。
填充游戏地图:接下来,代码遍历游戏地图的每个格子,并根据格子的值(gameMap[i][j]
)来填充相应的颜色。如果格子的值大于10,则减去10来获取正确的颜色索引,因为游戏可能使用额外的值来表示特殊状态(如方块停止下落)。
绘制下一个方块:代码从gameTetris
实例中获取下一个方块的类型(nextType
)和颜色(nextColor
)。然后,它绘制一个表示下一个方块的背景框,并根据下一个方块的形状调整其内部小方块的位置。不同的方块类型有不同的形状,因此代码通过条件语句来确定如何绘制每个小方块。
绘制文本信息:代码还绘制了界面上的文本信息,包括“下一块:”来提示玩家下一个方块即将出现,以及玩家的“分数”和“等级”。这些信息使用特定的颜色和字体大小绘制在屏幕上。
绘制音乐图标:最后,代码绘制了一个表示音乐的图标(尽管代码中并没有实际绘制图标的操作,只是预留了位置)。
2.6 游戏初始化
# 游戏初始化 def initGame(): # 将地图倒数对大行全部赋值为-1 for j in range(MAX_J): gameMap[MAX_I][j] = -1 # 将地图倒数最大列全部赋值为-1 for i in range(MAX_I): gameMap[i][MAX_J] = -1 # 分数初始化 score = 0 # 加载背景音乐文件 pygame.mixer.music.load('music/bgm.mp3') if isMusic: # 播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒) pygame.mixer.music.play(-1, 0.0)
分析:
该段代码的主要功能是初始化俄罗斯方块游戏。具体来说,它执行了以下操作:
设置游戏地图的边缘:代码通过两个循环将游戏地图的边缘格子(即最底部和最右侧的格子)设置为-1。这通常表示这些格子不属于游戏区域,或者不可用于放置方块。
初始化分数:将玩家的分数(score
)设置为0,表示游戏开始时玩家没有获得任何分数。
加载并播放背景音乐:代码使用pygame
模块的mixer
组件加载一个名为'bgm.mp3'的背景音乐文件。如果isMusic
变量为True
(表示允许播放音乐),则音乐将开始播放,并且设置为无限循环(-1表示循环播放),从音乐的开头(0.0秒处)开始播放。
这段代码是俄罗斯方块游戏初始化过程的一部分,确保游戏在开始时有正确的设置和配置,包括游戏地图的边缘定义、玩家分数的初始值,以及背景音乐的加载和播放。这些初始化步骤是游戏能够正常运行所必需的。
2.7 绘制有边框矩形
def myRect(screen, colorRect, pos, lineColor=COLORS[0], bold=1): pygame.draw.rect(screen, colorRect, pos) pygame.draw.rect(screen, lineColor, pos, bold)
分析:
该函数 myRect
的主要功能是在游戏屏幕(screen
)上绘制一个矩形。它接受以下参数:
screen
:这是一个pygame的Surface对象,通常代表游戏的主屏幕或窗口。colorRect
:这是矩形的填充颜色,通常是一个RGB元组,如(255, 255, 255)
表示白色。pos
:这是一个包含矩形位置和大小的元组,格式为(x, y, width, height)
。lineColor
:这是一个可选参数,用于指定矩形边框的颜色。如果没有提供,则默认为COLORS[0]
。bold
:这是一个可选参数,用于指定矩形边框的宽度。如果没有提供,则默认为1。
函数内部调用了两次 pygame.draw.rect
方法。第一次调用绘制了填充了颜色的矩形,第二次调用绘制了矩形的边框。通过这种方式,myRect
函数能够在一个步骤中同时绘制矩形的填充和边框。
需要注意的是,pygame.draw.rect
方法的第四个参数通常用于指定线条的粗细(边框宽度)。在这个函数中,当 bold
参数被传递时,它会被用作 pygame.draw.rect
方法的第四个参数。如果 bold
参数未被传递或设置为1,则边框宽度将默认为1。
2.8 绘制我的文字
# 绘制我的文字 def drawMyText(screen, text, x, y, bold, fColor, bColor=None): # 通过字体文件获得字体对象 fontObj = pygame.font.Font('font/SIMYOU.TTF', bold) # 配置要显示的文字 textSurfaceObj = fontObj.render(text, False, fColor, bColor) # 获得要显示的对象的rect textRectObj = textSurfaceObj.get_rect() # 设置显示对象的坐标 textRectObj.center = (x, y) # 绘制字体 screen.blit(textSurfaceObj, textRectObj)
分析:
获取字体对象:使用pygame.font.Font
函数和一个指定的字体文件(在这里是'font/SIMYOU.TTF')以及字体大小(由bold
参数指定)来创建一个字体对象。
渲染文本:使用字体对象的render
方法来创建一个文本Surface对象。这个方法需要文本字符串、一个布尔值(在这里是False
,表示文本不是抗锯齿的)、前景色和可选的背景色。
获取文本矩形:通过调用文本Surface对象的get_rect
方法来获取一个矩形对象,这个矩形代表了文本在屏幕上占据的空间。
设置文本坐标:将文本矩形的中心坐标设置为函数参数中指定的x
和y
坐标。
绘制文本:最后,使用screen.blit
方法将文本Surface对象绘制到屏幕上的指定位置。
2.9 游戏主体
def my_game(): # 得到屏幕 global screen, isMusic, isPause screen = pygame.display.set_mode((350, 465)) # 设置标题 pygame.display.set_caption('俄罗斯方块') # 初始化游戏 initGame() # 用于控制下落速度 count = 0 while True: if count == 3 + level * 2: if not isPause: # 只有暂停是 False 才能下移 if gameTetris.moveDown() == 1: gameTetris.stopTetris() gameTetris.createTetris() count = 0 # 事件判断 for event in pygame.event.get(): if event.type == pygame.QUIT: # 停止播放背景音乐 pygame.mixer.music.stop() pygame.quit() exit() elif event.type == pygame.MOUSEBUTTONDOWN: # 控制音乐开关 if 270 < event.pos[0] < 330 and 210 < event.pos[1] < 225: # 处理音乐开关 if isMusic: # 停止播放背景音乐 pygame.mixer.music.stop() else: # 播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒) pygame.mixer.music.play(-1, 0.0) isMusic = not isMusic # 处理游戏暂停开关 if 270 < event.pos[0] < 327 and 233 < event.pos[1] < 248: isPause = not isPause if isPause: pygame.display.set_caption('俄罗斯方块:已暂停') else: pygame.display.set_caption('俄罗斯方块') elif event.type == pygame.KEYDOWN: if event.key == K_UP: gameTetris.change() keyPressList = pygame.key.get_pressed() if keyPressList[K_DOWN]: gameTetris.moveDown() elif keyPressList[K_LEFT]: gameTetris.moveLeft() elif keyPressList[K_RIGHT]: gameTetris.moveRight() screen.fill((0, 0, 0)) drawMap() pygame.display.update() count += 1 pygame.time.Clock().tick(10)
分析:
该函数 my_game
是俄罗斯方块游戏的主函数,它负责初始化游戏、处理用户输入和事件、更新游戏状态以及渲染游戏画面。以下是该函数的主要功能:
初始化游戏:首先,它设置了一个全局的屏幕对象 screen
和一些全局变量(如 isMusic
和 isPause
)。然后,它调用 initGame
函数来初始化游戏,包括设置游戏地图的边缘、初始化分数以及加载并可能播放背景音乐。
控制下落速度:通过一个计数器 count
来控制方块的下落速度。当计数器达到一定的值时(由当前游戏级别 level
决定),如果游戏没有暂停(isPause
为 False
),则尝试将当前方块下移一格。如果下移成功,则停止当前方块的下落并创建一个新的方块。
事件处理:函数使用一个循环来处理pygame事件。这些事件可能包括用户点击关闭按钮(退出游戏)、点击鼠标(控制音乐的播放和暂停以及游戏的暂停和继续)、以及按下键盘键(旋转方块)。
- 当用户点击关闭按钮时,停止播放背景音乐并退出游戏。
- 当用户在特定的屏幕区域内点击鼠标时,切换音乐的播放状态或游戏的暂停状态。
- 当用户按下键盘上的上箭头键时,旋转当前方块。
键盘输入处理:函数还检查是否有键盘键被按下,并据此移动方块。如果下箭头键被持续按下,方块会持续下移;如果左或右箭头键被按下,方块会向左或向右移动。
渲染游戏画面:最后,函数使用黑色填充屏幕,并调用 drawMap
函数来绘制游戏地图和方块。这一步通常在每次循环的末尾进行,以确保游戏画面能够实时更新。
2.10 主程序运行
if __name__ == '__main__': my_game()
详细分析一下:
在Python中,if __name__ == '__main__':
是一个常见的代码块,用于判断当前脚本是否作为主程序运行。如果当前脚本是作为主程序直接运行的,而不是被其他脚本导入作为一个模块,那么 __name__
这个内置变量就会被设置为 '__main__'
。
在你提供的代码片段中,if __name__ == '__main__':
这一行之后紧跟着调用了 my_game()
函数。这意味着,只有当这个脚本被直接运行时,my_game()
函数才会被执行。如果这个脚本被其他脚本导入作为一个模块,那么 my_game()
函数将不会自动执行。
这样做的好处是,你可以在一个文件中既定义函数和类(用于在其他脚本中导入和使用),又包含一些只在当前脚本直接运行时才需要执行的代码(比如启动游戏的主函数)。
总结来说,if __name__ == '__main__':
这一行用于区分直接运行和模块导入两种情况,并据此决定是否执行某些代码。在你提供的代码中,它确保了 my_game()
函数只有在该脚本被直接运行时才会被执行。
3.运行效果
4.注意事项
1.所有的代码放到同一个py文件中
2.在数据可视化过程中,难免会需要导入不同的库,这里建议使用WIN+R打开命令提示符,并使用国内镜像安装库(下载快),比如安装wordcloud库是,使用下列代码:
pip install wordcloud -i https://pypi.tuna.tsinghua.edu.cn/simple/
这里用的是清华的镜像,其他镜像:
企业贡献:
(1)网易开源镜像站:http://mirrors.163.com/
(2)华为开源镜像站:https://mirrors.huaweicloud.com/
(3)阿里开源镜像站:https://developer.aliyun.com/mirror/
大学教学:
(1)清华大学开源镜像站:https://mirrors.tuna.tsinghua.edu.cn/
(2)浙江大学开源镜像站:http://mirrors.zju.edu.cn/
(3)东北大学开源镜像站:http://mirror.neu.edu.cn/
安装库的时候,建议更新一下你的pip库,命令:
python -m pip install --upgrade pip
游戏窗口的创建和初始化:你需要使用Pygame库创建一个游戏窗口,并设置其宽度、高度、标题等属性。你还需要初始化一些Pygame模块,如pygame.init()
来初始化所有导入的Pygame模块。
游戏循环:游戏的核心是一个循环,通常称为游戏主循环或事件循环。在这个循环中,你需要处理用户输入(如键盘按键、鼠标点击等)、更新游戏状态(如方块的位置、得分等)和绘制游戏界面。
方块的绘制和移动:你需要定义方块的形状和颜色,并在游戏窗口中绘制它们。你还需要实现方块的移动功能,包括旋转、左右移动和下落。你可能需要使用键盘事件来捕捉用户的输入,并根据输入更新方块的位置。
消除行和得分:当方块下落并填满一行时,你需要实现消除行的功能,并相应地更新得分。这可能需要你检查每一行是否已经填满,如果是,则消除该行并增加得分。
游戏结束条件:你需要设置游戏结束的条件,如当方块堆积到游戏窗口的顶部时,游戏结束。在游戏结束时,你可以显示一条消息或弹出一个对话框来通知玩家。
5.附录源码
import pygame from pygame.locals import * from sys import exit import random # 使用pygame.init()初始化pygame库 pygame.init() # 常量 MAX_I = 34 MAX_J = 15 # 定义地图的最大行数和列数 SIZE = 15 # 方块的大小 COLORS = [(0, 0, 0), (255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0), (255, 255, 255)] # 一个颜色列表,用于表示不同的方块 gameMap = [[0 for j in range(MAX_J + 3)] for i in range(MAX_I + 3)] # 一个二维列表,代表游戏地图。初始化为全0,表示没有方块。 tetrisHeight = 0 # 记录当前塔的高度,即已放下的方块行数 tetrises = [ [ [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] ], [ [1, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 0], [0, 0, 0, 0] ], [ [0, 1, 0, 0], [1, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [1, 0, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 0, 1, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] ] # 一个列表,包含7种不同的基本方块形状。每个形状都是一个二维列表,表示方块的各个部分。 # 分数 score = 0 # 等级 level = 1 # 音乐开关 isMusic = True # 游戏暂停 isPause = False # 播放音乐 def playMyMusic(src): if isMusic: sound = pygame.mixer.Sound(src) sound.play() # 方块类 class Tetris: __i = 0 __j = 0 __color = 0 __nextColor = 0 __nextType = 0 def __init__(self): self.__nextColor = random.randint(0, 3) + 1 # 一共四种颜色 self.__nextType = random.randint(0, 6) # 一共七种类型 self.createTetris() def createTetris(self): # 根据类型调整一下快出现的位置 if self.__nextType == 0: self.__i = 1 self.__j = 7 else: self.__i = 2 self.__j = 6 self.__color = self.__nextColor # 根据方块模板,放置整个到地图 for i in range(4): for j in range(4): if tetrises[self.__nextType][i][j] == 1: if gameMap[self.__i + i][self.__j + j] == 0: gameMap[self.__i + i][self.__j + j] = self.__color else: print('游戏失败!') exit() return -1 # 产生下一种类型和颜色 self.__nextColor = random.randint(0, 3) + 1 # 一共四种颜色 self.__nextType = random.randint(0, 6) # 一共七种类型 playMyMusic('music/get.wav') def moveDown(self): global gameMap # 判断是否可以下移 for j in range(4): for i in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: # 判断是否到底 if self.__i + i + 1 > MAX_I: return 1 # 判断前面是否有东西 if gameMap[self.__i + i + 1][self.__j + j] != 0: return 1 break # 下移 for j in range(4): for i in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i + 1][self.__j + j] = \ gameMap[self.__i + i + 1][self.__j + j], gameMap[self.__i + i][self.__j + j] self.__i += 1 def stopTetris(self): global tetrisHeight flag = True for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j] += 10 # 找到第一个颜色方块 if flag: tetrisHeight = MAX_I - self.__i if tetrisHeight < MAX_I - self.__i else tetrisHeight flag = False self.deleteRow() def moveLeft(self): # 判断是否能够左移 for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: if self.__j + j - 1 < 0: return 1 if gameMap[self.__i + i][self.__j + j - 1] != 0: return 1 break # 左移 for i in range(4): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i][self.__j + j - 1] = \ gameMap[self.__i + i][self.__j + j - 1], gameMap[self.__i + i][self.__j + j] self.__j -= 1 def moveRight(self): # 判断是否能右移 for i in range(4): for j in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: if self.__j + j + 1 >= MAX_J: return 1 if gameMap[self.__i + i][self.__j + j + 1] != 0: return 1 break # 右移 for i in range(4): for j in range(3, -1, -1): if gameMap[self.__i + i][self.__j + j] == self.__color: gameMap[self.__i + i][self.__j + j], gameMap[self.__i + i][self.__j + j + 1] = \ gameMap[self.__i + i][self.__j + j + 1], gameMap[self.__i + i][self.__j + j] self.__j += 1 def change(self): tMap = [[0 for j in range(4)] for i in range(4)] # 将所有方块顺时针旋转90度赋值到 tMap 中 i = 0 k = 3 while i < 4: for j in range(4): if MAX_I > self.__i + j >= 0 and MAX_J > self.__j + k >= 0 and gameMap[self.__i + j][ self.__j + k] == 0 or \ gameMap[self.__i + j][self.__j + k] == self.__color: tMap[j][k] = gameMap[self.__i + i][self.__j + j] else: return i += 1 k -= 1 # 赋值 for i in range(4): for j in range(4): gameMap[self.__i + i][self.__j + j] = tMap[i][j] playMyMusic('music/change.wav') def deleteRow(self): # 找到有方块的最后一行 lastRow = 0 t = False for i in range(3, -1, -1): for j in range(4): if gameMap[self.__i + i][self.__j + j] == self.__color + 10: lastRow = self.__i + i t = True break if t: break for i in range(lastRow, MAX_I - tetrisHeight - 1, -1): for j in range(MAX_J): if gameMap[i][j] == 0: break else: global score score += 10 playMyMusic('music/delete.wav') # 删除行 gameMap.pop(i) gameMap.insert(0, [0 for j in range(MAX_J + 3)]) # 增加等级 global level level += score // 1000 # 再次调用删除行函数操作删行 self.deleteRow() def nextTetris(self): return self.__nextType, self.__nextColor # 全局变量 screen = '' # 屏幕 gameTetris = Tetris() # 绘制游戏地图 def drawMap(): # 画上边框 for i in range(MAX_I - 4): # 画右边 myRect(screen, COLORS[2], [0, i * SIZE, SIZE, SIZE]) # 画左边 myRect(screen, COLORS[2], [(MAX_J + 1) * SIZE, i * SIZE, SIZE, SIZE]) # 画下面 for i in range(MAX_J + 2): myRect(screen, COLORS[2], [i * SIZE, (MAX_I - 4) * SIZE, SIZE, SIZE]) # 给地图涂色 for i in range(4, MAX_I): for j in range(MAX_J): t = gameMap[i][j] - 10 if gameMap[i][j] > 10 else gameMap[i][j] myRect(screen, COLORS[t], [(j + 1) * SIZE, (i - 4) * SIZE, SIZE, SIZE]) # 文字内容,下一个方块 drawMyText(screen, "下一块:", 305, 18, 15, (255, 255, 255)) # 绘制下一块方块 startX = 270 startY = 30 nextType, nextColor = gameTetris.nextTetris() # 显示下一个方块的背景 pygame.draw.rect(screen, COLORS[5], [startX, startY, SIZE * 4, SIZE * 4], 1) mySize = SIZE * 0.8 # 缩小下一个方块大小 # 根据形状,修改方块的位置(居中) if nextType == 0: startX += (SIZE * 4 - mySize) / 2 startY += (SIZE * 4 - mySize * 4) / 2 elif nextType == 2 or nextType == 3: startX += (SIZE * 4 - mySize * 2) / 2 startY += (SIZE * 4 - mySize * 3) / 2 elif nextType == 4 or nextType == 5 or nextType == 6: startX += (SIZE * 4 - mySize * 3) / 2 startY += (SIZE * 4 - mySize * 2) / 2 elif nextType == 1: startX += (SIZE * 4 - mySize * 4) / 2 startY += (SIZE * 4 - mySize * 4) / 2 # 绘制下一个方块 for i in range(4): for j in range(4): # 绘制有图形地方 if tetrises[nextType][i][j] == 1: # 根据不同的 myRect(screen, COLORS[nextColor], [startX + j * mySize, startY + i * mySize, mySize, mySize]) color = (255, 0, 0) # 绘制分数 scoreText = "分数:{}".format(score) drawMyText(screen, scoreText, 300, 120, 15, color) # 等级 drawMyText(screen, "等级:{}".format(level), 300, 140, 15, color) color = (255, 0, 255) # 绘制音乐 textMusic = "音乐:" if isMusic: textMusic += "开" else: textMusic += '关' drawMyText(screen, textMusic, 300, 220, 15, color) # 绘制游戏暂停 textPause = "暂停:" if isPause: textPause += "开" else: textPause += '关' drawMyText(screen, textPause, 300, 240, 15, color) # 绘制键值 color = (130, 205, 255) drawMyText(screen, "↑ 旋转90度", 300, 300, 15, color) drawMyText(screen, "← 向左移动", 300, 320, 15, color) drawMyText(screen, "→ 向右移动", 300, 340, 15, color) drawMyText(screen, "↓ 快速向下", 300, 360, 15, color) # 绘制背景 startY = 410 color = (255, 255, 0) flower = "★" drawMyText(screen, flower, 300, startY + 0, 15, color) flower = "★★" drawMyText(screen, flower, 300, startY + 20, 15, color) flower = "★★★" drawMyText(screen, flower, 300, startY + 40, 15, color) # 游戏初始化 def initGame(): # 将地图倒数对大行全部赋值为-1 for j in range(MAX_J): gameMap[MAX_I][j] = -1 # 将地图倒数最大列全部赋值为-1 for i in range(MAX_I): gameMap[i][MAX_J] = -1 # 分数初始化 score = 0 # 加载背景音乐文件 pygame.mixer.music.load('music/bgm.mp3') if isMusic: # 播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒) pygame.mixer.music.play(-1, 0.0) # 绘制有边框矩形 def myRect(screen, colorRect, pos, lineColor=COLORS[0], bold=1): pygame.draw.rect(screen, colorRect, pos) pygame.draw.rect(screen, lineColor, pos, bold) # 绘制我的文字 def drawMyText(screen, text, x, y, bold, fColor, bColor=None): # 通过字体文件获得字体对象 fontObj = pygame.font.Font('font/SIMYOU.TTF', bold) # 配置要显示的文字 textSurfaceObj = fontObj.render(text, False, fColor, bColor) # 获得要显示的对象的rect textRectObj = textSurfaceObj.get_rect() # 设置显示对象的坐标 textRectObj.center = (x, y) # 绘制字体 screen.blit(textSurfaceObj, textRectObj) # 游戏主体 def my_game(): # 得到屏幕 global screen, isMusic, isPause screen = pygame.display.set_mode((350, 465)) # 设置标题 pygame.display.set_caption('俄罗斯方块') # 初始化游戏 initGame() # 用于控制下落速度 count = 0 while True: if count == 3 + level * 2: if not isPause: # 只有暂停是 False 才能下移 if gameTetris.moveDown() == 1: gameTetris.stopTetris() gameTetris.createTetris() count = 0 # 事件判断 for event in pygame.event.get(): if event.type == pygame.QUIT: # 停止播放背景音乐 pygame.mixer.music.stop() pygame.quit() exit() elif event.type == pygame.MOUSEBUTTONDOWN: # 控制音乐开关 if 270 < event.pos[0] < 330 and 210 < event.pos[1] < 225: # 处理音乐开关 if isMusic: # 停止播放背景音乐 pygame.mixer.music.stop() else: # 播放背景音乐,第一个参数为播放的次数(-1表示无限循环),第二个参数是设置播放的起点(单位为秒) pygame.mixer.music.play(-1, 0.0) isMusic = not isMusic # 处理游戏暂停开关 if 270 < event.pos[0] < 327 and 233 < event.pos[1] < 248: isPause = not isPause if isPause: pygame.display.set_caption('俄罗斯方块:已暂停') else: pygame.display.set_caption('俄罗斯方块') elif event.type == pygame.KEYDOWN: if event.key == K_UP: gameTetris.change() keyPressList = pygame.key.get_pressed() if keyPressList[K_DOWN]: gameTetris.moveDown() elif keyPressList[K_LEFT]: gameTetris.moveLeft() elif keyPressList[K_RIGHT]: gameTetris.moveRight() screen.fill((0, 0, 0)) drawMap() pygame.display.update() count += 1 pygame.time.Clock().tick(10) if __name__ == '__main__': my_game()
注意:由于源码中有音乐、图片等不方便放在博客里,需要可私信或者评论区留言。