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++知识库]两百行代码实现简易扫雷 (详解)

首先进行规化,三子棋的实现中已说明过的点在此处不在赘述

一.菜单

相关作用在实现三子棋时已进行过概述,此处直接上代码:

void menu()
{
	printf("***************扫雷小游戏 ***************\n");
	printf("*************** 1. play   ***************\n");
	printf("*************** 0. exit   ***************\n");
	printf("*************** V 1.0.0   ***************\n");
}

二.菜单选项基本运行逻辑

同上,直接上代码

int input = 0;
	do
	{
		menu();
		printf("请输入选项>:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("打电动咯\n");
			game();
			break;
		case 0:
			printf("游戏结束,欢迎下次游玩\n");
			break;
		default:
			system("cls");
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);

三.game()函数-游戏的实现

扫雷游戏的实现同样可分为以下几个部分:?

1.棋盘实现 ?2.输入实现-玩家 3.输赢判断

一.棋盘实现

1.那么效仿三子棋,直接定义一个棋盘,然后进行初始化,放置地雷,打印......等等,是否有哪里不对??

我们玩一次扫雷时,地雷的位置应该是确定的,但是作为玩家我们应该是看不见的,而我们要做的就是进行排雷。试想一下,假如就这么定义一个棋盘实现功能,那地雷位置生成后,要进行棋盘打印,如何让地雷对玩家来说不可视呢?

比较好的思路是,创建两个相同大小的棋盘,一个为"实际棋盘",存放着地雷的信息,一个为"影棋盘",用于在每次排雷后打印出来给玩家提供信息。

#define ROW 9
#define COL 9
//1.棋盘实现  实际棋盘 显示棋盘
	char board[ROW+2][COL+2] = { 0 };
	char board_shadow[ROW+2][COL+2] = { 0 };

此处需要注意的是棋盘大小的设置。假如我们需要对9*9的棋盘进行排雷,那么我们应该创建多大的数组呢?

9*9??如果我们这样创建的话,试想一下,当我们进行排雷时,应对周围8个格子中的地雷数目进行检视并打印,若排查格位于中央,自然写个函数对周围8格计数即可。但若排查格位于边缘,此时想通过这个函数对周围8格计数,就会造成数组越界!

因此最好的做法是,创建一个11*11的数组,使外面形成一圈"保护圈",虽然我们并不需要对其进行打印和排查。

2.棋盘初始化,遍历数组即可,不再赘述,上代码

void Init_board(char board[ROW+2][COL+2])
{
	int i = 0;
	int j = 0;
	for (i = 0; i < ROW+2; i++)
	{
		for (j = 0; j < COL+2; j++)
		{
			board[i][j] = ' ';
		}
	}
}

需注意对2个数组均应调用此函数初始化

3.棋盘打印,与三字棋思路一致,依旧是分割线与棋盘列,只不过样式稍有差异

注意我们的数组大小为(ROW+2)*(COL+2),而实际上我们想要的雷阵大小为(ROW)*(COL),最外圈元素为无效元素。因此我们只需产生(1~ROW)*(1~COL)的随机数即可,下标为1即为数组第2行,下标为ROW即为倒数第2行,正好为除去外圈的雷阵


void print_board(char board[ROW+2][COL+2])
{
	int i = 0;
	int j = 0;
	printf("    ");
	for (j = 1;j <= COL; j++)//列信息显示
	{
		printf("%3d ", j);
	} 
	printf("\n");
	for (i = 1; i <= ROW + 1; i++)
	{
		printf("    ");
		for (j = 1; j <= COL + 1; j++)
		{
			printf("|");
			if (j <= COL)
				printf("---");
		}
		printf("\n");
		if (i <= ROW)
		{
			printf("%4d", i); //行信息显示
			for (j = 1; j <= COL + 1; j++)
			{
				printf("|");
				if (j <= COL)
					printf(" %c ", board[i][j]);
			}
		}
		printf("\n");
	}
}

不同点在于,为了方便玩家输入坐标,在最开头采用简单循环实现列数信息打印,循环内printf语句实现行数信息打印,效果如下:

?4.随机生成雷

#define Num_Boom 1

此处为便于修改,地雷数目也用标识符变量定义

以下为随机产生地雷函数:

void generate_boom(char board[ROW+2][COL+2])
{
	int count = Num_Boom;
	int x = 0;
	int y = 0;
	
	while (count)
	{
		x = rand() % ROW + 1;
		y = rand() % COL + 1;
		if (board[x][y] != '*')
		{
			board[x][y] = '*';
			count--;
		}
	}
}

?与三子棋类似,用rand()函数产生随机数并判断合法性,若合法(即此处还未放置地雷),则修改数组元素,此处将字符'*'作为地雷。每修改一次count计数器自减1,当count==0时,则说明指定地雷数已全部放置,循环结束。

二.玩家输入/判断输赢

由于扫雷只需玩家输入->判断输赢->玩家输入->判断输赢,而非三子棋玩家和电脑均需判断输赢,此处将玩家输入和判断输赢集成为一个函数。即玩家输入后,判断输赢。若胜负未分,则继续输入,否则给出结果。

void player_input(char board[ROW + 2][COL + 2], char board_shadow[ROW + 2][COL + 2])
{
	printf("雷数一共为: %d\n", Num_Boom);
	int x = 0;
	int y = 0;
	int count=0;
	while (1)
	{
		printf("请输入排查坐标>: (x y)\n");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= ROW && y >= 1 && y <= ROW)
		{
			if (board_shadow[x][y] == ' ')
			{
				if (board[x][y] == '*')
				{
					system("cls");
					printf("很遗憾,排雷失败\n");
					printf("此次排雷情况如下>:\n");
					print_board(board);
					break;
				}
				else
				{
					int ret = num_boom(board, x, y);
					if (ret == 0)
					{
						auto_open(board, board_shadow, x, y);
					}
					else
					{
						board_shadow[x][y] = num_boom(board, x, y) + 48;
						board[x][y] = num_boom(board, x, y) + 48;
					}
					if (is_full(board_shadow))
					{
						system("cls");
						printf("恭喜你,排雷成功\n");
						print_board(board);
						break;
					}
					system("cls");
					printf("此次成功排查,请继续>:\n");

					print_board(board_shadow);
				}

			}
			else
				printf("此处已进行排查,请输入下一排查坐标\n");
		}
		else
			printf("输入坐标非法,请重新输入\n");
	}
}

基本逻辑即:玩家输入,判断合法性,若不合法则重新输入

若合法,则检视其是否为雷,若为雷,则玩家输,游戏结束。

否则不为雷,需要检视这个排雷格周围的8个元素,并将其打印在"影棋盘"上给玩家提供信息,即修改"影棋盘"->打印"影棋盘"

此处先岔开话题,让我们来实现这个检视周围雷数的功能。只需遍历周围8格即可,若为存在格中元素为'*',则计数器count自增1,最终返回计数器count的值 (此处循环对本排查格中元素也进行了检索,此时本格必不为雷,不影响结果)

int num_boom(char board[ROW + 2][COL + 2], int x, int y)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		for (j = y - 1; j <= y + 1; j++)
		{
			if (board[i][j] == '*')
				count++;
		}
	}
	return count;
}

回到前文,调用此函数获得周边雷数信息后,直接修改"真实棋盘"和"影棋盘"的值,注意棋盘为字符数组创建,而此函数返回值为整型int,因此修改时应为x=y+48; 48为字符0的ascii码值,则x为整型变量y的字符形式。

之后判断是否满足排雷成功条件,即"影棋盘"中剩余' '元素的个数是否已经等于雷数Num_Boom,若已经相等,那么剩下的' '必然全都是雷(不然游戏在此之前就结束了),用is_full()函数进行计数

int is_full(char board[ROW + 2][COL + 2])
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (board[i][j] == ' ')
				count++;
		}
	}
	if (count == Num_Boom)
		return 1;
	else
		return 0;
}

至此,若满足胜利条件,则进入下方的if语句,提示扫雷成功,跳出循环,简单的扫雷已经可以运行起来了

但是,当我们玩玩后就会发现,我们平时玩的扫雷,有时候我们排查一个坐标,电脑会自动在附近展开一大片,给了更多的有效信息,而我们的代码至此并没有自动展开功能。如何实现此功能呢?

首先我们要明白自动展开的条件:当排查坐标周围雷数为0时,即对周围8个待排格进行检索并排查,不为0时则直接提供信息。?那么当对周围待排格进行排查操作时,同样有当排查坐标周围雷数为0时,即又对周围8个待排格进行检索并排查.........

这显然形成了一个递归。因此我们用递归算法来实现。

当雷数为0时和不为0时情况不同,故需要if语句进行判断。雷数不为0时则进行前文操作即可,若雷数为0时则调用自动展开函数

if (ret == 0)
{
    auto_open(board, board_shadow, x, y);
}
else
{
    board_shadow[x][y] = num_boom(board, x, y) + 48;
    board[x][y] = num_boom(board, x, y) + 48;
}

即这一段代码。

接下来对自动展开函数auto_open进行实现,由于需要对2个棋盘进行修改,故需传参2个函数及坐标x,y。下面为实现:

void auto_open(char board[ROW + 2][COL + 2], char board_shadow[ROW + 2][COL + 2], int x, int y)
{
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL && board [x][y]== ' ')//重大bug!!!!!
	{
		int ret = num_boom(board, x, y);
		board[x][y] = ret + 48;
		board_shadow[x][y] = ret + 48;
		if (ret == 0)
		{
			int i = 0;
			int j = 0;
			for (i = x - 1; i <= x + 1; i++)
			{
				for (j = y - 1; j <= y + 1; j++)
				{
					auto_open(board, board_shadow, i, j);
				}
			}
		}
	}
}

实现逻辑为:1.判断坐标是否合法(否则递归会到棋盘外圈,因此棋盘所有元素都将被自动排查,就是这个一开始没写,忽略了外圈元素,使我绞尽脑汁的想bug出在哪了?) 2.判断此元素是否为雷

若坐标不合法或元素为雷,那当然不需要自动展开

若坐标合法且不为雷,则此时对此坐标进行排查,并将信息输入数组,接下来判断,若雷数不为0,则不需要自动展开,此次函数结束。雷数为0,则对周围8个元素进行函数递归即可

至此自动展开函数auto_open便已经实现

void game()         //1.棋盘实现  2.输入实现-玩家 3.输赢判断
{
	//1.棋盘实现  实际棋盘 显示棋盘
	char board[ROW+2][COL+2] = { 0 };
	char board_shadow[ROW+2][COL+2] = { 0 };
	//棋盘初始化
	Init_board(board);
	Init_board(board_shadow);
	//棋盘打印
	//随机生成雷  雷:*
	generate_boom(board);

	print_board(board_shadow);
	//玩家输入/判断输赢
	player_input(board,board_shadow);
}

game()函数对这些功能的函数进行调用,完美的实现了扫雷的功能。

最后附上头文件:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9  //行
#define COL 9  //列
#define Num_Boom 10  //地雷数目
void game();
void Init_board(char board[ROW+2][COL+2]);
void print_board(char board[ROW+2][COL+2]);
void generate_boom(char board[ROW+2][COL+2]);
void player_input(char board[ROW+2][COL+2], char board_shadow[ROW+2][COL+2]);
int num_boom(char board[ROW + 2][COL + 2],int x,int y);
int is_full(char board[ROW + 2][COL + 2]);
void auto_open(char board[ROW + 2][COL + 2], char board_shadow[ROW + 2][COL + 2], int x, int y);
int num_message(char board[ROW + 2][COL + 2], int x, int y);

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:25:37  更:2021-11-14 21:25:54 
 
开发: 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/24 7:16:12-

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