C语言实现经典扫雷游戏全解析
扫雷是一款经典的单人益智游戏,玩家需要在不触雷的情况下揭开所有非雷方块。本文将详细分析一个用C语言实现的扫雷游戏代码,探讨其设计思路和关键技术实现
一. 扫雷游戏功能说明
1. 游戏可以通过菜单实现继续玩或者退出游戏
2. 扫雷的棋盘是9*9的格⼦
3. 默认随机布置10个雷
4. 可以排查雷:
如果位置不是雷,就显示周围有几个雷
如果位置是雷,就炸死游戏结束
把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
5. 游戏界面:
初始界面
排雷界面
二. 游戏分析和设计
2.1 文件结构设计
这个扫雷游戏项目采用了模块化设计,分为三个主要文件:
1. `test.c` - 包含游戏的主循环和菜单系统
2. `game.c` - 包含游戏核心逻辑的实现
3. `game.h` - 包含函数声明和常量定义
这种模块化设计使得代码结构清晰,便于维护和扩展
2.2 核心数据结构
test.c
//存放布置雷的信息
char mine[ROWS][COLS] = { 0 };
//存放排查后的信息
char show[ROWS][COLS] = { 0 };
这里使用了 ROWS 和 COLS 两个宏定义:
game.h
#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2
2.3 游戏结构分析
2.3.1 整体逻辑分析
界面显示询问玩家是否开始游戏(菜单),根据玩家的选择实行相应的程序(开始游戏 or 退出游戏 or 输入无效),这里需要一个do while循环结构和switch选择结构
test.c
//游戏菜单
void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}//测试逻辑
void test()
{int input = 0;do{menu();printf("请选择:> ");scanf("%d", &input);switch (input){case 1://printf("游戏开始\n");game();//游戏逻辑break;case 0:printf("游戏退出,欢迎下次再玩!\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);
}//主体
int main()
{test();return 0;
}
2.3.2 游戏逻辑分析
我们的游戏分为下面几个部分:
1. 初始化棋盘 InitBoard
2. 打印棋盘 DisplayBoard
3. 布置雷 SetMine
4. 排查雷 FindMine
下面我们一点一点的来分析一下:
注:下面涉及的函数定义均在 game.c 文件实现,函数调用均在 test.c 文件的 game() 函数中实现
1) 初始化棋盘
我们很容易想到用初始化二维数组的方法来初始化这个棋盘,定义一个 void InitBoard(char board[ROWS][COLS], int rows, int cols) 函数,
//初始化mine数组
void InitBoard(char board[ROWS][COLS], int rows, int cols)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = '0';}}
}
但是考虑到 show 棋盘和 mine 棋盘的初始化的内容不一样(mine---‘0’,show---'*'),按刚才的思路就需要我们定义两个函数,可这两个函数的逻辑又一样,所以我们这样来定义和调用函数:
game.c
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = set;}}
}
test.c
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
通常,我们能用参数解决的就不要再写一个函数了,就比如上面我们加了一个 set ,避免了再写一个函数来初始化
2) 打印棋盘
打印棋盘也就是将初始化后的棋盘显示在屏幕上:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{int i = 0;for (i = 1; i <= row; i++){int j = 0;for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}
}
我们来看一下效果:
为了方便让玩家迅速知道第几行第几列,我们引入行号和列号;为了让界面看起来更美观一点,再加入 printf("------扫雷游戏------\n"); ,现在再来看一下效果:
game.c
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("------扫雷游戏------\n");int i = 0;for (i = 0; i <= col; i++)//列号{printf("%d ", i);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);//行号int j = 0;for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}
}
test.c
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
3) 布置雷
布置的雷的位置要随机,因此我们用到了 srand 、 rand 和 time ,这三个函数的讲解见:C语言实现猜数字游戏-CSDN博客
test.c
//布置雷
SetMine(mine, ROW, COL);......
srand((unsigned int)time(NULL));//放在do while循环前一句
game.c
现在我们可以用 DisplayBoard(show, ROW, COL); 来看一下布置的雷的效果:
4) 排查雷
要点:
1. 判断玩家输入坐标是否合法--- if 语句
2. 坐标合法:判断是否踩到雷(输入坐标是否为雷)--- if 语句
3. 未踩到雷:计算并显示周围雷的数量 GetMineCount 函数;踩到雷:游戏结束,显示所有雷的位置
4. 检查游戏是否胜利
game.c
//告诉玩家该坐标周围有几个雷
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{int count = 0;int i = 0;for (i = x - 1; i <= x + 1; i++){int j = 0;for (j = y - 1; j <= y + 1; j++){count += mine[i][j] - '0';//-'0'是为了将字符数字转化为数字}}return count;
}//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;//横坐标int y = 0;//纵坐标int win = 0;while (win < row * col - MINE_COUNT)//雷都被找到时,游戏成功{printf("请输入要排查的坐标:>\n");scanf("%d %d", &x, &y);//判断玩家输入的坐标是否合法if (x >= 1 && x <= ROW && y >= 1 && y <= COL){//判断该坐标是否是雷if (mine[x][y] == '1'){printf("很遗憾,你被炸死了游戏结束\n");DisplayBoard(mine, ROW, COL);//显示所有雷的位置break;}else{//判断之前是否输入过该坐标if (show[x][y] != '*'){printf("输入坐标重复,请重新输入\n");}else{//标注该坐标周围有几个雷show[x][y] = GetMineCount(mine, x, y) + '0';//+'0'是为了将数字转化为字符数字DisplayBoard(show, ROW, COL);//显示本次排查后的棋盘win++;}}}else{printf("输入的坐标不合法,请重新输入\n");}}if (win == row * col - MINE_COUNT){printf("恭喜你,排雷成功!\n");}
}
test.c
//排查雷
FindMine(mine, show, ROW, COL);
下面让我们来看一下完整的代码:
三. 完整代码
game.h
//游戏逻辑的实现
//函数声明#include <stdio.h>
#include <stdlib.h>
#include <time.h>#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2#define MINE_COUNT 10//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
//游戏逻辑的实现
//函数定义#include "game.h"//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{int i = 0;for (i = 0; i < rows; i++){int j = 0;for (j = 0; j < cols; j++){board[i][j] = set;}}
}//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("------扫雷游戏------\n");int i = 0;for (i = 0; i <= col; i++)//列号{printf("%d ", i);}printf("\n");for (i = 1; i <= row; i++){printf("%d ", i);//行号int j = 0;for (j = 1; j <= col; j++){printf("%c ", board[i][j]);}printf("\n");}
}//布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{//随机生成雷的坐标int count = MINE_COUNT;while (count){int x = rand() % row + 1;int y = rand() % col + 1;//判断雷的坐标是否重复if (board[x][y] == '0'){board[x][y] = '1';count--;}}
}//告诉玩家该坐标周围有几个雷
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{int count = 0;int i = 0;for (i = x - 1; i <= x + 1; i++){int j = 0;for (j = y - 1; j <= y + 1; j++){count += mine[i][j] - '0';//-'0'是为了将字符数字转化为数字}}return count;
}//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;//横坐标int y = 0;//纵坐标int win = 0;while (win < row * col - MINE_COUNT)//雷都被找到时,游戏成功{printf("请输入要排查的坐标:>\n");scanf("%d %d", &x, &y);//判断玩家输入的坐标是否合法if (x >= 1 && x <= ROW && y >= 1 && y <= COL){//判断该坐标是否是雷if (mine[x][y] == '1'){printf("很遗憾,你被炸死了游戏结束\n");DisplayBoard(mine, ROW, COL);//显示所有雷的位置break;}else{//判断之前是否输入过该坐标if (show[x][y] != '*'){printf("输入坐标重复,请重新输入\n");}else{//标注该坐标周围有几个雷show[x][y] = GetMineCount(mine, x, y) + '0';//+'0'是为了将数字转化为字符数字DisplayBoard(show, ROW, COL);//显示本次排查后的棋盘win++;}}}else{printf("输入的坐标不合法,请重新输入\n");}}if (win == row * col - MINE_COUNT){printf("恭喜你,排雷成功!\n");}
}
test.c
//游戏的测试逻辑
//函数调用#include "game.h"//游戏菜单
void menu()
{printf("************************\n");printf("****** 1.play ******\n");printf("****** 0.exit ******\n");printf("************************\n");
}//游戏逻辑
void game()
{//存放布置雷的信息char mine[ROWS][COLS] = { 0 };//存放排查后的信息char show[ROWS][COLS] = { 0 };//初始化棋盘InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');//打印棋盘//DisplayBoard(mine, ROW, COL);//DisplayBoard(show, ROW, COL);//布置雷SetMine(mine, ROW, COL);//DisplayBoard(mine, ROW, COL);DisplayBoard(show, ROW, COL);//排查雷FindMine(mine, show, ROW, COL);}//测试逻辑
void test()
{int input = 0;srand((unsigned int)time(NULL));do{menu();printf("请选择:> ");scanf("%d", &input);switch (input){case 1://printf("游戏开始\n");game();break;case 0:printf("游戏退出,欢迎下次再玩!\n");break;default:printf("输入错误,请重新输入\n");break;}} while (input);
}//主体
int main()
{test();return 0;
}
四. 拓展
当前实现有几个可以扩展的方向:
1. 添加递归展开空白区域的功能(当点击到周围无雷的格子时自动展开相邻空白区域)
2. 添加标记雷的功能(允许玩家标记可能为雷的位置)
3. 增加难度级别选择(简单,中等,困难)
4. 添加计时和计分系统
这些拓展后续也会陆续补充进去~
结语
该扫雷游戏展示了如何使用基本的数组操作和逻辑控制来实现一个简易的游戏。通过模块化设计和函数划分,使代码具有良好的可读性和可维护性。核心算法如雷区布置、雷数计算和游戏状态判断都实现了高效的处理。对于初学者来说,这个项目是学习C语言数组操作、函数设计和游戏逻辑开发的不错的例子。通过分析和理解这个程序的实现,可以为进一步开发更复杂的游戏打下坚实基础。