C语言之扫雷小游戏(完整代码版)

说起扫雷游戏,这应该是很多人童年的回忆吧,中小学电脑课最常玩的必有扫雷游戏,那么大家知道它是如何开发出来的吗,扫雷游戏背后的原理是什么呢?今天就让我们一探究竟!

扫雷游戏介绍

如下图,简单版本的扫雷游戏,就是在一个9*9的方格阵中,埋藏了10颗地雷,而玩家就是根据这个方阵的反馈,进行10颗地雷位置的排查。

具体的游戏规则如下,玩家随机选择一个位置,如果选取位置不是雷,则显示数字。对于边缘的数字,则显示图中数字周围临近的5个方格中含有的地雷个数,例如下图红色圈起来的数字2,则说明红框之中必有两个地雷,即黑色×处。再看绿色框出来的数字2,它处于方格阵的中间位置,则看它周围临近的8个方格,含有2个地雷,也是黑色×处。

玩家可以根据数字的提示,筛选出10颗雷的位置,最终获得游戏胜利。反之,如果不幸选取到雷(即踩雷),则游戏失败。

扫雷游戏设计方案

  1. 创建两个两个二维数组,一个作为埋雷地图,另一个作为玩家视角的排雷视图。

  2. 设计一个9*9的扫雷游戏,但为了防止在统计坐标周围雷的个数的时候越界,设定数组的大小为11*11。

  3. 数组均为字符数组.

  4. 此次游戏实现同样采用多文件的形式设计。

test.c ------ 测试游戏功能是否完好的代码

game.c ------ 实现游戏逻辑的核心代码

game.h ------ 游戏变量及函数的声明

扫雷游戏具体实现

(一)game.h文件

cpp 复制代码
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define ROW 9
#define COL 9

#define ROWS 11
#define COLS 11

#define EASY_MODEL 10


void InitGame(char board[ROWS][COLS], int row, int col, char set);//初始化棋盘
void DisPlayBoard(char board[ROWS][COLS], int row, int col);//展示棋盘
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//初始化地雷
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetMine(char mine[ROWS][COLS], int row, int col);
//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int x, int y,int count);
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num);

(二)test.c文件

1.main函数

在实现游戏功能的时候,一般都是在main函数中写出大体的框架,当遇到需要实现一些功能时,我们再去设计函数完成对应功能。

这里设定游戏可以玩多局,即当游戏结束是不用退出可以继续选择是否进行下一局游戏,所以这里使用循环。同时,在第一局开始前询问玩家是否开始游戏,则采用do....while()循环。

这里设置一个菜单来提醒玩家是否进行游戏。同时,玩家选择1时表示开始游戏,选择0时表示游戏结束,退出程序,选择其他时要提示玩家输入非法,并重新进行选择,所以这里需要一个switch选择语句。同时设置随机种子,方便每次开始扫雷游戏时,地雷都能随机位置。

cpp 复制代码
int main()
{
	int input = 0;
	srand((unsigned int)(time(NULL)));//设置随机种子
	do {
		menu();
		scanf("%d", &input);
		switch (input)
		{
		default:
			printf("输入错误嗷,请重新输入!\n");
			break;
		case 1:
			printf("扫雷游戏启动....\n");
			game();
			break;
		case 0:
			printf("扫雷游戏结束咯....\n");
			break;
		}
	} while (input);
	return 0;
}

2.menu函数

cpp 复制代码
void menu()
{
	printf("**************************\n");
	printf("********  1. play  *******\n");
	printf("********  0. exit  *******\n");
	printf("**************************\n");
}

3.game函数

当玩家开始玩游戏时,game函数就是用来实现游戏整体框架,并调用其他实现具体功能的函数,具体代码如下。

cpp 复制代码
void game()
{
	char mine[ROWS][COLS];	//埋雷地图
	char show[ROWS][COLS];	//玩家窗口
	InitGame(mine, ROW, COL, '0');
	InitGame(show, ROW, COL, '*');//初始化棋盘
	InitMine(mine, show, ROW, COL);//埋雷
	DisPlayBoard(show, ROW, COL);
	StartGame(mine, show, ROW, COL);

}

其中mine数组时用来存储埋雷信息,show数组是用来存放排查出雷的信息。首先将mine数组的内容全都初始化为字符0,show数组将内容初始化为'*'表示此时位置还未被排雷。初始化棋盘后开始埋雷,将雷设置为1(具体后面会说)。mine数组的视图仅在程序开发时给程序员看,方便进行游戏调试。当正式游戏时,mine数组应该不给玩家看。

(三) game.c文件

1.InitGame函数(初始化棋盘)

此为初始化棋盘函数,由于需要确保函数的通用性,并且考虑到有一个mine视图和一个show视图需要同时初始化,而这两个视图初始化的内容也不同,所以设定一个set参数,每次调用函数的时候可以设置不同的初始化内容。

cpp 复制代码
void InitGame(char board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < ROWS; i++)
	{
		for (int j = 0; j < COLS; j++)
		{
			board[i][j] = set;
		}
	}
}

2.DisPlayBoard函数(显示棋盘 )

在实现DisPlayBoard函数时,不仅仅需要把数组内容给打印出来,还要考虑到玩家在玩游戏时输入地雷坐标方便,需要将整个方阵的横纵坐标给显示出来便于玩家判断。同时,还要提示玩家此局游戏中一共埋藏了多少颗雷。而雷的个数我们用一个全局变量EASY_COUNT来代替,这样也方便后续修改雷的个数。打印代码具体如下:

cpp 复制代码
void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("------------------------------\n");
	printf("本场游戏,一共有%d颗地雷\n", EASY_MODEL);
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);//输出列号
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);//输出行号
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

3.InitMine函数(埋雷)

本游戏设计10个雷,即EASY_MODEL的值设置为10。test.c的main函数中设计了随机种子,让雷的位置是随机的。使用rand函数来随机生成雷的位置的坐标,同时需要保证10颗雷的位置不能重复。因此采用while循环,如果成功布置雷就将count-1,直至count的值为0时结束循环。

最后,我们将放置雷的位置上的信息改为字符'1',而非雷的位置信息依旧为字符'0'(即修改mine数组中对应位置的内容),修改为1是为了方便后续统计一个坐标周围的8个位置中有多少颗雷。需要注意的是:由于数组的下标是从0开始,而玩家所看到的方格的坐标是从1开始,因此我们在随机生成的雷的坐标中需要对x,y进行+1操作。代码如下:

cpp 复制代码
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//初始化雷区
	int mine_num = EASY_MODEL;
	while (mine_num)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			mine_num--;
		}
	}
}

4.GetMine函数(统计方格四周雷的个数)

统计一个坐标为(x,y)位置周围雷的个数,就是统计其周围8个位置的内容有多少个字符'1'。其余8个位置的坐标如下图:

由于字符1和字符0的差距就是1,所以我们统计八个位置的累计地雷数,就是减8个字符0的ASCII值即可,具体代码如下:

cpp 复制代码
int GetMine(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y] + mine[x + 1][y] + mine[x][y + 1] + mine[x][y - 1] +
		mine[x - 1][y - 1] + mine[x + 1][y + 1] + mine[x - 1][y + 1] + mine[x + 1][y - 1] - 8 * '0';
}

5.StartGame函数(开始扫雷游戏,排查雷)

如果玩家未将所有雷排查完,则需要继续排查,因此必然需要一个循环,且当排查的个数=总数 - 雷的个数时,循环结束。

每次需要判断玩家输入的雷坐标是否合法,不合法则需重新输入,合法才继续判断。需要判断两个条件。(1)当前位置是否为雷,是雷则直接结束游戏。(2)若当前位置非雷,但是排查过了,需要提示玩家并重新输入排雷位置。(3)只有当前位置非雷,并且排查过,才可以。需要注意的是:输入的非法坐标不计入排查的次数中。具体代码如下:(本代码实现了大片排雷)

cpp 复制代码
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x, y;
	int num = EASY_MODEL;
	int count = 0;
	while (count < row * col - num)
	{
		printf("请输入你要扫雷的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '0' && show[x][y] != '*')
			{
				printf("刚才排除此地咯,请重新输入坐标!\n");
			}
			else if (mine[x][y] == '1')
			{
				printf("你被炸了,游戏结束!\n");
				break;
			}
			else
			{
				//count++;
				SearchMine(mine,show,row,col,x,y,&count);
				//count = SearchMine(mine, show, row, col, x, y, count);
				DisPlayBoard(show, 9, 9);
			}
		}
		else
		{
			printf("你输入的坐标不对嗷,请重新输入!\n");
		}
	}
	if (count == row * col - num) {
		printf("恭喜你获胜啦!\n");
	}
}

6.SearchMine函数(排查雷,实现大片扫雷)

使用GetMine函数获取周围8个坐标中雷的数量:

(1)如果是0,即周围8个坐标没有雷,那么就将中间设置为空白,在棋盘范围内,对该八个坐标再次进行SearchMine(递归);递归时排除已经是空格的坐标,防止陷入死循环。直至遇到一个坐标,其范围为内有雷。

(2)如果不是零,将该坐标设置为雷的数量(注意是字符类型)

cpp 复制代码
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num)
{
	if (x < 1 || x > row || y < 1 || y > col)	return;//坐标不合法直接不继续
	(*num)++;//num变量传的是排查雷个数变量的位置,*num是取出该变量位置上的具体值
	char tmp = GetMine(mine, x, y) + '0';//查看当前位置有几个地雷
	if (tmp == '0')
	{
		show[x][y] = ' ';
		for (int i = -1; i <= 1; i++)
		{
			for (int j = -1; j <= 1; j++)
			{
				int nx = x + i;
				int ny = y + j;
				if (show[nx][ny] == '*') 
				{
					SearchMine(mine, show, row, col, nx, ny, num);
				}
			}
		}
	}
	else
	{
		show[x][y] = tmp;
		//return;
	}
}

实现效果:

完整代码

1.test.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

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

void game()
{
	char mine[ROWS][COLS];	//埋雷地图
	char show[ROWS][COLS];	//玩家窗口
	InitGame(mine, ROW, COL, '0');
	InitGame(show, ROW, COL, '*');//初始化棋盘
	InitMine(mine, show, ROW, COL);//埋雷
	DisPlayBoard(mine, ROW, COL);
	DisPlayBoard(show, ROW, COL);
	StartGame(mine, show, ROW, COL);

}

int main()
{
	int input = 0;
	srand((unsigned int)(time(NULL)));
	do {
		menu();
		scanf("%d", &input);
		switch (input)
		{
		default:
			printf("输入错误嗷,请重新输入!\n");
			break;
		case 1:
			printf("扫雷游戏启动....\n");
			game();
			break;
		case 0:
			printf("扫雷游戏结束咯....\n");
			break;
		}
	} while (input);
	return 0;
}

2.geme.h

cpp 复制代码
#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

#define ROW 9
#define COL 9

#define ROWS 11
#define COLS 11

#define EASY_MODEL 10


void InitGame(char board[ROWS][COLS], int row, int col, char set);
void DisPlayBoard(char board[ROWS][COLS], int row, int col);
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetMine(char mine[ROWS][COLS], int row, int col);
//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int x, int y,int count);
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num);

3.game.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void InitGame(char board[ROWS][COLS], int row, int col, char set)
{
	for (int i = 0; i < ROWS; i++)
	{
		for (int j = 0; j < COLS; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("------------------------------\n");
	printf("本场游戏,一共有%d颗地雷\n", EASY_MODEL);
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);//输出列号
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);//输出行号
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//初始化雷区
	int mine_num = EASY_MODEL;
	while (mine_num)
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (mine[x][y] == '0')
		{
			mine[x][y] = '1';
			//show[x][y] = '#';
			mine_num--;
		}
	}
}

int GetMine(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y] + mine[x + 1][y] + mine[x][y + 1] + mine[x][y - 1] +
		mine[x - 1][y - 1] + mine[x + 1][y + 1] + mine[x - 1][y + 1] + mine[x + 1][y - 1] - 8 * '0';
}

void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num)
{
	if (x < 1 || x > row || y < 1 || y > col)	return;
	(*num)++;
	char tmp = GetMine(mine, x, y) + '0';
	if (tmp == '0')
	{
		show[x][y] = ' ';
		for (int i = -1; i <= 1; i++)
		{
			for (int j = -1; j <= 1; j++)
			{
				int nx = x + i;
				int ny = y + j;
				if (show[nx][ny] == '*') 
				{
					SearchMine(mine, show, row, col, nx, ny, num);
				}
			}
		}
	}
	else
	{
		show[x][y] = tmp;
		//return;
	}
}

//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count)
//{
//	if (x < 1 || x > row || y < 1 || y > col || show[x][y] != '*')
//		return count;
//
//	int tmp = GetMine(mine, x, y);
//	count++;
//	show[x][y] = tmp + '0';
//
//	if (tmp == 0)
//	{
//		for (int i = -1; i <= 1; i++)
//		{
//			for (int j = -1; j <= 1; j++)
//			{
//				int nx = x + i;
//				int ny = y + j;
//				if (nx >= 1 && nx <= row && ny >= 1 && ny <= col)
//				{
//					count = SearchMine(mine, show, row, col, nx, ny, count);
//				}
//			}
//		}
//	}
//
//	return count;
//}


void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x, y;
	int num = EASY_MODEL;
	int count = 0;//排查的雷个数
	while (count < row * col - num)
	{
		printf("请输入你要扫雷的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '0' && show[x][y] != '*')
			{
				printf("刚才排除此地咯,请重新输入坐标!\n");
			}
			else if (mine[x][y] == '1')
			{
				printf("你被炸了,游戏结束!\n");
				break;
			}
			else
			{
				//count++;
				SearchMine(mine,show,row,col,x,y,&count);
				//count = SearchMine(mine, show, row, col, x, y, count);
				DisPlayBoard(show, 9, 9);
			}
		}
		else
		{
			printf("你输入的坐标不对嗷,请重新输入!\n");
		}
	}
	if (count == row * col - num) {
		printf("恭喜你获胜啦!\n");
	}
}

总结:

程序开发整体上不是流程和逻辑还是比较容易实现的,难点在于实现大片扫雷的功能。大片扫雷功能主要是递归的实现,容易使程序崩溃,还需多多练习递归实现。附上程序运行部分效果图:

相关推荐
Moment20 分钟前
面试官:如果产品经理给你多个需求,怎么让AI去完成❓❓❓
前端·后端·面试
每天吃饭的羊21 分钟前
JSONP
前端
Hello.Reader26 分钟前
算法基础(十)——分治思想把大问题拆成小问题
java·开发语言·算法
gogoing27 分钟前
ESLint 配置字段说明
前端·javascript
gogoing39 分钟前
CSS 属性值计算过程(Computed Value)
前端·css
gogoing39 分钟前
webpack 的性能优化
前端·javascript
桃花键神43 分钟前
Bright Data Web Scraping指南 2026: 使用 MCP + Dify 自动采集海外社交媒体数据
大数据·前端·人工智能
gogoing1 小时前
await fetch() 的两阶段设计
前端·javascript
gogoing1 小时前
前端首屏加载优化
前端·javascript
gogoing1 小时前
重排与重绘
前端·javascript