总有一天你要一个人在暗夜中,向那座桥走过去
目录
一、文件及其对应代码
1.test.c
2.game.c
3.game.h
二、数组创建解析
1.创建两个数组的原因
2.预设数组较大的原因
三、计算周围雷的个数
四、向外扩展并延伸判断
扫雷游戏,相信大家都玩过,今天练习用C语言实现扫雷。先介绍一下规则,因为是C语言实现,所以每次要输入想排查的雷的坐标,若该坐标为雷则失败;若该坐标周边八个坐标中有雷,则显示雷个数,若无雷则继续向周边延伸排查,一直到某坐标周边8个坐标有雷为止
先看代码,再看思路及我觉得其中比较难实现的部分
本文中用到的一个工程中包含多个文件的方法,它们之间的关系及对应思路,在这个文章中有介绍过,大家可以去看http://t.csdn.cn/qnOxE
一、文件及其对应代码
1.test.c
#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,'*');
//打印棋盘
//DisplayBoard(mine, ROW, COL);
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
//扫雷游戏
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新选择:\n");
break;
}
} while (input);
return 0;
}
2.game.c
#include"game.h"
//初始化棋盘,初始11*11的
InitBoard(char Board[ROWS][COLS], int rows, int cols,char ret)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
Board[i][j] = ret;
}
}
}
//打印棋盘
DisplayBoard(char Board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("----------扫雷游戏---------\n");
for (i = 0; i <= col; 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");
}
}
//布置雷
SetMine(char mine[ROWS][COLS], int row, int col)
{
int count1 = 1;
while (count1 <= count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count1++;
}
}
}
//排查雷
void menu2()
{
printf("1.排查雷\n");
printf("2.标记雷\n");
}
FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int blank = row * col - count;
while (blank > 0)
{
printf("请输入你要排查的坐标:");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("抱歉,你被炸死了,游戏结束\n");
printf("雷的具体信息为:\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
Change(mine, show, ROW, COL, x, y);
DisplayBoard(show, ROW, COL);
blank--;
}
}
else
printf("输入错误,请重新输入:\n");
}
}
//判断周围有几个雷
int GetMineCount(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 Change(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int x, int y)
{
int count_ = GetMineCount(mine, x, y);
if (count_ == 0)
{
//遍历周围8个坐标
show[x][y] = ' ';
int i = 0;
for (i = x - 1; i <= x + 1; i++)
{
int j = 0;
for (j = y - 1; j <= y + 1; j++)
{
if(show[i][j]=='*')
Change(mine, show, ROW, COL, i,j);
}
}
}
else
show[x][y] = count_ + '0';
}
3.game.h
#pragma once
#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
InitBoard(char Board[ROWS][COLS], int rows, int cols,char ret);
DisplayBoard(char Board[ROWS][COLS], int row, int col);
SetMine(char mine[ROWS][COLS], int row, int col);
FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetMineCount(char mine[ROWS][COLS], int row, int col);
//int GetMineCount2(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y,int* blank);
void Change(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);
二、数组创建解析
1.创建两个数组的原因
一个mine数组和一个show数组,其中第一个数组用于布置雷,第二个数组是具体展现出来的效果,包括刚开始的时候未排查雷的游戏界面,以及玩家输入要排查的雷的坐标后展现出的排查点坐标周围的雷的个数和继续递归延展出的雷的个数
如下,这个面板就是应该要呈现在玩家面前的show数组的棋盘, 我们在C语言中用 ‘*’ 来表示下面的网格,再标上行号和列号方便玩家输入坐标(行号和列号需要打印上去,不能占用数组空间)。假如是9*9的棋盘,其中有10个雷,那么玩家就可以通过输入的坐标在这个棋盘上面进行排雷。将这个数组元素全部初始化为字符‘*’,当玩家输入某个坐标时,先判断这个坐标对应在mine数组中是不是雷,不是的话,再对它周围的8个元素进行排查,计算出周围有几个雷,将它对应的坐标元素更改为字符几,如果周围没有雷的话,先将对应坐标元素改为空格,然后通过函数递归对向外扩展,直到坐标周围有雷为止,周围有雷就可以显示周围雷的个数,也就初步到达了我们扫雷游戏的效果
而我们布置雷的数组,则是先初始化为字符‘0’,当电脑随机生成雷的坐标后,将置雷的数组对应坐标元素就可以改为字符‘1’即可
注意:show数组只是展现给玩家看的,我们设计的排雷步骤其实都是在mine数组内进行的,然后将结果展示在show数组上然后打印出来
2.预设数组较大的原因
如图所示,可以看到,明明是9*9的扫雷棋盘,但是却在创建数组的时候创建了11*11的数组。这是因为在我们判断一个坐标周围有没有雷的时候,是遍历它周围的8个数组元素,这就导致了一个问题,边界位置点的周围雷数量怎么计算。如果9*9的棋盘,就创建9*9的数组的话,那么数组外的元素都是随机值,这会导致对边界位置点的周围雷数判断出现错误。将数组创建为11*11,并且初始化时全部初始化为字符‘0’,这就相当于自动规定了数组边界点元素周围的棋盘外的雷数量为0,可以很好的解决这个问题,当然,在打印棋盘的时候,我们只需要打印数组的1-9行和1-9列即可
三、计算周围雷的个数
这个其实比较简单,但初学者很难把握,我们创建mine(布置雷)数组的时候,将所有字符元素全部初始化为字符‘0’,布置雷时,将有雷位置的元素值赋为字符‘1’,那么在判断周围有几个雷的时候,就可以将周围八个元素的值相加,然后再减去8*‘0’,就可以得到一个整数,这就是某网格坐标周围的雷数,再将这个整数加字符‘0’,就能将这个整数变为它对应的字符,再将这个字符赋值给show数组对应的坐标即可。最后打印出show数组即可
四、向外扩展并延伸判断
这个其实是游戏改进最难的地方,依照我们上面说的,如果周围没有雷的话,将那个点的元素赋值为‘0’即可,但这并不是我们想要的,试想,9*9的棋盘,除去10个雷,都还有71个位置需要排雷,更不用说更大的棋盘了,如果一个一个排的话,那么时间成本将会非常高,游戏趣味性也会变低。
根据扫雷游戏的原理,对于周围无雷的位置,自动继续向周边延伸排查。为了简易的实现这个功能,就需要用到函数递归的原理,当计算周围雷个数的函数的返回值为0时,将show数组对应坐标元素值赋为‘ ’(空格),然后继续对这个无雷位置的周围8个坐标进行排查,如果这些坐标周围8个点中有周围雷的个数依旧为0的话,就继续对这个数为0的点周围的8个坐标进行排查,如此一直往复,就可以将周围雷个数为0的点全部找出,大大节省了时间成本,思考过程还是比较抽象的,但代码不是很难,详细实现过程大家可以参考代码了解其实现过程。
如图,当排查一个位置后,将会一直向外排查并延伸,并且自动标出雷个数
注意:对一个点周围8个元素进行遍历时,要依次判断即将遍历的坐标对应的show数组元素值是不是‘*’,如果不是‘*’,证明这个位置已经判断过了,如果再对这个坐标进行递归,就会造成死递归,程序也会崩溃