目录
游戏说明:
第一次点击不会是雷。格子里的数字表示它周围(8个格子中)有几个雷。游戏目标是找出所有雷。“触雷” 则输,排查完所有没有雷的地方即成功。
一、写游戏代码思路:
1、首先基础思路:
(1)设置棋盘(例如在9*9的棋盘上布置10个雷),
(2)布置雷,
(3)存储雷。
2、开始设想:
设置两个一模一样的棋盘:
一个①(mine数组)棋盘布置好雷的信息(存储雷放置的位置);
一个②(show数组)棋盘存储排查出的信息(存储周围8个格子有多少雷)。
给玩家展示的是棋盘②数组,展示给自己看的是设置的内部信息①数组。
实际想要实现的是9*9的棋盘效果,而在创建数组的时候需要创建的是11*11的数组,在内存中多开辟了两行两列的空间。
因为创建①mine数组和②show数组的坐标是严格一一对应的,所以两棋盘大小一样。
因为要把棋盘②上的信息打印展示出来给玩家看,如看到1说明这个格子周围的其他8个格子中有1个雷。若有雷,把1标记为雷,即在坐标中放进字符1,没有雷的位置放进字符0。
棋盘①:mine数组初始还没有放雷的时候希望里面放的都是字符'0'。
为了保持棋盘②的神秘感,没有排查过的格子放置一个*,所以最开始的棋盘②满棋盘上全是'*',排查一个格子就在这个格子上放置一个想要放的数字(排查出周围雷的个数如1没有雷则是0),这里的数字也是字符数字,因为*是符号,所以这个棋盘的二维数组是个字符数组,char类型。
注意:ASCII值也是整数,字符的ASCII值也是整数,如字符3也是整型类型,字符3在底层数据中存的是它的ASCII值,ASCII值就是整数。整型范围比字符类型大很多。
因为①和②个棋盘是对应的关系,所以最好两个都设置成char类型的数组。
3、操作过程中部分代码的实现:
打印棋盘代码:
打印棋盘的时候为了方便查看可以把相应的列号(第几列)和行号(第几行)标出来。
所以:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}//完成一行的打印
printf("\n");
}
}
变成:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//列号的打印:
for (i = 1; 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");
}
}
实现效果:
发现错位:则让列号从0开始
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
//列号的打印:
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");
}
}
得到正确效果:
而实际上不应该打印mine数组,应该打印show数组,因为show数组打印出来看到的是全*,玩家可以在show数组上选择坐标进行排雷。
4、布置雷时,把雷放在mine数组里,此时打印展示show数组。也可以自己检测打印展示mine数组是否成功布置雷的信息(随机生成在何位置是否有雷)。
这里运行显示已经布置好10个雷:
5、排雷时,是在mine数组中找雷,找到雷之后把雷的信息放到show数组中。即在此实现了两个独立功能棋盘的衔接。
这里用到的相关知识:
字符0的ASCII值是48;
字符1的ASCII值是49;
字符2的ASCII值是50;
2+‘0’=50(2+字符0,即2+字符0的ASCII值)而50是字符2的ASCII值。
所以:
2+‘0’=50=‘2’;
所以:
数字+'0'='数字';
数字加上字符‘0’可以转化为对应的数字字符;
数字——+'0'——>数字字符
数字字符——-'0'——>数字
再如:5+'0'='5',则数字n+'0'='n',‘n’-0=n。
排雷排好后:
若想玩就可以在布置雷处把DisplayBoard(mine, ROW, COL)这行注释掉,实现DisplayBoard(show, ROW, COL)。
二、全部代码讲解:
①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()
{
//0、创建棋盘(数组)
//printf("扫雷\n");
//创建的数组是11*11,暂时初始化为全0
char mine[ROWS][COLS] = { 0 };//mine数组存放的是布置好的雷的信息
char show[ROWS][COLS] = { 0 };//show数组存放的是排查出雷的信息
//mine数组还没有放雷的时候希望里面放的都是字符0(因为是字符数组)
//show数组希望最开始放的全部是*
//1、初始化棋盘(数组)
//初始化mine数组为全'0'
//初始化show数组为全'*'
//初始化棋盘的函数:InitBoard()函数
InitBoard(mine, ROWS, COLS, '0');//意思是:InitBoard()函数把mine数组里的ROWS行和COLS列的内容全部初始化为字符0
InitBoard(show, ROWS, COLS, '*');//意思是:InitBoard()函数把show数组里的ROWS行和COLS列的内容全部初始化为字符*
//尽量做到实现同类型功能用一个函数就能搞定,InitBoard()函数里依次是:数组名,行、列,要初始化的内容
//2、打印棋盘
//打印mine数组和show数组,因为这两个数组类型一样(char),行和列一样,所以写一个函数就可以搞定了
//这里只需要打印9*9棋盘即可,大一圈的11*11在防止越界的时候才用得到
//DisplayBoard(mine, ROW, COL);//这里操作的是9*9
//DisplayBoard(show, ROW, COL);
//这里只是打印出来看一看
//3、布置雷
//如在棋盘上放置10个雷,用SetMine()函数来设置雷
//把雷放在mine数组里,布置到中间的9*9的格子中
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);//这里把布置好的雷打印出来看一下
DisplayBoard(show, ROW, COL);//打印出来看一下
//4、排雷
//即是在mine数组中找雷,找到雷之后把雷的信息放到show数组中,即涉及两个数组,而且这两个数组都是9行9列
FindMine(mine, show, ROW, COL);//注意这里FindMine()函数的参数形式
}
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;
}
②game.h文件中:
头文件里放的有头文件的包含,符号的声明、函数的声明这三类。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
//9行9列
//再定义:
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
//这里表示:操作的是中间的9*9,但实际数组创建的是11*11,
//所以又创建ROWS和COLS来代表11行和11列
//1、初始化棋盘的函数声明:
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//Board的数组是几行几列传过来的这里就用几行几列(是指定数组的行和列的符号)来接收;rows和cols是形参,接收真实的实参,传过来的字符(0或*)用字符变量set接收,(把字符*和字符0传给了set)
//2、打印棋盘的函数声明:
void DisplayBoard(char board[ROWS][COLS],int row,int col);
//虽然只操作9*9,但是show数组传show过来的还是11*11的棋盘(之前已经初始化完数组)
//3、布置雷的函数声明:
void SetMine(char mine[ROWS][COLS], int row, int col);
//4、排查雷的函数声明
void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);
③game.c文件中:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//1、实现初始化棋盘的函数:
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++)//这两个嵌套的for循环用形参的两个变量来限制访问这个数组的几行几列(11行11列)
{
board[i][j] = set;
}
}
}
//2、实现打印棋盘的函数:
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
//是想要把board的内容9*9的棋盘打印到屏幕上
//行是1~9
//列是1~9
int i = 0;
int j = 0;
//列号的打印:
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");
}
}
//3、实现布置雷的函数:
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;//计算布置雷的个数(这里布置的是10),布置的是10个,所以写个循环
//雷的布置一定是随机布置的(在随机产生的坐标中放进去雷)
//行坐标x是1~9,列坐标y也是1~9,生成的范围的值也是1~9
while (count)
{
int x = rand() % row + 1;//模9的余数是1~8,+1就是1~9
int y = rand() % col + 1;
//布置雷
//看mine数组x行y列是不是适合布置雷,,先判断坐标处是否已经有雷(因为之前设置的是若有雷,把1标记为雷,即在坐标中放进字符1;没有雷的位置放进字符0)
if (mine[x][y] == '0')//没有雷则接下来布置雷
{
mine[x][y] = '1';//放雷
count--;
//这里没有else,如果判断的坐标处已经有雷,则也不count--了,直到count为0,则说明已经放了10个雷了
}
}
//这里运行显示已经布置好10个雷,打印完就把test.c文件中的DisplayBoard(mine, ROW, COL);这行注释掉,不能让别人看到
}
//get_mine_count()函数的实现在这里实现,因为get_mine_count()函数是为了支撑FindMine()函数,只是在FindMine()函数中单独用一下
//别人不需要看到,也不需要暴露出来,所以不需要声明。如果在(如在game.c文件中的某一)函数彻彻底底的不想让别人看到,则可以在函数前面加+static,彻底保护这个函数了
static int get_mine_count(char mine[ROWS][COLS],int x,int y)//找在mine数组的x,y坐标周围有几个雷,算好返回
{
//在mine数组中:
//遍历某一个坐标(x,y)周围,判断是雷就+1,写8个if语句,但是这样写比较麻烦
//因为不是雷放的是0,是雷处放的是1,即一共有几个雷加起来的和就是
//但是遗憾的是放的是字符1,字符0,不是数字1、数字0
//所以让每一个元素减去'0',再加起来即可
//所以是周围8个元素的值加起来减去8*'0',即相当与每个都减了
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';
}//这里不会越界,因为设计的数字大一圈
//注意这个函数被static修饰之后只能在game.c文件内部使用。
//4、实现排查雷的函数:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//在mine数组里查找,查找到之后再写到show数组中去
//怎么查找呢?——输入坐标
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
//注意:这里要检测x和y的合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//坐标合法,则:
if (mine[x][y] == '1')
{
printf("很遗憾你被炸死了\n");
//怎么炸死的,显示当时雷的布置情况——死的明白:
//所以调用Displayboard()函数,把mine数组的row行col列打印出来
DisplayBoard(mine, row, col);//注意这里传的是row和col不是ROW和COL
break;
}//除了被炸死一种结束还有81个格子中放了10个雷,把71个格子排完也就是排雷成功而结束
else
{
//计算x,y坐标周围有几个雷
//即是mine数组坐标是x,y的位置周围有几个雷
int n = get_mine_count(mine, x, y);//该函数统计mine数组x,y坐标处周围有几个雷
//此时应该把n写到另外一个数组中,即由mine数组写到show数组中
//get_mine_count()函数返回的值是整型数字,是值,要把它转化为字符n才能再传给字符数组show,(show数组本身是字符类型,给它赋值要首先是个字符)
//show[x][y] = n;//注意这里的是字符n.因为若是整型数字n,则以n为ASCII值打印的字符是不认识的,
//而如果是字符n,以%c的形式放进去打印出来就是n
//数字n怎么转化为字符n呢?——加上个'0'即可
/*show[x][y] = n + '0';*/
show[x][y] = n + '0';//这里即是把mine数组的雷的信息传给show数组中了,实现了两个独立功能的棋盘的衔接
//则到这里已经排了一个位置了,可以打印一下查看
DisplayBoard(show, row, col);//注意这里传的是row和col不是ROW和COL
//进来一次排雷成功一次
win++;
}
}
else
{
printf("输入坐标非法,无法排雷,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
//也显示一下雷的情况
DisplayBoard(mine, row, col);//注意这里传的是row和col不是ROW和COL
}
}
三、实现代码:
①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] = { 0 };
char show[ROWS][COLS] = { 0 };
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
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:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
②game.h文件中:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_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 mine[ROWS][COLS], int row, int col);
void FindMine(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;
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");
}
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
static 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 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 - EASY_COUNT)
{
printf("请输入要排查的坐标:>");
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
{
int n = get_mine_count(mine, x, y);
show[x][y] = n + '0';
DisplayBoard(show, row, col);
win++;
}
}
else
{
printf("输入坐标非法,无法排雷,请重新输入!\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, row, col);
}
}
四、实现效果:
1、一种结果:
另一种结果:
(在game.h文件把#define EASY_COUNT 10改成#define EASY_COUNT 80,再在test.c文件中注释掉DisplayBoard(mine, ROW, COL);而不注释DisplayBoard(show, ROW, COL);
注意:
1)、传的实参数组名无论mine还是show数组,用来接收的形参变量用的都是一个board数组名;
(尽量做到实现同类型功能用一个函数就能搞定)
而在布置雷的时候,是void SetMine(char mine[ROWS][COLS], int row, int col),特定是在mine数组中布置。
在排雷时:void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);传的是两个数组,所以用两个数组接收。
2)、只有在创建棋盘和初始化棋盘的时候用的是(传的实参变量是)ROWS和COLS,在打印棋盘、布置雷、排雷时用的是(传的实参变量是)ROW和COL。
附加内容:
实现完整版扫雷游戏:(递归的应用)
扫雷游戏有一个功能是:会有展开一片的效果
要想实现这个功能需要的条件:1、该坐标处不是雷;2、该坐标周围也没有雷;3、该坐标没有被排查过。
一个坐标不是雷,这个坐标周围的8个格子没有雷,看该格子周围8个坐标展开的坐标没有雷,……(从一个点(某一个坐标)往外散,爆炸式往外扩展),若其中展开的有一个坐标的周围有雷,这个坐标显示1(或其他数字)就不需要爆炸式向外展开了,就停下来了。
该坐标周围的8个坐标各自展开排查雷时需要条件:排查过的坐标(已经排查过这个坐标没有雷)就不要再展开了(否则会形成死递归:向外扩展,又回来),所以把排查过的坐标的内容改一下,
比如设置为空格‘ ’,即标记起来。
当满足上述3个条件时,才开始递归展开去排查。
整体思路:
比如:排查时发现(4,4)这个坐标处不是雷,这个坐标周围没有雷,所以把(4,4)这个坐标设置为空格,(意味着该坐标已经被排查过),然后:因为这个坐标处不是雷,这个坐标周围没有雷,这个坐标没有被排查过这3个条件要再展开依次去排查(4,4)周围的8个坐标;
由一个坐标牵扯到周围8个坐标的展开,即形成了递归的效果。