您现在的位置是:首页 >技术交流 >扫雷—————(c语言)网站首页技术交流

扫雷—————(c语言)

吃椰子不吐壳 2024-06-17 10:43:10
简介扫雷—————(c语言)

家人们,上次写了三子棋呢,也是直接宕机了,但是生命不止,代码不止。 

今天,我们继续更新用二维数组实现一个经典的游戏--扫雷

目录

构思整体思路

1.输出棋盘

初始化棋盘

 输出棋盘

 2.布置雷

排雷

 源码

总结


构思整体思路

写之前呢,先看看真正的游戏扫雷是什么样的,我们选择基础版

中间那个黄色笑脸,就是重新开始游戏,左上角的10是雷的个数,右边呢是完成扫雷需要的时间(这里我们可以忽略,因为是简易版的扫雷,要什么自行车),同时我们还要观察,这个棋盘是9*9的棋盘,我们任意点击一个位置,他就会出现一大片没有雷的区域,并且在棋盘上还有这个坐标周围八个方格的雷的个数(还没有玩过扫雷的同学,可以去网上找视频看看怎么玩)

这里我们定义game.h(放函数声明)、game.c(放实现游戏的代码)、test.c(测试游戏的代码)

并且,我们要写一个菜单功能的函数,实现游玩

void menu() {
	printf("*****************************
");
	printf("********  1 . Play  *********
");
	printf("********  0 . Exit  *********
");
	printf("*****************************
");
}//菜单

int main() {
	int input = 0;
	srand((unsigned int)time(NULL));
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("欢迎下次游玩
");
			break;
		default:
			printf("选择错误,请重新选择
");
			break;
		}
	} while (input);

	return 0;
}

 这里,我们定义game()函数来实现调用游戏代码,接下来,就声明函数了

#pragma once
#pragma warning (disable:4996)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//雷的个数,可以手动调整难度
#define EASY_MIND 10
//要扫雷的区域,可手动调整大小,不可调整太多,需要配合调整雷的个数使用,否则会将所有雷区展开
#define ROW 9
#define COL 9
//实际的区域
#define ROWS ROW+2
#define COLS COL+2

//菜单
void menu();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计雷的数量
int GetMineCount(char mine[ROWS][COLS], int x, int y);
//递归展开无雷区域
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);

1.输出棋盘

思考:

1.要初始化输出的棋盘是怎么样的

2.怎么样初始化棋盘

3.雷怎么埋

 话不多说,直接上代码

 game.c

初始化棋盘

思考:

1.初始化的棋盘是几乘几的

2.初始化的符合定义为什么

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret) {
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			board[i][j] = ret;
		}
	}
}

1.其中,我们定义一个mine数组,里面放需要埋的雷,show数组放棋盘,所以说这两个数组必须是一模一样的,这样子我们就通过棋盘来扫雷了,我们初始化棋盘都为‘*’,雷为‘0’(就是没有雷,为什么是‘0’,后而不是0,后面会解释)。

2.我们到底要初始化几乘几呢,这个问题很好理解,扫雷的核心玩法就是你选的这个坐标的九宫格里面有多少个雷,从而一步步将雷扫光。中间如果出现一个1,那说明周围八个坐标雷的总数就是1个,如果有两个,就会显示2........依此类推,所以说最多的雷个数是8个,当然这个概率可能会非常小,可以忽略。

 

所以说我们要初始化的棋盘,这个坐标周围一定要有八个坐标,由于我们选择的是9*9的大小,所以说四条边周围一定要有坐标,所以说我们实际上初始化的棋盘必须是11*11的才能保证9*9的棋盘的四个边的格子周围有八个坐标。否则在后面统计雷的数量,就会造成数组越界访问


 输出棋盘

思考:

1.输出的棋盘是几乘几的

2.怎么样在棋盘给玩家加上坐标

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
	int i = 0;
	int j = 0;
	printf("--------扫雷游戏-------
");
	for (i = 0; i <= col; i++) {
		printf("%d ", i);
	}//打印下标
	printf("
");
	for (i = 1; i <= row; i++) {
		printf("%d ", i);
		for (j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);
		}
		printf("
");
	}
}

1.输出的棋盘当然是9*9的大小啦 

 2.在棋盘周围加上坐标,让玩家准确选择坐标,所以说在最开头的打印行的时候,加入一个for循环输出行坐标即可,在打印完行的时候,打印列坐标。让我们看看代码效果

这里我们把show传进去,就可以美美打印棋盘跟坐标。 

 棋盘打印完了接下来就是扫雷了!!!


 2.布置雷

 1.雷要怎么放

2.要放多少个雷

先来看代码

//布置雷
void SetMine(char board[ROWS][COLS], int row, int col) {
	//布置10个雷
	//随机生成
	int count = EASY_MIND;//雷的数量
	while (count) {
		int x = rand() % row + 1;//生成随机坐标
		int y = rand() % col + 1;//生成随机坐标

		if (board[x][y] == '0') {
			board[x][y] = '1';
			count--;//生成一个雷就减少一次
		}
	}//循环设置雷
}

 1.采用随机数生成坐标,从而在这个坐标放雷,所以说雷的坐标不能超过9*9的棋盘,如果是放在11*11的数组里面,会造成错误(会让游戏无法游玩)

2.我们把雷赋值为‘1’,所以说当我们扫到这个坐标的时候,我们就可以把他周围八个坐标的雷加起来了。为什么不是数字呢,是因为我们定义棋盘为‘*’(char类型),为了一致,我们也要让雷也是char类型,当然你也可以选择int类型,原理其实都一样。其中,定义雷的数量为count,每生成一个,雷的数量就减少一个,这样子我们就可以把雷放好了,我们看看雷的效果,用打印棋盘的函数来打印mine数组。

DisplayBoard(mine, ROW, COL);

 

芜湖,睁大眼睛数一数刚好十个。 雷放好了,接下来就是怎么扫了。


排雷

思考

1.扫雷的范围是多少

2.怎么样计算x,y周围八个坐标雷的数量

3.怎么样才能算扫完雷

int GetMineCount(char mine[ROWS][COLS], int x, int y) {
	return (mine[x][y + 1] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x + 1][y - 1] +
		mine[x - 1][y] + mine[x - 1][y + 1] + mine[x - 1][y - 1] - 8 * '0');
}//返回八个坐标的雷
   //因为是字符类型,所以说要转换成整形就需要减去'0'的ASCLL码值,八个就减去8*48(‘0’)

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	//排查坐标
	int x = 0;
	int y = 0;

	while (win < row * col - EASY_MIND) {
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (mine[x][y] == '1') {
				printf("恭喜你被炸死了!
");
				printf("请重新游玩
");
				DisplayBoard(mine, ROW, COL);//把雷打印出来让你死的明白
				break;
			}
			else {
				Expand(mine, show, x, y);//递归展开雷
				DisplayBoard(show, ROW, COL);
			}
		}
		else {
			printf("坐标非法,请重新输入!
");
		}
	}
	if (win == row * col - EASY_MIND)
		printf("排雷成功!
");
}

1.跟前面的三子棋一样,应用到这里原理也是一样的,如果踩到雷就死,结束循环,没有踩到雷就继续扫雷。

2.直接返回x,y周围八个坐标相加在减去相应的ASCLL码值即可

 3.怎么样判断输赢呢?首先,我们要实现展开一片无雷区域的效果,最简单的方法就是使用递归。

 直接上代码

条件

1.该坐标本身就不是雷
2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域

//展开无雷区域的条件
//1.该坐标本身就不是雷
//2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
//3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域
int win = 0;//这里定义全局变量才能实现Expand()函数的功能
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	int count = GetMineCount(mine, x, y);//查看周围是否有雷
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '*') { // 原来是未知才继续展开
			win++; // 统计已经展开的点数
			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 (i >= 1 && i <= ROW && j >= 1 && j <= COL) {
							Expand(mine, show, i, j);
						}//遍历x,y周围八个坐标
					}
				}
			}
			else {
				show[x][y] = count + '0';
			}
		}//在棋盘的未展开区域递归
	}//要在棋盘内递归
}//正确版本

 换个思路来想,我们把不是雷的区域全部找出来,剩下的就是雷了,所以说我们要赢,就需要一个变量来统计无雷的区域,因此定义一个全局变量win(因为是两个函数需要使用到win),每递归一次,win++一次,当递归碰到雷的时候就停止递归,并且把无雷区域重新赋值为空白符号,返回周围八个坐标雷的个数。当win达到条件的时候就直接退出循环判断输赢。

接下来乱杀电脑!


 源码

game.h

#pragma once
#pragma warning (disable:4996)
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//雷的个数,可以手动调整难度
#define EASY_MIND 10
//要扫雷的区域,可手动调整大小,不可调整太多,需要配合调整雷的个数使用,否则会将所有雷区展开
#define ROW 9
#define COL 9
//实际的区域
#define ROWS ROW+2
#define COLS COL+2

//菜单
void menu();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret);
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计雷的数量
int GetMineCount(char mine[ROWS][COLS], int x, int y);
//递归展开无雷区域
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);

 game.c

#include "game.h"

void menu() {
	printf("*****************************
");
	printf("********  1 . Play  *********
");
	printf("********  0 . Exit  *********
");
	printf("*****************************
");
}//菜单

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, char ret) {
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++) {
		for (j = 0; j < col; j++) {
			board[i][j] = ret;
		}
	}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col) {
	int i = 0;
	int j = 0;
	printf("--------扫雷游戏-------
");
	for (i = 0; i <= col; i++) {
		printf("%d ", i);
	}//打印下标
	printf("
");
	for (i = 1; i <= row; i++) {
		printf("%d ", i);
		for (j = 1; j <= col; j++) {
			printf("%c ", board[i][j]);
		}
		printf("
");
	}
}

//布置雷
void SetMine(char board[ROWS][COLS], int row, int col) {
	//布置10个雷
	//随机生成
	int count = EASY_MIND;//雷的数量
	while (count) {
		int x = rand() % row + 1;//生成随机坐标
		int y = rand() % col + 1;//生成随机坐标

		if (board[x][y] == '0') {
			board[x][y] = '1';
			count--;//生成一个雷就减少一次
		}
	}//循环设置雷
}
//递归展开无雷区
//void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
//	int count = GetMineCount(mine, x, y);
//	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
//
//		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[x][y] == '*') {
//						Expand(mine, show, x, y);
//					}
//				}
//			}
//		}
//	}
//	else {
//		show[x][y] = count + '0';
//	}
//}//错误版本

//展开无雷区域的条件
//1.该坐标本身就不是雷
//2.该坐标周围没有雷:if(count==0),count是x,y周围坐标的雷的数量
//3.该坐标没有展开过,就是if(show[x][y]=='*')的时候就可以展开无雷区域
int win = 0;//这里定义全局变量才能实现Expand()函数的功能
void Expand(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	int count = GetMineCount(mine, x, y);
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		if (show[x][y] == '*') { // 原来是未知才继续展开
			win++; // 统计已经展开的点数
			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 (i >= 1 && i <= ROW && j >= 1 && j <= COL) {
							Expand(mine, show, i, j);
						}//遍历x,y周围八个坐标
					}
				}
			}
			else {
				show[x][y] = count + '0';
			}
		}//在棋盘的未展开区域递归
	}//要在棋盘内递归
}//正确版本

int GetMineCount(char mine[ROWS][COLS], int x, int y) {
	return (mine[x][y + 1] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x + 1][y - 1] +
		mine[x - 1][y] + mine[x - 1][y + 1] + 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;

	while (win < row * col - EASY_MIND) {
		DisplayBoard(mine, ROW, COL);
		printf("请输入要排查的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (mine[x][y] == '1') {
				printf("恭喜你被炸死了!
");
				printf("请重新游玩
");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else {
				Expand(mine, show, x, y);
				DisplayBoard(show, ROW, COL);
			}
		}
		else {
			printf("坐标非法,请重新输入!
");
		}
	}
	if (win == row * col - EASY_MIND)
		printf("排雷成功!
");
        win =0;//重置
}

test.c

#include "game.h"

void game() {
	char mine[ROWS][COLS] = { 0 };//存放布置好的雷
	char show[ROWS][COLS] = { 0 };//实现扫雷的数组

	//初始化棋盘
	//1.mine最开始全是'0'
	//2.show最开始全是'*'
	//初始棋盘时,要注意要初始化11*11的
	//如果只初始化9*9,在遍历八个坐标时会造成越界访问,造成未知的错误
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	/*DisplayBoard(mine, ROW, COL);*///打印的是show数组
	DisplayBoard(show, ROW, COL);
	//1.布置雷
	SetMine(mine, ROW, COL);
	/*DisplayBoard(mine, ROW, COL);*/
	//2.排查雷

	FindMine(mine, show, ROW, COL);
}

int main() {
	int input = 0;
	srand((unsigned int)time(NULL));
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("欢迎下次游玩
");
			break;
		default:
			printf("选择错误,请重新选择
");
			break;
		}
	} while (input);

	return 0;
}

总结

实现了扫雷的部分基本功能, 但是没有实现雷的标记,还有对难度的选择。对于递归部分还不够深入了解,当然这部分也可以用循环来解决,但是博主尚未学会使用,还需要继续努力。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。