【C语言小游戏】扫雷游戏的实现

前言

        利用C语言数组、函数等一些基础知识实现简单的扫雷游戏。此游戏需有一定的数组、函数知识支撑。


1. 游戏简要及获胜规则

        扫雷是电脑上一个十分经典的小游戏,相信大家都和我一样在小的时候胡乱点过,今天我将带大家用C语言的知识重现一个简易版扫雷游戏,只要避开棋盘上所有地雷,我们就可以获得胜利。

2. 游戏逻辑

        实现此游戏,我们需要思考出一个可靠的逻辑

  1. 打印菜单给玩家进行选择,玩家可选择play(玩)或者exit(退出)
  2. 玩家选择play,进入游戏环节后,给玩家展示加密后的棋盘
  3. 玩家选择坐标排雷
  4. 玩家选到雷,游戏结束,玩家失败
  5. 玩家选择坐标不为地雷,回到第3步骤继续排雷
  6. 直到棋盘上只剩下雷时,游戏结束,玩家获胜

3.设计思路

        想让代码达到你想让它运行的结果,我们必须有一个清晰的思路。

3.1 写代码前的思考

        要想实现游戏的整个过程,我们必须先弄明白游戏最重要的棋盘该如何设计,这时就需要利用数组来创建好我们的棋盘,并利用各个函数进行相互调动,即可完成。所以我们需要创建两个二维数组来实现棋盘的逻辑,一个用来存放雷,一个提供给玩家进行排雷。在存放雷的棋盘中,我们用 '0' 来代表非雷,用 '1' 来代表地雷;在排雷棋盘中,我们存放 '*' 来供玩家排雷,排雷成功后将 '*' 变为显示此坐标周围八个坐标雷数。

        如果我们需要打印9 * 9的棋盘,我们应该创建多大的二维数组呢?也应该创建一个9 * 9的二维数组吗?其实仔细想想,要想实现,选择坐标后显示周围雷数的功能,我们必然会访问该坐标周围的空间,如果选择的坐标在棋盘的边缘的话,进行数雷功能时就会发生数组越界,要想解决此问题我们就需要在棋盘周围多一圈元素,也就是创建一个11 * 11的二维数组,当然在游戏环节中,我们并不需要将全部元素打印出来,打印一个9 * 9的棋盘即可。

         上面就是我们设置棋盘的大概思路,蓝色为最终显示的棋盘大小,接下来我们就编写代码来实现扫雷游戏。

3.2 创建文件

        首先先创建我们的文件,test.c(游戏的整体框架),game.c(游戏涉及的自定义函数),game.h(头文件,用于声明各种函数等)

 3.3 打印游戏菜单

        我们先写一个主函数,并创建test函数将游戏的整体框架放入其中

int main()
{
	test();//函数主体
	return 0;
}

        接着在test函数中调用menu函数打印菜单,选择1表示进行游戏,选择0表示退出游戏

void menu()
{
	printf("*********************\n");
	printf("****   1. play   ****\n");
	printf("****   0. exit   ****\n");
	printf("*********************\n");
}

        我们利用do-while循环实现扫雷游戏的整体简单逻辑,其中利用了switch语句对玩家的选择(input)进行了判断,选择为0就会退出游戏打破do-while循环,选择为1就会进入game函数

void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();//打印菜单
		printf("请玩家进行选择>:");
		scanf("%d", &input);
		switch (input)//判断玩家的选择
		{
		case 1:
			game();//进入游戏函数
			break;
		case 0:
			printf("你已退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

         以上就是游戏开始前的整体准备,接下来我们实现玩家选择1. play后进行的game函数。玩家在选择1后就会开始调用game函数,所以我们需要自定义一个game函数,我们分开来讲解game函数的实现逻辑。

        在进入游戏后,我们就需要创建二维数组来布置雷。

3.4 初始化棋盘

        我们先创建两个二维数组,一个用来布置雷,一个用来排地雷。

char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息

        这里ROWS,COLS分别代表行数和列数,是由我们通过define定义的常量,这里就需要我们的头文件了(一般在代码工程中,函数声明、自定义常量、库函数的调用等一般都写在头文件中,需要使用时直接调用头文件即可,调用方式与调用库函数略有不同,格式为:#include "xxxx.h",在这里调用的话就是,#include "game.h")

        在这里,先给大家附上三子棋游戏中的整个头文件(浏览即可,不必看懂,会按游戏步骤进行讲解)

#pragma once//头文件自带语句

//调用库函数
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

//自定义常量
#define ROW 9
#define COL 9
#define ROWS ROW+2 
#define COLS COL+2

//设置难度
#define EASY_COUNT 10

//各种函数声明
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化数组
void DisplyBoard(char board[ROWS][COLS], int row, int col);//打印数组
void SetMine(char mine[ROWS][COLS], int row, int col);//布置雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷

        在创建好数组后,我们就可以对他们进行初始化

InitBoard(mine, ROWS, COLS, '0');//初始化mine数组全为'0'
InitBoard(show, ROWS, COLS, '*');//初始化show数组全为'*'

        这时我们便需要自定义一个初始化函数InitBoard来实现此功能

        自定义函数首先在头文件中进行声明(可参考上述头文件代码),然后在game.c文件中编写实现逻辑,每一个自定义函数都是如此,之后的就不再进行说明了

        我们利用for循环嵌套来对数组进行初始化

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

3.5 布置地雷

        在棋盘初始化后,我们就可以在棋盘中布置地雷了

SetMine(mine, ROW, COL);

        我们自定义SetMine函数来实现此功能,在每一次进行游戏时,我们都希望雷的排列位置不同,所以我们利用rand函数来生成随机数,使用它时需要和srand函数来进行搭配使用,在srand函数中,我们又需要time函数来利用时间戳来生成不同的数,三个函数都为C语言中的库函数,我们需要调用他们

#include <time.h>
#include <stdlib.h>
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//设置雷数
	while (count)
	{
        //rand是一个库函数,用来生成随机数,范围设置为0~9
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';//地雷用'1'代表
			count--;
		}
	}
}

3.6 打印棋盘

        在随机布置好地雷后,就可以打印出show数组来供玩家进行排雷行动,利用自定义打印数组的函数来实现此功能

        注意:在打印棋盘时,我们只需要打印其中 9*9 的元素即可

void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		//打印行号
		printf("%d ", i);
		//打印数组
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

         实现后如图:

 3.7 游戏环节

        在以上操作都准备就绪后,玩家就可以选择坐标开始排雷了,这里自定义一个找雷函数

FindMine(mine, show, ROW, COL);

        要想将此环节实现,我们需要思考玩家如何获胜,和玩家怎样算做失败,在找雷的过程中,如果玩家排查坐标为 '1',则代表玩家选择了雷,被炸死,游戏失败;玩家排查坐标为 '0',则计算周围八个坐标雷数并显示,当棋盘中元素只剩下雷('1')时,玩家获胜。

        计算周围坐标雷数并显示,我们就需要另一个函数来实现了,可以自定义一个函数来辅助FindMine函数的实现,其返回值应该为雷的个数。

int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
	//返回周围雷数总和
	return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] -
		8 * '0';
        //数字对应的ASCII值和数字字符对应的ASCII值相差48,即'0'的ASCII值
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入要排查坐标(行 列)>:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("游戏结束,你被炸死了!\n");
				DisplyBoard(mine, row, col);
				printf("\n");
				break;
			}
			else
			{
				//计算坐标x,y周围雷的个数
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
                //数字对应的ASCII值和数字字符对应的ASCII值相差48,即'0'的ASCII值
				DisplyBoard(show, row, col);
				win++;
			}
		}
		else
		{
			printf("坐标非法,请重新输入>:");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排完所有雷,获得胜利!\n");
		DisplyBoard(mine, row, col);
		printf("\n");
	}
}

3.8 实现game函数总逻辑

        将上文提到的所有块合起来,就能实现我们的game函数的整体逻辑

        即为我们的game.c源文件

#include "game.h"

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		//打印行号
		printf("%d ", i);
		//打印数组
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//设置雷数
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
	//返回周围雷数总和
	return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] -
		8 * '0';
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入要排查坐标(行 列)>:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("游戏结束,你被炸死了!\n");
				DisplyBoard(mine, row, col);
				printf("\n");
				break;
			}
			else
			{
				//计算坐标x,y周围雷的个数
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplyBoard(show, row, col);
				win++;
			}
		}
		else
		{
			printf("坐标非法,请重新输入>:");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排完所有雷,获得胜利!\n");
		DisplyBoard(mine, row, col);
		printf("\n");
	}
}

4 游戏实现的整个代码工程

        game.h 头文件

#pragma once//头文件自带语句

//调用库函数
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

//自定义常量
#define ROW 9
#define COL 9
#define ROWS ROW+2 
#define COLS COL+2

//设置难度
#define EASY_COUNT 10

//各种函数声明
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化数组
void DisplyBoard(char board[ROWS][COLS], int row, int col);//打印数组
void SetMine(char mine[ROWS][COLS], int row, int col);//布置雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷

        game.c 源文件

#include "game.h"

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisplyBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		//打印行号
		printf("%d ", i);
		//打印数组
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = EASY_COUNT;//设置雷数
	while (count)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			count--;
		}
	}
}

int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
	//返回周围雷数总和
	return mine[x - 1][y - 1] +
		mine[x - 1][y] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] -
		8 * '0';
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	while (win < row * col - EASY_COUNT)
	{
		printf("请输入要排查坐标(行 列)>:");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("游戏结束,你被炸死了!\n");
				DisplyBoard(mine, row, col);
				printf("\n");
				break;
			}
			else
			{
				//计算坐标x,y周围雷的个数
				int n = get_mine_count(mine, x, y);
				show[x][y] = n + '0';
				DisplyBoard(show, row, col);
				win++;
			}
		}
		else
		{
			printf("坐标非法,请重新输入>:");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排完所有雷,获得胜利!\n");
		DisplyBoard(mine, row, col);
		printf("\n");
	}
}

        test.c 源文件

#include "game.h"

void menu()
{
	printf("*********************\n");
	printf("****   1. play   ****\n");
	printf("****   0. exit   ****\n");
	printf("*********************\n");
}

void game()
{
	//创建数组
	char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息
	char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息

	//初始化数组
	InitBoard(mine, ROWS, COLS, '0');//初始化mine数组全为'0'
	InitBoard(show, ROWS, COLS, '*');//初始化show数组全为'*'

	//布置雷的位置
	SetMine(mine, ROW, COL);
	//打印数组
	DisplyBoard(show, ROW, COL);
	//玩家找雷
	FindMine(mine, show, ROW, COL);
}

void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		menu();//打印菜单
		printf("请玩家进行选择>:");
		scanf("%d", &input);
		switch (input)//判断玩家的选择
		{
		case 1:
			game();//进入游戏函数
			break;
		case 0:
			printf("你已退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}

int main()
{
	test();//函数主体
	return 0;
}

5. 此代码的缺陷

        该逻辑下的代码只可实现扫雷游戏的简单操作,还是无法还原原版扫雷的功能,比如:

  1. 无法一下子展开周围空白元素,需要自己一个个去排查坐标
  2. 没办法保证玩家选择的第一个坐标是否为地雷,如果选到地雷坐标,玩家只能重新开始
  3. 无法计算玩家扫雷完成后的用时 

        读者可以自由发挥想象来完善该代码,比如1的问题就可以利用函数递归来解决,2的问题只要玩家在选择该坐标后,将该位置雷移到另一空白位置继续游戏即可


 结言

        这也是博主C语言学习路上的小小创作,肯定无法避免有一些不足和缺陷,希望各位大佬能指出博主的错误,我一定会积极修改,不断改进!

猜你喜欢

转载自blog.csdn.net/qq_60366454/article/details/121348361