需求分析+设计
用Python做一个传统的扫雷游戏。
- 游戏界面和操作
- 游戏界面:一系列游戏图标 + 时间和剩余旗子数 + (背景音乐)。
- 操作:开始游戏 / 重新开始游戏 + 退出游戏 + 打开格子 + 标记格子 + 显示格子信息。
游戏状态转化逻辑、游戏赢输规则和游戏界面和事件检测在主程序(main.py)中实现。游戏界面和事件检测主要用pygame设计。
设计格子(Grid)类和棋盘(ChessBoard)类用以封装格子和棋盘的数据和操作。
编码实现
文件目录结构(python环境与项目文件同级)
Mine-clearing game
|–data \
|–font \---------------------------------存放游戏界面显示的字体
|–pictures \----------------------------存放游戏界面显示的图片
|–include \
|–Mine_clear_class.py-------------存放自定义的Python类
|–main.py---------------------------------程序的入口文件
Mine_clear_class.py
# _*_ coding : utf-8 _*_
from enum import Enum
import random
# 坐标说明,本文件内坐标均是“矩阵坐标”即在矩阵中(x,y)表示x行y列。
class GridState(Enum): # 格子状态
normal = 1 # 初始化的状态,未被打开,未被标记
opened = 2 # 已打开的格子
flag = 3 # 标记为旗子
ask = 4 # 标记为问号
class GameStae(Enum): # 游戏状态
ready = 1, # 不稳定,会立即被start状态取代
start = 2,
lose = 3,
win = 4
class Grid: # 一个小格子
def __init__(self, x=0, y=0, is_mine=False):
self.x = x
self.y = y
self.is_mine = is_mine
self.around_mine_count = 0 # around_mine_count
self.state = GridState.normal
def __repr__(self):
re_str = "该格子的矩阵坐标: (" + str(self.y)+','+str(self.x)+')'+'\n'
re_str += "该格子的状态: " + str(self.state)+'\n'
re_str += "该格子是否是地雷:" + str(self.is_mine)+'\n'
re_str += "该格子周围地雷数:" + str(self.around_mine_count)
return re_str
def get_x(self): return self.x
def set_x(self, x): self.x = x
def get_y(self): return self.y
def set_y(self, y): self.y = y
def get_is_mine(self): return self.is_mine
def set_is_mine(self, is_mine): self.is_mine = is_mine
def get_around_mine_count(self): return self.around_mine_count
def set_around_mine_count(self, around_mine_count): self.around_mine_count = around_mine_count
def get_state(self): return self.state
def set_state(self, state): self.state = state
class ChessBoard: # 游戏棋盘和一些影响棋盘的操作
def __init__(self, WIDTH=10, HEIGHT=10, MINE_COUNT=10):
# 生成一个棋盘
self.WIDTH = WIDTH
self.HEIGHT = HEIGHT
self.MINE_COUNT = MINE_COUNT
self.grids = [[Grid(i, j) for i in range(WIDTH)]for j in range(HEIGHT)]
# 放置地雷
# random.sample 返回一个MINE_COUNT长的列表,内存放k个随机的唯一的元素。
# “//”代表取整
for i in random.sample(range(WIDTH * HEIGHT), MINE_COUNT):
x = i // WIDTH
y = i % WIDTH
self.grids[x][y].set_is_mine(True)
# 计算周围地雷数,即地雷周围每一个格子的around mine count数加一
for m in range(max(0, x-1), min(WIDTH, x+2)):
for n in range(max(0, y-1), min(HEIGHT, y+2)):
self.grids[m][n].set_around_mine_count(self.grids[m][n].get_around_mine_count()+1)
def get_all_grids(self): return self.grids
def get_one_grid(self, x, y): return self.grids[x][y]
# 后续需要依据游戏模式(简单、困难之类)才能更改这些变量。
def get_WIDTH(self): return self.WIDTH
def get_HEIGHT(self): return self.HEIGHT
def get_MINE_COUNT(self): return self.MINE_COUNT
def opened_grid(self, x, y): # 打开格子
self.grids[x][y].set_state(GridState.opened)
if self.grids[x][y].get_is_mine() == True: # 踩到雷了
return False
# 没踩到,打开周围3*3格子
if self.grids[x][y].get_around_mine_count() == 0:
for i in range(max(0, x-1), min(self.WIDTH, x+2)):
for j in range(max(0, y-1), min(self.HEIGHT, y+2)):
if self.grids[i][j].get_state() == GridState.normal:
self.opened_grid(i, j)
return True
def mark_grid(self, x, y): # 标记格子
if (self.grids[x][y].get_state() == GridState.normal):
self.grids[x][y].set_state(GridState.flag)
elif (self.grids[x][y].get_state() == GridState.flag):
self.grids[x][y].set_state(GridState.ask)
elif (self.grids[x][y].get_state() == GridState.ask):
self.grids[x][y].set_state(GridState.normal)
# 赢的方式1.打开所有非雷的格子
# 赢的方式2.标记所有雷的格子
# (逻辑判断均在主程序中实现)
def get_opened_grid_count(self): # 统计所有打开的格子数
res_count = 0
for i in range(self.WIDTH):
for j in range(self.HEIGHT):
if self.grids[i][j].get_state() == GridState.opened:
res_count += 1
return res_count
def get_flag_mine_count(self): # 统计所有标记对的格子数
# \ 是下一行接到这一行的意思,可以用括号代替。
res_count = 0
for i in range(self.WIDTH):
for j in range(self.HEIGHT):
if self.grids[i][j].get_state() == GridState.flag \
and self.grids[i][j].get_is_mine() == True:
res_count += 1
return res_count
Mine_clear_class.py文件总结:此文件较为独立,主要涉及到Grid类和ChessBoard类。在该文件内没有定义常量或者其他参数,所有变量(如WIDTH、HEIGHT等)均在实例化类时作为参数传进去,因此无需考虑多个文件间数据一致性问题。当设计“游戏难度”功能时,可以删除(del)类后,重新实例化。
编码后先进行简单的验证,避免运行主程序时出现大量语法错误。
如下实例化一个例子。
grid = Grid(0,1,True)
print(grid)
grid.set_around_mine_count(3)
print(grid)
'''
输出
该格子的矩阵坐标: (1,0)
该格子的状态: GridState.normal
该格子是否是地雷:True
该格子周围地雷数:0
该格子的矩阵坐标: (1,0)
该格子的状态: GridState.normal
该格子是否是地雷:True
该格子周围地雷数:3
'''
基础数据(data文件夹)
下一个较为好实现任务是建立data文件夹
|–data \
|–font \---------------------------------存放游戏界面显示的字体
|–pictures \----------------------------存放游戏界面显示的图片
这里先没有实现背景音乐功能,故先不放音效。
具体文件放在gitee和github了。
main.py(程序入口)
先捋一下这里要实现的功能:
- 呈现游戏界面(pygame模块) 【具体实现起来需要注意pygame呈现坐标系的特点】
- 事件检测(pygame.event模块) 如:鼠标左/右键等
- 游戏逻辑处理(if else) 如:鼠标左键对应的打开动作、点击笑脸对应的重新开始动作,和游戏输赢的判定。
- 游戏数据导入(图片和字体) 其实这个不想放在这里,想要单独拎出来一个文件存放导入的数据,但是可能比较复杂,故先放在一起了。
需要注意的一点编码与测试同行。
首先我们需要一个能跑起来进行测试的框架(像下面这样,切记不要贪多,一点一点慢慢加)。
import pygame
FIRST_SCREEN_WIDTH = 720
FIRST_SCREEN_HEIGHT = 720
if __name__ == "__main__":
pygame.init()
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
menu_screen.fill((255,255,255)) # 白色画布1(开始界面用的)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit("ok")
pygame.display.update()
运行后弹出如下图片,点击右上键x,就会进入到我们设计的退出逻辑中。
之后向源代码中加入细节。
import pygame
from pygame.locals import *
# 基础屏幕大小、格子块大小等全局信息。
FIRST_SCREEN_WIDTH = 720
FIRST_SCREEN_HEIGHT = 720
SIZE = 20
def Load_image():
global first_bac
first_bac= pygame.image.load('Mine-clearing game/data/pictures/first_bac.png').convert() # 加载并渲染
first_bac= pygame.transform.smoothscale(first_bac, (FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT)) # 平滑缩放(这里可以用学过的最近邻?或双线性)
def Load_font():
global first_font, first_font_size
first_font_size = FIRST_SCREEN_WIDTH//10
# 'C:/Windows/Fonts/xxx.ttf' # (系统自带字体文件夹,xxx是其名字)
first_font_path = 'Mine-clearing game/data/font/云峰静龙行书.ttf'
# pygame.font.Font参数(字体位置, 字体大小)
first_font = pygame.font.Font(first_font_path, first_font_size)
def Set_clock():
global clock, FPS
FPS = 60
# 获取一个Clock对象,限制游戏的运行速度(clock.tick(FPS)这样用)
clock = pygame.time.Clock() # 类似于sleep
def Set_colors():
global pink_color
pink_color = (245, 140, 190) # (定义RGB三元组)这个还可以在调淡色一点
def My_init():
Load_image()
Load_font()
Set_clock()
Set_colors()
def Print_text(screen, font, x, y, text, fcolor=(0, 0, 0)):
# font.render参数 (文本,抗锯齿否,字体颜色,背景颜色)
imgText = font.render(text, True, fcolor)
screen.blit(imgText, (x, y))
def Set_easy_mode():
print("so easy")
def Set_medium_mode():
print("so normal")
def Set_hard_mode():
print("so hard")
# 简单介绍一下模块内各种函数的作用,具体的可以用时现查
# 把各种加载操作放到别出去,看代码的时候可以直接缩小他们。
if __name__ == "__main__": # main
pygame.init() # 检查硬件调用接口、基础功能是否有问题(模糊一点就说初始化)
# pygame.display.set_mode参数(分辨率, 显示模式=0, 颜色位数=0, 不知道=0, 不知道=0)
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
My_init() # 自定义的初始化函数
pygame.display.set_caption("扫雷") # 设置当前窗口标题
# screen.blit参数(图像/字,位置,不知道,不知道)
# 第三个参数好像与动态图有关,可能其他程序会用得到。
menu_screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
# 之后要用这些参数定位鼠标动作,所以单起一个“稍短”的名字
(text_width, text_height) = first_font.size("游戏难度")
text_x = int((FIRST_SCREEN_WIDTH-text_width)/2)
base_text_y = int(FIRST_SCREEN_HEIGHT/6)
Print_text(menu_screen, first_font, text_x, base_text_y, "游戏难度", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*2, "简单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*3, "中单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*4, "困单模式", pink_color)
while True:
# 获取事件,包括鼠标和键盘
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit("ok")
# 暂时还想不到窗口最大化的好办法
elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠标按下(任何一键)
screen_x, screen_y = event.pos # 获取鼠标点击位置的pygame坐标
mouse_l, mouse_m, mouse_r = pygame.mouse.get_pressed()# 检测具体哪个键(bool型)(左,滚轮,右)
if mouse_l == True:
# \ 表示承接上一行,因为一行写不开了。
if text_x <= screen_x <= text_x + text_width \
and base_text_y*2 <= screen_y <= base_text_y*2 + text_height:
Set_easy_mode()
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*3 <= screen_y <= base_text_y*3 + text_height:
Set_medium_mode()
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*4 <= screen_y <= base_text_y*4 + text_height:
Set_hard_mode()
pygame.display.update()
clock.tick(FPS)
写了一些代码之后我们立刻进行测试,避免到最后才发现问题。
下面是测试结果,背景图片可自己定义。
下面是终端输出结果。
测试之后我们不难发现进一步扩充代码需要解决的问题。
1.只定义了一个屏幕。所以接下来可以尝试定义第二个屏幕(也可以将第一个屏幕覆盖),选完游戏难度后跳出第一个循环(最好是跳出第一个界面的循环,否则需要新增变量来记录目前是哪个界面)。这样设计之后,若想不退出游戏就能重新选择游戏难度,就需要将第二个屏幕整理到一个函数中,或令设一个大循环包括住两个屏幕。【总之要逻辑自洽。】
第一次用pygame,可能会陷入想要分离模块,但模块之间藕断丝连的情况。
所以接下来主要解决上一次编码留下的问题,并尽量做出二层的简单界面,方便调试。其中Load_image()、Load_font()、Set_clock()、Set_colors()、My_init()、Print_text()与解决问题没关系,所以都不用改。
新增WIDTH, HEIGHT, MINE_COUNT作为全局变量,它们是游戏的参数,随不同游戏难度而变化。
修改Set_easy_mode()、Set_medium_mode()、Set_hard_mode()函数,其更改全局游戏参数,且返回新的屏幕。
将第二个游戏屏幕设计成函数Secend_game_interface()。【暂时啥功能也没有,只会展示一个可视化界面+实现了退出功能】
新在主函数中加入reset_mode变量,表示是否由二层游戏界面退回到菜单界面,在该分支中设置屏幕切换。
import pygame
from pygame.locals import *
# 基础屏幕大小、格子块大小等全局信息。
FIRST_SCREEN_WIDTH = 720
FIRST_SCREEN_HEIGHT = 720
SIZE = 20 # 一个正方形格子的大小
WIDTH = 10 # 长宽多少个格子
HEIGHT = 10
MINE_COUNT = 10 # 地雷数
def Load_image():
global first_bac
first_bac= pygame.image.load('Mine-clearing game/data/pictures/first_bac.png').convert() # 加载并渲染
first_bac= pygame.transform.smoothscale(first_bac, (FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT)) # 平滑缩放(这里可以用学过的最近邻?或双线性)
def Load_font():
global first_font, first_font_size
first_font_size = FIRST_SCREEN_WIDTH//10
# 'C:/Windows/Fonts/xxx.ttf' # (系统自带字体文件夹,xxx是其名字)
first_font_path = 'Mine-clearing game/data/font/云峰静龙行书.ttf'
# pygame.font.Font参数(字体位置, 字体大小)
first_font = pygame.font.Font(first_font_path, first_font_size)
def Set_clock():
global clock, FPS
FPS = 60
# 获取一个Clock对象,限制游戏的运行速度(clock.tick(FPS)这样用)
clock = pygame.time.Clock() # 类似于sleep
def Set_colors():
global pink_color
pink_color = (245, 140, 190) # (定义RGB三元组)这个还可以在调淡色一点
def My_init():
Load_image()
Load_font()
Set_clock()
Set_colors()
def Print_text(screen, font, x, y, text, fcolor=(0, 0, 0)):
# font.render参数 (文本,抗锯齿否,字体颜色,背景颜色)
imgText = font.render(text, True, fcolor)
screen.blit(imgText, (x, y))
def Set_easy_mode():
# easy无需改参数,使用默认的
global WIDTH, HEIGHT, MINE_COUNT
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def Set_medium_mode():
global WIDTH, HEIGHT, MINE_COUNT
WIDTH = WIDTH*2
HEIGHT = HEIGHT*2
MINE_COUNT = MINE_COUNT*2
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def Set_hard_mode():
global WIDTH, HEIGHT, MINE_COUNT
WIDTH = WIDTH*3
HEIGHT = HEIGHT*3
MINE_COUNT = MINE_COUNT*3
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def Secend_game_interface(screen):
global WIDTH, HEIGHT, MINE_COUNT
screen.fill((0,0,0))
# screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
while True:
# 获取事件,包括鼠标和键盘
for event in pygame.event.get():
if event.type == pygame.QUIT:
# 需要回调参数
WIDTH = 10 # 长宽多少个格子
HEIGHT = 10
MINE_COUNT = 10 # 地雷数
print("成功退出")
return
pygame.display.update()
clock.tick(FPS)
# 简单介绍一下模块内各种函数的作用,具体的可以用时现查
# 把各种加载操作放到别出去,看代码的时候可以直接缩小他们。
if __name__ == "__main__": # main
pygame.init() # 检查硬件调用接口、基础功能是否有问题(模糊一点就说初始化)
# pygame.display.set_mode参数(分辨率, 显示模式=0, 颜色位数=0, 不知道=0, 不知道=0)
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
My_init() # 自定义的初始化函数
pygame.display.set_caption("扫雷") # 设置当前窗口标题
# screen.blit参数(图像/字,位置,不知道,不知道)
# 第三个参数好像与动态图有关,可能其他程序会用得到。
menu_screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
# 之后要用这些参数定位鼠标动作,所以单起一个“稍短”的名字
(text_width, text_height) = first_font.size("游戏难度")
text_x = int((FIRST_SCREEN_WIDTH-text_width)/2)
base_text_y = int(FIRST_SCREEN_HEIGHT/6)
Print_text(menu_screen, first_font, text_x, base_text_y, "游戏难度", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*2, "简单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*3, "中单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*4, "困单模式", pink_color)
reset_mode = False # 不会挑来跳去,便用一个bool变量代替。
while True:
# 获取事件,包括鼠标和键盘
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit("ok")
# 暂时还想不到窗口最大化的好办法
elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠标按下(任何一键)
screen_x, screen_y = event.pos # 获取鼠标点击位置的pygame坐标
mouse_l, mouse_m, mouse_r = pygame.mouse.get_pressed()# 检测具体哪个键(bool型)(左,滚轮,右)
if mouse_l == True:
# \ 表示承接上一行,因为一行写不开了。
if text_x <= screen_x <= text_x + text_width \
and base_text_y*2 <= screen_y <= base_text_y*2 + text_height:
game_screen = Set_easy_mode()
Secend_game_interface(game_screen)
# 退出来之后需要重新刷新屏幕。
reset_mode = True
# 这里要是有goto跳来跳去岂不美哉。但怕用多了自己也掌控不了它跳到哪里去了>_<
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*3 <= screen_y <= base_text_y*3 + text_height:
game_screen = Set_medium_mode()
Secend_game_interface(game_screen)
reset_mode = True
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*4 <= screen_y <= base_text_y*4 + text_height:
game_screen = Set_hard_mode()
Secend_game_interface(game_screen)
reset_mode = True
if reset_mode == True:
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
menu_screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
Print_text(menu_screen, first_font, text_x, base_text_y, "游戏难度", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*2, "简单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*3, "中单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*4, "困单模式", pink_color)
reset_mode = False
pygame.display.update()
clock.tick(FPS)
如此如此便解决了上一步的问题。
接下来需要实打实设计游戏逻辑了。
import pygame
import time
from pygame.locals import *
from include.Mine_clear_class import *
# 基础屏幕大小、格子块大小等全局信息。
FIRST_SCREEN_WIDTH = 720
FIRST_SCREEN_HEIGHT = 720
SIZE = 20 # 一个正方形格子的大小
WIDTH = 10 # 长宽多少个格子
HEIGHT = 10
MINE_COUNT = 10 # 地雷数
def Load_image():
# 把图片和笑脸大小声明为全局变量
global face_size
global first_bac,img_normal,img_flag,img_ask,img_mine,img_explode,img_wrong_mine,img_face_lose,img_face_normal,img_face_win
global img_mine_count
# first background
first_bac = pygame.image.load('Mine-clearing game/data/pictures/first_bac.png').convert() # 加载并渲染
first_bac = pygame.transform.smoothscale(first_bac, (FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT)) # 平滑缩放(这里可以用学过的最近邻?或双线性)
# open 状态 around_mines_count
img_amc0 = pygame.image.load('Mine-clearing game/data/pictures/0.bmp').convert()
img_amc0 = pygame.transform.smoothscale(img_amc0, (SIZE, SIZE))
img_amc1 = pygame.image.load('Mine-clearing game/data/pictures/1.bmp').convert()
img_amc1 = pygame.transform.smoothscale(img_amc1, (SIZE, SIZE))
img_amc2 = pygame.image.load('Mine-clearing game/data/pictures/2.bmp').convert()
img_amc2 = pygame.transform.smoothscale(img_amc2, (SIZE, SIZE))
img_amc3 = pygame.image.load('Mine-clearing game/data/pictures/3.bmp').convert()
img_amc3 = pygame.transform.smoothscale(img_amc3, (SIZE, SIZE))
img_amc4 = pygame.image.load('Mine-clearing game/data/pictures/4.bmp').convert()
img_amc4 = pygame.transform.smoothscale(img_amc4, (SIZE, SIZE))
img_amc5 = pygame.image.load('Mine-clearing game/data/pictures/5.bmp').convert()
img_amc5 = pygame.transform.smoothscale(img_amc5, (SIZE, SIZE))
img_amc6 = pygame.image.load('Mine-clearing game/data/pictures/6.bmp').convert()
img_amc6 = pygame.transform.smoothscale(img_amc6, (SIZE, SIZE))
img_amc7 = pygame.image.load('Mine-clearing game/data/pictures/7.bmp').convert()
img_amc7 = pygame.transform.smoothscale(img_amc7, (SIZE, SIZE))
img_amc8 = pygame.image.load('Mine-clearing game/data/pictures/8.bmp').convert()
img_amc8 = pygame.transform.smoothscale(img_amc8, (SIZE, SIZE))
img_mine_count = { # 字典数据结构
0: img_amc0,
1: img_amc1,
2: img_amc2,
3: img_amc3,
4: img_amc4,
5: img_amc5,
6: img_amc6,
7: img_amc7,
8: img_amc8
}
# 格子状态图:normal, flag, ask
img_normal = pygame.image.load('Mine-clearing game/data/pictures/normal.bmp').convert()
img_normal = pygame.transform.smoothscale(img_normal, (SIZE, SIZE))
img_flag = pygame.image.load('Mine-clearing game/data/pictures/flag.bmp').convert()
img_flag = pygame.transform.smoothscale(img_flag, (SIZE, SIZE))
img_ask = pygame.image.load('Mine-clearing game/data/pictures/ask.bmp').convert()
img_ask = pygame.transform.smoothscale(img_ask, (SIZE, SIZE))
# 雷的状态
img_mine = pygame.image.load('Mine-clearing game/data/pictures/mine.bmp').convert() # normal->mine , ask也默认这个
img_mine = pygame.transform.smoothscale(img_mine, (SIZE, SIZE))
img_explode = pygame.image.load('Mine-clearing game/data/pictures/explode.bmp').convert() # open->mine
img_explode = pygame.transform.smoothscale(img_explode, (SIZE, SIZE))
img_wrong_mine = pygame.image.load('Mine-clearing game/data/pictures/wrong_mine.bmp').convert() # flag->mine
img_wrong_mine = pygame.transform.smoothscale(img_wrong_mine, (SIZE, SIZE))
# 经典笑脸 ready和start , lose , win
face_size = int(SIZE * 1.5) # 笑脸大小
img_face_lose = pygame.image.load('Mine-clearing game/data/pictures/face_lose.bmp').convert()
img_face_lose = pygame.transform.smoothscale(img_face_lose, (face_size, face_size))
img_face_normal = pygame.image.load('Mine-clearing game/data/pictures/face_normal.bmp').convert()
img_face_normal = pygame.transform.smoothscale(img_face_normal, (face_size, face_size))
img_face_win = pygame.image.load('Mine-clearing game/data/pictures/face_win.bmp').convert()
img_face_win = pygame.transform.smoothscale(img_face_win, (face_size, face_size))
def Load_font():
# first 一层;second 二层
global first_font, second_font
first_font_size = FIRST_SCREEN_WIDTH//10
# 'C:/Windows/Fonts/xxx.ttf' # (系统自带字体文件夹,xxx是其名字)
first_font_path = 'Mine-clearing game/data/font/云峰静龙行书.ttf'
# pygame.font.Font参数(字体位置, 字体大小)
first_font = pygame.font.Font(first_font_path, first_font_size)
second_font_path = 'Mine-clearing game/data/font/kaiti.ttf'
second_font = pygame.font.Font(second_font_path, SIZE*2) # 得分的字体
def Set_clock():
global clock, FPS
FPS = 60
# 获取一个Clock对象,限制游戏的运行速度(clock.tick(FPS)这样用)
clock = pygame.time.Clock() # 类似于sleep
def Set_colors():
global pink_color, white_color
pink_color = (245, 140, 190) # (定义RGB三元组)这个还可以在调淡色一点
white_color = (255, 255, 255)
def My_init():
Load_image()
Load_font()
Set_clock()
Set_colors()
def Print_text(screen, font, x, y, text, fcolor=(0, 0, 0)):
# font.render参数 (文本,抗锯齿否,字体颜色,背景颜色)
imgText = font.render(text, True, fcolor)
screen.blit(imgText, (x, y))
def Set_easy_mode():
# easy无需改参数,使用默认的
global WIDTH, HEIGHT, MINE_COUNT
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def Set_medium_mode():
global WIDTH, HEIGHT, MINE_COUNT
WIDTH = WIDTH*2
HEIGHT = HEIGHT*2
MINE_COUNT = MINE_COUNT*2
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def Set_hard_mode():
global WIDTH, HEIGHT, MINE_COUNT
WIDTH = WIDTH*3
HEIGHT = HEIGHT*3
MINE_COUNT = MINE_COUNT*3
game_screen = pygame.display.set_mode((WIDTH * SIZE, (HEIGHT + 2) * SIZE)) # 因为要放笑脸,所以长一点
return game_screen
def matrixTOscreen(matrix_x, matrix_y):
screen_x = matrix_y * SIZE
screen_y = (matrix_x+2) * SIZE
return screen_x, screen_y
def screenTOmatrix(screen_x, screen_y):
matrix_x = screen_y // SIZE - 2
matrix_y = screen_x // SIZE
return matrix_x, matrix_y
def Secend_game_interface(screen):
global WIDTH, HEIGHT, MINE_COUNT
# 定义出游戏实例,定义游戏状态
chess_board = ChessBoard(WIDTH, HEIGHT, MINE_COUNT)
game_state = GameStae.ready
face_pos = ((WIDTH*SIZE - face_size) // 2, (SIZE * 2 - face_size) // 2) # 笑脸位置
(num_width, num_height) = second_font.size("999")
start_time = time.time()
duration_time = 0 # 游戏时长
# screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
while True:
# 大概顺序是操作,判断输赢,图片展示
# 获取事件,包括鼠标和键盘
for event in pygame.event.get():
if event.type == pygame.QUIT:
# 需要回调参数
WIDTH = 10 # 长宽多少个格子
HEIGHT = 10
MINE_COUNT = 10 # 地雷数
print("成功退出")
return
elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠标按下(任何一键)
screen_x, screen_y = event.pos # 获取鼠标点击位置的pygame坐标
matrix_x, matrix_y = screenTOmatrix(screen_x, screen_y) # 格子的矩阵坐标
grid = chess_board.get_one_grid(matrix_x, matrix_y)
mouse_l, mouse_m, mouse_r = pygame.mouse.get_pressed()# 检测具体哪个键(bool型)(左,滚轮,右)
# 打破ready状态,进入start,而且放在这里是个按下左键就开始的意思。
# 但是不跳出本次点击动作,可以顺带执行打开操作
if game_state == GameStae.ready:
game_state = GameStae.start
start_time = time.time()
if mouse_l == True:
# 优先级强判定
if face_pos[0] <= screen_x <= face_pos[0] + face_size \
and face_pos[1] <= screen_y <= face_pos[1] + face_size:
# 开始或重新开始游戏
del chess_board
chess_board = ChessBoard(WIDTH, HEIGHT, MINE_COUNT)
game_state = GameStae.ready
continue
if grid.get_state() == GridState.normal \
and (game_state == GameStae.ready or game_state == GameStae.start):
lose_flag = chess_board.opened_grid(matrix_x, matrix_y)
if(lose_flag == False):
game_state = GameStae.lose
break # 跳出事件循环
else:
if(chess_board.get_opened_grid_count() == WIDTH*HEIGHT - MINE_COUNT):
# 赢的条件1.打开所有非雷的格子
game_state = GameStae.win
break # 跳出事件循环
if mouse_r == True \
and (game_state == GameStae.ready or game_state == GameStae.start):
chess_board.mark_grid(matrix_x,matrix_y)
if(chess_board.get_flag_mine_count() == MINE_COUNT):
# 赢的条件2.标记完全部地雷
game_state = GameStae.win
break # 跳出事件循环
if mouse_m == True:
# 显示调试信息
print(grid)
# 下面是各种游戏状态的显示逻辑了
screen.fill(white_color) # 这句出现了很多次可能多余了
if game_state == GameStae.ready:
duration_time = int(0) # 游戏时长
if game_state == GameStae.start:
duration_time = int(time.time()- start_time) # 游戏时长
Print_text(screen, second_font, int((1.5*WIDTH*SIZE-num_width)*0.5), int((2*SIZE-num_height)*0.5), '%03d' % (duration_time), pink_color)
left_flags_count = chess_board.get_left_flag_count()
Print_text(screen, second_font, int((0.5*WIDTH*SIZE-num_width)*0.5), int((2*SIZE-num_height)*0.5), '%03d' % (left_flags_count), pink_color)
if game_state == GameStae.ready:
screen.blit(img_face_normal, face_pos)
for i in range(WIDTH):
for j in range(WIDTH):
pos = matrixTOscreen(i, j)
screen.blit(img_normal, pos)
if game_state == GameStae.start:
screen.blit(img_face_normal, face_pos)
for i in range(WIDTH):
for j in range(WIDTH):
# 开始的时候无需关心地雷此时的状态
grid = chess_board.get_one_grid(i, j)
pos = matrixTOscreen(i, j)
if grid.get_state() == GridState.normal:
screen.blit(img_normal, pos)
elif grid.get_state() == GridState.opened:
screen.blit(img_mine_count[grid.get_around_mine_count()], pos)
elif grid.get_state() == GridState.flag:
screen.blit(img_flag, pos)
elif grid.get_state() == GridState.ask:
screen.blit(img_ask, pos)
else:
print(game_state)
print(grid)
exit("something error unknown grid state")
elif game_state == GameStae.lose:
screen.blit(img_face_lose, face_pos)
for i in range(WIDTH):
for j in range(WIDTH):
# 这里需要加上对地雷的考虑了
grid = chess_board.get_one_grid(i, j)
pos = matrixTOscreen(i, j)
if grid.get_state() == GridState.normal and grid.get_is_mine() == False:
screen.blit(img_normal, pos)
elif grid.get_state() == GridState.normal and grid.get_is_mine() == True:
screen.blit(img_mine, pos)
elif grid.get_state() == GridState.opened and grid.get_is_mine() == False:
screen.blit(img_mine_count[grid.get_around_mine_count()], pos)
elif grid.get_state() == GridState.opened and grid.get_is_mine() == True:
screen.blit(img_explode, pos)
elif grid.get_state() == GridState.flag and grid.get_is_mine() == False :#标记错了
screen.blit(img_wrong_mine, pos)
elif grid.get_state() == GridState.flag and grid.get_is_mine() == True :#标记对了
screen.blit(img_flag, pos)
elif grid.get_state() == GridState.ask and grid.get_is_mine() == False:
screen.blit(img_ask, pos)
elif grid.get_state() == GridState.ask and grid.get_is_mine() == True:
screen.blit(img_mine, pos)
else:
print(game_state)
print(grid)
exit("something error unknown grid state")
elif game_state == GameStae.win:
screen.blit(img_face_normal, face_pos)
for i in range(WIDTH):
for j in range(WIDTH):
# 这里需要加上对地雷的考虑了
grid = chess_board.get_one_grid(i, j)
pos = matrixTOscreen(i, j)
if grid.get_state() == GridState.normal:
screen.blit(img_normal, pos)
elif grid.get_state() == GridState.opened:
screen.blit(img_mine_count[grid.get_around_mine_count()], pos)
elif grid.get_state() == GridState.flag:
screen.blit(img_flag, pos)
elif grid.get_state() == GridState.ask:
screen.blit(img_ask, pos)
else:
print(game_state)
print(grid)
exit("something error unknown grid state")
# else:
# do nothing
pygame.display.update()
clock.tick(FPS)
# 简单介绍一下模块内各种函数的作用,具体的可以用时现查
# 把各种加载操作放到别出去,看代码的时候可以直接缩小他们。
if __name__ == "__main__": # main
pygame.init() # 检查硬件调用接口、基础功能是否有问题(模糊一点就说初始化)
# pygame.display.set_mode参数(分辨率, 显示模式=0, 颜色位数=0, 不知道=0, 不知道=0)
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
My_init() # 自定义的初始化函数
pygame.display.set_caption("扫雷") # 设置当前窗口标题
# screen.blit参数(图像/字,位置,不知道,不知道)
# 第三个参数好像与动态图有关,可能其他程序会用得到。
menu_screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
# 之后要用这些参数定位鼠标动作,所以单起一个“稍短”的名字
(text_width, text_height) = first_font.size("游戏难度")
text_x = int((FIRST_SCREEN_WIDTH-text_width)/2)
base_text_y = int(FIRST_SCREEN_HEIGHT/6)
Print_text(menu_screen, first_font, text_x, base_text_y, "游戏难度", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*2, "简单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*3, "中单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*4, "困单模式", pink_color)
reset_mode = False # 不会挑来跳去,便用一个bool变量代替。
while True:
# 获取事件,包括鼠标和键盘
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit("ok")
# 暂时还想不到窗口最大化的好办法
elif event.type == pygame.MOUSEBUTTONDOWN: # 鼠标按下(任何一键)
screen_x, screen_y = event.pos # 获取鼠标点击位置的pygame坐标
mouse_l, mouse_m, mouse_r = pygame.mouse.get_pressed()# 检测具体哪个键(bool型)(左,滚轮,右)
if mouse_l == True:
# \ 表示承接上一行,因为一行写不开了。
if text_x <= screen_x <= text_x + text_width \
and base_text_y*2 <= screen_y <= base_text_y*2 + text_height:
game_screen = Set_easy_mode()
Secend_game_interface(game_screen)
# 退出来之后需要重新刷新屏幕。
reset_mode = True
# 这里要是有goto跳来跳去岂不美哉。但怕用多了自己也掌控不了它跳到哪里去了>_<
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*3 <= screen_y <= base_text_y*3 + text_height:
game_screen = Set_medium_mode()
Secend_game_interface(game_screen)
reset_mode = True
elif text_x <= screen_x <= text_x + text_width \
and base_text_y*4 <= screen_y <= base_text_y*4 + text_height:
game_screen = Set_hard_mode()
Secend_game_interface(game_screen)
reset_mode = True
if reset_mode == True:
menu_screen = pygame.display.set_mode((FIRST_SCREEN_WIDTH, FIRST_SCREEN_HEIGHT))
menu_screen.blit(first_bac, (0, 0)) # 载入游戏开始界面
Print_text(menu_screen, first_font, text_x, base_text_y, "游戏难度", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*2, "简单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*3, "中单模式", pink_color)
Print_text(menu_screen, first_font, text_x, base_text_y*4, "困单模式", pink_color)
reset_mode = False
pygame.display.update()
clock.tick(FPS)
# 此时一个正常扫雷该有的功能都具备了
# 接下来可以实现一些打趣的功能
# 1.将随机点开的第一个一直变为雷,直接输掉游戏
# 2.点完第一个,直接赢得游戏
# 这也好改可以直接改变输赢条件的逻辑。
主要添加了:
- 为了便于统计所剩旗子数,在ChessBoard类中新增self.LEFT_FLAG_COUNT变量,初始化为MINE_COUNT,并在标记操作mark_grid()中增加对其的修改。在类中添加get_left_flag_count(self):函数,返回self.LEFT_FLAG_COUNT。
- matrixTOscreen()矩阵左边转换为屏幕坐标;screenTOmatrix()屏幕坐标转换为矩阵坐标
这里矩阵坐标是指(i,j)表示i行j列的格子。
屏幕坐标是指pygame展示屏幕时,以其左上角为坐标原点,水平向右为x正半轴、竖直向下为y轴正半轴。
Secend_game_interface(screen):游戏界面和游戏逻辑:各种动作+游戏状态转变+屏幕显示。
至此,简单的扫雷游戏已经设计完毕了。
最终整个项目的文件均放在gitee和github了。文章来源:https://www.toymoban.com/news/detail-689160.html
写在最后
这篇文章主要用来提醒自己时刻注意要尽可能快速地进行验证。不能一直在编码,要时刻注意测试。文章来源地址https://www.toymoban.com/news/detail-689160.html
到了这里,关于Python小游戏——扫雷的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!