C语言实现简单的扫雷小游戏
1.功能总览
- 菜单
N*N
的棋盘,N
可自定义- 可自定义的雷的数量
X
(X<=N*N-1
)- 随机生成的雷
- 玩家只能逐一判断每个格子的情况
- 检查游戏是否胜利
2.主函数的实现
如果想完成一大段功能复杂的代码,我认为最好先写主函数,然后把大的框架搭好,具体部分由函数来完成:
int main()
{
int input = 0;
while (1)
{
menu();//菜单
scanf("%d", &input);
switch (input)
{
case 1:
game();//游戏内容
break;
case 0:
printf("exit\n");
return 0;
default:
printf("error\n");
}
}
}
这段代码做到了以下几点:
1.退出条件
input==0
2.异常处理default
3.菜单menu
(未完成)
4.游戏内容game
(未完成)
修正:引用了库函数,需添加头文件:
#include <stdio.h>
3.菜单menu()
的实现
由主函数内容可知,我们需指引玩家输入1/0
来进入/退出
游戏
void menu()
{
printf("************\n");
printf("***1.game***\n");
printf("***0.exit***\n");
printf("************\n");
}
4.游戏内容game()
的实现
这部分就是最主要的部分了
与主函数的实现类似,我们还是先搭架子
void game()
{
int N, X;
DIY(&N, &X);
char** pb1 = create_board(N + 2);
char** pb2 = create_board(N + 2);
zero_board(pb1, N + 2, '0');
zero_board(pb2, N + 2, '*');
set_boom(pb1, N, X);
show_board(pb2, N);
check_boom(pb1, pb2, N, X);
}
这段代码做到了以下几点:
1.自定义
DIY(&N, &X)
(未完成)
2.创造两个棋盘create_board
(未完成) 注:1存雷,2展示
3.初始化棋盘zero_board
(未完成)
4.设置雷set_boom
(未完成)
5.展示棋盘show_board
(未完成)
6.查雷check_boom
(未完成)
4.1自定义DIY(&N, &X)
void DIY(int* pN, int* pX)
{
T:
printf("自定义棋盘长宽N:");
scanf("%d", pN);
printf("自定义雷的数量X:");
scanf("%d", pX);
if (*pN<1||*pX<1||*pX > (*pN) * (*pN) - 1)
{
printf("error\n");
goto T;
}
}
这里加入了异常处理 (*pN<1||*pX<1||*pX > (*pN) * (*pN) - 1)
4.2创造棋盘create_board
这里我的思路是使用malloc函数,(VS不支持变长数组)
并用二级指针接收函数的返回值char** pb1/2 = create_board(N + 2);
N+2
是因为后期要判断一个格子旁有几个雷,所以创造棋盘时开大一圈便于操作
char** create_board(int N)
{
char** p = (char**)malloc(N * sizeof(char*));
//创建1个指针数组,其N个元素为指向char类型的指针
for (int i = 0; i < N; i++)
{
*(p + i) = (char*)malloc(N * sizeof(char));
//创建N个数组,类型char,元素个数N
}
return p;
}
修正:引用了库函数,需添加头文件:
#include <stdlib.h>
4.3初始化棋盘zero_board
1,2棋盘都要初始化,为避免代码重复,添加char sign
void zero_board(char** p, int N,char sign)
{
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
p[i][j] = sign;
}
}
}
4.4设置雷set_boom
void set_boom(char** p, int N,int X)
{
while (X)
{
int x = (rand() % N + 1);
int y = (rand() % N + 1);
if (p[x][y] == '0')
{
p[x][y] = '1';
X--;//每设置成功一个,还需设置的数目-1
}
}
}
修正:
- 引用了
rand()
,需在main
函数入口处设置随机数起点:
srand((unsigned int)time(NULL));
+引用了time
,需添加头文件:#include <time.h>
4.5.展示棋盘show_board
void show_board(char** p, int N)
{
for (int x = 0; x <= N; x++)
{
printf("%d ", x);
}
printf("\n");
for (int i = 1; i <= N; i++)
{
printf("%d ", i);
for (int j = 1; j <= N; j++)
{
printf("%c ",p[i][j]);
}
printf("\n");
}
}
这里每行每列开头加了数字,方便观看
棋盘2:
棋盘1:
4.6查雷check_boom
void check_boom(char** p1, char** p2, int N,int X)
{
int left = N * N - X;//指有多少不是雷的
while (left)
{
printf("输入坐标:");
int x = 0;
int y = 0;
scanf("%d%d", &x, &y);
if (x<1 || x>N || y<1 || y>N||p2[x][y] != '*')
{
printf("error\n");
}
else if (p1[x][y] == '1')//点到雷了
{
printf("BOOM!!!\n");
show_board(p1, N);//展示雷
return;//结束游戏
}
else
{
p2[x][y] = count_boom(p1, x, y) + '0';
show_board(p2, N);
left--;
}
}
if (!left)//当不是雷,且未排除的数目为0
//即left==0时,!left为真
{
printf("WIN!!!\n");
show_board(p1, N);
}
}
我认为只要有输入就需加异常处理,这里是x<1 || x>N || y<1 || y>N||p2[x][y] != '*'
- 上面有一个函数
count_boom
,用于查找对应点附近有多少雷,代码如下:
int count_boom(char** p, int x, int y)
{
int count = 0;
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
count += p[x + i][y + j] - '0';
}
}
return count;
}
5.可优化的点
1.安全方面(这个点其实我会,但不是本文重点,所以我没做)
真不是我懒
- 使用了
malloc
,应注意检查是否分配内存成功- 在不再需要这块内存时,用
free
释放以避免内存泄漏
注:因为是用循环申请的,所以要用循环释放
2.异常处理
- 我在测试
N,X
的输入时发现,如果输入了字符
,会马上死循环,贴个图:
因为%d
是读取整形的, 当输入字符的时候,会导致读取异常,然后pN
和pX
就读不到值,是随机值,然后就死循环了
至于处理方法,我不知道,你们有谁知道吗?
3.游戏体验
- 如像网页版扫雷一样点一下出来一大片,可惜我不会递归(暂时)
- 添加标记
- 添加排雷的倒计时
- 可使用
system("cls");
清屏,避免堆积过多无用信息- 棋盘大了容易走形
总结+完整代码
大化小很重要,像搭建房屋一样,先框架,在筑墙,最后装修。
希望本篇文章对你有所帮助!
当然,上面的代码还有很大优化空间,本人也仅仅是个C语言初学者,如有任何意见,欢迎各位提出!