经典扫雷游戏【C语言】【详解】

  • 大家好,今天将和大家一起来用C语言实现经典扫雷游戏~

文章目录

  • 一、游戏介绍
  • 二、游戏整体框架
  • 三、游戏具体各功能的实现
    • 1)游戏菜单及流程
      • a)游戏实现思路
    • 2)初始化棋盘
    • 3)打印棋盘
    • 4)布置雷
    • 5)排查雷
      • 5.1)展开一大片无雷区
      • 5.2)判断周围有多少雷
    • 6)标记雷
    • 7)取消标记雷
    • 8)game.h的讲解
  • 四、完整代码
    • 1)‍test.c
    • 2)‍game.h
    • 3)‍game.c

一、游戏介绍

  • 《扫雷》是一款大众类的益智小游戏。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。如下图:当你翻开一个格子后,若没有地雷,它会自动判断其周围8个格子里有没有地雷,如果有则会显示有几个地雷,没有就显示0。如果说翻到地雷,就说明你失败了。(我在这里讲解的是9×9的棋盘)

经典扫雷游戏【C语言】【详解】_第1张图片

二、游戏整体框架

test.c:游戏逻辑的测试,包含游戏菜单的打印,游戏设计的基本逻辑的展示
game.c:游戏功能的具体实现,游戏的核心代码
game.h:头文件的使用、符号的声明以及函数的声明

三、游戏具体各功能的实现

1)游戏菜单及流程

a)游戏实现思路

首先就是要给出菜单,让用户选择是否进行游戏还是退出游戏,另外就是定义棋盘,给棋盘初始化,让用户输入要检测的坐标,然后判断是不是雷,不是雷就继续排雷,如果是雷就结束游戏。

#include "game.h"

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

void game()
{
	int flag = 1;
	char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
	char show[ROWS][COLS] = {0};//存放排查出的雷的信息
	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印一下棋盘
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	
	//判断要排查雷还是标记雷还是取消标记雷
	int judge = 1;
	while (FLAG == 1)
	{
		printf("请问想要排查雷还是标记雷,0为标记雷,1为排查雷,2为取消标记雷:>");
		scanf("%d", &judge);

		if (judge == 1)
		{
			//排查雷
			flag = FindMine(mine, show, ROW, COL, FLAG);
		}
		else if (judge == 0)
		{
			//标记雷
			MarkMine(mine, show, ROW, COL);
		}
		else if (judge == 2)
		{
			//取消标记雷
			Cancel_MarkMine(mine, show, ROW, COL);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
		if (flag == 0)
			break;
	}
}

int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		meun();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();//扫雷游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
	return 0;
}
  • ROW表示行,COL表示列
  • mine是表示布置好雷的棋盘
  • show是表示排查后展示的棋盘
  • judge是判断要进行哪一项操作
  • FLAG是为了判断是否游戏结束(不管成功还是失败)
  • 这里是用了个do…while循环,是为了直接进入游戏,看是否要进行游玩,而且一次游戏结束还能再进行游戏

2)初始化棋盘

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)打印棋盘

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 1;
	int j = 1;
	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");
	}
	printf("------------扫雷游戏--------------\n");
}
  • 打印棋盘是为了更好的玩游戏,当用户结束一个操作后(排查雷,标记雷,取消标记雷),会自动打印棋盘(更新棋盘)

4)布置雷

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//布置10个雷
	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--;
		}
	}
}
  • 这里我是设定布置10个雷count,若需要改变可以上game.h里面改变(下面有解释)
  • 这里的话是运用了随机值的函数
  • 而为了更好的随机这里用到了srand函数(main函数里面有),这是时间戳转换,这样就将随机值控制在1 - 9之,即对应的坐标。

5)排查雷

int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int flag)
{
	//1. 输入排查的坐标
	//2. 检查坐标处是不是雷
	//  (1)如果是雷 - 很遗憾炸死了 - 游戏结束
	//  (2)如果不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续

	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row*col- EASY_COUNT)
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);//x - (1-9);y - (1-9);
		//判断坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				DisplayBoard(mine, row, col);
				flag = 0;
				break;
			}
			else
			{
				//不是雷的话,统计一下x,y坐标周围有几个雷
				int count = get_mine_count(mine, x, y);
				show[x][y] = count + '0';
				ExplosionSpread(mine, show, row, col, x, y, count);  //递归展开一片无雷区
				//显示排查出的信息
				DisplayBoard(show, row, col);
				win++;
				break;
			}
		}
		else
		{
			printf("坐标不合法,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, row, col);
		flag = 0;
	}
	return flag;
}
  • 排查雷这一块需要注意的就是循环条件,这里设置的循环条件是win(排查掉的个数)<= 总个数 - 雷的个数
  • 另外我这里设置雷为’1‘,字符1
  • 如果排查到雷,就结束游戏
  • 如果排查到不是雷,就判断周围有几个雷,如果没有雷则会进入一大片无雷区的判断

5.1)展开一大片无雷区

void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count)
{
	count = get_mine_count(mine, x, y);
	if (count == 0)
	{
		show[x][y] = ' ';   //如果该坐标周围没有雷,就把该坐标置成空格,并向周围八个坐标展开
		int i = 0;
		int j = 0;
		for (i = x - 1; i <= x + 1; i++)
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				if (show[i][j] == '*')    //限制递归条件,防止已经排查过的坐标再次递归,从而造成死递归
					ExplosionSpread(mine, show, row, col, i, j, count);
			}
		}
	}
	else
	{
		show[x][y] = '0' + count;
	}
}
  • 想要展开一大片无雷区就需要判断,该格子是不是无雷,只有当无雷的时候,才能展开去判断是否一大片都为无雷区
  • 另外这里为了防治已经排查过的坐标再次递归,从而造成死递归(庞大的计算,计算机计算不过来,可以计算,但可能要以天为单位),因此这里要限制递归条件,只有当旁边没有被排查过时,才能进行递归

5.2)判断周围有多少雷

static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y] +
		mine[x - 1][y - 1] +
		mine[x - 1][y + 1] +
		mine[x][y - 1] +
		mine[x][y + 1] +
		mine[x + 1][y] +
		mine[x + 1][y - 1] +
		mine[x + 1][y + 1] - 8 * '0';
}
  • 这个的话由于我使用的是用字符’1’来当作雷,所以想要计算雷的个数,就用ASCII码值来计算
  • 另外我这边是比较方便、通俗易懂的写法,你也可以使用循环,即for(x = x - 1, x <= x + 1; x++)

6)标记雷

//标记雷
void MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//1.输入标记的坐标
	//2.标记的部分用#来表示
	int x = 0;
	int y = 0;
	
	while (1)
	{
		printf("请输入要标记的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '0'
				|| show[x][y] == '1'
				|| show[x][y] == '2'
				|| show[x][y] == '3'
				|| show[x][y] == '4'
				|| show[x][y] == '5'
				|| show[x][y] == '6'
				|| show[x][y] == '7'
				|| show[x][y] == '8')
			{
				printf("输入的坐标已经被排查过,不需要标记,请重新输入\n");
			}
			else if (show[x][y] == '*')
			{
				show[x][y] = '#';
				DisplayBoard(show, row, col);
				break;
			}
			else if (show[x][y] == '#')
			{
				printf("输入的坐标已经标记过,不需要标记,请重新输入\n");
			}
		}
	}
}
  • 为了更贴合网页版的扫雷,于是添加了标记雷的效果
  • 这里就是要判断标记的情况只能是未被排查的坐标,并且不能是已经标记过的坐标

7)取消标记雷

//取消标记雷
void Cancel_MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//1.输入标记的坐标
	//2.标记的部分变成原来的*
	int x = 0;
	int y = 0;

	while (1)
	{
		printf("请输入要标记的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '#')
			{
				show[x][y] = '*';
				DisplayBoard(show, row, col);
				break;
			}
			else
			{
				printf("输入的位置并未被标记,请重新输入标记过的坐标\n");
			}
		}
	}
}
  • 这里就是为了有些时候可能标记错雷,需要取消,所以添加了本功能

8)game.h的讲解

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1 

#include 
#include 
#include 

#define EASY_COUNT 10//雷个数
#define ROW 9//行
#define COL 9//列
#define FLAG 1//判断是否结束

#define ROWS ROW+2
#define COLS COL+2

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);

//排查雷
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int flag);

//标记雷
void MarkMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

//取消标记雷
void Cancel_MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//递归展开无雷区
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count);
  • 这里我运用了define定义的方法,这是为了更好的修改数据,比如我需要20个雷,就修改这个雷的值就可以了,不需要修改其他值,而如果我不定义,直接用在其他代码里面,修改起来就会使得很麻烦,既要改这里,又要改那里
  • 头文件的引用也放在里面,这样我只需要引入这个game.h头文件就可以了,不需要其他的头文件
  • 另外声明了函数,这样函数就能正常的工作了

四、完整代码

1)‍test.c

#include "game.h"

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

void game()
{
	int flag = 1;
	char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
	char show[ROWS][COLS] = {0};//存放排查出的雷的信息
	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印一下棋盘
	//DisplayBoard(mine, ROW, COL);
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);
	
	//判断要排查雷还是标记雷还是取消标记雷
	int judge = 1;
	while (FLAG == 1)
	{
		printf("请问想要排查雷还是标记雷,0为标记雷,1为排查雷,2为取消标记雷:>");
		scanf("%d", &judge);
		if (judge == 1)
		{
			//排查雷
			flag = FindMine(mine, show, ROW, COL, FLAG);
		}
		else if (judge == 0)
		{
			//标记雷
			MarkMine(mine, show, ROW, COL);
		}
		else if (judge == 2)
		{
			//取消标记雷
			Cancel_MarkMine(mine, show, ROW, COL);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}

		if (flag == 0)
			break;
	}
}

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



2)‍game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1 

#include 
#include 
#include 

#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define FLAG 1

#define ROWS ROW+2
#define COLS COL+2

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);

//排查雷
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int flag);

//标记雷
void MarkMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

//取消标记雷
void Cancel_MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

//递归展开无雷区
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count);

3)‍game.c

#define _CRT_SECURE_NO_WARNINGS 1 
#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 DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 1;
	int j = 1;
	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");
	}
	printf("------------扫雷游戏--------------\n");

}

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	//布置10个雷
	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--;
		}
	}
}


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

//递归展开无雷区
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count)
{
	count = get_mine_count(mine, x, y);
	if (count == 0)
	{
		show[x][y] = ' ';   //如果该坐标周围没有雷,就把该坐标置成空格,并向周围八个坐标展开
		int i = 0;
		int j = 0;
		for (i = x - 1; i <= x + 1; i++)
		{
			for (j = y - 1; j <= y + 1; j++)
			{
				if (show[i][j] == '*')    //限制递归条件,防止已经排查过的坐标再次递归,从而造成死递归
					ExplosionSpread(mine, show, row, col, i, j, count);
			}
		}
	}
	else
	{
		show[x][y] = '0' + count;
	}

}

//标记雷
void MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//1.输入标记的坐标
	//2.标记的部分用#来表示

	int x = 0;
	int y = 0;

	while (1)
	{
		printf("请输入要标记的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '0'
				|| show[x][y] == '1'
				|| show[x][y] == '2'
				|| show[x][y] == '3'
				|| show[x][y] == '4'
				|| show[x][y] == '5'
				|| show[x][y] == '6'
				|| show[x][y] == '7'
				|| show[x][y] == '8')
			{
				printf("输入的坐标已经被排查过,不需要标记,请重新输入\n");
			}
			else if (show[x][y] == '*')
			{
				show[x][y] = '#';
				DisplayBoard(show, row, col);
				break;
			}
			else if (show[x][y] == '#')
			{
				printf("输入的坐标已经标记过,不需要标记,请重新输入\n");
			}
			
		}
	}
}

//取消标记雷
void Cancel_MarkMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	//1.输入标记的坐标
	//2.标记的部分变成原来的*
	int x = 0;
	int y = 0;

	while (1)
	{
		printf("请输入要标记的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (show[x][y] == '#')
			{
				show[x][y] = '*';
				DisplayBoard(show, row, col);
				break;
			}
			else
			{
				printf("输入的位置并未被标记,请重新输入标记过的坐标\n");
			}
		}
	}

}

//排查雷
int FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int flag)
{
	//1. 输入排查的坐标
	//2. 检查坐标处是不是雷
	//  (1)如果是雷 - 很遗憾炸死了 - 游戏结束
	//  (2)如果不是雷 - 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续

	int x = 0;
	int y = 0;
	int win = 0;

	while (win < row*col- EASY_COUNT)
	{
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);//x - (1-9);y - (1-9);
		//判断坐标的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了\n");
				DisplayBoard(mine, row, col);
				flag = 0;
				break;
			}
			else
			{
				//不是雷的话,统计一下x,y坐标周围有几个雷
				int count = get_mine_count(mine, x, y);
				show[x][y] = count + '0';
				ExplosionSpread(mine, show, row, col, x, y, count);  //递归展开一片无雷区
				//显示排查出的信息
				DisplayBoard(show, row, col);
				win++;
				break;
			}
		}
		else
		{
			printf("坐标不合法,请重新输入\n");
		}
	}
	if (win == row * col - EASY_COUNT)
	{
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, row, col);
		flag = 0;
	}
	return flag;
}

你可能感兴趣的:(c语言,游戏,经验分享,visual,studio)