在介绍简易版扫雷游戏代码实现之前,我们要先了解一下扫雷游戏的玩法。
如图当我们点击一个格子时它会有一些数字显示的,这些数字表示的是和这个格子相邻的8个格子共存在多少个雷,比如这里的1,表示和他相邻的8个格子,只有一个是雷。2,就表示有两个雷。那么我们则根据数字来推断周围雷的位置,进而找出所有非雷位置,实现通关。
从这个游戏的玩法我们就能大概推断出我们要做的事情,当然我们要实现的只是简易版扫雷,很简易。
第一步:搭建一个菜单,供玩家选择玩游戏还是退出游戏。
第二步:构建一个9*9的格子或者其他规格的格子。
第三步:埋雷。
第四步:排雷。
从我们要干的事情可以看出这个代码量不小,写在一个文件里会显得特别乱,逻辑不清晰,也不方便我们查找问题,所以我们要分文件完成不同的功能。
一:text.c文件:代码的测试。
二:game.c文件:游戏逻辑的搭建
三:game.h文件:头文件,函数的声明以及相关量的宏定义。
那么我们开始:首先就是在text.c里构建一个大体框架,然后由表及里不断填充内容完成我们的代码。
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;
}
这是游戏大体框架的实现,使用input输入选择。
接着就是不断由表及里填充内容了。下图是我们整个代码工程中会用到的头文件,函数声明以及宏定义,都是在game.h中进行声明。这些我们都会遇到并解释,先大体了解一下。
先大概解释一下,比如#define ROW 9代表ROW=9.text.c文件与game.c文件中出现的ROW都会等于9.
接下来我们调用的函数都会在game.c中定义。即在text.c中调用,在game.c中实现定义。
1.创建9*9表格,这个我们第一时间就想到了二维数组,数组创建是在game()函数中实现。
这里有人可能会奇怪,为什么我们要创建两个11*11的表格,这是因为我们如果创建9*9的表格,当我们排查边缘坐标时可能会出现越界情况。如下图。
还有就是为什么创建两个,这是因为我们后续会用‘0’表示非雷,‘1’表示雷,如果用一个数组,当这个数组数据要表达多重意思时它会出错,所以我们要创建两个。下面具体分析原因。
我们在棋盘上布置了雷,棋盘上雷的信息(1)和⾮雷的信息(0),假设我们排查了某 ⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录 存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪⾥呢?如果存放在布 置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。 这⾥我们肯定有办法解决,⽐如:雷和⾮雷的信息不要使⽤数字,使⽤某些字符就⾏,这样就避免冲 突了,但是这样做棋盘上有雷和⾮雷的信息,还有排查出的雷的个数信息,就⽐较混杂,不够⽅便。 这⾥我们采⽤另外⼀种⽅案,我们专⻔给⼀个棋盘(对应⼀个数组mine)存放布置好的雷的信息,再 给另外⼀个棋盘(对应另外⼀个数组show)存放排查出的雷的信息。这样就互不⼲扰了,把雷布置到 mine数组,在mine数组中排查雷,排查出的数据存放在show数组,并且打印show数组的信息给后期 排查参考。
2.初始化数组,一个初始化成‘*’,另一个初始化成‘0’。采用的是双重循环。
3.打印出表格
第一个for循环是打印出列号,方便我们查找。双重循环里面的第一个printf打印的是行号。
双循环则是实现我们表格的打印。据体结果看下图。
4.布置雷,从这一步开始就真正进入核心代码。
调用Setmine函数,并且布置完打印出来让我们看看。
这里由于我们要实现随机选位置布置雷,所以就用到了猜数字时使用的rand()函数来生成随机数形成随机的x,y,同时要生成一个种子来当作基础值使得生成的数不断变化,即使用srand()函数来生成种子。同时这个srand函数在一次运行程序时仅需调用一次即可,所以我们要放在int main()主函数下。
这里我们就随机生成了雷,这时候有人可能会问为什么要使用宏定义去定义那么多的数据,这是因为我们不只是要实现9*9的扫雷游戏,如果我们要实现11*11的扫雷游戏呢,这样一来如果不使用宏定义去定义数据那么我们则需要一个个去改数据,会很麻烦。所以为了后续我们的更改,所以使用宏定义,这样只需更改一个数据。
如下图生成雷成功。
5.排雷,调用Findmine函数实现,里面又需要调用一个Stamine函数来统计我们所排查的坐标周围8个格子中雷的数目。
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 - Minecount)
{
printf("请输入排查坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//一定是两个等号,判断语句一定是两个等号表示判断,一个等号是赋值,如果使用一个等号会出现意料之外的错误。一定要养成习惯,多加注意。
{
printf("很抱歉,你被炸死了。\n");
Disboard(mine, ROW, COL);
break;
}
else
{
show[x][y] = Stamine(mine, x, y) + '0';
Disboard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == row * col - Minecount)
{
printf("恭喜你,排雷成功。\n");
Disboard(mine, ROW, COL);
}
}
这里还有一个小知识点,那就是我们在表格里打印的是字符,而return要返回整型,那我们该怎么办呢。其实很简单,那就是用Ascll值。即'0'-'0'=0,'1'-'0'=1,所以我们就找到了方法实现字符数字与整型数字间的转换。所以return返回值加上'0'得到字符数字存入show[][]数组里面,实现我们的目的。
定义的win则是来判断我们是否通关的条件,比如81个格子,71个非雷格子,由于我们这里是简易版扫雷所以是一个一个格子进行排查,所以当win经过一次一次排查到达71时就会显示我们通关了。
到这我们的简易版扫雷已经完成了。完整版代码如下。
game.h文件:
#pragma once
#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 Minecount 10
//初始化棋盘。
void initboard(char board[ROWS][COLS], int rows, int cols, char set);//这里不能写int ROWS,因为形参这里必须是变量名,实参可以是常量也可以是变量名,我上面对ROWS进行了宏定义,ROWS在预处理阶段就成常量了,不能作形参。
//展现棋盘
void Disboard(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);
text.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 show[ROWS][COLS];
char mine[ROWS][COLS];
initboard(show, ROWS, COLS, '*');
initboard(mine, ROWS, COLS, '0');
//Disboard(show, ROW, COL);
Setmine(mine, ROW, COL);
Disboard(mine, ROW, COL);
Findmine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
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;
}
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;
for (i = 0; i < ROWS; i++)
{
int j = 0;
for (j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
void Disboard(char board[ROWS][COLS], int row, int col)
{
printf("***************扫雷*****************\n");
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("***************扫雷*****************\n");
}
void Setmine(char board[ROWS][COLS], int row, int col)
{
int count = Minecount;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
static int Stamine(char mine[ROWS][COLS], int x, int y)
{
return (mine[x - 1][y + 1] + mine[x - 1][y] + mine[x - 1][y - 1] + mine[x + 1][y + 1]
+ mine[x + 1][y] + mine[x + 1][y - 1] + mine[x][y + 1] + mine[x][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 - Minecount)
{
printf("请输入排查坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')//一定是两个等号,判断语句一定是两个等号表示判断,一个等号是赋值,如果使用一个等号会出现意料之外的错误。一定要养成习惯,多加注意。
{
printf("很抱歉,你被炸死了。\n");
Disboard(mine, ROW, COL);
break;
}
else
{
show[x][y] = Stamine(mine, x, y) + '0';
Disboard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
if (win == row * col - Minecount)
{
printf("恭喜你,排雷成功。\n");
Disboard(mine, ROW, COL);
}
}