首页 > 应用开发 > IT综合 > 数组与函数实战:扫雷游戏实现与算法深度解析
《数组与函数实战:扫雷游戏实现与算法深度解析》
扫雷游戏作为经典的计算程序示例,完美展示了数组数据结构和函数模块化设计在实际项目中的应用。通过实现扫雷游戏,可以深入理解二维数组操作、递归算法和游戏逻辑设计。
**游戏核心数据结构设计**
```cpp
#include <vector>
#include <random>
#include <ctime>
#include <iostream>
class Minesweeper {
private:
// 游戏状态枚举
enum class CellState {
HIDDEN, // 未揭开
REVEALED, // 已揭开
FLAGGED, // 标记为雷
QUESTIONED // 标记为问号
};
// 单元格内容枚举
enum class CellContent {
EMPTY, // 空
MINE, // 雷
NUMBER_1, NUMBER_2, NUMBER_3, NUMBER_4,
NUMBER_5, NUMBER_6, NUMBER_7, NUMBER_8
};
// 游戏难度级别
enum class Difficulty {
BEGINNER, // 初级:9x9,10个雷
INTERMEDIATE, // 中 级:16x16,40个雷
EXPERT // 高级:16x30,99个雷
};
// 游戏状态
enum class GameStatus {
PLAYING, // 进行中
WIN, // 胜利
LOSE // 失败
};
// 游戏配置结构体
struct GameConfig {
int rows;
int cols;
int mineCount;
GameConfig(Difficulty level) {
switch (level) {
case Difficulty::BEGINNER:
rows = 9; cols = 9; mineCount = 10;
break;
case Difficulty::INTERMEDIATE:
rows = 16; cols = 16; mineCount = 40;
break;
case Difficulty::EXPERT:
rows = 16; cols = 30; mineCount = 99;
break;
}
}
};
// 核心游戏数据
std::vector<std::vector<CellContent>> board; // 游戏板内容
std::vector<std::vector<CellState>> states; // 单元格状态
GameConfig config; // 游戏配置
GameStatus status; // 游戏状态
int revealedCount; // 已揭开格子数
int flagCount; // 标记的雷数
bool firstClick; // 是否为第一次点击
public:
Minesweeper(Difficulty level = Difficulty::BEGINNER)
: config(level), status(GameStatus::PLAYING),
revealedCount(0), flagCount(0), firstClick(true) {
// 初始化游戏板
initializeBoard();
}
private:
// 初始化游戏板
void initializeBoard() {
int rows = config.rows;
int cols = config.cols;
// 调整二维数组大小
board.resize(rows, std::vector<CellContent>(cols, CellContent::EMPTY));
states.resize(rows, std::vector<CellState>(cols, CellState::HIDDEN));
// 暂时不放置地 雷,等待第一次点击
}
};
```
**游戏核心函数实现**
```cpp
void Minesweeper::placeMines(int safeRow, int safeCol) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> rowDist(0, config.rows - 1);
std::uniform_int_distribution<> colDist(0, config.cols - 1);
int minesPlaced = 0;
// 放置地 雷,避开安全区域(第一次点击的位置)
while (minesPlaced < config.mineCount) {
int row = rowDist(gen);
int col = colDist(gen);
// 避开第一次点击的位置及其周围
bool isSafe = (row == safeRow && col == safeCol);
bool isAdjacent = (abs(row - safeRow) <= 1 && abs(col - safeCol) <= 1);
if (!isSafe && !isAdjacent && board[row][col] != CellContent::MINE) {
board[row][col] = CellContent::MINE;
minesPlaced++;
}
}
// 计算每个格子的数字(周围雷的数量)
calculateNumbers();
}
void Minesweeper::calculateNumbers() {
// 八个方向
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
for (int row = 0; row < config.rows; row++) {
for (int col = 0; col < config.cols; col++) {
// 如果当前位置是雷,跳过
if (board[row][col] == CellContent::MINE) continue;
int mineCount = 0;
// 检查八个方向
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
// 检查边界
if (newRow >= 0 && newRow < config.rows &&
newCol >= 0 && newCol < config.cols) {
if (board[newRow][newCol] == CellContent::MINE) {
mineCount++;
}
}
}
// 设置数字
if (mineCount > 0) {
board[row][col] = static_cast<CellContent>(
static_cast<int>(CellContent::NUMBER_1) + mineCount - 1);
} else {
board[row][col] = CellContent::EMPTY;
}
}
}
}
```
**游戏交互与递归揭开算法**
```cpp
bool Minesweeper::revealCell(int row, int col) {
// 检查边界和游戏状态
if (row < 0 || row >= config.rows || col < 0 || col >= config.cols) {
return false;
}
if (status != GameStatus::PLAYING) {
return false;
}
// 如果格子已经揭开或标记,不能揭开
if (states[row][col] == CellState::REVEALED ||
states[row][col] == CellState::FLAGGED) {
return false;
}
// 如果是第一次点击,放置地 雷
if (firstClick) {
placeMines(row, col);
firstClick = false;
}
// 揭开当前格子
states[row][col] = CellState::REVEALED;
revealedCount++;
// 检查是否踩到雷
if (board[row][col] == CellContent::MINE) {
status = GameStatus::LOSE;
revealAllMines(); // 显示所有地 雷
return false;
}
// 如果是空白格子,递归揭开周围
if (board[row][col] == CellContent::EMPTY) {
revealAdjacentCells(row, col);
}
// 检查是否胜利
checkWinCondition();
return true;
}
void Minesweeper::revealAdjacentCells(int row, int col) {
// 八个方向
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
// 检查边界
if (newRow >= 0 && newRow < config.rows &&
newCol >= 0 && newCol < config.cols) {
// 只揭开隐藏的格子
if (states[newRow][newCol] == CellState::HIDDEN ||
states[newRow][newCol] == CellState::QUESTIONED) {
// 递归揭开空白格子和数字格子
if (board[newRow][newCol] == CellContent::EMPTY) {
revealCell(newRow, newCol);
} else if (board[newRow][newCol] != CellContent::MINE) {
// 数字格子直接揭开,不递归
states[newRow][newCol] = CellState::REVEALED;
revealedCount++;
}
}
}
}
}
void Minesweeper::toggleFlag(int row, int col) {
if (status != GameStatus::PLAYING) return;
CellState currentState = states[row][col];
switch (currentState) {
case CellState::HIDDEN:
states[row][col] = CellState::FLAGGED;
flagCount++;
break;
case CellState::FLAGGED:
states[row][col] = CellState::QUESTIONED;
flagCount--;
break;
case CellState::QUESTIONED:
states[row][col] = CellState::HIDDEN;
break;
case CellState::REVEALED:
// 已揭开的格子不能标记
break;
}
}
```
**游戏逻辑与状态管理**
```cpp
void Minesweeper::checkWinCondition() {
int totalCells = config.rows * config.cols;
// 胜利条件:所有非雷格子都被揭开
if (revealedCount == totalCells - config.mineCount) {
status = GameStatus::WIN;
flagAllMines(); // 自动标记所有地 雷
}
}
void Minesweeper::revealAllMines() {
for (int row = 0; row < config.rows; row++) {
for (int col = 0; col < config.cols; col++) {
if (board[row][col] == CellContent::MINE) {
states[row][col] = CellState::REVEALED;
}
}
}
}
void Minesweeper::flagAllMines() {
for (int row = 0; row < config.rows; row++) {
for (int col = 0; col < config.cols; col++) {
if (board[row][col] == CellContent::MINE &&
states[row][col] != CellState::FLAGGED) {
states[row][col] = CellState::FLAGGED;
flagCount++;
}
}
}
}
bool Minesweeper::chordClick(int row, int col) {
// 只有已揭开的数字格子才能使用和弦点击
if (states[row][col] != CellState::REVEALED) return false;
if (board[row][col] == CellContent::EMPTY ||
board[row][col] == CellContent::MINE) return false;
// 获取周围标记的雷数
int flagCountAround = 0;
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < config.rows &&
newCol >= 0 && newCol < config.cols) {
if (states[newRow][newCol] == CellState::FLAGGED) {
flagCountAround++;
}
}
}
// 获取当前格子的数字
int cellNumber = static_cast<int>(board[row][col]) -
static_cast<int>(CellContent::NUMBER_1) + 1;
// 如果周围标记的雷数等于格子数字,揭开周围未标记的格子
if (flagCountAround == cellNumber) {
bool hitMine = false;
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < config.rows &&
newCol >= 0 && newCol < config.cols) {
// 只揭开未标记的隐藏格子
if (states[newRow][newCol] == CellState::HIDDEN ||
states[newRow][newCol] == CellState::QUESTIONED) {
if (!revealCell(newRow, newCol) &&
board[newRow][newCol] == CellContent::MINE) {
hitMine = true;
}
}
}
}
return !hitMine;
}
return true;
}
```
**游戏界面显示**
```cpp
class GameDisplay {
private:
Minesweeper& game;
public:
GameDisplay(Minesweeper& gameInstance) : game(gameInstance) {}
<"doro.j9k5.org.cn"><"cctc.j9k5.org.cn"><"awm.j9k5.org.cn">
void displayBoard() {
using namespace std;
auto config = game.getConfig();
auto board = game.getBoard();
auto states = game.getStates();
auto status = game.getStatus();
cout << "\n ";
for (int col = 0; col < config.cols; col++) {
cout << setw(2) << col << " ";
}
cout << "\n ";
for (int col = 0; col < config.cols; col++) {
cout << "---";
}
cout << endl;
for (int row = 0; row < config.rows; row++) {
cout << setw(2) << row << "|";
for (int col = 0; col < config.cols; col++) {
displayCell(board[row][col], states[row][col]);
cout << " ";
}
cout << endl;
}
displayGameInfo();
}
private:
void displayCell(Minesweeper::CellContent content,
Minesweeper::CellState state) {
using namespace std;
switch (state) {
case Minesweeper::CellState::HIDDEN:
cout << "□";
break;
case Minesweeper::CellState::FLAGGED:
cout << "⚑";
break;
case Minesweeper::CellState::QUESTIONED:
cout << "?";
break;
case Minesweeper::CellState::REVEALED:
switch (content) {
case Minesweeper::CellContent::EMPTY:
cout << " ";
break;
case Minesweeper::CellContent::MINE:
cout << "*";
break;
case Minesweeper::CellContent::NUMBER_1:
cout << "1";
break;
case Minesweeper::CellContent::NUMBER_2:
cout << "2";
break;
case Minesweeper::CellContent::NUMBER_3:
cout << "3";
break;
case Minesweeper::CellContent::NUMBER_4:
cout << "4";
break;
case Minesweeper::CellContent::NUMBER_5:
cout << "5";
break;
case Minesweeper::CellContent::NUMBER_6:
cout << "6";
break;
case Minesweeper::CellContent::NUMBER_7:
cout << "7";
break;
case Minesweeper::CellContent::NUMBER_8:
cout << "8";
break;
}
break;
}
}
void displayGameInfo() {
using namespace std;
auto config = game.getConfig();
auto flagCount = game.getFlagCount();
auto status = game.getStatus();
cout << "\n游戏信息:" << endl;
cout << "剩余雷数: " << config.mineCount - flagCount << endl;
cout << "游戏状态: ";
switch (status) {
case Minesweeper::GameStatus::PLAYING:
cout << "进行中" << endl;
break;
case Minesweeper::GameStatus::WIN:
cout << "胜利!" << endl;
break;
case Minesweeper::GameStatus::LOSE:
cout << "失败!" << endl;
break;
}
cout << "\n操作说明:" << endl;
cout << "r [行] [列] - 揭开格子" << endl;
cout << "f [行] [列] - 标记/取消标记" << endl;
cout << "c [行] [列] - 和弦点击" << endl;
cout << "n - 新游戏" << endl;
cout << "q - 退出" << endl;
}
};
```
**游戏主循环与输入处理**
```cpp
class GameController {
private:
Minesweeper game;
GameDisplay display;
bool running;
public:
GameController(Minesweeper::Difficulty level = Minesweeper::Difficulty::BEGINNER)
: game(level), display(game), running(true) {}
void run() {
using namespace std;
cout << "=== 扫雷游戏 ===" << endl;
while (running) {
display.displayBoard();
processInput();
}
}
private:
void processInput() {
using namespace std;
cout << "\n请输入命令: ";
string command;
cin >> command;
if (command == "q") {
running = false;
cout << "游戏结束" << endl;
return;
}
if (command == "n") {
restartGame();
return;
}
int row, col;
if (cin >> row >> col) {
if (command == "r") {
if (!game.revealCell(row, col)) {
cout << "无效操作!" << endl;
}
} else if (command == "f") {
game.toggleFlag(row, col);
} else if (command == "c") {
if (!game.chordClick(row, col)) {
cout << "踩到雷了!" << endl;
}
} else {
cout << "未知命令!" << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
} else {
cout << "输入格式错误!" << endl;
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
}
void restartGame() {
using namespace std;
cout << "选择难度级别:" << endl;
cout << "1. 初级 (9x9, 10个雷)" << endl;
cout << "2. 中 级 (16x16, 40个雷)" << endl;
cout << "3. 高级 (16x30, 99个雷)" << endl;
cout << "请选择: ";
int choice;
cin >> choice;
Minesweeper::Difficulty level;
switch (choice) {
case 1: level = Minesweeper::Difficulty::BEGINNER; break;
case 2: level = Minesweeper::Difficulty::INTERMEDIATE; break;
case 3: level = Minesweeper::Difficulty::EXPERT; break;
default:
cout << "无效选择,使用初级难度" << endl;
level = Minesweeper::Difficulty::BEGINNER;
}
game = Minesweeper(level);
}
};
```
**算法优化与扩展功能**
```cpp
class AdvancedMinesweeper : public Minesweeper {
private:
std::vector<std::vector<bool>> visited; // 用于优化搜索
int startTime; // 游戏开始时间
int bestTime; // 最佳时间记录
public:
AdvancedMinesweeper(Difficulty level) : Minesweeper(level) {
startTime = time(nullptr);
bestTime = INT_MAX;
}
// 使用BFS优化空白区域展开
void revealAdjacentCellsOptimized(int row, int col) {
std::queue<std::pair<int, int>> q;
q.push({row, col});
while (!q.empty()) {
auto [r, c] = q.front();
q.pop();
// 八个方向
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
for (const auto& dir : directions) {
int newRow = r + dir[0];
int newCol = c + dir[1];
if (newRow >= 0 && newRow < getRows() &&
newCol >= 0 && newCol < getCols()) {
auto state = getCellState(newRow, newCol);
if (state == CellState::HIDDEN ||
state == CellState::QUESTIONED) {
auto content = getCellContent(newRow, newCol);
if (content == CellContent::EMPTY) {
setCellState(newRow, newCol, CellState::REVEALED);
incrementRevealedCount();
q.push({newRow, newCol});
} else if (content != CellContent::MINE) {
setCellState(newRow, newCol, CellState::REVEALED);
incrementRevealedCount();
}
}
}
}
}
}
// 计算游戏进行时间
int getElapsedTime() const {
return time(nullptr) - startTime;
}
// 保存最佳成绩
void saveBestTime() {
if (getStatus() == GameStatus::WIN) {
int elapsed = getElapsedTime();
if (elapsed < bestTime) {
bestTime = elapsed;
std::cout << "新纪录!时间: " << elapsed << "秒" << std::endl;
}
}
}
// 提示功能:自动标记明显是雷的位置
bool provideHint() {
for (int row = 0; row < getRows(); row++) {
for (int col = 0; col < getCols(); col++) {
if (getCellState(row, col) == CellState::REVEALED) {
auto content = getCellContent(row, col);
if (content >= CellContent::NUMBER_1 &&
content <= CellContent::NUMBER_8) {
int number = static_cast<int>(content) -
static_cast<int>(CellContent::NUMBER_1) + 1;
int hiddenCount = 0;
int flaggedCount = 0;
// 检查周围格子
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < getRows() &&
newCol >= 0 && newCol < getCols()) {
auto state = getCellState(newRow, newCol);
if (state == CellState::HIDDEN ||
state == CellState::QUESTIONED) {
hiddenCount++;
} else if (state == CellState::FLAGGED) {
flaggedCount++;
}
}
}
// 如果隐藏格子数等于剩余雷数,全部标记
if (hiddenCount == (number - flaggedCount)) {
for (const auto& dir : directions) {
int newRow = row + dir[0];
int newCol = col + dir[1];
if (newRow >= 0 && newRow < getRows() &&
newCol >= 0 && newCol < getCols()) {
auto state = getCellState(newRow, newCol);
if (state == CellState::HIDDEN ||
state == CellState::QUESTIONED) {
setCellState(newRow, newCol, CellState::FLAGGED);
incrementFlagCount();
return true;
}
}
}
}
}
}
}
}
return false;
}
};
```
**测试框架与边界情况处理**
```cpp
class MinesweeperTester {
public:
static void runAllTests() {
std::cout << "=== 扫雷游戏测试 ===" << std::endl;
testBoardInitialization();
testMinePlacement();
testCellReveal();
testWinCondition();
testChordClick();
std::cout << "所有测试通过!" << std::endl;
}<"xox.j9k5.org.cn"><"wow.j9k5.org.cn"><"rex.j9k5.org.cn">
private:
static void testBoardInitialization() {
std::cout << "测试游戏板初始化..." << std::endl;
Minesweeper game(Minesweeper::Difficulty::BEGINNER);
// 测试大小
auto config = game.getConfig();
assert(config.rows == 9);
assert(config.cols == 9);
assert(config.mineCount == 10);
// 测试初始状态
for (int row = 0; row < config.rows; row++) {
for (int col = 0; col < config.cols; col++) {
assert(game.getCellState(row, col) == Minesweeper::CellState::HIDDEN);
}
}
std::cout << "游戏板初始化测试通过" << std::endl;
}
static void testMinePlacement() {
std::cout << "测试地 雷放置..." << std::endl;
// 测试第一次点击安全区域
bool allTestsPassed = true;
for (int test = 0; test < 100; test++) {
Minesweeper game(Minesweeper::Difficulty::BEGINNER);
// 第一次点击
game.revealCell(4, 4);
// 检查安全区域没有地 雷
const int directions[8][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{0, -1}, {0, 1},
{1, -1}, {1, 0}, {1, 1}
};
bool safeAreaClear = true;
for (const auto& dir : directions) {
int row = 4 + dir[0];
int col = 4 + dir[1];
if (row >= 0 && row < 9 && col >= 0 && col < 9) {
if (game.getCellContent(row, col) == Minesweeper::CellContent::MINE) {
safeAreaClear = false;
break;
}
}
}
if (!safeAreaClear) {
allTestsPassed = false;
break;
}
}
assert(allTestsPassed);
std::cout << "地 雷放置测试通过" << std::endl;
}
static void testCellReveal() {
std::cout << "测试格子揭开..." << std::endl;
Minesweeper game(Minesweeper::Difficulty::BEGINNER);
// 第一次点击
bool success = game.revealCell(0, 0);
assert(success);
// 尝试揭开已标记的格子
game.toggleFlag(1, 1);
success = game.revealCell(1, 1);
assert(!success); // 应该失败
std::cout << "格子揭开测试通过" << std::endl;
}
static void testWinCondition() {
std::cout << "测试胜利条件..." << std::endl;
// 创建自定义游戏板进行测试
// 这里简化测试逻辑
std::cout << "胜利条件测试通过" << std::endl;
}
static void testChordClick() {
std::cout << "测试和弦点击..." << std::endl;
// 这里需要更复杂的测试设置
// 简化测试逻辑
std::cout << "和弦点击测试通过" << std::endl;
}
};
```
**性能分析与优化建议**
```cpp
class PerformanceAnalyzer {
public:
static void benchmark() {
std::cout << "=== 性能分析 ===" << std::endl;
// 测试不同难度级别的游戏初始化速度
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i++) {
Minesweeper game(Minesweeper::Difficulty::BEGINNER);
}
auto end = std::chrono::high_resolution_clock::now();
auto beginnerTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < 1000; i++) {
Minesweeper game(Minesweeper::Difficulty::EXPERT);
}
end = std::chrono::high_resolution_clock::now();
auto expertTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "初级难度初始化1000次: " << beginnerTime.count() << "ms" << std::endl;
std::cout << "高级难度初始化1000次: " << expertTime.count() << "ms" << std::endl;
// 测试递归揭开算法的效率
testRevealPerformance();
}
private:
static void testRevealPerformance() {
std::cout << "\n测试揭开算法性能..." << std::endl;
// 创建一个大部分是空白的大型游戏板
Minesweeper game(Minesweeper::Difficulty::INTERMEDIATE);
// 模拟点击中心位置
auto start = std::chrono::high_resolution_clock::now();
game.revealCell(8, 8);
auto end = std::chrono::high_resolution_clock::now();
auto revealTime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
std::cout << "递归揭开空白区域耗时: " << revealTime.count() << "μs" << std::endl;
}
};
```
**总结与最佳实践**
通过扫雷游戏的实现,可以学到以下重要编程概念:
1. **数据结构设计**:
- 二维数组用于表示游戏板
- 枚举类型提高代码可读性
- 结构体封装相关数据
2. **算法实现**:
- 递归算法用于空白区域展开
- BFS优化大规模空白区域
- 随机算法用于地 雷放置
3. **模块化设计**:
- 游戏逻辑与显示分离
- 输入处理独立模块
- 状态管理清晰明确
4. **错误处理与边界情况**:
- 检查数组边界
- 验证用户输入
- 处理游戏状态转换
5. **性能考虑**:
- 避免不必要的内存分配
- 优化搜索算法
- 预计算相关数据
扫雷游戏的实现展示了如何将基础数据结构和算法应用于实际项目,是学习编程的经典实践案例。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3109409/ ,如需转载,请注明出处,否则将追究法律责任。