一、游戏规则
规则非常简单:
- 游戏盘面上随机布置有一定数量的地雷
- 玩家需要通过输入坐标,来开启格子,避免踩到地雷
- 开启的格子会显示周围8个方向上地雷的数量
- 步步为营,最终排查掉所有非地雷格子即为胜利
二、代码解析
1. 定义游戏盘面
首先需要定义游戏盘面的大小,也就是行数和列数。同时定义一个稍大一点的数组,用于打印行号和列号:
#define R 9
#define C 9
#define RS 11
#define CS 11
此外,定义非地雷格子数方便后续调整和测试:
//定义非地雷格子数
#define TOTAL_CLEAR_BLOCKS 71
2. 初始化游戏盘面
通过一个初始化函数,可以把游戏盘面所有的元素都设置为指定字符,比如:
void InitBoard(char arr[RS][CS], int r, int c, char format) {
for (int i = 0; i < r; i++) {
for (int j = 0; j < c; j++) {
arr[i][j] = format;
}
}
}
这样就可以分别初始化隐藏盘面和显示盘面了:
InitBoard(under, RS, CS, '0');
InitBoard(uncover, RS, CS, '*');
3. 打印游戏盘面
打印游戏盘面也封装成函数,每次根据需要打印隐藏盘面或显示盘面:
void PrintBoard(char arr[RS][CS], int r, int c) {
printf("-------------------\n");
//打印列号
for (int i = 0; i <= c; i++) {
printf("%d ", i);
}
printf("\n");
//打印行号和盘面
for (int i = 1; i <= r; i++) {
printf("%d ", i);
for (int j = 1; j <= c; j++) {
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
4. 设置地雷
通过随机数生成地雷坐标,保存在隐藏盘面:
void Set(char under[RS][CS], int r, int c) {
int x = 1, y = 1;
int num_of_bombs = r * c - TOTAL_CLEAR_BLOCKS;//定义设置地雷的总数
while (num_of_bombs) {
//生成1~9的随机数分别作为设置地雷的x、y坐标
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (under[x][y] != '1') {
under[x][y] = '1';
num_of_bombs--;
}
}
}
5. 排查扫雷
这是游戏的核心逻辑。输入坐标后,先判断是否踩雷,如果是则重新开始。
如果不是,计算周围8格的地雷数量,更新显示盘面,进入循环排查直到开启全部非地雷格子。
void Find(char under[RS][CS], char uncover[RS][CS], int r, int c) {
int x = 1, y = 1;
int count = 0;
while (count < TOTAL_CLEAR_BLOCKS) {
printf("请输入你要排查的坐标:");
scanf("%d %d", &x, &y);
if (x > 0 && x < 10 && y>0 && y < 10) {
if (under[x][y] == '1') {
printf("你已触雷,所有雷排布如下:\n");
PrintBoard(under, R, C);
printf("请重新开始:\n");
menu();
break;
}
else {
int around = 0;
//计算周围8格的地雷数量
around = under[x - 1][y - 1] + under[x][y - 1] + under[x + 1][y - 1] + under[x - 1][y]
+ under[x + 1][y] + under[x - 1][y + 1] + under[x][y + 1] + under[x][y] - 8 * '0';
uncover[x][y] = around + '0';
PrintBoard(uncover, R, C);
count++;
}
}
else {
printf("输入坐标非法,请重新输入\n");
}
}
if (count == TOTAL_CLEAR_BLOCKS) {
printf("恭喜你排雷成功\n");
}
}
其中,因为盘面元素为 char 类型,故涉及到整数与对应 char 类型字符的转换,整数转到其指示的字符只需要加上'0'即可,而字符转到其指示的整数只需要减去'0'即可,举例如下:
int num = 3;
char ch = 3 + '0'; // ch为'3'
char ch2 = '9';
int num2 = ch2 - '0'; // num2的值为9
6. 扫雷游戏核心函数
void game() {
char under[RS][CS];
char uncover[RS][CS];
InitBoard(under, RS, CS, '0');
InitBoard(uncover, RS, CS, '*');
printf("\n");
PrintBoard(uncover, R, C);
Set(under, R, C);
PrintBoard(under, R, C);
Find(under, uncover, R, C);
}
7. 菜单打印函数
void menu() {
printf("------------------------------------------\n");
printf("------------- 1.开始游戏 ---------------\n");
printf("------------- 0.退出游戏 ---------------\n");
printf("------------------------------------------\n");
}
8. 主函数流程
主函数中包括:
- 调用扫雷游戏核心函数
- 循环直到玩家选择退出
int main() {
int input = 0;
srand((unsigned int) time(NULL));
do {
menu();
printf("开始游戏(1)或退出游戏(0),请输入:");
scanf("%d", &input);
if (input == 1) {
game();
}
if (input == 0) {
printf("已退出游戏\n");
}
} while (input);
return 0;
}
9. 头文件定义与声明
#define R 9
#define C 9
#define RS 11
#define CS 11
//定义非地雷格子数
#define TOTAL_CLEAR_BLOCKS 71
void menu();
void game();
//游戏内函数声明
void InitBoard(char arr[RS][CS], int r, int c, char format);
void PrintBoard(char arr[RS][CS], int r, int c);
void Set(char under[RS][CS], int r, int c);
void Find(char under[RS][CS], char uncover[RS][CS], int r, int c);
三、后续展望
上述实现较为简单,后续可以进一步优化和拓展,下方列举了一些方向以供参考:
错误处理:在代码中添加一些错误处理逻辑,以便处理无效输入或其他潜在的错误情况,而不是简单地假定输入都是有效的。
随机数生成:使用
rand()
函数生成随机数是可以的,但请注意,它可能不够随机,尤其是在某些情况下。你可以考虑使用更现代的随机数生成库,如C++11引入的<random>
库。更多的游戏规则:扩展游戏以包括更多的规则,例如,添加难度级别、允许玩家标记潜在雷区等。
计时功能:添加一个计时器,以测量玩家完成游戏所需的时间。
图形界面:考虑将游戏扩展为具有图形用户界面(GUI),这将使游戏更具吸引力和可玩性。
保存和加载游戏:允许玩家保存游戏状态,并在以后恢复游戏,这将增加游戏的可玩性。
代码清洁和格式化:确保你的代码符合一致的代码风格,并定期进行代码清洁,以删除不再使用的代码和注释。