简介:
扫雷游戏是一个家喻户晓的小游戏,此次文章则着重实现扫雷游戏的一些基本功能,若读者有其余想法可在该基础上进行叠加。本文章采用边分析效果边实现的代码的方式进行阐述。
文件分配方式:
由于此次代码可能比较多,所以采用分文件的方式进行编写。创建test.c文件作为主函数的位置;创建game.c文件来保存该游戏相关函数实现部分;创建game.h作为头文件以及各个函数声明的位置。具体格式如下:
功能一:菜单实现
对于任何一款游戏,刚开始界面均有菜单来指示你是要开始游戏还是退出游戏。因此在主函数中创建void Menu()函数来实现该功能,此代码比较简单,主要就是printf打印。具体代码如下:
void Menu()
{
printf("****************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("****************\n");
}
此代码规定1作为开始游戏的选项;0作为退出游戏的选项。
功能二:主函数内部逻辑的实现
结合功能1所规定的两个选项,不同的选项对应不同的功能,所以可用switch-case语句来实现;当一场游戏结束时,应该在给用户进行重新选择的权力--是继续游戏还是退出游戏,所以这个功能可以采用循环结构来实现。对于循环结束的标志,可以发现当用户输入1为继续,输入0为退出,因此循环结构可以通过判断用户输入是否为1来作为标志。具体代码如下:
void test()
{
int input = 1;
srand((unsigned int)time(NULL));
while (input)
{
Menu();
printf("请选择你的游戏方式\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
system("pause");
system("cls");
break;
case 0:
printf("游戏已退出!");
break;
default:
printf("输入错误,请重新输入");
break;
}
}
}
补充:
1.game()函数是游戏运行的主函数;
2.srand((unsigned int)time(NULL));这句代码是根据时间戳来设置随机数种子,使产生的随机数随当前系统时间发生改变,注意:需要添加头文件#include<time.h>和#include<stdlib.h>;
3.system("pause")和system("cls")的实现功能为:第一个语句实现的功能可姑且理解为‘暂停’,只有按下任意键才可继续下面操作;第二个语句是清除运行窗口的界面。
功能三:游戏界面的实现
对于此功能我们可以结合下面两个图。由下面两个图可得到,在最开始的“外层”游戏界面和点击空白处的“内层”界面是有区别的。若用C语言程序实现,有三个要点:
第一:为了区分“外层”和“内层”,我们可以采用两种不同的字符来代表“外层”和“内层”。在此次程序中,采用‘*’作为“外层”界面显示;采用‘0’作为“内层”界面显示。其中我们将“外层”作为外界显示,即打印的目标;而“内层”是我们实现其余功能的部分。
第二:为了能把不同的字符按照下图存储并打印,我们可设置两个二维数组来进行存储和打印。其中设置“外层”数组为:char show[ROWS][COLS] = { 0 };“内层”数组为:char mine[ROWS][COLS] = { 0 };初始化函数为void Init_Board();打印函数为void Display_Board();
第三:若设置游戏区域为9*9大小的,那我们需要在游戏区域外界在扩大一圈,这是为了防止后续查询雷的个数时发生数组越界访问。
//game()函数中的两个二维数组的定义和初始化函数的调用
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
Init_Board(mine, ROWS, COLS, '0');
Init_Board(show, ROWS, COLS, '*');
}
//二维数组初始化函数
void Init_Board(char arr[ROWS][COLS], int rows, int cols, char ch)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
arr[i][j] = ch;
}
}
}
//二维数组打印函数
void Display_Board(char arr[ROWS][COLS], int row, int col)
{
for (int i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
运行框中打印效果如下:
功能四:雷的设置
在扫雷游戏中,雷的位置往往都是随机的,所以在此次程序中使用生成伪随机数函数:rand(),其中可以使用rand() % row + 1语句来自适应棋盘大小。并且功能二的srand((unsigned int)time(NULL))语句就是来配合rand()函数来使用的。具体代码如下:
void set_mine(char arr[ROWS][COLS], int row, int col)
{
int count = LEI_NUM;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (arr[x][y] != '1')
{
arr[x][y] = '1';
count--;
}
}
}
由上述代码可知,该程序将雷的位置设为‘1’,并且为了防止在一个地方重复设置雷,所以加入if语句进行判断。
功能五:扫雷功能的实现
小功能一:输入坐标返回周围雷的数量
由功能四可得,我们使用坐标进行雷的设置,因此同样使用坐标来实现对雷的查询。
扫雷游戏中返回的雷的数量是点击处周围八个位置雷的总个数。同样,在此次程序中。我们通过计算输入坐标处的周围八个位置的雷的个数来实现该功能。具体代码如下:
int get_mine_num(char arr[ROWS][COLS], int x, int y)
{
return arr[x - 1][y - 1] + arr[x - 1][y] + arr[x - 1][y + 1] + arr[x][y - 1] + arr[x][y + 1]
+ arr[x + 1][y - 1] + arr[x + 1][y] + arr[x + 1][y + 1] - 8 * '0';
}
解释:因为在功能四中我们设置字符1来表示雷,但是字符串无法直接进行叠加,所以采用ASCII码的关系进行计数。由下面部分ASCII码表可得,‘0’ -- 80;‘1’ -- 81,所以将‘1’ - ‘0’ = int类型的数值1;上述代码就借助此原理来对雷的数量进行计数。
小功能二:扩展连续的无雷位置的功能
该功能的示意图如下:
经过对上面的示意图进行分析可得满足这种情况下的三种条件:1.点击处无雷;2.点击处的周围均无雷,即get_mine_num()函数返回0;3.点击处周围的八个位置的其中一个位置的周围也存在都无雷的情况;针对于该功能的实现,将结合下面代码进行说明:
void expand_Board(char arr[ROWS][COLS], char arr1[ROWS][COLS], int x, int y)
{
if (x < 1 || x >= ROWS || y < 1 || y >= COLS || arr1[x][y] != '*')
{
return;
}
arr1[x][y] = get_mine_num(arr, x, y) + '0';
if (arr1[x][y] == '0')
{
for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (i != x || j != y)
{
expand_Board(arr, arr1, i, j);
}
}
}
}
}
因为我们需要不断的判断输入坐标周围位置的周围位置是否有雷,所以此功能采用函数递归的方式,不断去调用函数本身来实现无雷位置的拓展(空白位置用‘0’来表示)。
函数递归需要设置一个终止条件,否则会陷入死循环,所以该程序设置if语句来判断递归时传递给函数x,y坐标是否满足数组越界,若满足,则return返回。
arr1[x][y] = get_mine_num(arr, x, y) + '0';该语句与小功能一的字符类型转整数类型相同,这是通过返回的整数类型加上‘0’,转换为字符类型
小功能三:标记雷的功能
当用户判断某一位置是雷,则可以通过输入该坐标将该位置用另一种方式进行显示,来进行标识。在此次程序中采用‘#’作为标记雷时的标志。
综上:对所有小功能进行整合得到扫雷功能的实现
具体代码如下:
void fine_mine(char arr[ROWS][COLS], char arr1[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int num = 0;
int count = row * col - LEI_NUM;
while (count)
{
printf("请选择你的操作:\n");
printf("**** 1.扫雷 ****\n");
printf("**** 0.标记雷 ****\n");
scanf("%d", &num);
switch (num)
{
case 1:
printf("请输入你的坐标\n");
scanf("%d%d", &x, &y);
if (x > row || x<1 || y>col || y < 1)
{
printf("输入有误,请重新输入\n");
}
else
{
if (arr[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
return;
}
else
{
if (arr1[x][y] == '*')
{
int ret = get_mine_num(arr, x, y);
if (ret)
{
arr1[x][y] = ret + '0';
count--;
Display_Board(arr1, ROW, COL);
}
else
{
expand_Board(arr, arr1, x, y);
Display_Board(arr1, ROW, COL);
}
}
else
{
printf("该坐标已经被搜寻过\n");
}
}
}
break;
case 0:
printf("请输入雷的坐标\n");
scanf("%d%d", &x, &y);
arr1[x][y] = '#';
Display_Board(arr1, ROW, COL);
break;
default:
printf("输入错误,请重新输入");
break;
}
}
if (count == 0)
{
printf("恭喜你,扫雷成功\n");
}
}
补充:
1.在上述代码中,扫雷和标记雷是并列关系,通过switch-case语句来实现。
2.通过count变量来表示雷的数量,当检测到雷时,count会减一,当count == 0时,则游戏成功结束。
3.该段代码中也加入输入坐标大小的判断语句,防止发生数组越界的错误。
附录:
game()函数的具体实现:
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
Init_Board(mine, ROWS, COLS, '0');
Init_Board(show, ROWS, COLS, '*');
set_mine(mine,ROW,COL);
Display_Board(mine, ROW, COL);
printf("------扫雷游戏------\n");
Display_Board(show, ROW, COL);
fine_mine(mine, show, ROW, COL);
}
头文件:
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define LEI_NUM 10
void Menu();
void Init_Board(char arr[ROWS][COLS], int rows, int cols, char ch);
void Display_Board(char arr[ROWS][COLS], int row, int col);
void set_mine(char arr[ROWS][COLS], int row, int col);
void fine_mine(char arr[ROWS][COLS], char arr1[ROWS][COLS], int row, int col);
void expand_Board(char arr[ROWS][COLS], char arr1[ROWS][COLS], int x, int y);