IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【C语言小游戏】扫雷游戏的实现 -> 正文阅读

[游戏开发]【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语言学习路上的小小创作,肯定无法避免有一些不足和缺陷,希望各位大佬能指出博主的错误,我一定会积极修改,不断改进!

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-11-18 11:29:09  更:2021-11-18 11:29:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 23:54:13-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码