扫雷游戏是在固定区域随机内存在一定数量的地雷,将除去地雷的区域全部扫开就算胜利,选择扫去区域为地雷被炸飞,游戏失败。
下面开始提取关键信息。
(一)游戏设定:
1.扫雷游戏应当有开始和结束的选项。
2.进入扫雷游戏后需要画出雷区范围。
3.选择需要扫描的区域。如果没雷,扫描区域要显示周围八个区域的地雷数;如果有雷,被炸飞,游戏结束;如果所有没雷的区域都被扫描完,玩家胜利,游戏结束。
4.游戏结束后,应当返回初始界面,重新开始选择开始或者结束游戏。
(二)游戏梳理
1.游戏开始界面选择
游戏每一次结束后都会返回初始界面,可以通过while()语句构成一个循环,玩家输入“0”才会退出循环结束游戏。初始界面需要写个目录函数来明确选项,让玩家明白游戏进程位置和后续选择的操作,给予玩家更好的游戏体验。通过scanf()获取输入数值,通过将输入数值来判断玩家选择。
//test.c
#include<stdio.h>
void menu()//目录函数
{
printf("***********************\n");
printf("******1.开始游戏*******\n");
printf("******0.结束游戏*******\n");
printf("***********************\n");
}
void game()//游戏函数
{
printf("进入游戏\n");
}
int main()
{
int input = 1;
while (input)
{
menu();//目录,显示选项
printf("请选择:>");
scanf("%d", &input);//获取输入数值
switch(input)
{
case 0:
printf("游戏结束\n");
break;
case 1:
game();//游戏函数
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
为了直观简洁,我们可以将input变量传入switch()语句中,通过input数值执行对应语句。如此这般,初始界面就完成了。
接下来对上面功能进行测试:
经过初步测试,代码可以正常运行。
2.扫雷游戏实现
扫雷游戏是在一个x行y列的矩形区域内进行游戏。因此,我们需要先建立一个矩形雷区,可以通过创建一个二维数组来建立雷区,数值为0表示没雷,数值为1表示存在地雷。可以设定地雷数为10颗,然后随机在雷区内生成10颗地雷。然后将雷区显示出来,看雷区是否存在10个地雷。
关键步骤:
(1)建立一个二维数组作为雷区
(2)将雷区全部赋值为'0',表示将雷区全部清理为无雷区域
(3)在无雷区域内随机存放10颗雷
(4)显示雷区
代码实现:
//test.c
#include"game.h"
void menu()
{
printf("***********************\n");
printf("******0.结束游戏*******\n");
printf("******1.开始游戏*******\n");
printf("***********************\n");
}
void game()
{
char mine[ROW][COL] = { '0' };
Board_Init(mine, ROW, COL, '0');//区域初始化函数,将雷区全部赋值为'0'
Mine_Init(mine, ROW, COL);//雷区初始化函数,将雷区随机生成指定的地雷数
Display(mine, ROW, COL);//显示函数,将雷区情况显示出来
}
int main()
{
int input = 1;
srand((unsigned int)time(NULL));//rand函数想要随机生成数字,想要调用srand函数。
while (input)
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 0:
printf("游戏结束\n");
break;
case 1:
game();
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
//game.c
#include"game.h"
void Board_Init(char board[ROW][COL], int row, int col, char a)//board为初始化区域的数组,row为行数,col为列数,a为数组需要初始化的内容
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = a;
}
}
}
void Mine_Init(char mine[ROW][COL], int row, int col)//mine为雷区函数,row为行,col为列
{
int count = COUNT;//地雷数
while (count > 0)
{
int x = (rand() % row) + 1;//生成地雷的行数的随机数
int y = (rand() % col) + 1;//生成地雷的列数的随机数
//rand()函数生成随机数的范围为0-32767,如随机生成1-10,需要余上10,可得到数字(0-9),再加上1为(1-10)
if (mine[x][y] == '0')//判断生成的地雷区域是否已经存在地雷,没有才进入
{
count--;
mine[x][y] = '1';//在生成地雷区域内放置地雷
}
}
}
void Display(char board[ROW][COL], int row, int col)//board为需要显示区域的数组,row为行数,col为列数
{
printf("**********扫雷*********\n");
for (int i = 0; i < row + 1; i++)
{
printf("%d ", i);//显示该区域的列数
}
printf("\n");
for (int i = 0; i < row; i++)
{
printf("%d ", i + 1);//显示该区域的行数
for (int j = 0; j < col; j++)
{
printf("%c ", board[i][j]);//打印该区域内的数值
}
if (i <= row)
{
printf("\n");
}
}
printf("**********扫雷*********\n");
}
//game.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9//行数
#define COL 9//列数
#define COUNT 10//地雷数
void Board_Init(char board[ROW][COL], int row, int col, char a);//区域初始化函数
void Mine_Init(char mine[ROW][COL], int row, int col);//雷区初始化函数
void Display(char board[ROW][COL], int row, int col);//显示函数
为了方便阅读代码,新创建了game.c和game.h文件。
我们将扫雷游戏过程中的函数声明放进game.h文件中,将函数的具体实现放在game.c文件中。将需要通过#define定义的常量和扫雷游戏调用的库都放在game.h文件中声明和调用。test.c和game.c文件直接调用game.h文件就可以使用这些内容,十分方便。
通过#define来定义行数、列数和地雷数,是为了方便后续的调整。比如说,你有一天突然想扩大或者缩小雷区或者改变地雷数量,就可以直接在#define定义中修改,不需要到每个用过这些数值的函数里去修改,方便简洁且避免漏存在修改数值的行为。
在main()函数中新加入了一条srand((unsigned int)time(NULL));程序。srand函数内输入的数如果不是会变化的值,那么rand函数生成的随机数在每次重新进入程序都是一样的,因此需要在srand函数中放入时间戳,时间戳是随时间变化的,符合要求。time函数是用来生成时间戳的数值,返回类型是size_t,传入参数为指针类型,不知道用NULL(空指针)传入即可。srand()函数传入参数的类型为unsigned int,因此我们需要将time()函数的返回类型强制转换为unsigned int。
代码实现效果:
可以看出确实随机生成了10颗地雷。但是地雷不可以直接被看到,因此我们需要再创建一个显示数组。在游戏刚开始时,玩家看到雷区内全都是未知数,选择扫描后才会显示周围地雷数或者被地雷炸飞。可以将显示数组内全部赋值为'*'。为了方便计算被扫描后的区域显示周围的地雷数,我们将在原先的地雷数组上增加上下两行和左右两列。如游戏中地雷区域为9行9列,在代码定义地雷数组为11行11列,上下两行和左右两列不会在游戏中显示,地雷也只会放置在游戏显示的9行9列地雷区域中。代码中定义的显示数组和地雷数组同为11行11列。
改变点:
(1)雷区扩大两行两列
(2)地雷只会在除了雷区上下两行和左右两列的区域中放置(如11行11列的雷区,行为0-10,列为0-10的区域,地雷只会在行1-9和列1-9的区域中放置)
在扫雷游戏中,给玩家展现雷区内未知的样子。因此,在游戏结束之前只可以展示显示数组中的内容。玩家在没扫到雷区前会反复进行扫雷操作,因此,我们可以写个循环语句,只有被地雷炸飞或者将全部没雷的区域扫干净时,才可以跳出循环。在循环内写一个判断玩家胜利的函数,一旦成立就跳出循环。
核心步骤:
(1)建立一个二维数组作为雷区展示数组
(2)将雷区展示数组全部赋值为'*',表示区域内容未知
(3)显示雷区的展示数组
(4)搭建一个循环语句
(5)在循环语句中写一个玩家操作函数,可选择的区域只能在雷区显示数组去掉两行两列的范围内,且该区域没有被扫描过
(6)在循环语句中,玩家选择后就显示玩家扫描过的区域
(7)在循环语句中,判断玩家成功扫描完雷区或者被地雷炸飞才能退出循环
代码实现:
//test.c
#include"game.h"
void menu()
{
printf("***********************\n");
printf("******0.结束游戏*******\n");
printf("******1.开始游戏*******\n");
printf("***********************\n");
}
void game()
{
char mine[ROWS][COLS] = { '0' };//雷区数组
char show[ROWS][COLS] = { '0' };//雷区展示数组
Board_Init(mine, ROWS, COLS, '0');//雷区数组全部赋值为"0"
Board_Init(show, ROWS, COLS, '*');//雷区展示数组全部赋值为"*"
Mine_Init(mine, ROW, COL);//在雷区中随机埋指定数量的地雷
Display(show, ROW, COL);//显示雷区展示数组内容
int ret = 1;
while (ret)
{
if (player(mine, show, ROW, COL) == 0)//执行玩家函数,并判断是否扫到雷,扫到雷返回0,等式成立
{
break;//跳出循环
}
Display(show, ROW, COL);
ret = Is_win(show, ROW, COL);//雷区全部扫描完返回0,当0传入下一次while(ret)循环中会结束
}
if (0 == ret)//游戏成功时,ret会对于0,等式才会成立
{
printf("恭喜你排雷成功\n");
}
}
int main()
{
int input = 1;
srand((unsigned int)time(NULL));
while (input)
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch(input)
{
case 0:
printf("游戏结束\n");
break;
case 1:
game();
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
//game.c
#include"game.h"
void Board_Init(char board[ROWS][COLS], int rows, int cols, char a)//board为初始化区域的数组,row为行数,col为列数,a为数组需要初始化的内容
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = a;
}
}
}
void Mine_Init(char mine[ROWS][COLS], int row, int col)//mine为雷区函数,row为行,col为列
{
int count = COUNT;//地雷数
while (count > 0)
{
int x = (rand() % row) + 1;//生成地雷的行数的随机数
int y = (rand() % col) + 1;//生成地雷的列数的随机数
//rand()函数生成随机数的范围为0-32767,如随机生成1-10,需要余上10,可得到数字(0-9),再加上1为(1-10)
if (mine[x][y] == '0')//判断生成的地雷区域是否已经存在地雷,没有才进入
{
count--;
mine[x][y] = '1';//在生成地雷区域内放置地雷
}
}
}
void Display(char board[ROWS][COLS], int row, int col)//board为需要显示区域的数组,row为行数,col为列数
{
printf("**********扫雷*********\n");
for (int i = 0; i < row + 1; i++)
{
printf("%d ", i);//显示该区域的列数
}
printf("\n");
for (int i = 1; i < row + 1; i++)
{
printf("%d ", i);//显示该区域的行数
for (int j = 1; j < col + 1; j++)
{
printf("%c ", board[i][j]);//打印该区域内的数值
}
if (i <= row)
{
printf("\n");
}
}
printf("**********扫雷*********\n");
}
int player(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//mine为雷区数组,show为雷区展示数组,row为行,col为列
{
int x = 0;
int y = 0;
while (1)
{
printf("请选择:>");
scanf("%d %d", &x, &y);//玩家输入需要扫描的行x列y
if (x > 0 && y > 0 && x <= row && y <= col && show[x][y] == '*')//判断输入位置是否在规定区域内且未被扫描过
{
if (mine[x][y] != '1')//如果该区域不存在地雷
{
for (int i = x - 1; i <= x + 1; i++)//
{
for (int j = y - 1; j <= y + 1; j++)
{
show[x][y] += (mine[i][j] - '0');//将输入位置周围区域加自己总共9个区域遍历并计算地雷数量
}
}
show[x][y] = show[x][y] - '*' + '0';//由于show原先存储内容为'*’,因此需要减去一个 '*',字符'0'加上周围地雷数会等于字符地雷数,如1就会是字符'1'
return 1;//返回1表示没扫到地雷
}
else//扫到地雷
{
printf("被雷炸,游戏结束\n");
Display(mine, ROW, COL);//游戏结束前,显示一下全部的地雷分布
return 0;//扫到地雷返回0
}
}
else
{
printf("输入坐标错误,请重新选择\n");
}
}
}
int Is_win(char show[ROWS][COLS], int row, int col)
{
int count = 0;
for (int i = 1; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
if (show[i][j] == '*')
{
count++;
if (count > COUNT)
{
return 1;//区域内仍然有无地雷区域未扫描,返回1表示游戏继续
}
}
}
}
return 0;//返回0表示区域内无地雷区域全部扫描完,游戏胜利
}
//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
void Board_Init(char board[ROWS][COLS], int rows, int cols,char a);//区域初始化函数
void Mine_Init(char mine[ROWS][COLS], int row, int col);//雷区初始化函数
void Display(char board[ROWS][COLS], int row, int col);//显示函数
int player(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//玩家操作函数
int Is_win(char show[ROWS][COLS], int row, int col);//判断玩家胜利函数
为了方便测试我将地雷数设置为80个,且显示地雷分布情况。
可以看出游戏可以正常运行。