C语言经典小游戏之扫雷(超详解释+源码)

"少年气,是历尽千帆举重若轻的沉淀,也是乐观淡然笑对生活的豁达!" 今天我们学习一下扫雷游戏怎么用C语言来实现!

扫雷小游戏

1.游戏介绍

一个扫雷盘面由许多方格(cell)组成,方格中随机分布着一定数量的雷(mine),一个格子中至多只有1雷。胜利条件是打开所有安全格(非雷格,safe cell),失败条件是打开了一个雷格(踩雷)。下面图片中是一个9*9的示例:

2.游戏准备

和前面的三子棋一样,这里,我们也需要三个源文件来共同实现这个程序。

(1)头文件game.h,头文件里是用来存放函数的声明,#define常量的定义,库函数的引用的。

(2)源文件test.c,这个文件里面放的是游戏的测试逻辑。

(3)源文件game.c,这个文件里面放的是游戏的实现逻辑(函数实现)。

3.游戏实现

3.1生成菜单

这里呢,我们和三子棋一样,还是通过switch语句给用户选择,当用户输入不同的数字,我们的程序就会给出不同的功能。

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
#include <stdio.h>
void menu()
{
	printf("******************************\n");
	printf("*********   1.play   *********\n");
	printf("*********   0.exit   *********\n");
	printf("******************************\n");
}
void game()
{

}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

大致的框架就出来了,现在我们就需要看扫雷游戏的具体实现了。

3.2游戏的具体实现

这里我们简化游戏,设置一个9*9的棋盘,安置10个雷

排查过程如下:

1.如果这个位置是雷,那么游戏结束。

2.如果把不是雷的位置都找出来了,那么游戏结束。

3.如果这个位置不是雷,就计算这个位置的周围的8个格子有几个雷,并显示出雷的个数。

3.2.1初始化棋盘

我们这里的棋盘是9*9的,可以和三子棋一样,先将每个棋盘都初始化为0,如下图所示,然后有雷的地方填上1,如下图所示:

但是,我们从游戏规则中知道,当我们点到一个不是雷的格子的时候,要返回它周围八个格子中雷的个数。如下图中,如果我们点到了绿色1的那个格子,那么该位置将返回1这个值,此时,这里的1就会和表示雷的1就混起来了。

同时,当我们点到一个处于四边的格子的时候,还会出现越界的问题:

这个时候,我们就可以考虑在排查雷的时候,将棋盘扩展成11*11的棋盘。

同时,我们可以将排查雷的9*9的格子里都初始化为*,避免出现两个1意义不同混淆的情况。这样的话,我们就得到两个11*11的棋盘。

c 复制代码
//game.h
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘函数的声明
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set);

//game.h*
//初始化棋盘的定义
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

//test.c
void game()
{
	char assign[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	Init_Board(assign,ROWS,COLS,'0');
	Init_Board(show, ROWS, COLS, '*');
}

初始化完成之后,我们想要验证一下对不对呢?这个时候,我们就需要将棋盘打印出来。

3.2打印棋盘

虽然我们这里初始化的是11*11的棋盘,但是用户需要的是中间区域的9*9,因此,我们只需要打印中间的9*9就可以了。

c 复制代码
//game.h
//打印棋盘函数的声明
void Display_Board(char board[ROWS][COLS], int row, int col);

//game.c
//打印棋盘函数的定义
//我们只需要打印出中间的9*9的格子
void Display_Board(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	for (i = 1; i <= row; i++)
	{
		int j = 0;
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

//test.c
void game()
{
	Display_Board(assign, ROW, COL);
}

这里我们需要把行号和列号也打印出来,能够看的更清晰。代码进行这样的修改就好啦!

再优化一下,就如下图所示:

3.3布置雷

我们希望的是在这个9*9的棋盘里随机生成10个雷,这里我们就想到了能够产生随机数的函数rand()和srand(),使用这两个函数,需要添加头文件#include <time.h>和#include <stdlib.h>,同时,在主函数内还需要添加语句srand((unsigned int)time(NULL));来产生随机数的种子。

c 复制代码
//game.h
#define Easy_Thunder 10
#include <stdlib.h>
#include <time.h>
//布置雷函数的声明
void Set_thunder(char board[ROWS][COLS], int row, int col);

//game.c
//布置雷函数的定义
void Set_thunder(char board[ROWS][COLS], int row, int col)
{
	int count = Easy_Thunder;
	while (count)
	{
		int x = rand() % row + 1;//生成横坐标
		int y = rand() % col + 1;//生成纵坐标
		if (board[x][y] == '0')//防止在同一个地方重复布雷
		{
			board[x][y] = '1';
			count--;
		}
	}
}

//test.c
void game()
{
	char assign[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	Init_Board(assign,ROWS,COLS,'0');
	Init_Board(show, ROWS, COLS, '*');
	Display_Board(assign, ROW, COL);
	Set_thunder(assign, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	return 0;
}

那么,代码写好了,我们来验证一下,这样是不是能够布置出十个雷呢?

3.4排查雷

我们开始随机点一个格子,当这个格子不是雷的时候,可以计算它周围八个格子字符的ASCII码值,减去8个'0'的ASCII码值,就可以知道,这个格子周围有多少个雷了,然后我们加上'0'的ASCII码值就能在该处的格子上返回相应的字符。当我们把所有不是雷的格子找出来的时候,给用户提示排雷成功。当我们踩到雷的时候,也给出相应的提示给用户,游戏结束。这里和三子棋一样,我们可以添加清屏的操作system("cls");,让我们的游戏面板不是那么的冗长。

c 复制代码
//game.h
//排查雷函数的声明
void Find_thunder(char assign[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//game.c
int Getassign(char assign[ROWS][COLS], int row, int col)//查找不是雷的格子周围有几个雷
{
	return (assign[row - 1][col - 1]//字符'0'的ASCII值为48,字符'1'的ASCII值为49
		+ assign[row - 1][col]
		+ assign[row - 1][col + 1]
		+ assign[row][col - 1]
		+ assign[row][col + 1]
		+ assign[row + 1][col - 1]
		+ assign[row + 1][col]
		+ assign[row + 1][col + 1] - 8 * '0');
}
//排查雷函数的定义
void Find_thunder(char assign[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//计算没有踩到雷的次数
	while (win<col*row-Easy_Thunder)
	{
		printf("请输入坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (assign[x][y] == '1')
			{
				printf("很遗憾,你被炸死了!\n");
				Display_Board(assign, ROW, COL);
				break;
			}
			else
			{
				//如果这个位置不是雷就统计周围八个格子雷的个数
				int c = Getassign(assign, x, y);
				show[x][y] = c + '0';
				Display_Board(show, ROW, COL);
				system("cls");
				Display_Board(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("坐标非法,请重新输入!\n");
		}
	}
	if (win == row * col - Easy_Thunder)
	{
		printf("恭喜你排雷成功!\n");
		Display_Board(assign, ROW, COL);
	}	
}

//test.c
void game()
{
	Find_thunder(assign, show, ROW, COL);//排雷函数的引用
}

这里,我们随机输入,不用思考,试一下踩到雷的结果:

那么,如果我们仔细思考,看看当我们把所有不是雷的格子找出来的时候,能不能成功:

4.扫雷游戏的详细代码

c 复制代码
//game.h
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define Easy_Thunder 10
#include <stdlib.h>
#include <time.h>
//初始化棋盘函数的声明
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘函数的声明
void Display_Board(char board[ROWS][COLS], int row, int col);
//布置雷函数的声明
void Set_thunder(char board[ROWS][COLS], int row, int col);
//排查雷函数的声明
void Find_thunder(char assign[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
//初始化棋盘的定义
void Init_Board(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	for (i = 0; i < rows; i++)
	{
		int j = 0;
		for (j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}
//打印棋盘函数的定义
//我们只需要打印出中间的9*9的格子
void Display_Board(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("--------扫雷--------\n");
	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 Set_thunder(char board[ROWS][COLS], int row, int col)
{
	int count = Easy_Thunder;//布置10个雷
	while (count)
	{
		int x = rand() % row + 1;//生成横坐标
		int y = rand() % col + 1;//生成纵坐标
		if (board[x][y] == '0')//防止在同一个地方重复布雷
		{
			board[x][y] = '1';
			count--;
		}
	}
}
int Getassign(char assign[ROWS][COLS], int row, int col)//查找不是雷的格子周围有几个雷
{
	return (assign[row - 1][col - 1]//字符'0'的ASCII值为48,字符'1'的ASCII值为49
		+ assign[row - 1][col]
		+ assign[row - 1][col + 1]
		+ assign[row][col - 1]
		+ assign[row][col + 1]
		+ assign[row + 1][col - 1]
		+ assign[row + 1][col]
		+ assign[row + 1][col + 1] - 8 * '0');
}
//排查雷函数的定义
void Find_thunder(char assign[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//计算没有踩到雷的次数
	while (win<col*row-Easy_Thunder)
	{
		printf("请输入坐标:>");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (assign[x][y] == '1')
			{
				printf("很遗憾,你被炸死了!\n");
				Display_Board(assign, ROW, COL);
				break;
			}
			else
			{
				//如果这个位置不是雷就统计周围八个格子雷的个数
				int c = Getassign(assign, x, y);
				show[x][y] = c + '0';
				Display_Board(show, ROW, COL);
				system("cls");//清屏操作
				Display_Board(show, ROW, COL);
				win++;
			}
		}
		else
		{
			printf("坐标非法,请重新输入!\n");
		}
	}
	if (win == row * col - Easy_Thunder)
	{
		printf("恭喜你排雷成功!\n");
		Display_Board(assign, ROW, COL);
	}	
}

//test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
#include <stdio.h>
void menu()
{
	printf("******************************\n");
	printf("*********   1.play   *********\n");
	printf("*********   0.exit   *********\n");
	printf("******************************\n");
}
void game()
{
	char assign[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	Init_Board(assign,ROWS,COLS,'0');
	Init_Board(show, ROWS, COLS, '*');
	Display_Board(assign, ROW, COL);
	Set_thunder(assign, ROW, COL);
	Find_thunder(assign, show, ROW, COL);
}
int main()
{
	srand((unsigned int)time(NULL));
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch(input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
	} while (input);
	return 0;
}

以上就是关于扫雷游戏的全部代码啦!当然这个程序还是存在可优化的空间(比如我们的游戏只能一个一个点,但是不能像网页版的那样能展示一片,这还有待思考),欢迎大家在评论区交流,优化代码。

相关推荐
Scott9999HH5 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社6 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海6 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖7 小时前
JDK 26 新特性详解
java·开发语言
马优晨7 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区9 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大10 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai10 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜10 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa10 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio