标题 扫雷实现——可标雷可展开,强迫症患者福音
目录
想必大家以前无聊的时候都玩过扫雷游戏,小时候我不会玩这游戏,觉得这游戏也太乏味了,在一个单调的板子上点来点去有啥好玩的,直到自己动手实现这个游戏,才知道这个游戏的背后要付出多少时间…
我简单地阐述下游戏规则,我们随机点开一个格子,如果有雷,则被炸死,如果无雷,则这个格子上会显示一个数字,这个数字代表着这个格子周围雷的个数。
接下来我们开始实现这个游戏吧。
因为这个游戏需要设计的函数很多,所以我们把工程分为了三个部分:头文件部分(用来声明函数)、函数实现部分、进入游戏部分。
游戏准备
首先我们得打印一个游戏菜单,来确定我们是否要进入游戏。
如下图所示
那我们怎么完成选择从而进入游戏呢?还有,游戏结束时我们如果想再来一把,该怎么设置呢?
我们可以用到switch语句,来实现进入与退出游戏。而想要一直玩游戏,我们则要用到循环,这里可以用do…while循环,让我们先选择是否要进入游戏,进入则选择1,判断条件为真,我们可以玩完一次游戏后再进入循环;而如果选择0,则判断条件为假,跳出循环,从而达到退出游戏的目的。当然,如果我们选择错误,则进入default语句重新选择。
进入游戏后,我们便要开始实现游戏。
要想实现游戏,玩家看到的是被覆盖的布雷界面,而我们设计者看到的则是真实的布雷界面,这里我们用数组来完成这个界面。显然,一个数组如果要实现两种界面是很麻烦的,于是,我们这里用两个数组来分别实现设计者界面和玩家界面。
所以我们设定了下图两种界面。
board1是设计者界面
board2是玩家界面
因为这里我们要在玩家界面用字符来覆盖真实界面,所以我们我们定义字符数组。
设计者界面与玩家界面的初始化
下面是设计者界面的设计与初始化。
void realboard(char board1[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 1; i < row; i++)
{
for (j = 1; j < col; j++)
{
board1[i][j] = '0';
}
}
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board1[i][j]);
}
printf("\n");
}
printf("\n");
}
这里我设计的是10*10的界面,这个大小大家可以自行设定,由于行和列我们在整个工程中要频繁使用,所以这里我们可以用定义宏的方式使它们可以在整个工程中使用。
如下图
那我们为什么要把它们定义成12呢?
后面我们实现计算周围雷个数函数的时候会讲到
回到我们设计的设计者界面中,这里我们不仅完成了界面的初始化,还让他打印了一次,这样的函数设计其实是不好的。我们在设计函数时应当尽量使函数的功能单一,大家不要学博主这种设计的方式噢!!
我们在后面还要实现打印界面的函数,所以这里是很多此一举的!
大家可以看到,我们把数组初始化为’0’时,是从第二行第二列开始的,这是因为我们要把第一行第一列留出来标序号,这样更方便玩家用坐标选择要排的雷。所以我们打印出来的界面是这样的。
这里我们把界面上全定义为’0’,用’0’来表示无雷,之后我们要埋雷时,用’1’表示有雷。
接下来我们来完成对玩家界面的初始化
void playerboard(char board2[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board2[i][j] = '*';
}
}
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j );
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board2[i][j]);
}
printf("\n");
}
printf("\n");
}
这里我们还是把界面打印了一次,再次说明,这样是不对的!!
我们把玩家界面标上了序号,方便玩家选择坐标,把其他部分初始化为’*’,达到覆盖的效果。当我们选择一个坐标时,则这个坐标的表面被揭开,显示出周围雷的个数。
初始化效果如下图
界面的打印
之后我们应该设计一个打印界面的函数,以便在每次扫雷时可以看到布雷的情况以及被炸死或完成游戏后对真实布雷界面的打印,好让玩家“死也瞑目”。
代码如下
void Display_board(char board2[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board2[i][j]);
}
printf("\n");
}
printf("\n");
}
埋雷函数的实现
我们完成了对界面的打印,接下来就应该埋雷了,雷的位置要足够随机,所以这里我们用到rand()函数,而使用rand()函数,我们又得调用srand()函数,函数的参数要是个随机变化的起始值,我们想到,时间是每时每刻都在变化的,所以我们这里再调用一个time函数,随后,我们便可以完成对雷的布置,附上代码和图片。
void Mineburying(char board1[ROW][COL], int row, int col)
{
int count = sum;
while (count)
{
int x = rand() % 10 + 1;
int y = rand() % 10 + 1;
if (board1[x][y] == '0')
{
board1[x][y] = '1';
count--;
}
}
}
因为我们设计的是10*10的界面,所以用随机数%10后再加1,坐标的范围就是1-10,再把坐标代入board1即设计者界面中,在此坐标处埋下一颗雷。我们可以自己设置埋雷的个数。
因为我们在判断胜利条件时还得用到这个数字,所以这里我们也用宏的方式来定义,当我们想改变埋雷的个数时,只需要改变这个数字大小即可。
我们可以看下随机布置雷局后的界面。
当然,在游戏正式开始时,我们不可这么做,否则雷的布局都打印出来了,不就相当于让玩家白嫖吗
计算雷的个数
完成了埋雷,我们就得封装计算坐标周围雷个数的函数了,假设坐标为(x,y),我们要计算它周围的雷的个数,如下图
所以,我们要统计的就是上图中八个坐标总共有多少雷。
那我们之前说到了,为什么1010的界面要定义ROW和COL为12呢?
这里我们要把第一行和第一列用来标号,那用定义为11就行了啊,还要12干嘛呢?原因如下:
当我们选择的是边缘坐标时,如果只用1111的话,会出现数组越界的情况,所以我们选择再加上一行一列,只是打印时只将前十一行和十一列打印出来。
像下图这样,我们只在10*10表格内玩游戏。(画图技术有限,还望包涵)
函数设计如下
int count(char board1[ROW][COL], int x, int y)
{
int num = 0;
if (x >= 1 && x<= ROW - 2 && y >= 1 && y <= COL - 2)
{
if (board1[x - 1][y - 1] == '1')
{
num++;
}
if (board1[x - 1][y] == '1')
{
num++;
}
if (board1[x - 1][y + 1] == '1')
{
num++;
}
if (board1[x][y - 1] == '1')
{
num++;
}
if (board1[x][y + 1] == '1')
{
num++;
}
if (board1[x + 1][y - 1] == '1')
{
num++;
}
if (board1[x + 1][y] == '1')
{
num++;
}
if (board1[x + 1][y + 1] == '1')
{
num++;
}
}
return num;
}
看起来可能眼睛痛,但实际上就是判断那八个坐标在board1中是不是雷,如果是,则num++
标记雷与取消标记
那如果我们在玩的过程中想标记雷怎么办呢?
我们可以设计一个mark函数来对自己认为是雷的地方进行标记
如果我们想标记雷的话,就把这个坐标换成’#’,这样就与其他地方区分开了。
代码如下
void mark(char board2[ROW][COL], int x,int y)
{
board2[x][y] = '#';
}
这个函数的实现很简单,只需要几行代码。
那我们既然标记了雷,如果想取消标记是不是也得设计一个函数把它变回’*‘呢?
我们定义一个cancel函数来实现对’#'的取消标记。
代码如下
void cancel(char board2[ROW][COL], int x, int y)
{
board2[x][y] = '*';
}
展开函数的实现
如果我们就这样开始玩一个10*10的扫雷的话,会比较费时间,我们可以展开所选坐标周围的雷区来加速游戏的进程。
规则是这样的:如果选择的坐标周围有雷,则显示雷的个数;如果选择的坐标周围8个坐标都没有雷的话,就展开周围的坐标并在对应坐标上标上它们周围雷的个数,而要是还存在周围8个坐标没有雷的坐标,就继续展开,(禁止套娃!!!)我们是不是想到了函数的递归呢?
先附上一张效果图
接下来我们开始实现open_mine函数
void open_mine(char board1[ROW][COL], char board2[ROW][COL], int x, int y)
{
int ret = count(board1, x, y);
if (ret == 0)
{
board2[x][y] = ' ';
if (board2[x - 1][y - 1] == '*' && x - 1 > 1 && y - 1 > 0)
{
open_mine(board1, board2, x - 1, y - 1);
}
if (board2[x - 1][y] == '*' && x - 1 > 1)
{
open_mine(board1, board2, x - 1, y);
}
if (board2[x - 1][y + 1] == '*' && x - 1 > 1 && y + 1 < COL - 1)
{
open_mine(board1, board2, x - 1, y + 1);
}
if (board2[x][y - 1] == '*' && y - 1 > 0)
{
open_mine(board1, board2, x, y - 1);
}
if (board2[x][y + 1] == '*' && y + 1 < COL - 1)
{
open_mine(board1, board2, x, y + 1);
}
if (board2[x + 1][y - 1] == '*' && x + 1 < ROW - 1 && y - 1 > 0)
{
open_mine(board1, board2, x + 1, y - 1);
}
if (board2[x + 1][y] == '*' && x + 1 < ROW - 1)
{
open_mine(board1, board2, x + 1, y);
}
if (board2[x + 1][y + 1] == '*' && x + 1 < ROW - 1 && y + 1 < COL - 1)
{
open_mine(board1, board2, x + 1, y + 1);
}
}
else
{
board2[x][y] = ret + '0';
}
}
我们先把输入的坐标传进函数,判断坐标周围雷的个数是否为0,若为0,开始套娃;若不为0,则把该坐标设置成字符数字,即整型数字加48,转化为字符后即为对应的字符数字,而’0’的ASCII码值就是48,所以加上’0’即可完成转化。
判断玩家是否胜利
把上面这些函数设计好后,我们是不是要判断是否胜利呢?
我们可以对玩家界面上’*‘和‘#’的个数进行统计,如果总数等于雷的个数,则游戏胜利。
这里我们定义一个Iswin函数,来判断游戏是否胜利
int Iswin(char board2[ROW][COL], char row, int col)
{
int i = 1;
int j = 1;
int count = 0;
for (i = 1; i < row - 1; i++)
{
for (j = 1; j < col - 1; j++)
{
if (board2[i][j] == '*'|| board2[i][j] == '#')
count++;
}
}
return count;
}
游戏函数的最终实现
完成这些我们所需函数的设计后,我们就可以实现我们进行游戏的play函数了
void play(char board2[ROW][COL], char board1[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
int z = 0;
while (Iswin(board2, ROW, COL) != sum)
{
printf("please choose!\n");
printf(" 不需要标记请第三个值输入0\n 需要在输入1\n 取消标记请输入2\n");
scanf("%d %d%d", &x, &y, &z);
if (x < ROW - 1 && x > 0 && y < COL - 1 && y > 0)
{
if (board1[x][y] == '0')
{
if (z == 0)
{
open_mine(board1, board2, x, y);
}
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
continue;
}
if (board1[x][y] == '1')
{
if (z == 1)
{
mark(board2, x, y);
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
}
if (z == 2)
{
cancel(board2, x, y);
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
}
if (z == 0)
{
printf("boom boom boom!!!\n\n\n");
Display_board(board1, ROW, COL);
return;
}
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
到了这里,我们前面其实还有个是否标记雷的开关没有设计,我们在play函数里实现。
因为我们要不断地选取地址排雷,所以用while循环来进行一次又一次地选择,而判断条件就是最后’#‘和’*'的总和是否等于雷的个数。进去之后,我们输入的是三个数字,前两个为坐标,第三个为一个开关,若选择0,则选择一个地址排雷;若选择1,则将这个地址进行标记;若选择2,则选择取消标记。而如果选取的地址是累,则会踩雷从而结束游戏。
游戏代码
1.头文件
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 12
#define COL 12
#define sum 10
//设计师界面初始化
void realboard(char board1[ROW][COL], int, int);
//玩家界面初始化
void playerboard(char board2[ROW][COL], int, int);
//埋雷函数
void Mineburying(char board[ROW][COL], int , int );
//打印玩家界面
void Display_board(char board2[ROW][COL], int row, int col);
//玩游戏函数
void play(char board2[ROW][COL], char board1[ROW][COL], int row, int col);
//计算坐标周围雷个数
int count(char board1[ROW][COL], int x, int y);
//判断是否赢得游戏
int Iswin(char board1[ROW][COL], char row, int col);
//展开函数,将周围雷个数为0的坐标周围的雷个数显示
void open_mine(char board1[ROW][COL], char board2[ROW][COL], int x, int y);
//标记函数,将是地雷的点标记出来
void mark(char board2[ROW][COL], int x, int y);
//取消标记函数
void cancel(char board2[ROW][COL], int x, int y);
- 函数设计
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void realboard(char board1[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 1; i < row; i++)
{
for (j = 1; j < col; j++)
{
board1[i][j] = '0';
}
}
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board1[i][j]);
}
printf("\n");
}
printf("\n");
}
void playerboard(char board2[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board2[i][j] = '*';
}
}
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j );
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board2[i][j]);
}
printf("\n");
}
printf("\n");
}
void Mineburying(char board1[ROW][COL], int row, int col)
{
int count = sum;
while (count)
{
int x = rand() % 10 + 1;
int y = rand() % 10 + 1;
if (board1[x][y] == '0')
{
board1[x][y] = '1';
count--;
}
}
}
void Display_board(char board2[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (j = 0; j < col - 1; j++)
{
printf("%2d ", j);
}
printf("\n");
for (i = 1; i < row - 1; i++)
{
printf("%2d ", i);
for (j = 1; j < col - 1; j++)
{
printf("%2c ", board2[i][j]);
}
printf("\n");
}
printf("\n");
}
void play(char board2[ROW][COL], char board1[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
int z = 0;
while (Iswin(board2, ROW, COL) != sum)
{
printf("please choose!\n");
printf(" 不需要标记请第三个值输入0\n 需要在输入1\n 取消标记请输入2\n");
scanf("%d %d%d", &x, &y, &z);
if (x < ROW - 1 && x > 0 && y < COL - 1 && y > 0)
{
if (board1[x][y] == '0')
{
if (z == 0)
{
open_mine(board1, board2, x, y);
}
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
continue;
}
if (board1[x][y] == '1')
{
if (z == 1)
{
mark(board2, x, y);
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
}
if (z == 2)
{
cancel(board2, x, y);
printf("\n");
Display_board(board2, ROW, COL);
printf("\n");
}
if (z == 0)
{
printf("boom boom boom!!!\n\n\n");
Display_board(board1, ROW, COL);
return;
}
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
if (Iswin(board2, ROW, COL) == sum)
Display_board(board1, ROW, COL);
printf("\n\n\n\n\nCongratulations! You win!!!\n\n\n\n\n");
}
int count(char board1[ROW][COL], int x, int y)
{
int num = 0;
if (x >= 1 && x<= ROW - 2 && y >= 1 && y <= COL - 2)
{
if (board1[x - 1][y - 1] == '1')
{
num++;
}
if (board1[x - 1][y] == '1')
{
num++;
}
if (board1[x - 1][y + 1] == '1')
{
num++;
}
if (board1[x][y - 1] == '1')
{
num++;
}
if (board1[x][y + 1] == '1')
{
num++;
}
if (board1[x + 1][y - 1] == '1')
{
num++;
}
if (board1[x + 1][y] == '1')
{
num++;
}
if (board1[x + 1][y + 1] == '1')
{
num++;
}
}
return num;
}
void open_mine(char board1[ROW][COL], char board2[ROW][COL], int x, int y)
{
int ret = count(board1, x, y);
if (ret == 0)
{
board2[x][y] = ' ';
if (board2[x - 1][y - 1] == '*' && x - 1 > 1 && y - 1 > 0)
{
open_mine(board1, board2, x - 1, y - 1);
}
if (board2[x - 1][y] == '*' && x - 1 > 1)
{
open_mine(board1, board2, x - 1, y);
}
if (board2[x - 1][y + 1] == '*' && x - 1 > 1 && y + 1 < COL - 1)
{
open_mine(board1, board2, x - 1, y + 1);
}
if (board2[x][y - 1] == '*' && y - 1 > 0)
{
open_mine(board1, board2, x, y - 1);
}
if (board2[x][y + 1] == '*' && y + 1 < COL - 1)
{
open_mine(board1, board2, x, y + 1);
}
if (board2[x + 1][y - 1] == '*' && x + 1 < ROW - 1 && y - 1 > 0)
{
open_mine(board1, board2, x + 1, y - 1);
}
if (board2[x + 1][y] == '*' && x + 1 < ROW - 1)
{
open_mine(board1, board2, x + 1, y);
}
if (board2[x + 1][y + 1] == '*' && x + 1 < ROW - 1 && y + 1 < COL - 1)
{
open_mine(board1, board2, x + 1, y + 1);
}
}
else
{
board2[x][y] = ret + '0';
}
}
int Iswin(char board2[ROW][COL], char row, int col)
{
int i = 1;
int j = 1;
int count = 0;
for (i = 1; i < row - 1; i++)
{
for (j = 1; j < col - 1; j++)
{
if (board2[i][j] == '*'|| board2[i][j] == '#')
count++;
}
}
return count;
}
void mark(char board2[ROW][COL], int x,int y)
{
board2[x][y] = '#';
}
void cancel(char board2[ROW][COL], int x, int y)
{
board2[x][y] = '*';
}
- 主函数
#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 board1[ROW][COL];
char board2[ROW][COL];
realboard(board1, ROW, COL);
printf("\n");
playerboard(board2, ROW, COL);
Mineburying(board1, ROW, COL);
play(board2, board1, 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:
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
以上就是博主一直修修补补写出来的扫雷游戏,细心的小伙伴可以明显看出有很多地方累赘粗糙,代码可优化的地方还有很多不仅可以更简单,还可以使功能更完善,比如胜利条件还可以是当所有标记雷的位置与真实雷的位置一致时,还有设置游戏的难度,计时等。希望大家可以设计出更完善,可读性更高的代码。
看完记得三连喔!!!