1.游戏逻辑以及代码整体的组成
首先,需要了解扫雷游戏的整体逻辑,扫雷是一款在一个确定大小的棋盘上,通过选中一个位置,从而得知这个格子周围共8个格子所含有的地雷总数,最终在不选中含有地雷格子的情况下使棋盘上只剩下含有地雷的格子,即可通关,选中含有地雷的格子时,地雷爆炸,游戏结束。
由此我们可以得知,要实现一个扫雷小游戏,我们必须的步骤为:1.创建棋盘 2.埋下地雷 3.玩家扫雷,这三个步骤。其中,由于棋盘并非为一条直线,因此,为了方便作图与理解,我们这里采用二维数组的形式创建棋盘。详细内容如下
2.棋盘的创建及初始化
这里我们首先假定需要一个9 * 9大小的扫雷棋盘,由于需要打印棋盘方便玩家选择排雷点,因此我们创建两个char类型的二维数组,一个用于存放实际地雷位置,一个用于打印(隐藏真实的地雷信息)。因为存在判断所选点位四周共八个点的地雷数总和,因此如果创造9 * 9的棋盘在后续使用中会因为角落点位遍历时存在内存泄漏的可能性。为了解决这个问题,我选择直接给行和列都加上2作为边界,防止造成内存的越界访问,同时也能解决玩家输入行列的时候与数组下标不对应的问题。棋盘地雷的数据则用二维数组存储,代码如下
#define ROW 9 //实际棋盘行数
#define COL 9 //实际棋盘列数
#define ROWS ROW + 2 //防止内存访问越界创造的棋盘使用行数
#define COLS ROW + 2 //防止内存访问越界创造的棋盘使用列数
char Board[ROWS][COLS];
char ShowBoard[ROWS][COLS];
创建好了以后自然是初始化,这里我选择将玩家可以看到的棋盘全部初始化为‘*’,将实际存储地雷数据的棋盘初始化为‘0’,等到埋下地雷时将对应位置置为‘1’,这样就能通过‘0’和‘1’的ASCII码值更直观的计算出有几个炸弹,在通过数字与其对应的字符ASCII码值差为48得到炸弹的数量并以字符形式存储于数组中。
3.地雷的位置生成及放置
由于地雷需要随机生成,所以还是掏出经典的随机数生成法,用srand函数重置随机数的生成起始点,用rand函数得出随机值,由于这里我们是9 * 9的棋盘,而我们为了防止内存越界访问扩大了实际的棋盘大小(行列均扩大了2),这是我们画图就能发现,真正游戏里需要显示的棋盘对应的数组下标正好是1 ~ 9,那为了生成1 ~ 9的数字,只需要用rand % 行或列数再+ 1就可以了。最后检测一下生成的位置是否已经有地雷即可,代码如下
#define MINENUMS 20 //设定地雷个数
srand((unsigned int)time(NULL));//重置随机数起始点
void LayMines(char Board[ROWS][COLS])
{
//生成随机数
int count = MINENUMS;
//雷的下标范围 1~9
while (count > 0)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (Board[x][y] == '0')
{
Board[x][y] = '1';
count--;
}
}
}
这里用一个count作为计数器,先让它等于我们所需要的地雷数量,在每次成功布置地雷以后--,直到等于0,就能完成全部的地雷布置,而你若想改变地雷数量,之需改变之前设定好的宏(#define MINENUMS)后面的数即可。
4.打印棋盘
前期准备做了这么多,该看一下棋盘打印出来的效果了。打印很简单,就是两个for循环嵌套即可,这里要注意,我们只需要那9 * 9的棋盘被打印,其他地方是为了防止内存跨界访问所设的边界,因此需要打印的数组行、列下标是1 ~ 9。如下:
void PrintBoard(char ShowBoard[ROWS][COLS], int rows, int cols)
{
printf("********扫雷*******\n");
printf("0 1 2 3 4 5 6 7 8 9\n");
for (int i = 1; i <= ROW; i++)
{
if (i > 0 && i <= 9)
{
printf("%d ", i);
}
for (int j = 1; j <= COL; j++)
{
printf("%c ", ShowBoard[i][j]);
}
printf("\n");
}
}
这里我们可以在打印的时候顺便打印出他的行数和列数,以防止到时候测试和游玩的时候数错行(毕竟还没更新到界面化,等学会图形化了在优化吧),这里列数偷了个懒,使用了面向结果编程,求大佬放过。
5.扫雷
那现在万事俱备,东西都齐了,那就开始扫雷吧,首先是提示玩家输入想踩的坐标,接收到以后进行合法性判断(看这个坐标是否在棋盘内),随后再确认这个坐标是否已经踩过,因为已经踩过的点说明没有雷,没有再踩的价值。以上两点都满足的情况,那么就只需要判断该坐标是不是雷,雷是‘1’,若是雷,那么直接结束游戏,回到初始界面,若不是雷,就计算这个点周围所有点的ASCII码之和再减去8个‘0’的ASCII码,这样就计算出了有几个‘1’与‘0’之差,再用这个差值加上‘0’就是差值的对应字符。
例:
int main()
{
char a = '0';
char b = '1';
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
上述代码的打印结果分别是 48 49,而我们假设排雷的点周围八个格子都有雷,那么就是8个‘1’的ASCII码,共计392,这个时候如果减去8个‘0’,就是392 - 384,结果为8,但是要把这个数字8变成字符8,就需要让它加上‘0’也就是48,才能找到8对应的ASCII码值也就是56,再让数组存储这个字符就能做到显示四周炸弹数量。代码如下:
//找雷
void FindMines(char Board[ROWS][COLS], char ShowBoard[ROWS][COLS], int rows, int cols)
{
int Countinue = 1;
int win = 0;
while (Countinue)
{
//提示玩家输入
int x = 0;
int y = 0;
PrintBoard(ShowBoard, ROWS, COLS);
printf("请输入要排查的坐标:行 列");
if (scanf("%d %d", &x, &y) == 2)
{
//判断输入合法性
if (x > 0 && x <= ROW && y > 0 && y <= COL)//要求输入1~9的数字
{
if (ShowBoard[x][y] != '*')//所选点位判断过
{
printf("这个点已经判断过了,请重新输入:\n");
}
else
{
if (Board[x][y] == '1')//所选的点是炸弹
{
//ShowBoard[x][y] = '1';
printf("炸弹爆炸了,游戏结束\n");
PrintBoard(ShowBoard, ROWS, COLS);
break;
}
else if (Board[x][y] == '0')//所选的点不是炸弹
{
//计算该点周围八个点的ACS码减去8个‘0’的ACS码的值
int nums = Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] + Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0';
ShowBoard[x][y] = nums + '0';//计算该点位的炸弹数量对应的ACS码字符并放入ShowBoard
//printf("%d\n", nums);
//PrintBoard(ShowBoard, ROWS, COLS);
win++;
}
if (win == COUNT)
{
printf("排雷成功,游戏结束\n");
break;
}
}
}
else
{
printf("输入不合法,请重新输入:\n");
}
}
}
}
至此扫雷小游戏需要的所用功能都已经实现了,剩下的就是写一个do while里套switch的选择界面开始游玩了
#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 ROW + 2 //防止内存访问越界创造的棋盘使用列数
#define MINENUMS 20 //设定地雷个数
#define COUNT ROW * COL - MINENUMS //非地雷的格子数量
//雷阵初始化
void InitBoard(char Board[ROWS][COLS], char ShowBoard[ROWS][COLS], int rows, int cols);
//打印游戏界面(棋盘)
void PrintBoard(char ShowBoard[ROWS][COLS], int rows, int cols);
//埋雷
void LayMines(char Board[ROWS][COLS]);
//找雷
void FindMines(char Board[ROWS][COLS], char ShowBoard[ROWS][COLS], int rows, int cols);
#include"game.h"
//初始化雷阵
void InitBoard(char Board[ROWS][COLS], char ShowBoard[ROWS][COLS], int rows, int cols)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
Board[i][j] = '0';
}
}
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
ShowBoard[i][j] = '*';
}
}
}
//打印雷阵
void PrintBoard(char ShowBoard[ROWS][COLS], int rows, int cols)
{
printf("********扫雷*******\n");
printf("0 1 2 3 4 5 6 7 8 9\n");
for (int i = 1; i <= ROW; i++)
{
if (i > 0 && i <= 9)
{
printf("%d ", i);
}
for (int j = 1; j <= COL; j++)
{
printf("%c ", ShowBoard[i][j]);
}
printf("\n");
}
}
//埋雷
void LayMines(char Board[ROWS][COLS])
{
//生成随机数
int count = MINENUMS;
//雷的下标范围 1~9
while (count > 0)
{
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (Board[x][y] == '0')
{
Board[x][y] = '1';
count--;
}
}
}
//找雷
void FindMines(char Board[ROWS][COLS], char ShowBoard[ROWS][COLS], int rows, int cols)
{
int Countinue = 1;
int win = 0;
while (Countinue)
{
//提示玩家输入
int x = 0;
int y = 0;
PrintBoard(ShowBoard, ROWS, COLS);
printf("请输入要排查的坐标:行 列");
if (scanf("%d %d", &x, &y) == 2)
{
//判断输入合法性
if (x > 0 && x <= ROW && y > 0 && y <= COL)//要求输入1~9的数字
{
if (ShowBoard[x][y] != '*')//所选点位判断过
{
printf("这个点已经判断过了,请重新输入:\n");
}
else
{
if (Board[x][y] == '1')//所选的点是炸弹
{
//ShowBoard[x][y] = '1';
printf("炸弹爆炸了,游戏结束\n");
PrintBoard(ShowBoard, ROWS, COLS);
break;
}
else if (Board[x][y] == '0')//所选的点不是炸弹
{
//计算该点周围八个点的ACS码减去8个‘0’的ACS码的值
int nums = Board[x - 1][y - 1] + Board[x - 1][y] + Board[x - 1][y + 1] + Board[x][y - 1] +
Board[x][y + 1] + Board[x + 1][y - 1] + Board[x + 1][y] + Board[x + 1][y + 1] - 8 * '0';
ShowBoard[x][y] = nums + '0';//计算该点位的炸弹数量对应的ACS码字符并放入ShowBoard
//printf("%d\n", nums);
//PrintBoard(ShowBoard, ROWS, COLS);
win++;
}
if (win == COUNT)
{
printf("排雷成功,游戏结束\n");
break;
}
}
}
else
{
printf("输入不合法,请重新输入:\n");
}
}
}
}
#include"game.h"
//void test()
//{
// char Board[ROWS][COLS];
// char ShowBoard[ROWS][COLS];
//
// //第一步:初始化
// InitBoard(Board, ShowBoard, ROWS, COLS);
// PrintBoard(Board, ROWS, COLS);
// PrintBoard(ShowBoard, ROWS, COLS);
//
// LayMines(Board);
// PrintBoard(Board, ROWS, COLS);
// FindMines(Board, ShowBoard, ROWS, COLS);
// PrintBoard(Board, ROWS, COLS);
//
//
//}
void menu()
{
printf("**************************\n");
printf("********* 1.game *********\n");
printf("********* 0.exit *********\n");
printf("**************************\n");
printf("请选择(1/0):");
}
void game()
{
char Board[ROWS][COLS];
char ShowBoard[ROWS][COLS];
//初始化
InitBoard(Board, ShowBoard, ROWS, COLS);
//埋雷
LayMines(Board);
//扫雷
FindMines(Board, ShowBoard, ROWS, COLS);
}
int main()
{
srand((unsigned int)time(NULL));//重置随机数起始点
//printf("%d\n", COUNT);
//test();
int input = 1;
do
{
menu();
if (scanf("%d", &input) == 1)
{
switch (input)
{
case 0:
printf("已退出\n");
break;
case 1:
game();
//printf("game\n");
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
}
} while (input);
return 0;
}
不过这个游戏还差的多,高情商:有很大提升空间,低情商:一堆不完善的地方。比如,这个代码是有不小的概率开局就踩炸弹然后gg的,其实可以规避这个让人痛苦的机制,我们可以设置一个计数器,开局时设置为0,而计数器为0时,玩家不管踩哪个点都不会碰到雷,第一次踩雷时我们可以把埋雷放在玩家踩雷之后,不过埋雷需要多检测一下是否与玩家踩雷位置冲突,复杂了点。
其次,从小玩的踩雷都有一个特点,点一个点会有一大片都扫了,原理是,踩了一个点,若这个点周围8个位置都没有炸弹,那么就把这8个点也都自动踩了,而这8个点里面若也存在周围没有炸弹的点,那么重复上面的动作,你可能注意到了,上述过程和套娃很像,打开一个点,一个点开八个点,八个点再去开... 显然,遇到套娃就得想到递归,需要在递归中判断:1.首先这个点是‘0’ 2.这个点周围8个点也都是‘0’ 3.已经判断过是‘0’的点不用再判断(否则递归会陷入这个门进去再从这个门出来再进去的死递归)
希望自己学有所成以后能把这个游戏完善~