扫雷
一.简介
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
背景
扫雷在科技历史上也扮演了相似的角色。这个基于数字的逻辑谜题最早来自20世纪六七十年代,当时Jerimac
Ratliff推出的名为“Cube”的游戏已经非常受人欢迎。几十年后的1992年,扫雷游戏被加入了Windows3.1,这并不是为了展示Windows是游戏操作系统专家,而是为了训练用户的鼠标左右键操作能力,让这些动作变得非常自然,并培养鼠标移动的速度和准确性。
起源
扫雷最原始的版本可以追溯到1973年一款名为“方块”的游戏。
不久,“方块”被改写成了游戏“Rlogic”。在“Rlogic”里,玩家的任务是作为美国海军陆战队队员,为指挥中心探出一条没有地雷的安全路线,如果路全被地雷堵死就算输。两年后,汤姆·安德森在“Rlogic”的基础上又编写出了游戏“地雷”,由此奠定了现代扫雷游戏的雏形。
1981年,微软公司的罗伯特·杜尔和卡特·约翰逊两位工程师在Windows3.1系统上加载了该游戏,扫雷游戏才正式在全世界推广开来。
这款游戏的玩法是在一个99(初级),1616(中级),16*30(高级),或自定义大小的方块矩阵中随机布置一定量的地雷(初级为10个,中级为40个,高级为99个)。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。 [2]
二 .扫雷游戏
1.游戏前置步骤
本次扫雷为了更加简洁,将代码模块化,分为3个模块,test.c(测试的逻辑),game.c(游戏的实现),game.h(游戏的头文件,我将所有的声明都放在这里面)
废话不多说,接下开始游戏
void menu()
{
printf("*******************************\n");
printf("****** 1.进入游戏 *****\n");
printf("****** 0.退出游戏 *****\n");
printf("*******************************\n");
}
void test()
{
int input = 0;
do
{
menu();
printf("请输入:");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("输入无效,请重新输入");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
一个简易的菜单,相信这里不用多说吧,我们进入正题
2.布置棋盘
1.为何创建两个棋盘
一个普通的9*9棋盘应该是如上的,我们要在里面布置雷,如果我们令雷为1,不是雷为0,当玩家输入的坐标里是1的话就被炸死,是0的话就继续输入,但这里有个问题,就是你不能确定这个1是你放进去的还是玩家触碰到的,所以我们这里准备两个棋盘,一个用于放雷(不打印),一个用于玩家显示(当然这里还有更多好处,请慢慢往后看)
2.为何创建11*11的棋盘
这里如果我们直接创建9*9的棋盘的话,我们需要统计每个坐标周围8个坐标中是否有雷,这样的话当我们统计最外面一圈时就会发生越界
所以这里我们需要多创建2行2列
这里似乎show数组只需要打印9 * 9就可以了,但其实用11 * 11更方便,这样的话两个数组就可以方便转换,不需要再考虑转换坐标
3.初始化数组
在test.c中
这里初始化两个数组,为了方便能用一个函数同时初始化两个数组,我们将每个数组要初始化成的元素也传过去
在game.h中
我们用一个函数同时初始化两个数组,用set来接受数组传过来的字符
在game.c中
这里虽然我们只需要显示9*9的格子,但我们依然需要将数组全部初始化,既是为了防止越界,也是让我们不用计算
4.打印棋盘
在test.c中
这里我们只需要让show数组展示出来,布置雷的数组不需要展示,同时,我们只需要显示9*9的棋盘,所以传ROW,COL
虽然我们只需要打印9 * 9,但数组本身是11 * 11的,所以在传参时数组也要写为ROWS,COLS
我们只展示9*9,但数组是11 * 11,所以我们第0行第10行,第0列第10列就不能打印,只打印中间空白部分
同时为了让行列看起来更方便,我们将每行每列的编号打印出来
3.布置雷
在test.c中
在game.h中
在game.c中
.png)
这里随机数的布置方法就不多说了,如果不懂的可以看看这篇 随机数布置方法
ps.所对应的头文件放在了game.h里,srand函数放在了test.c里,这里为了重复就不再展示了
4.排查雷
1.大概框架
在test.c中
在game.h中
在game.c中
ascll码表
从上可以看出字符0的值其实是48,且每个相邻字符的差值都是1,所以我们转换为字符,只需要+字符0就可以了
2.统计周围雷的个数
由于里面都是字符,所以我们只需要将周围8个坐标加起来,再减去8*字符0就可以得出整数
5.判断如何赢
三.扫雷强化
1.爆炸式展开
该函数实现的功能是:若排查的位置周围没有雷则向四周爆炸式展开,直至遇到周围有地雷的坐标时停下来
1.思路
我们首先以下x,y为中心看周围8个坐标是否有雷,再以x+1,y为中心看周围8个坐标,再x+1,y+1为中心看周围8个坐标,以此类推,其实每个点的看法都一样,那么我们就用递归解决问题
2.难点
从以上推论不难看出每次查看坐标时都有可能重复查看,这样就有可能重复展开某一个坐标,再递归时就会发生死递归啦
那要解决这个问题其实也简单,就是要用到show数组,如果周围没有雷,那么这个坐标在show数组里由‘ * ’变为空格,这样再进行下次递归时只排查‘ * ’就可以啦
当然在展开时有可能发生数组越界,所以我们需要添加限制条件
ps:这里可以看出Unload函数里我们传了许多参数,其实每个参数都有它的作用
1.传mine用于get_mine_count计算周围是否有雷
2.传show用于操作
3.传row,col用于限制条件
4.传x,y用于确定坐标
好了,以上就是扫雷的全部内容啦,以下是源代码
test.c
#include"game.h"
void menu()
{
printf("********************************\n");
printf("****** 1.进入游戏 ******\n");
printf("****** 0.退出游戏 ******\n");
printf("********************************\n");
}
void game()
{
srand((unsigned int)time(NULL));
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//创建两个数组一个用于显示,一个用于放雷
InitiBoard(mine, ROWS, COLS,'0');
InitiBoard(show, ROWS, COLS,'*');
//初始化数组
DisplayBoard(show, ROW, COL);
//打印棋盘
SetMine(mine,ROW,COL);
//布置雷,我们只需要在mine数组中9*9的格子中放雷
FindMine(mine,show, ROW, COL);
//排查雷,我们要在mine里排查,但要在show里显示
}
void test()
{
int input = 0;
do
{
menu();
printf("请输入:");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("输入无效,请重新输入");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
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 EASY 10//简单难度10个雷
void InitiBoard(char mine[ROWS][COLS],int row,int col,char set);
//初始化数组
void DisplayBoard(char show[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
#include"game.h"
int get_mine_count(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][y - 1] + mine[x][y + 1] +
mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
void InitiBoard(char Board[ROWS][COLS], int row, int col, 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 show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("-------扫雷--------\n");//增加美观
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
//打印每列的编号
for (i = 1; i <= row; i++)
{
printf("%d ",i);
//打印每行的编号
for (j = 1; j <= col;j++)
{
printf("%c ", show[i][j]);
}
printf("\n");
}
printf("-------扫雷--------\n");//增加美观
}
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY;
//雷的个数
while (count)
{
//生成随机下标(从1-9)
int x = rand() % row + 1;
int y = rand() % col + 1;
//放置雷
if (mine[x][y] == '0')//如果这个位置还没布置雷
{
mine[x][y] = '1';
count--;//布置了一个就减一个雷
}
}
}
void Upload(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col,int x,int y)
{
int n = get_mine_count(mine,x,y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{//判断该展开坐标是否合法
if (n == 0)//如果该坐标周围没雷,开始展开
{
show[x][y] = ' ';//将该坐标变为空格
int i = 0;
int j = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{//表示出周围8个坐标
if (show[i][j] == '*')
{
Upload(mine, show, row, col, i, j);
}
}
}
}
else
{
show[x][y] = n + '0';//如果该坐标周围有雷,将周围的雷用字符显示出来
}
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int win = 0;
int x = 0;
int y = 0;
while (win<row*col- EASY)//如果坐标不合法可以重复输入
{
printf("请输入坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 0 && y <= col)//判断坐标是否合法
{
if (mine[x][y] == '1')//判断该坐标是否为雷
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, row, col);
break;
}
else
{
Upload(mine,show,row,col,x,y);//如果该坐标不是雷,那么判断是否展开
DisplayBoard(show,ROW, COL);//在show数组里显示,这里将两个数组联系起来了
win++;
}
}
else
{
printf("输入坐标非法,请重新输入\n");
}
}
if (win == row * col - EASY)
{
printf("恭喜你,成功了");
}
}