1. 简介
扫雷是一个经典的小游戏,现在我们使用Python语言来编写一个扫雷小游戏。本文将会详细介绍如何实现扫雷游戏,包括界面设计和核心逻辑实现等方面。
2. 界面设计
2.1 Pygame
Pygame是Python编写游戏的一种库,他可以为我们提供游戏的框架,包括窗口、图像、声音等等。对于本文的扫雷游戏来说,我们需要使用Pygame来实现游戏界面。
首先我们需要安装Pygame库:
!pip install pygame
接下来我们就可以开始编写代码了。我们需要先导入Pygame库,如下所示:
import pygame
from pygame.locals import *
接下来我们需要初始化Pygame,如下所示:
pygame.init()
然后我们需要创建一个窗口,并设置窗口大小和标题,如下所示:
window = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Minesweeper')
以上代码创建了一个800 * 600像素大小的窗口,并设置了窗口标题为“Minesweeper”。
2.2 界面元素
接下来我们需要在窗口中添加游戏界面元素。一个典型的扫雷游戏界面包括地图和计数器。
地图:扫雷游戏的核心就在于地图,我们可以使用二维数组来模拟一个M * N的地图。
计数器:计数器用于显示玩家还剩下多少雷没有被找到。
以上介绍了扫雷游戏界面的两个主要元素,接下来我们来一步步实现。
2.3. 地图
本游戏的核心是地图,我们需要使用二维数组来表示地图。在地图中,0代表没有雷,1代表有雷。我们还可以使用另一个二维数组作为标记,标记哪些位置已经被翻开,哪些位置还没有被翻开。
生成地图:我们可以使用Python的随机数函数来生成雷的位置。代码如下:
import random
def generate_map(m, n, num_mines):
map = [[0] * n for i in range(m)]
mines = random.sample(range(m * n), num_mines)
for mine in mines:
i = mine // n
j = mine % n
map[i][j] = 1
return map
以上代码中,generate_map函数用于生成地图,m和n分别代表地图的纵横长度,num_mines代表地雷数量。
接下来我们可以在游戏界面中显示地图。我们可以通过遍历地图数组,来决定每个格子显示的状态。例如,如果某个位置没有被翻开,则显示一个未知的方块;如果某个位置翻开并且有雷,则显示一个地雷;如果某个位置翻开并且没有雷,则显示周围雷的数量。
显示地图:我们可以使用pygame中的矩形来显示地图中的每个小格子。代码如下:
cell_size = 32 # 每个格子的大小
# 绘制地图
def draw_map(map):
rows = len(map)
cols = len(map[0])
for i in range(rows):
for j in range(cols):
rect = pygame.Rect(j * cell_size, i * cell_size, cell_size, cell_size)
if map[i][j] == 0: # 未翻开
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
elif map[i][j] == 1: # 有雷
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
draw_mine(rect) # 显示地雷
else: # 显示周围雷的数量
num = count_mines(map, i, j)
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
if num > 0:
draw_text(rect, str(num))
以上代码中,我们使用了两个函数来绘制地图中的方块:一个是draw_mine函数用于绘制地雷;一个是draw_text函数用于绘制数字。
2.4 计数器
我们需要在游戏界面上显示玩家还剩下多少雷没有被找到。我们可以使用一个全局的变量来存储还剩下多少雷没有被找到,并在游戏界面中将其显示出来。
显示计数器:计数器可以用Pygame的文字显示功能来实现,代码如下:
num_mines_left = num_mines # 还剩下多少雷没有找到
font = pygame.font.SysFont(None, 32) # 字体
text_mines_left = font.render("Mines left: {}".format(num_mines_left), True, (255, 255, 255)) # 创建文本
window.blit(text_mines_left, (400, 10)) # 绘制文本
2.5 完整代码
下面是界面设计部分的完整代码:
import pygame
from pygame.locals import *
import random
pygame.init()
# 创建窗口
window = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Minesweeper')
cell_size = 32 # 每个格子的大小
# 字体
font = pygame.font.SysFont(None, 32)
# 生成地图
def generate_map(m, n, num_mines):
map = [[0] * n for i in range(m)]
mines = random.sample(range(m * n), num_mines)
for mine in mines:
i = mine // n
j = mine % n
map[i][j] = 1
return map
# 计算某个位置周围的雷的数量
def count_mines(map, i, j):
rows = len(map)
cols = len(map[0])
num = 0
for r in range(max(i-1, 0), min(i+2, rows)):
for c in range(max(j-1, 0), min(j+2, cols)):
if map[r][c] == 1:
num += 1
return num
# 绘制地雷
def draw_mine(rect):
x, y, w, h = rect
pygame.draw.circle(window, (0, 0, 0), (x + w // 2, y + h // 2), cell_size // 4)
# 绘制文本
def draw_text(rect, text):
x, y, w, h = rect
text_surface = font.render(text, True, (0, 0, 0))
text_rect = text_surface.get_rect()
text_rect.center = (x + w // 2, y + h // 2)
window.blit(text_surface, text_rect)
# 绘制地图
def draw_map(map):
rows = len(map)
cols = len(map[0])
for i in range(rows):
for j in range(cols):
rect = pygame.Rect(j * cell_size, i * cell_size, cell_size, cell_size)
if map[i][j] == 0: # 未翻开
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
elif map[i][j] == 1: # 有雷
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
draw_mine(rect) # 显示地雷
else: # 显示周围雷的数量
num = count_mines(map, i, j)
pygame.draw.rect(window, (192, 192, 192), rect) # 灰色方块
pygame.draw.rect(window, (255, 255, 255), rect, 1) # 白色边框
if num > 0:
draw_text(rect, str(num))
# 主循环
def main_loop():
# 生成地图
num_rows = 16
num_cols = 16
num_mines = 40
map = generate_map(num_rows, num_cols, num_mines)
while True:
# 事件处理
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# 绘制地图
draw_map(map)
# 显示计数器
num_mines_left = num_mines # 还剩下多少雷没有找到
text_mines_left = font.render("Mines left: {}".format(num_mines_left), True, (255, 255, 255))
window.blit(text_mines_left, (400, 10))
# 更新屏幕
pygame.display.update()
# 运行游戏
if __name__ == '__main__':
main_loop()
3. 核心逻辑实现
3.1 点击事件
我们需要实现翻开某个格子的操作,也就是在游戏中点击某个格子后,该格子会出现对应的数字或地雷。
点击事件:我们可以使用pygame的事件处理机制来检测鼠标点击事件。代码如下:
def main_loop():
# 生成地图
num_rows = 16
num_cols = 16
num_mines = 40
map = generate_map(num_rows, num_cols, num_mines)
while True:
# 事件处理
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONUP:
if event.button == 1: # 左键
x, y = event.pos
i = y // cell_size
j = x // cell_size
if map[i][j] == 0: # 未翻开
map[i][j] = 2 # 已标记
# 绘制地图
draw_map(map)
# 显示计数器
num_mines_left = num_mines # 还剩下多少雷没有找到
text_mines_left = font.render("Mines left: {}".format(num_mines_left), True, (255, 255, 255))
window.blit(text_mines_left, (400, 10))
# 更新屏幕
pygame.display.update()
以上代码中,我们检测到鼠标左键被点击后,获取鼠标点击的坐标,并根据坐标计算出点击的格子的位置。如果该位置未翻开,则将该位置标记为2(已标记,未翻开)。下一步,我们需要实现将某个格子翻开的操作。
3.2 翻开格子
我们需要实现如下操作:当未翻开的格子被点击后,如果该位置没有雷,则翻开该位置,并显示周围8个位置中雷的数量;如果该位置有雷,则游戏失败。
翻开格子:我们可以在之前检测到鼠标点击事件的代码中,添加如下代码来实现翻开格子的操作:
elif event.type == MOUSEBUTTONUP:
if event.button == 1: # 左键
x, y = event.pos
i = y // cell_size
j = x // cell_size
if map[i][j] == 0: # 未翻开
if count_mines(map, i, j) == 0: # 周围没有雷,翻开周围8个格子
flip_empty(map, i, j)
elif map[i][j] == 1: # 有雷,游戏结束
gameover()
else: # 显示周围雷的数量
map[i][j] = count_mines(map, i, j)
以上代码中,我们添加了一个flip_empty函数用于翻开周围8个位置中没有雷的格子,并添加了一个gameover函数用于在游戏结束时显示“Game Over”。
翻开周围的空格:我们可以使用递归的方式来翻开周围的空格,代码如下所示:
def flip_empty(map, i, j):
rows = len(map)
cols = len(map[0])
if i >= 0 and i < rows and j >= 0 and j < cols and map[i][j] == 0:
map[i][j] = count_mines(map, i, j)
if map[i][j] == 0:
flip_empty(map, i-1, j-1)
flip_empty(map, i-1, j)
flip_empty(map, i-1, j+1)
flip_empty(map, i, j-1)
flip_empty(map, i, j+1)
flip_empty(map, i+1, j-1)
flip_empty(map, i+1, j)
flip_empty(map, i+1, j+1)
以上代码中,我们首先检查该位置是否在地图范围内并且是否为0,如果是,我们就将该位置的值设置为周围雷的数量。如果该位置周围没有雷,我们就递归地翻开周围的8个位置。
游戏结束:我们可以使用Pygame的文字显示功能来在游戏结束后显示“Game Over”。代码如下所示:
def gameover():
text_game_over = font.render("Game Over", True, (255, 0, 0))
window.blit(text_game_over, (400, 30))