前言
本篇博客是描述使用C语言做一个简易的扫雷游戏
游戏规则
用户进入会看到一个菜单,输入1开始游戏,输入0即结束游戏,输入除0和1外的其他数字,会提示重新输入。
输入1之后,会打印一个9x9的输出,默认值是*
,在这边我将它叫做棋盘,用户只需输入想要点开的棋盘坐标
*
代表为被点开#
代表炸弹- 数字代表周围的一圈有几个炸弹
代码实现
使用头文件引用一些用到的库和定义一些全局变量
-
这里我用
game.h
来命名-
这里有个问题就是如果是一个9x9的棋盘,四边没法判断地雷的个数,例如左下角的周围只有3个
-
于是这里我采取是将整个棋盘放大一圈
-
-
#pragma once #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 // 炸弹数
-
实现用户进入游戏的功能
这里我新建了一个test.c
的文件,实现用户输入1进入游戏,0退出的功能。
-
test.c
-
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" // 引用头文件 void menu() { printf("***************************************\n"); printf("***** 1 play ******\n"); printf("***** 0 exit ******\n"); printf("***************************************\n"); } void game() { // 游戏主函数 } int main() { int input = 0; do { menu(); // 打印菜单 printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("下次再来!\n"); break; default: printf("输入错误!\n"); break; } } while (input); return 0; }
-
游戏主函数
创建并初始化棋盘
这里我采用的是初始化两个A,B棋盘,A上显示每个左边对应的值,B显示的全是*
,用来给用户选择,选中的情况下就将A上对应的值赋值到B上。
mine
<–> A棋盘,show
<–> B棋盘
-
在
test.c
中的game函数中实现-
void game() { // 创建 char mine[ROWS][COLS]; // A棋盘 char show[ROWS][COLS]; // B棋盘 }
-
-
创建
game.c
,在这个文件中完成初始化函数-
// 初始化函数 // board->二维数组,rows->行数,cols->列数,set->初始化成什么字符串 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) { int i = 0; int j = 0; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { board[i][j] = set; } } }
-
-
在
game.h
中声明-
// 初始化 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
-
-
然后再在
test.c
中引用-
void game() { // 创建并初始化 char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); }
-
打印棋盘
-
在
game.c
实现打印棋盘的函数,这里为了更好的视觉效果,我在左侧和上面标注了行号和列号-
// 打印 void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; printf("\n\n------------- 开始位置 -----------------\n"); for (i = 0; i <= col; i++) printf("%d ", i); // 打印行号 printf("\n"); for (i = 1; i <= row; i++) { printf("%d ", i); // 打印列号 for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("------------- 结束位置 -----------------\n\n\n"); }
-
-
在
game.h
中声明-
// 打印 void DisplayBoard(char board[ROWS][COLS], int row, int col);
-
-
在
test.c
中引用,因为进游戏的第一时间需要将B棋盘打印出来,即全是*
的那个-
void game() { // 创建并初始化 char mine[ROWS][COLS]; // A棋盘 char show[ROWS][COLS]; // B棋盘 InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); // 打印 DisplayBoard(show, ROW, COL); }
-
布置棋盘
-
先在
game.c
中完成相关的函数-
// 在布置非雷的位置,需要统计统计周围的个数 int get_mine_count(char mine[ROWS][COLS], int x, int y) { int i = 0; int j = 0; int count = 0; for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if (mine[x + i][y + j] == '#') count++; } } return count; } // 布置整个布局 --> 在布置雷的时候应该将其他的位置也设置好了 void LayoutBoard(char mine[ROWS][COLS], int row, int col) { // 1.布置雷 int count = MINE_COUNT; while (count>0) { int x = rand() % row + 1; int y = rand() % col + 1; if (mine[x][y] == '#') // 已经是雷的情况下 continue; // 不是雷的情况下,布置 mine[x][y] = '#'; count--; } // 布置除雷外的其他位置 int i = 0; int j = 0; for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { if (mine[i][j] != '#') { // 这里有个知识点,例如周围只有一个雷,返回的是1,但是mine是一个字符数组,所以这里我需要加上一个字符0,这样就能变成字符1,具体可以看一下ascii码值表 mine[i][j] = get_mine_count(mine, i, j) + '0'; } } } }
-
-
在
game.h
中声明-
// 布置棋盘 void LayoutBoard(char mine[ROWS][COLS], int row, int col);
-
-
在
test.c
中引用-
void game() { // 创建并初始化 char mine[ROWS][COLS]; // A棋盘 char show[ROWS][COLS]; // B棋盘 InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); // 打印 DisplayBoard(show, ROW, COL); // 布置整个布局(雷+雷数)这里布置的是mine,也就是A棋盘,因为A棋盘是不给玩家看的,所以可以直接布局 LayoutBoard(mine, ROW, COL); }
-
玩家开始排查雷
-
在
game.c
实现相关函数-
排查雷分为以下几种方案
- 输入的坐标超出棋盘,提示用户重新输入
- 输入正确,排查是不是雷
- 是雷的情况下,结束
- 不是雷的情况下
- 判断用户是否已经点开,点开了提示用户重新输入
- 没有点开
- 如果不是0,继续下一轮
- 如果是0,递归点开周围一片,直到非0
- 当B棋盘中的
*
的数量等于地雷的数量,玩家获胜结束
以下函数
CheckMine
是主函数,reveal
是递归点开周围一片的,NoDisplay
是统计未点开的*
的个数 -
// 判断点开的是否是0,是就递归点开四周,直到出现非0,不是就只显示当前点开的这个 void reveal(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) { if (x< 1 || y< 1 || x>ROW || y>COL || mine[x][y] == '#' || show[x][y] != '*') // 超出边界/是地雷/点开了 结束递归 return; show[x][y] = mine[x][y]; // 先将点开的显示 if (mine[x][y] != '0') { // 当点开的不是0,结束递归 return; } for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { // 开始递归 reveal(mine, show, x + i, y + j); } } } // 统计还有多少个是* int NoDisplay(char show[ROWS][COLS], int row, int col) { int count = 0; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { if (show[i][j] == '*') count += 1; } } return count; } // 排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; while (1) { printf("请输入你要选的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { // 当用户输入的坐标在范围内,因为棋盘四周加了一圈,所以实际的是小一圈的,即1-row,1-col if (show[x][y] != '*') { // 判断是否已经选过了 printf("该坐标您已经选过了,请重新选择!\n\n"); continue; } if (mine[x][y] == '#') { // 是雷的情况下 printf("游戏结束,以下是所有的布局!\n"); DisplayBoard(mine, row, col); // 游戏结束,将答案显示 break; } else { // 不是雷的情况 reveal(mine, show, x, y); // 调用函数 DisplayBoard(show, row, col); // 显示棋盘让用户看到自己选的 int win = NoDisplay(show, row, col); // 获取没有点开的数量 if (win != MINE_COUNT) { // 提示用户有多少没点开 printf("你还有%d个坐标没选!\n\n\n", win); } else { // 剩余的*下都是雷,用户赢了,结束游戏,并显示答案 printf("你居然赢了!\n"); DisplayBoard(mine, col, row); break; } } } else { // 输入坐标超出范围的情况下 printf("输入的坐标错误,请重新输入!\n\n"); } } }
-
-
在
game.h
中声明-
// 排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
-
-
在
test.c
引用-
void game() { // 初始化 char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); // 打印 //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); // 布置整个布局(雷+雷数) LayoutBoard(mine, ROW, COL); // 排查雷 CheckMine(mine, show, ROW, COL); }
-
总结
这里采用的是整个布局一起布置,也可以在边排查雷的时候变算出点开的位置周围有几个雷并输出。
这边也没有实现填雷的功能,不过很简单,只需要让用户输入一个指定的数字进入填雷状态,然后提示用户输入坐标,将B棋盘中用户输入的坐标改成相应的字符就行了。
以下是全部代码,有问题可以留言
-
game.h
-
#pragma once #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 LayoutBoard(char mine[ROWS][COLS], int row, int col); // 排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
-
-
game.c
-
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char board[ROWS][COLS], int rows, int cols, char set) { int i = 0; int j = 0; for (i = 0; i < rows; i++) { for (j = 0; j < cols; j++) { board[i][j] = set; } } } // 打印 void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; printf("\n\n------------- 开始位置 -----------------\n"); for (i = 0; i <= col; i++) printf("%d ", i); printf("\n"); for (i = 1; i <= row; i++) { printf("%d ", i); for (j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("------------- 结束位置 -----------------\n\n\n"); } // 统计周围的个数 int get_mine_count(char mine[ROWS][COLS], int x, int y) { int i = 0; int j = 0; int count = 0; for (i = -1; i <= 1; i++) { for (j = -1; j <= 1; j++) { if (mine[x + i][y + j] == '#') count++; } } return count; } // 布置整个布局 --> 在布置雷的时候应该将其他的位置也设置好了 void LayoutBoard(char mine[ROWS][COLS], int row, int col) { // 1.布置雷 int count = MINE_COUNT; while (count>0) { int x = rand() % row + 1; int y = rand() % col + 1; if (mine[x][y] == '#') // 已经是雷的情况下 continue; // 不是雷的情况下,布置 mine[x][y] = '#'; count--; } // 布置除雷外的其他位置 int i = 0; int j = 0; for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { if (mine[i][j] != '#') { mine[i][j] = get_mine_count(mine, i, j) + '0'; } } } } void reveal(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) { if (x< 1 || y< 1 || x>ROW || y>COL || mine[x][y] == '#' || show[x][y] != '*') // 超出边界/地雷/选过了 return; show[x][y] = mine[x][y]; if (mine[x][y] != '0') { // 结束递归 return; } for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { // 开始递归 reveal(mine, show, x + i, y + j); } } } // 统计还有多少个是* int NoDisplay(char show[ROWS][COLS], int row, int col) { int count = 0; for (int i = 1; i <= row; i++) { for (int j = 1; j <= col; j++) { if (show[i][j] == '*') count += 1; } } return count; } // 排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { /* 超出范围,提示一下 排查是不是雷 是雷的情况下,游戏结束 不是雷的情况下, 需要统计周围雷的个数 */ int x = 0; int y = 0; while (1) { int sw = 0; //printf("0代表选择坐标,1代表进入填雷状态!\n您本次是想选择还是想填雷(0/1)>"); //scanf("%d", &sw); printf("请输入你要选的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { // 判断是否已经选过了 if (show[x][y] != '*') { printf("该坐标您已经选过了,请重新选择!\n\n"); continue; } // 判断是不是雷 if (mine[x][y] == '#') { printf("游戏结束,以下是所有的布局!\n"); DisplayBoard(mine, row, col); // 显示所有 break; } else { // 不是雷的情况 reveal(mine, show, x, y); DisplayBoard(show, row, col); int win = NoDisplay(show, row, col); if (win != MINE_COUNT) { printf("你还有%d个坐标没选!\n\n\n", win); } else { printf("你居然赢了!\n"); DisplayBoard(mine, col, row); break; } } } else { // 输入坐标超出范围的情况下 printf("输入的坐标错误,请重新输入!\n\n"); } } }
-
-
test.c
-
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void menu() { printf("***************************************\n"); printf("***** 1 play ******\n"); printf("***** 0 exit ******\n"); printf("***************************************\n"); } void game() { // 初始化 char mine[ROWS][COLS]; char show[ROWS][COLS]; InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); // 打印 //DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL); // 布置整个布局(雷+雷数) LayoutBoard(mine, ROW, COL); //DisplayBoard(mine, ROW, COL); // 排查雷 CheckMine(mine, show, ROW, COL); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); // 打印菜单 printf("请输入你的选择:>"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("下次再来!\n"); break; default: printf("输入错误!\n"); break; } } while (input); return 0; }
-