Bootstrap

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是读取整形的, 当输入字符的时候,会导致读取异常,然后pNpX就读不到值,是随机值,然后就死循环了
    至于处理方法,我不知道,你们有谁知道吗?

3.游戏体验

  • 如像网页版扫雷一样点一下出来一大片,可惜我不会递归(暂时)
  • 添加标记
  • 添加排雷的倒计时
  • 可使用system("cls");清屏,避免堆积过多无用信息
  • 棋盘大了容易走形

总结+完整代码

大化小很重要,像搭建房屋一样,先框架,在筑墙,最后装修。

完整代码请点击此处

希望本篇文章对你有所帮助!
当然,上面的代码还有很大优化空间,本人也仅仅是个C语言初学者,如有任何意见,欢迎各位提出!

;