C语言的第二个小项目 —– 《扫雷小游戏》
扫雷这个游戏在我高三之前我一直认为扫雷这款游戏是一个运气游戏,但是第一次知道其规则之后觉得扫雷是一个很神奇的游戏,因为那些数字和雷的数量怎么能这么凑巧?现在一想,当时真的太天真,因为现在知道了这个游戏开发者的思维,让我来帮你分析分析。
别急,先来把扫雷机制想想:当每次玩家点击一个块时,这个块会显示周围九个块区域有雷的数量,那么我们就完全可以设计两个面板,一个面板相对静态的记录雷的位置,另一个面板用来和玩家进行交互,那么这样做的话问题就很简单了。
接着我们从头想一下扫雷的整个过程:开始电脑肯定得初始化一下两个面板,接着就先给静态的面板中放置雷,注意这个雷是随机放置的,而且数量不易太多,否则会失去可玩性,紧接着肯定就是开始交互喽,游戏开始,玩家每点一次块,首先判断静态面板中的此位置是不是雷,如果是,那么游戏结束,如果不是那么就统计周围九个块区域的雷的个数并显示出来,然后可以进行下一次排雷。当交互面板中剩余的没排的位置刚好就是静态面板中雷的位置时,那么说明排雷成功。不过这里有两个注意事项:
- 第一不会踩到雷
- 展开的时候如果当前位置周围雷的数量为零,那么说明周围没有雷,那还不如让电脑自己帮我们展开,如果展开的也是0那么久让它继续展开。
游戏的过程大概就是这样。下面进行开发的分析:
首先是头文件,需要用的函数,由上面的扫雷的过程我们可以刨析出一下函数:
面板的初始化
静态面板中雷的布置
计算周围雷的数量
展开机制
交互界面(打印交互的面板)
游戏主函数
菜单函数
判断是否排完雷
所以头文件应当如下:
#ifndef __MINE_H__ //防止被重复使用
#define __MINE_H__
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#define ROW 12 //实际面板的大小
#define COL 12
#define MINE_NUM 10 //雷的数量
//面板的初始化
void init(char f_mine[ROW][COL], char f_show[12][12]);
//设置雷
void set_mine(char f_mine[ROW][COL]);
//计算周围雷的数量
int mine_count(char f_mine[ROW][COL], int f_x, int f_y);
//展开机制
void open(char f_show[ROW][COL], char f_mine[ROW][COL], int i, int j);
//打印交互面板
void print_show(char f_show[ROW][COL]);
//游戏主函数
void game(char f_mine[ROW][COL], char f_show[12][12]);
//菜单函数
int menu();
//判断是否排完雷
int is_end(char f_show[ROW][COL]);
#endif // !__MINE_H__
下面我介绍一下头文件中定义的标识符常量:
#define MINR_NUM 10
#define ROW 12
#define COL 12
首先 MINE_NUM 指的是雷的数量很容易就能翻译出来
其次说明这次的期盼我们打算用和用户交互的界面是 10 X 10 的,但是为什么要设置成 12 X 12 呢?我们可以想象一下,如果采用 10 X 10 的,那么边上的块在计算周围九个块中雷的数量时就会发生越界访问而导致崩溃,因此向外面再加一层,然后在进行计算周围雷的是数量时就不会发生此类现象。两边以及上下各加一层也就成了 12 X 12 。
下面来介绍游戏的主函数:
游戏开始时肯定得先初始化两个面板,完成之后为了第一次不会踩到雷,所以我们可以先让玩家进行一次选择,然后在随机布置雷,然后再判断是否是雷,如果是雷,那么结束游戏,返回踩雷,信号。如果不是雷那么进行展开机制,然后在进行判断是否排完雷,如果排玩雷,那么结束游戏,返回成功信号,如果没有排完,随后再进行第二次排雷,循环不断………
所以生成如下游戏主函数代码:
//游戏函数
void game(char f_mine[12][12], char f_show[12][12])
{
int x = 0;
int y = 0;
init(f_mine, f_show);
print_show(f_show);
while(1)
{
printf("请输入要打开的坐标:");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= ROW - 2 && y >= 1 && y <= COL - 2)
break;
else
printf("输入错误!");
}
set_mine(f_mine, x, y);
print_show(f_mine);
while (is_end(f_show) == 0)
{
if (x >= 1 && x <= ROW - 2 && y >= 1 && y <= COL - 2)
{
if (f_mine[x][y] == '1')
{
printf("BOOM!你被炸死了!\n");
print_show(f_mine);
return;
}
else
{
open(f_show, f_mine, x, y);
}
}
else
{
printf("输入非法!\n");
}
print_show(f_show);
printf("请输入要打开的坐标:");
scanf("%d%d", &x, &y);
}
printf("恭喜!恭喜!贺喜!贺喜呀!\n");
}
注意:每次输入都要进行校验,是否超出范围。
接下来就是游戏里的各种函数了:
#include"mine.h"
//打印菜单
int menu()
{
int choose = 0;
printf("*********扫雷游戏**********\n");
printf("********1、开始游戏********\n");
printf("********0、退出游戏********\n");
printf("***************************\n");
printf("请输入序号:");
scanf("%d", &choose);
return choose;
}
//初始化雷表和面板
void init(char f_mine[ROW][COL], char f_show[ROW][COL])
{
int i = 0;
int j = 0;
for (i=0; i<ROW; i++)
{
for (j=0; j<COL; j++)
{
f_mine[i][j] = '0';
f_show[i][j] = '*';
}
}
}
//打印show面板
void print_show(char f_show[ROW][COL])
{
int i = 0;
int j = 0;
printf(" 1 2 3 4 5 6 7 8 9 10\n");
printf("-----------------------------------\n");
for (i=1; i<ROW-1; i++)
{
printf("%2d |", i);
for (j=1; j<COL-1; j++)
{
if (f_show[i][j] == '0')
{
printf("%2c|", ' ');
}
else
{
printf("%2c|", f_show[i][j]);
}
}
printf("\n-----------------------------------\n");
}
}
//布置雷
void set_mine(char f_mine[ROW][COL], int f_x, int f_y)
{
int count = MINE_NUM;
srand((unsigned) time(NULL));
int i = 0;
int j = 0;
while (count > 0)
{
i = rand() % 10 +1;
j = rand() % 10 + 1;
if (f_mine[i][j] == '0' && i != f_x && j != f_y)
{
f_mine[i][j] = '1';
count--;
}
}
}
//统计周围雷的数量
int mine_count(char f_mine[ROW][COL], int f_x, int f_y)
{
int count = 0;
int i;
int j;
if (f_mine[f_x - 1][f_y - 1] == '1') count++;
if (f_mine[f_x - 1][f_y] == '1') count++;
if (f_mine[f_x - 1][f_y + 1] == '1') count++;
if (f_mine[f_x ][f_y - 1] == '1') count++;
if (f_mine[f_x][f_y ] == '1') count++;
if (f_mine[f_x][f_y + 1] == '1') count++;
if (f_mine[f_x + 1][f_y - 1] == '1') count++;
if (f_mine[f_x + 1][f_y] == '1') count++;
if (f_mine[f_x + 1][f_y+1] == '1') count++;
return count;
}
//展开周围
void open(char f_show[ROW][COL], char f_mine[ROW][COL], int i, int j)
{
int ret = mine_count(f_mine, i, j);
f_show[i][j] = '0' + ret;
if (ret == 0)
{
//向周围延伸,一次性可以展开与本次相连的无雷的情况
if ((i - 1) >= 1 && (i - 1) <= (ROW - 1) && (j - 1)>=1 && (j - 1)<=(COL - 1) && f_show[i - 1][j - 1] == '*')
open(f_show, f_mine, i - 1, j - 1);
if ((i - 1) >= 1 && (i - 1) <= (ROW - 1) && j>=1 && j<=(COL - 1) && f_show[i - 1][j] == '*')
open(f_show, f_mine, i - 1, j);
if ((i - 1) >= 1 && (i - 1) <= (ROW - 1) && (j + 1)>=1 && (j + 1)<=(COL - 1) && f_show[i - 1][j+1] == '*')
open(f_show, f_mine, i - 1, j + 1);
if (i >= 1 && i <= (ROW - 1) && (j - 1)>=1 && (j - 1)<=(COL - 1) && f_show[i][j - 1] == '*')
open(f_show, f_mine, i, j - 1);
if (i >= 1 && i <= (ROW - 1) && (j + 1)>=1 && (j + 1)<=(COL - 1) && f_show[i][j + 1] == '*')
open(f_show, f_mine, i, j + 1);
if ((i + 1) >= 1 && (i + 1) <= (ROW - 1) && (j - 1)>=1 && (j - 1)<=(COL - 1) && f_show[i + 1][j - 1] == '*')
open(f_show, f_mine, i + 1, j - 1);
if ((i + 1) >= 1 && (i + 1) <= (ROW - 1) && j>=1 && j<=(COL - 1) && f_show[i + 1][j] == '*')
open(f_show, f_mine, i + 1, j);
if ((i + 1) >= 1 && (i + 1) <= (ROW - 1) && (j + 1)>=1 && (j + 1)<=(COL - 1) && f_show[i + 1][j + 1] == '*')
open(f_show, f_mine, i + 1, j + 1);
}
}
//判断是否排完雷
int is_end(char f_show[ROW][COL])
{
int i = 0;
int j = 0;
int count = 0;
for (i=1; i<ROW-1; i++)
{
for (j=1; j<COL-1; j++)
{
if (f_show[i][j] == '*')
count++;
}
}
if (count == MINE_NUM)
return 1;
else
return 0;
}
这里我主要介绍一下展开周围的函数 open 函数,传进来的坐标参数说明玩家输入了此坐标,那么首先将此位置的块展开,即将数组的 * 符号改为周围雷的数量,但是因为这是一个字符数组,所以用 ‘0’ + 数量可以很好的解决这个问题。展开之后,需要判断一下此位置是否为 0 ,如果为 0的话那么我们可以先去判断周围九个块区域是否为 0 ,如果为 0 的话那么我们可以将此位置认为是输入的要展开的坐标,因为这个周围也可能是 0 那么我们就可以用递归的方法,即自己调用自己。如果执行到某一步不是的 0 的话那么不用管,因为这个函数第一步就是将传入的参数展开就行。但是要注意一点,调用自己的时候条件不光是当前位置周围没雷,条件还要是当前位置合法而且还没有被展开,才可以进行递归。
讲完函数游戏也就基本完成了,下面放入我的main函数代码:
#include"mine.h"
int main()
{
char mine[ROW][COL] = { '0' };
char show[ROW][COL] = { '0' };
int choose = 0;
do
{
choose = menu();
switch (choose)
{
case 1:
game(mine, show);
break;
case 0:
printf("游戏已退出!\n");
break;
default:
printf("输入错误:\n");
break;
}
} while (choose != 0);
return 0;
}