前言
扫雷,这个小游戏相信大家应该不会很陌生吧,今天我们就来用C语言制作一个简易版的扫雷游戏,主要运用的是C语言二维数组的知识点和应用。
初步设计思路
在制作扫雷之前,我们要先理一下制作的初步思路。
这里我们制作一个棋盘大小为9*9的扫雷,里面放上10个雷,棋盘就用二维数组来表示。但是,在我们布置好雷之后要将棋盘打印出来,并且不能让雷暴露,这里如果只用一个二维数组表示的话很困难,所以可以使用两个二维数组,一个用来布置雷,另一个将雷隐藏并打印棋盘给玩家看。
在扫雷游戏中,没有踩到雷时会显示周围8个格子中的雷的个数,这里我们设置的棋盘是9*9,踩在边界处时检测周围8个格子会超出数组的范围,所以我们在定义数组时,可以将行与列各+2,也就是创建11*11的数组,但打印棋盘时纸打印中间9*9的部分。
![](https://i-blog.csdnimg.cn/blog_migrate/aa38730e5aef5968b96b8975251fec86.png)
为了让代码看起来更美观,我们可以用三个代码块来实现扫雷小游戏,分别是test.c(测试)、game.c()(游戏主体)、game.h(头文件)。因为在test.c和game.c中会同时使用一些全局变量或函数声明,所以我们把这些都放在game.h中,在test.c和game.c中包含game.h。
#include"game.h"
主函数
现在就可以开始编写程序了,首先是主函数,在主函数中,我们定义一个名为test的函数来调用游戏
/* test.c */
int main()
{
test();
return 0;
}
菜单
在函数test中定义一个名为menu的函数用来打印菜单,然后在函数menu中发挥想象力制作一个好康的菜单。之后在函数test中调用switch语句来对玩家的选项做出反馈,在switch语句中定义一个名为game的函数,当玩家要玩游戏时,便通过它来进入游戏。
/* test.c */
//菜单
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
void test()
{
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);
}
游戏主体设计思路
在函数game中,要编写出游戏的主体,这里可以将游戏分为4个模块来实现,分别是:初始化棋盘、打印棋盘、布置雷、检测雷。
因为在最开始设计时的思路就是两个棋盘,所以我们要先创建两个二维数组。为了后面编写代码时可以减少代码量,在game.h中用宏定义来控制棋盘
/* game.h */
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
/* test.c */
char mine[ROWS][COLS] = { 0 };//布置雷的
char show[ROWS][COLS] = { 0 };//给玩家看的
初始化棋盘
首先是初始化棋盘,数组mine是用来布置雷的,为了后面排查雷的代码更方便编写,这里将数组mine中所有元素初始化为字符'0';数组show是用来给玩家看的,将其初始化为字符'*',这里为了减少代码量,在函数声明时就可以将两个数组对应的字符放进去。
/* game.h */
//初始化棋盘的函数声明
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
/* test.c */
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
/* game.c */
//初始化棋盘的函数体
void init_board(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;
}
}
}
打印棋盘
其次是打印棋盘,这里要打印的棋盘是9*9大小的,所以将ROW和COL放进函数中,而不是ROWS和COLS(头文件中宏定义的常量),不过,放进函数中的数组还是要11*11的,因为这样初始化后的数组可以直接在函数中使用,不需要进行下标计算,打印时只需要打印从[1][1]到[9][9]就可以了,这也是将ROW和COL放进函数中的原因。这里为了让棋盘看上去更美观,我加了一些符号,老铁们可以自行发挥。
/* game.h */
//打印棋盘的函数声明
void print_board(char board[ROWS][COLS], int row, int col);
/* test.c */
//打印棋盘
//print_board(mine, ROW, COL);//在调试代码时使用,游戏时注释掉,否则就将布置的雷打印出来了
print_board(show, ROW, COL);
/* game.c */
//打印棋盘的函数体
void print_board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf(" ---------扫雷----------\n");
printf("| | ");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
}
printf("|");
printf("\n|---|-------------------|\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("|");
printf("\n");
}
}
布置雷
然后是布置雷,这里用随机数来布置雷,范围是1~9,代码中row(col)的值是9,所以对其取余后的值就是0~8,再+1,这样就满足要求了。为了避免精度丢失的问题,不要忘了强制转换为unsigned int类型。
在布置雷的函数体中,while循环的条件设置为剩下雷的数量count,每布置一个雷就-1,这样将所有的雷都布置好后循环就结束了。为了后面排查雷更方便一点,这里将是雷的元素变为字符'1',在布置好之后可以调用打印棋盘的函数来看一下效果。
/* game.h */
//布置雷的函数声明
void set_mine(char mine[ROWS][COLS], int row, int col);
//雷的数量
#define COUNT 10
/* test.c */
//布置雷
set_mine(mine, ROW, COL);
//随机数
srand((unsigned int)time(NULL));
/* game.c */
//布置雷的函数体
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
排查雷
最后就是最难的部分——排查雷。因为是将玩家输入的坐标放入数组mine中进行比较,并通过数组show打印出来,所以在定义函数find_mine时要将两个数组都放进去。通过if-else语句的嵌套来实现玩家输入坐标后的几种反馈,在玩家失败后将布置雷的棋盘打印出来,让玩家死的明白。
其中,当玩家没有踩到雷时,要对输入坐标周围8个格子的排查,将周围的雷的数量替换掉输入的坐标所代表的元素('0')。这里可以定义一个函数get_mine_count用来统计周围的雷的数量,将其返回值赋给变量ret。
通过对照ASCII表可以知道,字符'0'~'9'减去字符'0'所得到的值就是对应的数字。比如,字符'0'在ASCII表中的值是48,字符'1'在ASCII表中的值是49,,那么'1'减去'0'的值就是1,其他的也是同理。那么同理,代码中将输入坐标周围的8个元素加起来,再减去8*'0',所得到的值就是周围的雷的数量,将其赋值给ret,然后让ret加上字符'0',结果就是数字所对应的字符,用它替换掉输入的坐标在数组show中所代表的元素,打印出来的效果就是这个坐标周围有几个雷。
/* game.h */
//排查雷的函数声明
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
/* test.c */
//排查雷
find_mine(mine, show, ROW, COL);
/* game.c */
//没踩到雷时统计周围8个格子的雷总和
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
//排查雷的函数体
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (win < (row * col - COUNT))
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("这个坐标被排查过了,请重新输入\n");
continue;
}
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
print_board(mine, ROW, COL);
break;
}
else
{
int ret = get_mine_count(mine, x, y);
show[x][y] = ret + '0';
print_board(show, ROW, COL);
win++;
}
}
else
{
printf("输入坐标不合法,请重新输入\n");
}
}
if (win == (row * col - COUNT))
{
printf("恭喜你,排雷成功\n");
print_board(mine, 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 COUNT 10
//初始化棋盘的函数声明
void init_board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘的函数声明
void print_board(char board[ROWS][COLS], int row, int col);
//布置雷的函数声明
void set_mine(char mine[ROWS][COLS], int row, int col);
//排查雷的函数声明
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
/* game.c */
#include"game.h"
//初始化棋盘的函数体
void init_board(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 print_board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf(" ---------扫雷----------\n");
printf("| | ");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
}
printf("|");
printf("\n|---|-------------------|\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("|");
printf("\n");
}
}
//布置雷的函数体
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//没踩到雷时统计周围8个格子的雷总和
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] +
mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] +
mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}
//排查雷的函数体
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (win < (row * col - COUNT))
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("这个坐标被排查过了,请重新输入\n");
continue;
}
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
print_board(mine, ROW, COL);
break;
}
else
{
int ret = get_mine_count(mine, x, y);
show[x][y] = ret + '0';
print_board(show, ROW, COL);
win++;
}
}
else
{
printf("输入坐标不合法,请重新输入\n");
}
}
if (win == (row * col - COUNT))
{
printf("恭喜你,排雷成功\n");
print_board(mine, ROW, COL);
}
}
/* test.c */
#include"game.h"
//菜单
void menu()
{
printf("**************************\n");
printf("******* 1.play *******\n");
printf("******* 0.exit *******\n");
printf("**************************\n");
}
//游戏
void game()
{
//分两个二维数组,一个布置雷,一个显示
//为了防止棋盘边缘检测出错,所以数组行与列各多2
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//初始化棋盘
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//打印棋盘
//print_board(mine, ROW, COL);
print_board(show, ROW, COL);
//布置雷
set_mine(mine, ROW, COL);
//print_board(mine, ROW, COL);
//排查雷
find_mine(mine, show, ROW, COL);
}
void test()
{
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);
}
int main()
{
test();
return 0;
}
其实这篇文章中的代码还有很多值得优化的地方,比如在真正的扫雷游戏中,如果选中的位置周围没有雷,讲道理来说会扩散开(如下图);还有在统计输入坐标周围的雷的数量时,本文中所使用的代码太过繁琐,理论上应该可以通过递归来实现,还有诸多等等。这些在后期实力提高之后我会逐步优化(毕竟俺也是小白)
![](https://i-blog.csdnimg.cn/blog_migrate/46c37cbdf5591006dc59ae199c501af5.jpeg)
最后感谢大家的支持,关于本篇文章有任何疑问或值得优化的地方,欢迎大家在评论区留言或讨论,也可以私信提出,祝愿大家在提高实力的同时保持发量的茂密!!!
![](https://i-blog.csdnimg.cn/blog_migrate/7368b1f09f9b17a1b9dbdf01c5854ce2.jpeg)