Python通過Pygame實(shí)現(xiàn)一個(gè)經(jīng)典掃雷游戲
項(xiàng)目概述
Pygame 是 Python 中一個(gè)功能強(qiáng)大的 2D 游戲開發(fā)庫,它提供了處理圖形、聲音、輸入和事件循環(huán)的完整工具集。本文通過 Pygame 實(shí)現(xiàn)一個(gè)經(jīng)典掃雷游戲項(xiàng)目。
在這個(gè)游戲中,玩家需要在一個(gè)網(wǎng)格中找出所有地雷而不觸發(fā)它們。玩家點(diǎn)擊格子揭示內(nèi)容,數(shù)字表示周圍 8 個(gè)相鄰格子中地雷的數(shù)量。其中:
- 標(biāo)記:經(jīng)典操作模式,左鍵點(diǎn)擊:揭示格子,翻開指定格子,查看內(nèi)容;右鍵點(diǎn)擊:標(biāo)記/取消標(biāo)記,對疑似地雷的格子添加或移除旗幟標(biāo)記。
- 揭示:中鍵點(diǎn)擊:對已揭示的數(shù)字格,若周圍旗幟數(shù)=數(shù)字,自動(dòng)揭示其余格子。
- 鍵盤操作:R:重新開始游戲;M:切換難度。
- 勝利條件:所有「非地雷格子」都被揭示 = 游戲勝利。
- 失敗條件:揭示了一個(gè)「地雷格子」 = 游戲失敗。
- 首次點(diǎn)擊保護(hù):第一次點(diǎn)擊永遠(yuǎn)不會(huì)踩雷,地雷在點(diǎn)擊后動(dòng)態(tài)生成。
游戲?qū)崿F(xiàn)
初始化與基礎(chǔ)設(shè)置
在游戲啟動(dòng)前,我們需要初始化 Pygame 及其子模塊,并定義游戲所需的基礎(chǔ)參數(shù)。
# 初始化 Pygame
pygame.init()
# 難度配置:(行數(shù), 列數(shù), 地雷數(shù))
DIFFICULTIES = {
'easy': (9, 9, 10),
'medium': (16, 16, 40),
'hard': (16, 30, 99)
}
# 默認(rèn)難度
CURRENT_DIFFICULTY = 'medium'
ROWS, COLS, MINE_COUNT = DIFFICULTIES[CURRENT_DIFFICULTY]
# 屏幕設(shè)置
CELL_SIZE = 30
UI_HEIGHT = 60
WIDTH = COLS * CELL_SIZE
HEIGHT = ROWS * CELL_SIZE + UI_HEIGHT
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame Minesweeper")
# 顏色定義(經(jīng)典掃雷風(fēng)格)
COLORS = {
'bg': (192, 192, 192),
'cell': (210, 210, 210),
'cell_revealed': (180, 180, 180),
'border_light': (255, 255, 255),
'border_dark': (128, 128, 128),
'mine': (0, 0, 0),
'flag': (255, 0, 0),
'wrong_flag': (255, 100, 100),
'text': {
1: (0, 0, 255), # 藍(lán)
2: (0, 128, 0), # 綠
3: (255, 0, 0), # 紅
4: (0, 0, 128), # 深藍(lán)
5: (128, 0, 0), # 棕
6: (0, 128, 128), # 青
7: (0, 0, 0), # 黑
8: (128, 128, 128) # 灰
}
}
字體加載函數(shù)
def load_safe_font(size, bold=False, italic=False):
"""加載字體"""
try:
return pygame.font.SysFont('arial', size, bold=bold, italic=italic)
except (TypeError, OSError, AttributeError):
try:
font_dir = os.path.join(os.environ.get("WINDIR", "C:\\Windows"), "Fonts")
font_path = os.path.join(font_dir, "arial.ttf")
if os.path.exists(font_path):
f = pygame.font.Font(font_path, size)
if bold:
f.set_bold(True)
if italic:
f.set_italic(True)
return f
except Exception:
pass
# 最終回退到默認(rèn)字體
f = pygame.font.Font(None, size)
if bold:
f.set_bold(True)
if italic:
f.set_italic(True)
return f
# 初始化字體
font = load_safe_font(20, bold=True)
big_font = load_safe_font(36, bold=True)
small_font = load_safe_font(16)
ui_font = load_safe_font(18)
該函數(shù)采用三級(jí)降級(jí)策略:
- 優(yōu)先使用
SysFont加載系統(tǒng)字體 - 失敗時(shí)嘗試直接讀取 Windows 字體目錄
- 最終回退到 pygame 內(nèi)置默認(rèn)字體
確保游戲在不同環(huán)境(Windows/Linux/macOS)和 pygame 版本下都能正常顯示文字。
游戲狀態(tài)枚舉
from enum import Enum
class GameState(Enum):
READY = 1 # 游戲準(zhǔn)備中(首次點(diǎn)擊前)
PLAYING = 2 # 游戲進(jìn)行中
WON = 3 # 游戲勝利
LOST = 4 # 游戲失敗
使用枚舉管理游戲狀態(tài),使?fàn)顟B(tài)判斷更清晰、類型更安全。
核心類設(shè)計(jì)
1. 格子類(Cell)
Cell 類封裝了掃雷中每個(gè)格子的所有狀態(tài)和行為。
構(gòu)造函數(shù) __init__:
def __init__(self, row, col):
self.row = row # 行索引
self.col = col # 列索引
self.is_mine = False # 是否為地雷
self.is_revealed = False # 是否已被揭示
self.is_flagged = False # 是否被標(biāo)記為旗幟
self.is_wrong_flag = False # 是否錯(cuò)誤標(biāo)記(游戲結(jié)束時(shí)顯示)
self.adjacent_mines = 0 # 周圍地雷數(shù)量(0-8)
計(jì)算周圍地雷數(shù) count_adjacent_mines:
def count_adjacent_mines(self, grid):
"""計(jì)算周圍8格的地雷數(shù)量"""
count = 0
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = self.row + dr, self.col + dc
if 0 <= nr < len(grid) and 0 <= nc < len(grid[0]):
if grid[nr][nc].is_mine:
count += 1
self.adjacent_mines = count
遍歷周圍 8 個(gè)方向,統(tǒng)計(jì)地雷數(shù)量并存儲(chǔ)。
揭示格子 reveal:
def reveal(self):
"""揭示格子"""
if self.is_revealed or self.is_flagged:
return
self.is_revealed = True
標(biāo)記格子為已揭示,并避免重復(fù)操作。
繪制方法 draw:
def draw(self, surface, cell_size, offset_x, offset_y, game_state):
x = offset_x + self.col * cell_size
y = offset_y + self.row * cell_size
# 繪制格子背景(3D邊框效果)
if self.is_revealed:
pygame.draw.rect(surface, COLORS['cell_revealed'],
(x, y, cell_size, cell_size))
else:
pygame.draw.rect(surface, COLORS['cell'],
(x, y, cell_size, cell_size))
# 繪制亮/暗邊框營造立體感
pygame.draw.line(surface, COLORS['border_light'], (x, y), (x + cell_size, y), 2)
pygame.draw.line(surface, COLORS['border_light'], (x, y), (x, y + cell_size), 2)
pygame.draw.line(surface, COLORS['border_dark'], (x + cell_size, y), (x + cell_size, y + cell_size), 2)
pygame.draw.line(surface, COLORS['border_dark'], (x, y + cell_size), (x + cell_size, y + cell_size), 2)
# 繪制內(nèi)容:地雷/數(shù)字/旗幟
if self.is_revealed:
if self.is_mine:
# 繪制地雷(圓形+十字)
cx, cy = x + cell_size//2, y + cell_size//2
pygame.draw.circle(surface, COLORS['mine'], (cx, cy), cell_size//3)
pygame.draw.line(surface, COLORS['flag'], (cx-8, cy-8), (cx+8, cy+8), 2)
pygame.draw.line(surface, COLORS['flag'], (cx+8, cy-8), (cx-8, cy+8), 2)
elif self.adjacent_mines > 0:
color = COLORS['text'].get(self.adjacent_mines, (0, 0, 0))
text = font.render(str(self.adjacent_mines), True, color)
text_rect = text.get_rect(center=(x + cell_size//2, y + cell_size//2))
surface.blit(text, text_rect)
elif self.is_flagged:
# 繪制旗幟(旗桿+旗面)
color = COLORS['wrong_flag'] if (self.is_wrong_flag and game_state == GameState.LOST) else COLORS['flag']
pygame.draw.line(surface, (100, 100, 100),
(x + cell_size//3, y + cell_size//4),
(x + cell_size//3, y + 3*cell_size//4), 2)
pygame.draw.polygon(surface, color, [
(x + cell_size//3, y + cell_size//4),
(x + cell_size//3 + 12, y + cell_size//3),
(x + cell_size//3, y + cell_size//2 - 2)
])
2. 游戲主類(MineSweeperGame)
MineSweeperGame 類是整個(gè)游戲的控制中心,負(fù)責(zé)初始化、狀態(tài)管理、事件處理、邏輯更新和渲染。
構(gòu)造函數(shù) __init__:
def __init__(self, rows=16, cols=16, mines=40):
self.rows = rows
self.cols = cols
self.mine_count = mines
self.cell_size = CELL_SIZE
self.offset_x = 0
self.offset_y = UI_HEIGHT
self.grid = [] # 格子二維數(shù)組
self.first_click = True # 是否首次點(diǎn)擊(用于保證首次安全)
self.state = GameState.READY
self.start_time = None
self.elapsed_time = 0
self.flags_placed = 0
self.create_grid()
創(chuàng)建網(wǎng)格 create_grid:
def create_grid(self):
"""創(chuàng)建空白網(wǎng)格"""
self.grid = []
for r in range(self.rows):
row = [Cell(r, c) for c in range(self.cols)]
self.grid.append(row)
生成地雷 place_mines:
def place_mines(self, exclude_row, exclude_col):
"""排除首次點(diǎn)擊位置后隨機(jī)放置地雷"""
positions = [(r, c) for r in range(self.rows) for c in range(self.cols)
if not (r == exclude_row and c == exclude_col)]
mine_positions = random.sample(positions, self.mine_count)
for r, c in mine_positions:
self.grid[r][c].is_mine = True
# 計(jì)算每個(gè)格子的相鄰地雷數(shù)
for row in self.grid:
for cell in row:
cell.count_adjacent_mines(self.grid)
擴(kuò)散算法 flood_fill:
def flood_fill(self, start_row, start_col):
"""BFS擴(kuò)散揭示空白區(qū)域:揭示所有相連的空白格+邊緣數(shù)字格"""
queue = deque([(start_row, start_col)])
visited = set()
while queue:
r, c = queue.popleft()
if (r, c) in visited:
continue
visited.add((r, c))
cell = self.grid[r][c]
# 只跳過被標(biāo)記的格子,允許已揭示的格子繼續(xù)擴(kuò)散
if cell.is_flagged:
continue
# 揭示當(dāng)前格子(如果已揭示,reveal() 會(huì)直接返回,無副作用)
cell.reveal()
# 只有空白格子(相鄰地雷數(shù)=0)才繼續(xù)擴(kuò)散周圍
if cell.adjacent_mines == 0 and not cell.is_mine:
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
neighbor = self.grid[nr][nc]
# 只將未揭示且未標(biāo)記的鄰居加入隊(duì)列
if not neighbor.is_revealed and not neighbor.is_flagged:
queue.append((nr, nc))
檢查勝利條件 check_win:
def check_win(self):
"""檢查是否所有非地雷格子都被揭示"""
for row in self.grid:
for cell in row:
if not cell.is_mine and not cell.is_revealed:
return False
return True
快速揭示 chord_reveal:
def chord_reveal(self, row, col):
"""雙擊已揭示數(shù)字:若旗幟數(shù)=數(shù)字,揭示其余格子"""
cell = self.grid[row][col]
if not cell.is_revealed or cell.adjacent_mines == 0:
return
flag_count = 0
to_reveal = []
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = row + dr, col + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
neighbor = self.grid[nr][nc]
if neighbor.is_flagged:
flag_count += 1
elif not neighbor.is_revealed:
to_reveal.append((nr, nc))
if flag_count == cell.adjacent_mines:
for nr, nc in to_reveal:
target = self.grid[nr][nc]
if target.is_mine:
self.state = GameState.LOST
self.reveal_all_mines()
return
target.reveal()
if target.adjacent_mines == 0 and not target.is_mine:
self.flood_fill(nr, nc)
if self.check_win():
self.state = GameState.WON
self.mark_correct_flags()
事件處理方法 handle_events:
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.MOUSEBUTTONDOWN:
if self.state in [GameState.READY, GameState.PLAYING]:
mx, my = pygame.mouse.get_pos()
col = (mx - self.offset_x) // self.cell_size
row = (my - self.offset_y) // self.cell_size
if 0 <= row < self.rows and 0 <= col < self.cols:
cell = self.grid[row][col]
# 左鍵:揭示
if event.button == 1 and not cell.is_flagged:
if self.first_click:
self.first_click = False
self.place_mines(row, col)
self.start_time = pygame.time.get_ticks()
self.state = GameState.PLAYING
if cell.is_mine:
self.state = GameState.LOST
self.reveal_all_mines()
else:
# 先揭示起始格子
cell.reveal()
# 如果是空白格,觸發(fā)擴(kuò)散
if cell.adjacent_mines == 0:
self.flood_fill(row, col)
# 檢查勝利
if self.check_win():
self.state = GameState.WON
self.mark_correct_flags()
# 右鍵:標(biāo)記旗幟
elif event.button == 3 and not cell.is_revealed:
if cell.is_flagged:
cell.is_flagged = False
self.flags_placed -= 1
else:
cell.is_flagged = True
self.flags_placed += 1
# 中鍵 或 Ctrl+左鍵:快速揭示
elif (event.button == 2 or
(event.button == 1 and pygame.key.get_mods() & pygame.KMOD_CTRL)):
if cell.is_revealed and cell.adjacent_mines > 0:
self.chord_reveal(row, col)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r: # R: 重開
self.__init__(self.rows, self.cols, self.mine_count)
elif event.key == pygame.K_m: # M: 切換難度
self.change_difficulty()
elif event.key == pygame.K_ESCAPE: # ESC: 退出
return False
return True
繪制方法 draw:
def draw(self):
screen.fill(COLORS['bg'])
# 繪制所有格子
for row in self.grid:
for cell in row:
cell.draw(screen, self.cell_size, self.offset_x, self.offset_y, self.state)
# 繪制頂部信息欄
self.draw_ui()
# 游戲狀態(tài)遮罩
if self.state == GameState.WON:
self.draw_overlay("VICTORY!", "Time: " + str(self.elapsed_time) + "s",
(100, 255, 100), (200, 255, 200))
elif self.state == GameState.LOST:
self.draw_overlay("BOOM!", "Better luck next time!",
(255, 100, 100), (255, 200, 200))
pygame.display.flip()
主循環(huán)方法 run:
def run(self):
clock = pygame.time.Clock()
running = True
while running:
running = self.handle_events()
self.draw()
clock.tick(60)
pygame.quit()
sys.exit()
全部代碼
import pygame
import random
import sys
import os
from enum import Enum
from collections import deque
# ==================== 初始化配置 ====================
pygame.init()
# 難度配置:(行數(shù), 列數(shù), 地雷數(shù))
DIFFICULTIES = {
'easy': (9, 9, 10),
'medium': (16, 16, 40),
'hard': (16, 30, 99)
}
# 默認(rèn)難度
CURRENT_DIFFICULTY = 'medium'
ROWS, COLS, MINE_COUNT = DIFFICULTIES[CURRENT_DIFFICULTY]
CELL_SIZE = 30
UI_HEIGHT = 60
WIDTH = COLS * CELL_SIZE
HEIGHT = ROWS * CELL_SIZE + UI_HEIGHT
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Pygame Minesweeper")
# 顏色定義(經(jīng)典掃雷風(fēng)格)
COLORS = {
'bg': (192, 192, 192),
'cell': (210, 210, 210),
'cell_revealed': (180, 180, 180),
'border_light': (255, 255, 255),
'border_dark': (128, 128, 128),
'mine': (0, 0, 0),
'flag': (255, 0, 0),
'wrong_flag': (255, 100, 100),
'text': {
1: (0, 0, 255), # Blue
2: (0, 128, 0), # Green
3: (255, 0, 0), # Red
4: (0, 0, 128), # Dark Blue
5: (128, 0, 0), # Brown
6: (0, 128, 128), # Teal
7: (0, 0, 0), # Black
8: (128, 128, 128) # Gray
}
}
# ==================== 字體加載函數(shù) ====================
def load_safe_font(size, bold=False, italic=False):
"""安全加載字體,修復(fù) pygame 2.6+ 字體初始化 bug"""
try:
return pygame.font.SysFont('arial', size, bold=bold, italic=italic)
except (TypeError, OSError, AttributeError):
try:
font_dir = os.path.join(os.environ.get("WINDIR", "C:\\Windows"), "Fonts")
font_path = os.path.join(font_dir, "arial.ttf")
if os.path.exists(font_path):
f = pygame.font.Font(font_path, size)
if bold:
f.set_bold(True)
if italic:
f.set_italic(True)
return f
except Exception:
pass
f = pygame.font.Font(None, size)
if bold:
f.set_bold(True)
if italic:
f.set_italic(True)
return f
# 初始化字體
font = load_safe_font(20, bold=True)
big_font = load_safe_font(36, bold=True)
small_font = load_safe_font(16)
ui_font = load_safe_font(18)
# ==================== 游戲狀態(tài)枚舉 ====================
class GameState(Enum):
READY = 1
PLAYING = 2
WON = 3
LOST = 4
# ==================== 格子類 ====================
class Cell:
def __init__(self, row, col):
self.row = row
self.col = col
self.is_mine = False
self.is_revealed = False
self.is_flagged = False
self.is_wrong_flag = False
self.adjacent_mines = 0
def count_adjacent_mines(self, grid):
"""計(jì)算周圍8格的地雷數(shù)量"""
count = 0
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = self.row + dr, self.col + dc
if 0 <= nr < len(grid) and 0 <= nc < len(grid[0]):
if grid[nr][nc].is_mine:
count += 1
self.adjacent_mines = count
def reveal(self):
"""揭示格子"""
if self.is_revealed or self.is_flagged:
return
self.is_revealed = True
def draw(self, surface, cell_size, offset_x, offset_y, game_state):
x = offset_x + self.col * cell_size
y = offset_y + self.row * cell_size
# 繪制格子背景
if self.is_revealed:
pygame.draw.rect(surface, COLORS['cell_revealed'],
(x, y, cell_size, cell_size))
else:
pygame.draw.rect(surface, COLORS['cell'],
(x, y, cell_size, cell_size))
pygame.draw.line(surface, COLORS['border_light'],
(x, y), (x + cell_size, y), 2)
pygame.draw.line(surface, COLORS['border_light'],
(x, y), (x, y + cell_size), 2)
pygame.draw.line(surface, COLORS['border_dark'],
(x + cell_size, y), (x + cell_size, y + cell_size), 2)
pygame.draw.line(surface, COLORS['border_dark'],
(x, y + cell_size), (x + cell_size, y + cell_size), 2)
# 繪制內(nèi)容
if self.is_revealed:
if self.is_mine:
cx, cy = x + cell_size // 2, y + cell_size // 2
pygame.draw.circle(surface, COLORS['mine'], (cx, cy), cell_size // 3)
pygame.draw.line(surface, COLORS['flag'], (cx - 8, cy - 8), (cx + 8, cy + 8), 2)
pygame.draw.line(surface, COLORS['flag'], (cx + 8, cy - 8), (cx - 8, cy + 8), 2)
elif self.adjacent_mines > 0:
color = COLORS['text'].get(self.adjacent_mines, (0, 0, 0))
text = font.render(str(self.adjacent_mines), True, color)
text_rect = text.get_rect(center=(x + cell_size // 2, y + cell_size // 2))
surface.blit(text, text_rect)
elif self.is_flagged:
color = COLORS['wrong_flag'] if (self.is_wrong_flag and game_state == GameState.LOST) else COLORS['flag']
pygame.draw.line(surface, (100, 100, 100),
(x + cell_size // 3, y + cell_size // 4),
(x + cell_size // 3, y + 3 * cell_size // 4), 2)
pygame.draw.polygon(surface, color, [
(x + cell_size // 3, y + cell_size // 4),
(x + cell_size // 3 + 12, y + cell_size // 3),
(x + cell_size // 3, y + cell_size // 2 - 2)
])
# ==================== 游戲主類 ====================
class MineSweeperGame:
def __init__(self, rows=16, cols=16, mines=40):
self.rows = rows
self.cols = cols
self.mine_count = mines
self.cell_size = CELL_SIZE
self.offset_x = 0
self.offset_y = UI_HEIGHT
self.grid = []
self.first_click = True
self.state = GameState.READY
self.start_time = None
self.elapsed_time = 0
self.flags_placed = 0
self.create_grid()
def create_grid(self):
"""創(chuàng)建空白網(wǎng)格"""
self.grid = []
for r in range(self.rows):
row = [Cell(r, c) for c in range(self.cols)]
self.grid.append(row)
def place_mines(self, exclude_row, exclude_col):
"""排除首次點(diǎn)擊位置后隨機(jī)放置地雷"""
positions = [(r, c) for r in range(self.rows) for c in range(self.cols)
if not (r == exclude_row and c == exclude_col)]
mine_positions = random.sample(positions, self.mine_count)
for r, c in mine_positions:
self.grid[r][c].is_mine = True
for row in self.grid:
for cell in row:
cell.count_adjacent_mines(self.grid)
def flood_fill(self, start_row, start_col):
"""BFS擴(kuò)散揭示空白區(qū)域:揭示所有相連的空白格+邊緣數(shù)字格"""
queue = deque([(start_row, start_col)])
visited = set()
while queue:
r, c = queue.popleft()
if (r, c) in visited:
continue
visited.add((r, c))
cell = self.grid[r][c]
# 只跳過被標(biāo)記的格子,允許已揭示的格子繼續(xù)擴(kuò)散
if cell.is_flagged:
continue
# 揭示當(dāng)前格子(如果已揭示,reveal() 會(huì)直接返回,無副作用)
cell.reveal()
# 只有空白格子(相鄰地雷數(shù)=0)才繼續(xù)擴(kuò)散周圍
if cell.adjacent_mines == 0 and not cell.is_mine:
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
neighbor = self.grid[nr][nc]
# 只將未揭示且未標(biāo)記的鄰居加入隊(duì)列
if not neighbor.is_revealed and not neighbor.is_flagged:
queue.append((nr, nc))
def check_win(self):
"""檢查是否所有非地雷格子都被揭示"""
for row in self.grid:
for cell in row:
if not cell.is_mine and not cell.is_revealed:
return False
return True
def reveal_all_mines(self):
"""游戲失敗時(shí)揭示所有地雷"""
for row in self.grid:
for cell in row:
if cell.is_mine:
cell.is_revealed = True
elif cell.is_flagged and not cell.is_mine:
cell.is_wrong_flag = True
def mark_correct_flags(self):
"""游戲勝利時(shí)自動(dòng)標(biāo)記剩余地雷"""
for row in self.grid:
for cell in row:
if cell.is_mine and not cell.is_flagged:
cell.is_flagged = True
def chord_reveal(self, row, col):
"""雙擊已揭示數(shù)字:若旗幟數(shù)=數(shù)字,揭示其余格子"""
cell = self.grid[row][col]
if not cell.is_revealed or cell.adjacent_mines == 0:
return
flag_count = 0
to_reveal = []
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = row + dr, col + dc
if 0 <= nr < self.rows and 0 <= nc < self.cols:
neighbor = self.grid[nr][nc]
if neighbor.is_flagged:
flag_count += 1
elif not neighbor.is_revealed:
to_reveal.append((nr, nc))
if flag_count == cell.adjacent_mines:
for nr, nc in to_reveal:
target = self.grid[nr][nc]
if target.is_mine:
self.state = GameState.LOST
self.reveal_all_mines()
return
target.reveal()
if target.adjacent_mines == 0 and not target.is_mine:
self.flood_fill(nr, nc)
if self.check_win():
self.state = GameState.WON
self.mark_correct_flags()
def draw_ui(self):
"""繪制頂部信息欄"""
pygame.draw.rect(screen, (80, 80, 90), (0, 0, WIDTH, UI_HEIGHT))
pygame.draw.line(screen, (255, 255, 255), (0, UI_HEIGHT), (WIDTH, UI_HEIGHT), 2)
if self.start_time and self.state == GameState.PLAYING:
self.elapsed_time = (pygame.time.get_ticks() - self.start_time) // 1000
time_text = ui_font.render("Time: " + str(self.elapsed_time) + "s", True, (255, 255, 255))
screen.blit(time_text, (10, 20))
remaining = max(0, self.mine_count - self.flags_placed)
mine_text = ui_font.render("Mines: " + str(remaining), True, (255, 80, 80))
mine_width = mine_text.get_width()
screen.blit(mine_text, (WIDTH - mine_width - 10, 20))
diff_text = ui_font.render("[" + CURRENT_DIFFICULTY.upper() + "]", True, (150, 200, 255))
diff_width = diff_text.get_width()
screen.blit(diff_text, (WIDTH // 2 - diff_width // 2, 20))
if self.state == GameState.READY:
status = ui_font.render("Click to Start", True, (200, 200, 200))
elif self.state == GameState.PLAYING:
status = ui_font.render("Good Luck!", True, (150, 255, 150))
elif self.state == GameState.WON:
status = ui_font.render("YOU WIN!", True, (100, 255, 100))
else:
status = ui_font.render("GAME OVER", True, (255, 100, 100))
status_width = status.get_width()
screen.blit(status, (WIDTH // 2 - status_width // 2, 40))
def draw_overlay(self, title, subtitle, title_color, subtitle_color):
"""繪制游戲結(jié)束遮罩"""
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 160))
screen.blit(overlay, (0, 0))
title_surf = big_font.render(title, True, title_color)
subtitle_surf = font.render(subtitle, True, subtitle_color)
hint_surf = ui_font.render("Press [R] Restart | [M] Change Difficulty", True, (200, 200, 200))
screen.blit(title_surf, (WIDTH // 2 - title_surf.get_width() // 2, HEIGHT // 2 - 60))
screen.blit(subtitle_surf, (WIDTH // 2 - subtitle_surf.get_width() // 2, HEIGHT // 2 - 10))
screen.blit(hint_surf, (WIDTH // 2 - hint_surf.get_width() // 2, HEIGHT // 2 + 40))
def change_difficulty(self):
"""循環(huán)切換難度"""
global CURRENT_DIFFICULTY, ROWS, COLS, MINE_COUNT
diff_list = list(DIFFICULTIES.keys())
idx = diff_list.index(CURRENT_DIFFICULTY)
CURRENT_DIFFICULTY = diff_list[(idx + 1) % len(diff_list)]
ROWS, COLS, MINE_COUNT = DIFFICULTIES[CURRENT_DIFFICULTY]
self.__init__(ROWS, COLS, MINE_COUNT)
def handle_events(self):
"""處理輸入事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
return False
if event.type == pygame.MOUSEBUTTONDOWN:
if self.state in [GameState.READY, GameState.PLAYING]:
mx, my = pygame.mouse.get_pos()
col = (mx - self.offset_x) // self.cell_size
row = (my - self.offset_y) // self.cell_size
if 0 <= row < self.rows and 0 <= col < self.cols:
cell = self.grid[row][col]
# 左鍵:揭示
if event.button == 1 and not cell.is_flagged:
if self.first_click:
self.first_click = False
self.place_mines(row, col)
self.start_time = pygame.time.get_ticks()
self.state = GameState.PLAYING
if cell.is_mine:
self.state = GameState.LOST
self.reveal_all_mines()
else:
# 先揭示起始格子
cell.reveal()
# 如果是空白格,觸發(fā)擴(kuò)散
if cell.adjacent_mines == 0:
self.flood_fill(row, col)
# 檢查勝利
if self.check_win():
self.state = GameState.WON
self.mark_correct_flags()
# 右鍵:標(biāo)記旗幟
elif event.button == 3 and not cell.is_revealed:
if cell.is_flagged:
cell.is_flagged = False
self.flags_placed -= 1
else:
cell.is_flagged = True
self.flags_placed += 1
# 中鍵:快速揭示
elif event.button == 2:
if cell.is_revealed and cell.adjacent_mines > 0:
self.chord_reveal(row, col)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
self.__init__(self.rows, self.cols, self.mine_count)
elif event.key == pygame.K_m:
self.change_difficulty()
elif event.key == pygame.K_ESCAPE:
return False
return True
def draw(self):
"""繪制整個(gè)游戲畫面"""
screen.fill(COLORS['bg'])
for row in self.grid:
for cell in row:
cell.draw(screen, self.cell_size, self.offset_x, self.offset_y, self.state)
self.draw_ui()
if self.state == GameState.WON:
self.draw_overlay("VICTORY!", "Time: " + str(self.elapsed_time) + "s",
(100, 255, 100), (200, 255, 200))
elif self.state == GameState.LOST:
self.draw_overlay("BOOM!", "Better luck next time!",
(255, 100, 100), (255, 200, 200))
pygame.display.flip()
def run(self):
"""游戲主循環(huán)"""
clock = pygame.time.Clock()
running = True
while running:
running = self.handle_events()
self.draw()
clock.tick(60)
pygame.quit()
sys.exit()
# ==================== 程序入口 ====================
if __name__ == "__main__":
print("Pygame Minesweeper - Classic Edition")
print("Controls:")
print(" Left Click : Reveal cell")
print(" Right Click : Place/Remove flag")
print(" Middle Click / Ctrl+Left: Quick reveal around number")
print(" [R] : Restart game")
print(" [M] : Change difficulty (Easy/Medium/Hard)")
print(" [ESC]: Exit")
print("-" * 40)
game = MineSweeperGame(ROWS, COLS, MINE_COUNT)
game.run()
以上就是Python通過Pygame實(shí)現(xiàn)一個(gè)經(jīng)典掃雷游戲的詳細(xì)內(nèi)容,更多關(guān)于Python Pygame掃雷游戲的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python基于scipy實(shí)現(xiàn)信號(hào)濾波功能
本文將以實(shí)戰(zhàn)的形式基于scipy模塊使用Python實(shí)現(xiàn)簡單濾波處理。這篇文章主要介紹了Python基于scipy實(shí)現(xiàn)信號(hào)濾波功能,需要的朋友可以參考下2019-05-05
python實(shí)現(xiàn)無邊框進(jìn)度條的實(shí)例代碼
這篇文章主要介紹了python實(shí)現(xiàn)無邊框進(jìn)度條的實(shí)例代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Selenium+BeautifulSoup+json獲取Script標(biāo)簽內(nèi)的json數(shù)據(jù)
這篇文章主要介紹了Selenium+BeautifulSoup+json獲取Script標(biāo)簽內(nèi)的json數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
Python函數(shù)式編程指南(二):從函數(shù)開始
這篇文章主要介紹了Python函數(shù)式編程指南(二):從函數(shù)開始,本文講解了定義一個(gè)函數(shù)、使用函數(shù)賦值、閉包、作為參數(shù)等內(nèi)容,需要的朋友可以參考下2015-06-06

