目录
前言:
扫雷是一款经典而富有挑战性的电脑游戏,它不仅考验玩家的智力和策略,还能够提升观察力和反应能力。本文将介绍如何使用C语言来编写一个简单的扫雷游戏,让我们一起来开发这个有趣的项目吧!
效果展示
🔗:https://live.csdn.net/v/315352
【c语言】扫雷游戏(包含递归展开+标记功能) 源码可运行
1.游戏规则
扫雷游戏的基本规则很简单:在一个由方格组成的棋盘上,随机分布着一些地雷。玩家需要揭开方格,如果方格下面没有地雷,则显示该方格周围的地雷数目;如果周围的地雷数为零则展开棋盘周围的位置;如果方格下面有地雷,则游戏结束。玩家可以标记怀疑有地雷的方格,以帮助自己记忆。
2.开始编写
搭建起这个扫雷游戏的框架,我们首先创建一个 game() 函数,用于存放游戏的主要逻辑。在函数中,我们使用一个二维字符数组来表示棋盘,其中每个方格的状态都会被保存。另外,我们还需要一个字符数组来记录已经被翻开的方格。
//test.c
void game()
{
char board[SIZE][SIZE] = { 0 }; // 棋盘数组,用于保存格子的状态
char revealed[SIZE][SIZE] = { 0 }; // 标记已翻开的格子
srand(time(NULL)); // 初始化随机种子
InitBord(board, revealed); // 初始化棋盘和已翻开格子的状态
system("cls"); // 清屏
printf("**************** 游戏难度 ****************\n");
printf("***(1)5个雷 (2)10个雷 (3)15个雷 ***\n");
printf("***(4)20个雷 (5)25个雷 (6)30个雷 ***\n");
printf("***(7)35个雷 (8)40个雷 (9)45个雷 ...\n");
AGAIN:
printf("---请选择游戏难度:>");
int Level = 0;
scanf("%d", &Level);
MineNum = Level * 5; // 设置地雷数量
if (MineNum > SIZE * SIZE)
{
printf("你的所选的难度太大,不适合该棋盘大小,请重新输入!\n");
goto AGAIN; // 如果地雷数量超过棋盘格子总数,则要求重新选择难度
}
placeMine(board); // 在棋盘上布置地雷
while (1)
{
system("cls"); // 清屏
printf("---------扫雷游戏----------\n");
printf("*** 雷数:%2d 标记数:%2d ***\n", MineNum, MarkNum);
printf("---------------------------\n");
if (WinNum == MineNum && MarkNum == MineNum)
{
printf("!!!!!!!!!恭喜你赢啦!!!!!!!!!!\n");
printMine(board); // 打印所有地雷的位置
MarkNum = 0;
WinNum = 0;
break; // 游戏胜利,退出循环
}
if (printBoard(board, revealed)) // 打印棋盘和已翻开的格子
{
printf("你踩雷了!\n"); // 打印棋盘和已翻开的格子
printMine(board);
MarkNum = 0;
WinNum = 0;
break;
}
printf("\n请输入要揭示的格子坐标(x ,y);\n");
printf("如果怀疑哪里是雷输入请在做标前加上负号(-x,-y)\n");
printf("重复标记可以取消标记怀疑的雷!(雷被全部标记正确为胜)\n");
printf("请输入:");
int x, y;
scanf("%d %d", &y, &x);
if (x > 0 && y > 0 && x <= SIZE && y <= SIZE) // 揭示格子
{
reveal(revealed, board, x - 1, y - 1);
}
else if (x < 0 && y < 0 && x >= -SIZE && y >= -SIZE)
{
MarkMine(board, revealed, -x - 1, -y - 1); // 标记雷
}
else
{
printf("输入有误请重新输入!\n"); // 输入不合法,要求重新输入
}
}
}
函数文件解析
(1)初始化函数(InitBoard)
代码如下:
//初始化棋盘
void InitBord(char board[SIZE][SIZE] ,char revealed[SIZE][SIZE])
{
int i, j;
for ( i = 0; i < SIZE; i++)
{
for ( j = 0; j < SIZE; j++)
{
board[i][j] = EMPTY;
revealed[i][j] = EMPTY;
}
}
}
(2)在棋盘上布雷(placeMine)
代码如下:
//在棋盘上布置雷
void placeMine(char board[SIZE][SIZE])
{
int count = 0;
while (count < MineNum)
{
int x = rand() % SIZE; //在棋盘上随机布雷
int y = rand() % SIZE;
if (board[x][y] == EMPTY)
{
board[x][y] = MINE;
count++;
}
}
}
(3)统计周围八个格子的地雷数(countMines)
这段代码通过遍历当前坐标 (x, y) 周围八个方向上的格子,使用偏移量计算出每个方向上的新坐标 (nx, ny)。然后通过检查新坐标的合法性和对应格子是否为地雷,来统计周围的地雷数量。最后将统计到的地雷数量返回。
int countMines(char board[SIZE][SIZE], int x, int y)
{
int count = 0;
int round[8][2] = { { 1, -1}, { 1, 0}, { 1, 1}
,{ 0, -1}, { 0, 1}
,{-1, -1}, {-1, 0}, {-1, 1} };
// 定义一个二维数组 round 来表示相对于当前坐标 (x, y) 周围八个格子的偏移量
// 遍历每个方向上的格子
for (int i = 0; i < 8; i++)
{
int dx = round[i][0]; // 根据 round 数组获取当前方向的 x 偏移量
int dy = round[i][1]; // 根据 round 数组获取当前方向的 y 偏移量
int nx = x + dx; // 计算当前方向的 x 坐标
int ny = y + dy; // 计算当前方向的 y 坐标
// 检查当前方向的坐标是否在合法范围内,并且当前方向的格子是否为地雷
if (nx >= 0 && ny >= 0 && nx < SIZE && ny < SIZE && board[nx][ny] == MINE)
{
count++;// 如果是地雷,则地雷数量加一
}
}
return count; // 返回统计到的地雷数量
}
(4)揭示格子(reveal)
这段代码首先检查当前待揭示的格子是否已经被揭示过或者标记过,如果是,则直接返回。否则,将当前格子标记为已揭示状态。然后判断当前格子周围是否有地雷,如果没有地雷,则递归地揭示周围八个方向上的格子。递归过程中,会不断检查并揭示每个格子周围的相邻格子,直到不能再继续揭示为止。这段代码实现了扫雷游戏中的连锁揭露效果。
//揭示格子
void reveal(char revealed[SIZE][SIZE], char board[SIZE][SIZE], int x, int y)
{
//如果格子已经揭示过,或者标记过,直接返回
if (revealed[x][y] == REVEAL || revealed[x][y] == MARK)
{
return;
}
//标记格子为以揭示
revealed[x][y] = REVEAL;
//如果格子周围没有地雷,则递归揭示周围的格子
if (countMines(board, x, y) == 0)
{
int round[8][2] = { { 1, -1}, { 1, 0}, { 1, 1}
,{ 0, -1}, { 0, 1}
,{-1, -1}, {-1, 0}, {-1, 1} };
// 定义一个二维数组 round 来表示相对于当前坐标 (x, y) 周围八个格子的偏移量
// 遍历每个方向上的格子
for (int i = 0; i < 8; i++)
{
int dx = round[i][0]; // 根据 round 数组获取当前方向的 x 偏移量
int dy = round[i][1]; // 根据 round 数组获取当前方向的 y 偏移量
int nx = x + dx; // 计算当前方向的 x 坐标
int ny = y + dy; // 计算当前方向的 y 坐标
// 检查当前方向的坐标是否在合法范围内
if (nx >= 0 && ny >= 0 && nx < SIZE && ny < SIZE)
{
reveal(revealed, board, nx, ny);// 递归揭示当前方向上的格子
}
}
}
}
(5)标记出你怀疑的雷(MarkMine)
}
这段代码首先检查当前待标记的格子的状态,如果格子已经被揭示过,则直接返回。如果格子已经被标记过,则取消标记,更新计数和状态。取消标记时,会减少标记计数 MarkNum,并且如果取消标记的格子是地雷,则减少胜利计数 WinNum,并将格子状态恢复为未揭示状态。如果格子既没有被揭示过也没有被标记过,则进行标记操作,并更新计数和状态。标记操作时,如果标记的格子是地雷,则增加胜利计数 WinNum,并增加标记计数 MarkNum,并将格子状态设置为标记状态。这段代码实现了扫雷游戏中对怀疑为地雷的格子进行标记和相应计数的功能。
// 标记出你怀疑的雷
```c
void MarkMine(char board[SIZE][SIZE], char revealed[SIZE][SIZE], int x, int y)
{
// 如果格子已经揭示过,则直接返回
if (revealed[x][y] == REVEAL)
{
return;
}
// 如果格子已经被标记过,则取消标记,更新计数和状态
else if(revealed[x][y] == MARK)
{
MarkNum--; // 减少标记计数
if (board[x][y] == MINE)
{
WinNum--;// 如果取消标记的格子是地雷,则减少胜利计数
}
revealed[x][y] = UNREVEAL; // 将格子状态恢复为未揭示
return;
}
else
{
// 如果格子没有被揭示过且没有被标记过,则进行标记,并更新计数和状态
if (board[x][y] == MINE)
{
WinNum++; // 如果标记的格子是地雷,则增加胜利计数
}
MarkNum++; // 增加标记计数
revealed[x][y] = MARK; // 增加标记计数
}
}
(6)打印扫雷棋盘,并返回游戏是否结束的标志(printBoard)
这段代码使用嵌套循环遍历二维数组,打印出扫雷游戏棋盘的状态。首先打印列号,然后打印分隔线。接下来,通过循环遍历行和列,判断每个格子的状态并进行相应打印。如果格子已经被揭示过,根据其内容打印地雷符号或周围地雷数量。如果格子被标记过,则打印标记符号。如果格子既没有被揭示过也没有被标记过,则打印未揭示的格子符号。在打印过程中,如果发现地雷,则设置游戏结束标志为已结束。最后返回游戏是否结束的标志。这段代码实现了打印扫雷游戏棋盘的功能,并可以判断游戏是否结束。
int printBoard(char board[SIZE][SIZE], char revealed[SIZE][SIZE])
{
int over = 0;// 游戏结束标志,默认为未结束
printf(" ");
for (int i = 1; i <= SIZE; i++)
{
printf("%-2d", i); // 打印列号
}
printf("\n");
printf(" +");
for (int i = 1; i <= SIZE; i++)
{
printf("--"); // 打印分隔线
}
printf("\n");
for (int i = 0; i < SIZE; i++)
{
printf("%2d", i + 1); // 打印行号
printf("|");
for (int j = 0; j < SIZE; j++)
{
if (revealed[i][j] == REVEAL) // 如果格子已经揭示过
{
if (board[i][j] == MINE)
{
printf(" %c", MINE); // 打印地雷符号
over = 1; // 设置游戏结束标志为已结束
}
else {
int count = countMines(board, i, j); // 计算周围地雷数量
printf("%2d", count); // 打印周围地雷数量
}
}
else if (revealed[i][j] == MARK) // 如果格子被标记过
{
printf(" %c", MARK); // 打印标记符号
}
else
{
printf(" %c", UNREVEAL); // 打印未揭示的格子符号
}
}
printf("\n");
}
return over; // 返回游戏是否结束的标志
}
(7) 打印所有格子的状态,包括地雷和周围地雷数量( printMine)
这段代码与前面的打印棋盘函数类似,但不同之处在于它会打印出所有格子的状态,包括地雷和周围地雷数量。代码首先打印列号,然后打印分隔线。接下来,通过循环遍历行和列,判断每个格子的状态并进行相应打印。如果格子是地雷,则打印地雷符号。如果格子不是地雷,则计算其周围地雷数量,并打印该数字。最后输出整个棋盘的状态。
// 打印所有格子的状态,包括地雷和周围地雷数量
void printMine(char board[SIZE][SIZE])
{
printf(" ");
for (int i = 1; i <= SIZE; i++)
{
printf("%-2d", i); // 打印列号
}
printf("\n");
printf(" +");
for (int i = 1; i <= SIZE; i++)
{
printf("--"); // 打印分隔线
}
printf("\n");
for (int i = 0; i < SIZE; i++)
{
printf("%2d", i + 1); // 打印行号
printf("|");
for (int j = 0; j < SIZE; j++)
{
if (board[i][j] == MINE) // 如果格子是地雷
{
printf(" %c", MINE); // 打印地雷符号
}
else {
int count = countMines(board, i, j); // 计算周围地雷数量
printf("%2d", count); // 打印周围地雷数量
}
}
printf("\n");
}
}
完整代码+详细注释
(1)头文件 game.h
//game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define SIZE 9//棋盘的大小(可更改)
#define EMPTY '-'
#define MINE '*'
#define REVEAL '.'
#define UNREVEAL '+'
#define MARK '?'
//初始化棋盘
void InitBord(char board[SIZE][SIZE], char revealed[SIZE][SIZE]);
//在棋盘上布置雷
void placeMine(char board[SIZE][SIZE]);
//统计周围八个格子的地雷数
int countMines(char board[SIZE][SIZE], int x, int y);
//揭示格子
void reveal(char revealed[SIZE][SIZE], char board[SIZE][SIZE], int x, int y);
// 标记出你怀疑的雷
void MarkMine(char board[SIZE][SIZE], char revealed[SIZE][SIZE], int x, int y);
// 打印扫雷棋盘,并返回游戏是否结束的标志
int printBoard(char board[SIZE][SIZE], char revealed[SIZE][SIZE]);
// 打印所有格子的状态,包括地雷和周围地雷数量
void printMine(char board[SIZE][SIZE]);
(2) 源文件 game.c
// game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
extern int MineNum;
extern int MarkNum;
extern int WinNum;
//初始化棋盘
void InitBord(char board[SIZE][SIZE] ,char revealed[SIZE][SIZE])
{
int i, j;
for ( i = 0; i < SIZE; i++)
{
for ( j = 0; j < SIZE; j++)
{
board[i][j] = EMPTY;
revealed[i][j] = EMPTY;
}
}
}
//在棋盘上布置雷
void placeMine(char board[SIZE][SIZE])
{
int count = 0;
while (count < MineNum)
{
int x = rand() % SIZE; //在棋盘上随机布雷
int y = rand() % SIZE;
if (board[x][y] == EMPTY)
{
board[x][y] = MINE;
count++;
}
}
}
//统计周围八个格子的地雷数
int countMines(char board[SIZE][SIZE], int x, int y)
{
int count = 0;
int round[8][2] = { { 1, -1}, { 1, 0}, { 1, 1}
,{ 0, -1}, { 0, 1}
,{-1, -1}, {-1, 0}, {-1, 1} };
// 定义一个二维数组 round 来表示相对于当前坐标 (x, y) 周围八个格子的偏移量
// 遍历每个方向上的格子
for (int i = 0; i < 8; i++)
{
int dx = round[i][0]; // 根据 round 数组获取当前方向的 x 偏移量
int dy = round[i][1]; // 根据 round 数组获取当前方向的 y 偏移量
int nx = x + dx; // 计算当前方向的 x 坐标
int ny = y + dy; // 计算当前方向的 y 坐标
// 检查当前方向的坐标是否在合法范围内,并且当前方向的格子是否为地雷
if (nx >= 0 && ny >= 0 && nx < SIZE && ny < SIZE && board[nx][ny] == MINE)
{
count++;// 如果是地雷,则地雷数量加一
}
}
return count; // 返回统计到的地雷数量
}
//揭示格子
void reveal(char revealed[SIZE][SIZE], char board[SIZE][SIZE], int x, int y)
{
//如果格子已经揭示过,或者标记过,直接返回
if (revealed[x][y] == REVEAL || revealed[x][y] == MARK)
{
return;
}
//标记格子为以揭示
revealed[x][y] = REVEAL;
//如果格子周围没有地雷,则递归揭示周围的格子
if (countMines(board, x, y) == 0)
{
int round[8][2] = { { 1, -1}, { 1, 0}, { 1, 1}
,{ 0, -1}, { 0, 1}
,{-1, -1}, {-1, 0}, {-1, 1} };
// 定义一个二维数组 round 来表示相对于当前坐标 (x, y) 周围八个格子的偏移量
// 遍历每个方向上的格子
for (int i = 0; i < 8; i++)
{
int dx = round[i][0]; // 根据 round 数组获取当前方向的 x 偏移量
int dy = round[i][1]; // 根据 round 数组获取当前方向的 y 偏移量
int nx = x + dx; // 计算当前方向的 x 坐标
int ny = y + dy; // 计算当前方向的 y 坐标
// 检查当前方向的坐标是否在合法范围内
if (nx >= 0 && ny >= 0 && nx < SIZE && ny < SIZE)
{
reveal(revealed, board, nx, ny);// 递归揭示当前方向上的格子
}
}
}
}
// 标记出你怀疑的雷
void MarkMine(char board[SIZE][SIZE], char revealed[SIZE][SIZE], int x, int y)
{
// 如果格子已经揭示过,则直接返回
if (revealed[x][y] == REVEAL)
{
return;
}
// 如果格子已经被标记过,则取消标记,更新计数和状态
else if(revealed[x][y] == MARK)
{
MarkNum--; // 减少标记计数
if (board[x][y] == MINE)
{
WinNum--;// 如果取消标记的格子是地雷,则减少胜利计数
}
revealed[x][y] = UNREVEAL; // 将格子状态恢复为未揭示
return;
}
else
{
// 如果格子没有被揭示过且没有被标记过,则进行标记,并更新计数和状态
if (board[x][y] == MINE)
{
WinNum++; // 如果标记的格子是地雷,则增加胜利计数
}
MarkNum++; // 增加标记计数
revealed[x][y] = MARK; // 增加标记计数
}
}
// 打印扫雷棋盘,并返回游戏是否结束的标志
int printBoard(char board[SIZE][SIZE], char revealed[SIZE][SIZE])
{
int over = 0;// 游戏结束标志,默认为未结束
printf(" ");
for (int i = 1; i <= SIZE; i++)
{
printf("%-2d", i); // 打印列号
}
printf("\n");
printf(" +");
for (int i = 1; i <= SIZE; i++)
{
printf("--"); // 打印分隔线
}
printf("\n");
for (int i = 0; i < SIZE; i++)
{
printf("%2d", i + 1); // 打印行号
printf("|");
for (int j = 0; j < SIZE; j++)
{
if (revealed[i][j] == REVEAL) // 如果格子已经揭示过
{
if (board[i][j] == MINE)
{
printf(" %c", MINE); // 打印地雷符号
over = 1; // 设置游戏结束标志为已结束
}
else {
int count = countMines(board, i, j); // 计算周围地雷数量
printf("%2d", count); // 打印周围地雷数量
}
}
else if (revealed[i][j] == MARK) // 如果格子被标记过
{
printf(" %c", MARK); // 打印标记符号
}
else
{
printf(" %c", UNREVEAL); // 打印未揭示的格子符号
}
}
printf("\n");
}
return over; // 返回游戏是否结束的标志
}
// 打印所有格子的状态,包括地雷和周围地雷数量
void printMine(char board[SIZE][SIZE])
{
printf(" ");
for (int i = 1; i <= SIZE; i++)
{
printf("%-2d", i); // 打印列号
}
printf("\n");
printf(" +");
for (int i = 1; i <= SIZE; i++)
{
printf("--"); // 打印分隔线
}
printf("\n");
for (int i = 0; i < SIZE; i++)
{
printf("%2d", i + 1); // 打印行号
printf("|");
for (int j = 0; j < SIZE; j++)
{
if (board[i][j] == MINE) // 如果格子是地雷
{
printf(" %c", MINE); // 打印地雷符号
}
else {
int count = countMines(board, i, j); // 计算周围地雷数量
printf("%2d", count); // 打印周围地雷数量
}
}
printf("\n");
}
}
(3)源文件 test.c
//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int MineNum = 0;
int MarkNum = 0;
int WinNum = 0;
void menu()
{
printf("\n---------扫雷游戏----------\n");
printf("********* 1.play *********\n");
printf("********* 0.play *********\n");
printf("---------------------------\n");
}
void game()
{
char board[SIZE][SIZE] = { 0 }; // 棋盘数组,用于保存格子的状态
char revealed[SIZE][SIZE] = { 0 }; // 标记已翻开的格子
srand(time(NULL)); // 初始化随机种子
InitBord(board, revealed); // 初始化棋盘和已翻开格子的状态
system("cls"); // 清屏
printf("**************** 游戏难度 ****************\n");
printf("***(1)5个雷 (2)10个雷 (3)15个雷 ***\n");
printf("***(4)20个雷 (5)25个雷 (6)30个雷 ***\n");
printf("***(7)35个雷 (8)40个雷 (9)45个雷 ...\n");
AGAIN:
printf("---请选择游戏难度:>");
int Level = 0;
scanf("%d", &Level);
MineNum = Level * 5; // 设置地雷数量
if (MineNum > SIZE * SIZE)
{
printf("你的所选的难度太大,不适合该棋盘大小,请重新输入!\n");
goto AGAIN; // 如果地雷数量超过棋盘格子总数,则要求重新选择难度
}
placeMine(board); // 在棋盘上布置地雷
while (1)
{
system("cls"); // 清屏
printf("---------扫雷游戏----------\n");
printf("*** 雷数:%2d 标记数:%2d ***\n", MineNum, MarkNum);
printf("---------------------------\n");
if (WinNum == MineNum && MarkNum == MineNum)
{
printf("!!!!!!!!!恭喜你赢啦!!!!!!!!!!\n");
printMine(board); // 打印所有地雷的位置
MarkNum = 0;
WinNum = 0;
break; // 游戏胜利,退出循环
}
if (printBoard(board, revealed)) // 打印棋盘和已翻开的格子
{
printf("你踩雷了!\n"); // 打印棋盘和已翻开的格子
printMine(board);
MarkNum = 0;
WinNum = 0;
break;
}
printf("\n请输入要揭示的格子坐标(x ,y);\n");
printf("如果怀疑哪里是雷输入请在做标前加上负号(-x,-y)\n");
printf("重复标记可以取消标记怀疑的雷!(雷被全部标记正确为胜)\n");
printf("请输入:");
int x, y;
scanf("%d %d", &y, &x);
if (x > 0 && y > 0 && x <= SIZE && y <= SIZE) // 揭示格子
{
reveal(revealed, board, x - 1, y - 1);
}
else if (x < 0 && y < 0 && x >= -SIZE && y >= -SIZE)
{
MarkMine(board, revealed, -x - 1, -y - 1); // 标记雷
}
else
{
printf("输入有误请重新输入!\n"); // 输入不合法,要求重新输入
}
}
}
int main()
{
while (1)
{
menu();
printf("请输入:");
int input;
scanf("%d", &input);
if (input)
{
game();
}
else
{
printf("游戏结束!\n");
break;
}
}
return 0;
}