1.简要叙述:
扫雷这款游戏大家基本都玩过,这里不对规则进行过多陈述,直接对扫雷游戏中的一些功能进行解析:
1.1 一个可以选择游玩还是退出的目录
1.2 可以设置不同大小的棋盘来实现不同的游戏难度
1.3 在有解的情况下,可以随意更改雷的个数
1.4 若玩家选到雷则游戏失败并告诉玩家所有雷的分布
1.5若玩家成功排除所有雷则游戏成功
2.代码展示:
//game.h文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#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 type 10 //定义雷的数目
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);//初始化
void Displayboard(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);//寻找地雷
//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 Display(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++)
{
printf("%d ", i);//打印行号
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void Setmine(char board[ROWS][COLS], int row, int col)//放置10个雷
{
int x = 0;
int y = 0;
int count = type;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int getminecount(char mine[ROWS][COLS], int x, int y)
{
return(mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1]
+ mine[x - 1][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 - type)
{
printf("请输入想要排查的坐标");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了");
Display(mine, ROW, COL);
break;
}
else
{
win++;
int count = 0;
count = getminecount(mine, x, y);
show[x][y] = count + '0';
Display(show, ROW, COL);
}
}
else
{
printf("坐标非法");
break;
}
}
if (win == row * col - type)
{
printf("恭喜你,成功通关\n");
Display(mine, ROW, COL);
}
}
//test.c文件代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("**************************\n");
printf("******* 1.start ******\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, '*');
Display(mine, ROW, COL);//打印棋盘
/*Display(show, ROW, COL);*/
Setmine(mine, ROW, COL);//放置雷
//Display(mine, ROW, COL);//打印放置雷后的棋盘
Findmine(mine, show, ROW, COL);//排雷
}
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do
{
printf("请输入:\n");
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出");
break;
default:
printf("请重新输入:");
break;
}
} while ( input);
return 0;
}
3.代码解析:
3.1目录:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("**************************\n");
printf("******* 1.start ******\n");
printf("******* 0.exit ******\n");
printf("**************************\n");
}
int main()
{
int input = 0;
do
{
printf("请输入:\n");
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出");
break;
default:
printf("请重新输入:");
break;
}
} while ( input);
return 0;
}
首先声明:此升序把0定义为没有雷,把1定义为存在雷
和上次的三子棋一样,都是先利用menu函数设置一个目录,再在主函数中对menu函数进行引用,利用了switch函数对玩家输入的不同数字进行选择来进入到不同的内容。同时,如果遇到玩家输入的输入不是0/1的情况,需要重新输入,因此,在最外面利用while循环进行检测,如果输入0,则因为是假达到退出游戏的目的。若输入的是除了1/0之外的数,则再次进入循环,输入数字。
3.2 对于设置char mine 和 char show两个数组的解析:
首先,我们先随便找一个扫雷的游戏进行查看:
可以看到,在我们对其中的一个区块点击后,如果区块不是雷且周围没有雷,则不显示,如果区块不是雷,但是其周围有雷,则显示该区块周围雷的个数,不过对于区块周围,则有两种情况:
第一种情况,则是区块周围有八个雷
第二种情况,则是区块处于游戏区域的边缘,周围区块数量<8
第一种情况是正常的,针对第二种情况,我们可以采用两个棋盘的办法进行解决:
即放置一定数量的雷时,按照n*n的大小进行放置,但是创建棋盘时,则按照(n+2)*(n+2)进行创建(或者可以理解为在初始化棋盘的时候按照(n+2)*(n+2)进行创建,但是在对棋盘进行打印的时候,则是按照n*n进行打印的):在绿色的棋盘中放置地雷,但是创建棋盘时确实以(n+2)*(n+2)创建,这样,即使雷的位置在绿色区域的边缘,也会因为红色区域并不会对结果造成影响,从而达到第一种情况的效果,对雷的数目进行正确的显示。
同时,创建的两个数组,一个被用来存放我们布置好的雷依次来达到玩家游戏失败时,可以看到雷分布消息的目的。一个用来显示以查询的雷的信息。我们把这两个数组分别命名为:mine show.同时为了区分二者,mine数组在初始化时填满‘0’,show数组在初始化时填入‘*’
void game()
{
char mine[ROWS][COLS] = { 0 };//存放布置好的雷
char show[ROWS][COLS] = { 0 };//存放已查询出来的雷的信息
Initboard(mine, ROWS, COLS, '0');//棋盘初始化
Initboard(show, ROWS, COLS, '*');
Display(mine, ROW, COL);//打印棋盘
//Display(show, ROW, COL);
}
如图,在game函数中创建了两个数组,并且,定义了棋盘初始化函数为Initboard,同时在game.h文件中对Initboard函数进行声明
#define _CRT_SECURE_NO_WARNINGS 1
#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 type 10 //定义雷的数目
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);//初始化
void Displayboard(char board[ROWS][COLS], int row, int col);//打印棋盘
这里可以看到,用#define分别定义了五个量,其中,为了达到上面所说的打印(n+2)*(n+2)棋盘的目的,还额外设置了ROWS变量。
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);//初始化
Initboard(mine, ROWS, COLS, '0');//棋盘初始化
Initboard(show, ROWS, COLS, '*');
再看到初始化函数,上面说到两个数组的初始化内容是不同的,为了达到这个目的地,可以额外设置一个set变量,并且在函数的定义中,对set变量进行说明,之后,在game.c文件中,通过设置board[ii][j]= set 可以达到上面的目的,代码如下:
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;
}
}
}
下面的打印棋盘,和三子棋中的思路一样,唯一需要注意的是函数的定义中,我们创建数组的大小是(n+2)*(n+2),但是后面的rows 和 cols的值是 ROW 和 COL 即n .
void Display(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++)
{
printf("%d ", i);//打印行号
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
3.2如何放置棋子:
#define type 10 //定义雷的数目
void Setmine(char board[ROWS][COLS], int row, int col)//放置10个雷
{
int x = 0;
int y = 0;
int count = type;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
首先,雷的放置位置一定是随机的,也就是说,需要产生两个随机数来当作雷的横纵坐标。这里便可以使用rand函数,来随机生成随机数,不过在调用rand函数之前,需要先调用srand函数,在srand函数中,调用time函数来满足其随机性,这里需要注意,每次游戏雷的位置都是随机的,所以,每次开始游戏之前都要让产生一次随机数
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do
{
printf("请输入:\n");
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出");
break;
default:
printf("请重新输入:");
break;
}
} while ( input);
return 0;
}
同时,随机数的大小不可以大于设置的棋盘的大小,也就是必须大于等于1同时小于等于ROW/COL,为了达成这个目标,可以通过
x = rand() % row + 1;
y = rand() % col + 1;
来实现,同时,雷的位置不可以重复,也就是说,每个位置只能存在一颗雷。可以通过if进行判断,如果生成随机数所在的位置不为1,则填入1,如果为一,则本次填入雷不生效,再生成一组随机数,进行判断,填入。
3.4 排除地雷
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 - type)
{
printf("请输入想要排查的坐标");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了");
Display(mine, ROW, COL);
break;
}
else
{
win++;
int count = 0;
count = getminecount(mine, x, y);
show[x][y] = count + '0';
Display(show, ROW, COL);
}
}
else
{
printf("坐标非法");
break;
}
}
if (win == row * col - type)
{
printf("恭喜你,成功通关\n");
Display(mine, ROW, COL);
}
}
在判断排雷时,需要注意以下几个点:
1.输入的排雷坐标是否正确:
这里就涉及到三个情况: 坐标合法 坐标非法 坐标重复
上面说到过,输入的坐标的的大小必须>=1 并且 <= ROW/COL,首先使用一次if语句进行判断,如果输入的坐标满足这个条件,则进入内部进一步判断,如果坐标不满足条件,也就是坐标非法,则通过打印函数告知,坐标非法。
2.如何判断游戏胜利或者失败:
上面说过,定义1为地雷,0则不是地雷,如果在排雷时输入的坐标对应的二维数组的内容恰好是1,则判断为游戏失败。如果输入坐标所对应的二维数组中的内容为0,上面说到过,为0的地方需要显示此处附近的八个区域的存在雷的数目:对于如何判断存在雷的数目,这里需要引入数字字符和数字之间的转换关系:
字符‘0’对应的ASCII值为48,同时,对于数字来说,ASCII值是连贯的,也就是说‘1’,‘2’,‘3’数字字符对应的ASCII值依次为49 50 51,所以,可以得出一个结论,任何数字字符-‘0’=这个数字字符所对应的数字,任何数字+‘0’ = 这个数字所对应的数字字符
借由上面的补充,我们可以把排查地址周围的八个字符中的数字字符加在一起,最后再减去8个‘0’,也就可以得出这八个区域的数字之和,因为我们设置的存在雷为1,所以,所得到的结果就是周围存在的雷的个数,把排查区域的坐标定义为 x y,就可以得到他周围8个区域的坐标为:
因此;通过上面的思路,便可以得到如下代码
int getminecount(char mine[ROWS][COLS], int x, int y)
{
return(mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1]
+ mine[x - 1][y + 1] - 8 * '0');
}
不过这里依然存在一个问题,就是排查雷这个过程循环的次数排查雷是一个循环的过程,游戏胜利的条件是排查出所有的雷,或者可以理解为,我们找到了所有不是雷的区域,而这个区域的大小正是创建数组的ROW*COL-布置的雷的个数,所以得到循环的条件正是创建的win变量(用于记录找到了几次非雷地区)小于ROW*COL-布置的雷的个数。
while ( win < row * col - type)
并且每次输入的坐标所对应的内容是非雷时,win便++一次,可以得到如下代码
win++;
int count = 0;
count = getminecount(mine, x, y);
show[x][y] = count + '0';
Display(show, ROW, COL);
最后,当排雷过程没有失败并且win不满足上述while循环中的条件时,则可以判断为胜利,也就是,win = 上述条件时:
if (win == row * col - type)
{
printf("恭喜你,成功通关\n");
Display(mine, ROW, COL);
}