---
title: 使用Python面向对象设计实现一个功能完整的命令行扫雷游戏，包含计时与第一步安全保护机制
created: 2026-03-30
updated: 2026-03-30
type: summary
tags: ["cli", "game", "notes", "python", "telegram"]
external: http://go/ui/posts/2299/
---

# 扫雷游戏 Python 实现

我来为你实现一个完整的命令行扫雷游戏，包含所有基本功能和加分要求。

## 代码实现

```python
#!/usr/bin/env python3
"""
命令行扫雷游戏
- 9×9 棋盘，10 个地雷
- 支持插旗、递归展开、第一步保护、计时功能
"""

import random
import time
import sys


class Minesweeper:
    """扫雷游戏类"""
    
    def __init__(self):
        """初始化游戏"""
        self.width = 9
        self.height = 9
        self.mine_count = 10
        
        # 游戏状态
        self.board = []           # 棋盘：-1=地雷，0-8=周围雷数
        self.revealed = []        # 是否已揭开
        self.flagged = []         # 是否已插旗
        self.game_over = False
        self.won = False
        self.first_move = True    # 是否第一步
        self.start_time = None
        self.end_time = None
        
        self._init_game()
    
    def _init_game(self):
        """初始化/重置游戏"""
        self.board = [[0 for _ in range(self.width)] for _ in range(self.height)]
        self.revealed = [[False for _ in range(self.width)] for _ in range(self.height)]
        self.flagged = [[False for _ in range(self.width)] for _ in range(self.height)]
        self.game_over = False
        self.won = False
        self.first_move = True
        self.start_time = None
        self.end_time = None
    
    def display_board(self):
        """显示当前棋盘状态"""
        print("\n    ", end="")
        for c in range(self.width):
            print(f" {c}", end="")
        print()
        print("   +" + "---" * self.width + "-+")
        
        for r in range(self.height):
            print(f" {r} |", end="")
            for c in range(self.width):
                cell = self._get_cell_display(r, c)
                print(f" {cell}", end="")
            print(" |")
        
        print("   +" + "---" * self.width + "-+")
    
    def _get_cell_display(self, row, col):
        """获取格子的显示字符"""
        if self.flagged[row][col]:
            return 'F'
        if not self.revealed[row][col]:
            return '.'
        if self.board[row][col] == -1:
            return '*'
        if self.board[row][col] == 0:
            return ' '
        return str(self.board[row][col])
    
    def _is_valid_coord(self, row, col):
        """检查坐标是否有效"""
        return 0 <= row < self.height and 0 <= col < self.width
    
    def reveal(self, row, col):
        """揭开格子（对外接口）"""
        # 坐标有效性检查
        if not self._is_valid_coord(row, col):
            print(f"❌ 无效坐标！请输入 0-{self.height - 1} 之间的行列号。")
            return
        
        # 检查是否已标记
        if self.flagged[row][col]:
            print("⚠️  该格子已插旗，请先取消插旗（再输入 f）！")
            return
        
        # 检查是否已揭开
        if self.revealed[row][col]:
            print("⚠️  该格子已经揭开了！")
            return
        
        # 执行揭开
        self._reveal(row, col)
    
    def _reveal(self, row, col):
        """揭开格子（内部递归实现）"""
        # 防御性检查
        if not self._is_valid_coord(row, col):
            return
        if self.revealed[row][col] or self.flagged[row][col]:
            return
        
        # 揭开格子
        self.revealed[row][col] = True
        
        # 第一步保护：生成地雷
        if self.first_move:
            self._generate_mines_safe(row, col)
            self.first_move = False
            self.start_time = time.time()
        
        # 踩到地雷
        if self.board[row][col] == -1:
            self.game_over = True
            self._reveal_all_mines()
            print("\n💥 你踩到地雷了！游戏结束！")
            return
        
        # 周围有雷，停止展开
        if self.board[row][col] > 0:
            pass
        # 周围无雷，递归展开
        else:
            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 self._is_valid_coord(nr, nc):
                        if not self.revealed[nr][nc] and not self.flagged[nr][nc]:
                            self._reveal(nr, nc)
        
        # 检查胜利
        if self._check_victory():
            self.game_over = True
            self.won = True
            self.end_time = time.time()
            print("\n🎉 恭喜你！所有非地雷格子都已揭开，你赢了！")
    
    def _generate_mines_safe(self, safe_row, safe_col):
        """在第一次揭开后生成地雷（保证安全）"""
        # 计算安全区域（第一次点击位置及周围8格）
        excluded = set()
        for dr in range(-1, 2):
            for dc in range(-1, 2):
                nr, nc = safe_row + dr, safe_col + dc
                if self._is_valid_coord(nr, nc):
                    excluded.add((nr, nc))
        
        # 生成可用位置列表
        available = [
            (r, c) for r in range(self.height)
            for c in range(self.width)
            if (r, c) not in excluded
        ]
        
        # 随机选择地雷位置
        mine_positions = random.sample(available, self.mine_count)
        
        # 放置地雷
        for r, c in mine_positions:
            self.board[r][c] = -1
        
        # 计算每个格子的周围雷数
        for r in range(self.height):
            for c in range(self.width):
                if self.board[r][c] == -1:
                    continue
                count = sum(
                    1 for dr in range(-1, 2)
                    for dc in range(-1, 2)
                    if dr != 0 or dc != 0
                    if self._is_valid_coord(r + dr, c + dc)
                    if self.board[r + dr][c + dc] == -1
                )
                self.board[r][c] = count
    
    def _reveal_all_mines(self):
        """游戏结束时显示所有地雷"""
        for r in range(self.height):
            for c in range(self.width):
                if self.board[r][c] == -1:
                    self.revealed[r][c] = True
    
    def _check_victory(self):
        """检查是否所有非地雷格子都已揭开"""
        for r in range(self.height):
            for c in range(self.width):
                if self.board[r][c] != -1 and not self.revealed[r][c]:
                    return False
        return True
    
    def toggle_flag(self, row, col):
        """切换格子标记状态"""
        # 坐标有效性检查
        if not self._is_valid_coord(row, col):
            print(f"❌ 无效坐标！请输入 0-{self.height - 1} 之间的行列号。")
            return
        
        # 检查是否已揭开
        if self.revealed[row][col]:
            print("⚠️  无法对已揭开的格子插旗！")
            return
        
        # 切换标记状态
        self.flagged[row][col] = not self.flagged[row][col]]
        
        if self.flagged[row][col]:
            print("🚩 已在该位置插旗。")
        else:
            print("🚩 已取消插旗。")
    
    def format_time(self, seconds):
        """格式化时间显示"""
        if seconds < 60:
            return f"{int(seconds)} 秒"
        minutes = int(seconds // 60)
        secs = int(seconds % 60)
        return f"{minutes} 分 {secs} 秒"
    
    def play(self):
        """游戏主循环"""
        self.display_board()
        print("\n📋 命令说明：")
        print("   r 行 列 - 揭开格子（例：r 3 4）")
        print("   f 行 列 - 插旗/取消插旗（例：f 0 0）")
        print("   q       - 退出游戏")
        print("   坐标范围：0-8")
        
        while True:
            try:
                user_input = input("\n请输入命令: ").strip().split()
            except (EOFError, KeyboardInterrupt):
                print("\n\n👋 退出游戏，再见！")
                sys.exit(0)
            
            if not user_input:
                continue
            
            cmd = user_input[0].lower()
            
            # 退出
            if cmd == 'q':
                print("👋 退出游戏，再见！")
                break
            
            # 揭开
            elif cmd == 'r':
                if len(user_input) < 3:
                    print("❌ 命令格式错误！正确格式：r 行 列（例：r 3 4）")
                    continue
                try:
                    row, col = int(user_input[1]), int(user_input[2])
                except ValueError:
                    print("❌ 行和列必须是数字！")
                    continue
                self.reveal(row, col)
            
            # 插旗
            elif cmd == 'f':
                if len(user_input) < 3:
                    print("❌ 命令格式错误！正确格式：f 行 列（例：f 0 0）")
                    continue
                try:
                    row, col = int(user_input[1]), int(user_input[2])
                except ValueError:
                    print("❌ 行和列必须是数字！")
                    continue
                self.toggle_flag(row, col)
            
            # 无效命令
            else:
                print(f"❌ 未知命令：{cmd}")
                print("   有效命令：r（揭开）、f（插旗）、q（退出）")
                continue
            
            # 检查游戏是否结束
            if self.game_over:
                self.display_board()
                elapsed = self.end_time - self.start_time
                print(f"\n⏱️  用时：{self.format_time(elapsed)}")
                break
            
            # 显示当前棋盘
            self.display_board()
            flag_count = sum(sum(row) for row in self.flagged)
            print(f"\n已插旗数量：{flag_count} / {self.mine_count}")


def main():
    """主函数"""
    print("=" * 40)
    print("       🎮  欢迎来到扫雷游戏  🎮")
    print("=" * 40)
    print("\n📊 游戏配置：9×9 棋盘，10 个地雷")
    
    while True:
        game = Minesweeper()
        game.play()
        
        # 询问是否再来一局
        try:
            again = input("\n是否再来一局？（y/n）: ").strip().lower()
            if again != 'y' and again != 'Y':
                print("\n👋 感谢游玩，再见！")
                break
        except (EOFError, KeyboardInterrupt):
            print("\n\n👋 退出游戏，再见！")
            break


if __name__ == "__main__":
    main()
```

## 功能说明

### 已实现的功能

| 功能 | 说明 |
|------|------|
| ✅ 基本规则 | 9×9 棋盘，10 个地雷 |
| ✅ 三种状态 | 未揭开(. )、已揭开(数字/空格)、已插旗(F) |
| ✅ 递归展开 | 揭开空白格子时自动展开相邻格子 |
| ✅ 插旗功能 | `f` 命令切换插旗状态 |
| ✅ 第一步保护 | 第一次揭开后再生成地雷，确保安全 |
| ✅ 计时系统 | 记录并显示游戏用时 |
| ✅ 输入验证 | 处理各种非法输入 |
| ✅ 边界检查 | 坐标越界检查 |

### 使用方法

```bash
python minesweeper.py
```

### 游戏演示

```
========================================
       🎮  欢迎来到扫雷游戏  🎮
========================================

📊 游戏配置：9×9 棋盘，10 个地雷

    0 1 2 3 4 5 6 7 8
   +---------------------+
 0 | . . . . . . . . . |
 1 | . . . . . . . . . |
 2 | . . . . . . . . . |
 3 | . . . . . . . . . |
 4 | . . . . . . . . . |
 5 | . . . . . . . . . |
 6 | . . . . . . . . . |
 7 | . . . . . . . . . |
 8 | . . . . . . . . . |
   +---------------------+

📋 命令说明：
   r 行 列 - 揭开格子（例：r 3 4）
   f 行 列 - 插旗/取消插旗（例：f 0 0）
   q       - 退出游戏
   坐标范围：0-8

请输入命令: r 4 4

    0 1 2 3 4 5 6 7 8
   +---------------------+
 4 | . . . 1 0 1 . . . |
 5 | . . . 1 0 1 . . . |
 6 | . . . 1 1 1 . . . |
   +---------------------+
```

### 代码特点

1. **面向对象设计**：使用 `Minesweeper` 类封装所有游戏逻辑
2. **防御性编程**：多处检查确保程序稳定运行
3. **清晰的代码结构**：每个方法职责单一，易于维护
4. **友好的用户交互**：错误提示清晰，指导用户正确操作
