C语言学习,鹏哥C语言————扫雷(可进一步拓展难度)

一、扫雷

        扫雷,故名思意,即预先制成棋盘,并与其中生成MINE,玩家将其中MINE全部识别出,即获得游戏胜利

二、代码实现

        test.c    测试游戏的逻辑
        game.c 游戏功能的实现
        game.h 游戏代码的申明(函数声明、符号申明)

注:本篇已实现扫雷时一扫一片的功能,实现插旗和拔旗,相较于鹏哥的课堂已有进步,万般部族,还望指点。

三、具体实现方法和相关想法

        1、实现思路:

                  首先我们需要9*9的棋盘以实现对游戏基本盘的实现对游戏内容的基本实现。这里需要二维数组作为工具,而因为扫雷涉及到排雷和布置雷,所以我们需要两个二维数组。
                  然后,我们需要对期盼内容进行初始化,布置雷,然后让玩家进行“扫雷”。再进行判断,如果“触碰”到雷,判玩家游戏失败,如果所有的雷均被查出,玩家游戏胜利。

        2、主函数逻辑实现:

                  主函数区域主要需要需要对游戏最初开始以及选取模式等负责,这里使用DO WHILE 加SWITCH语句进行设置,相对容易实现,此处设计初始面板MENU以方便玩家选择和游玩。

        3、 游戏主体函数实现: 

                  首先,我们需要建立最基本的信息,即两个二维数组,因为后续符号设置中,采用了“#”和“*”,此处为字符二维数组,这里设置行列为ROWS和COLS,这两个值这里是11,后续会及进行解释。
                  然后,进行初始化内容,MINE数组因为要放置'雷',所以这里初始化的时候,给他的宣称为“ ”(space),而SHOW数组因为为了给予玩家进行游戏体验,为了保持神秘感,我们给他初始化为“*”
                  再然后,进行雷的生成,这里运用到随机数的方法,随机生成两个1~9的数进行随机填充,以实现对游戏基本盘的实现,即9*9的棋盘,10个MINE。
                  这里为例,为了方便调试,设置了一个打印函数(显示板),后续也有用,但这里存在可以用来调试BUG
                  最后,进行合理的判断即可。

void game()
{
	//难度选择
	//DifficultySelect();

	
	//建立炸弹盘 //建立排查盘
	char mine[ROWS][COLS] = { '0' };//放置雷的信息
	char show[ROWS][COLS] = { '0' };//排查雷的信息
	
	//棋盘初始化
	Initboard(mine, ROWS, COLS,' ');
	Initboard(show, ROWS, COLS,'*');
	

	//生成MINE
	SetMINE(mine, ROW, COL);
	
	//棋盘打印
	//Displayboard(mine, ROW, COL);
	/*Displayboard(show,ROW,COL);*/

	//扫雷(包含一次展开一片)
	Minesweeper(mine, show, ROW, COL);

4、棋盘初始化建设:

                 这里设置了两组函数进行初始化和打印,Initboard用于实现数组初始化,二维数组可以通过两组FOR循环来对每一个数据进行填充,这里又巧妙的设置了替换元素的变量,以实现可以为不同数组进行初始化。
                   棋盘的打印,为了美观和方便玩家进行游玩,可以进行横纵坐标的打印以及边界线的打印,这里运用新的变量z进行第一组横坐标的填充,而纵坐标其实只需要根据变量i的值进行合理打印即可,最后就可以制造出一个美观且直接的棋盘。(下图有实际操作建设出的)
这里展示手动示范eg:这里仅供参考,实际图可看下方附件一。
 0  |   1  |   2  |   3
 1  |  ---  |  ---  |  ---
 2  | %c | %c | %c
 3  |  ---  |  ---  |  ---
 4  | %c | %c | %c
 

附件一:

5、实现对地雷的埋放:

                  这里为了实现功能,构建了一个SetMINE函数以实现对雷的安放,使用TIME函数构建时间戳,利用RAN生成随机数,再%上row或者对用col实现产生 0~8的随机数,+1产生1~9的随机值,这样任意组合即可随机生成地雷,利用count判断数量是否达标,最后只要判断此时加入的X Y两个值是否合法即可。

void SetMINE(char board[ROWS][COLS], int row, int col)
{
	int x = 1;
	int y = 1;
	int count = 0;
	while (count < MINE)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (board[x][y] == ' ' && board[x][y] != '#')
		{
			board[x][y] = '#';
			count++;
		}
	}
}

6、实现玩家游玩以及判断输赢

                  这里搭建一个Minesweeper的函数实现对扫雷过程的整体框架,首先让玩家输入对应坐标,判断这些坐标是否合法,如果合法。即可进行判断是否是炸弹,若为炸弹,则游戏失败,即游戏结束。如果不是炸弹,则需要进行判断此坐标周边八个左边是否是雷,并统计个数。
                                            此处为了游戏方便,我们可以进一步拓展
例如:若这八个坐标均不是雷,可以以这八个坐标分别为中心,再次向外拓展,直至碰到雷并标记。以此方法可以实现一扫一片,极快的加速的游戏进程。而这里为了不使数组数据访问出界,我们设置数组时即让ROWS和COLS为11,使得81个游戏方格内任何一个方格都可有8个外围,且不越界访问。
                   
然后使对胜利的判断,我们发现,只需要满足棋盘中的空白方格数量等于row * col - MINE(初始雷数量),即完成了扫雷游戏,所以,我们需要一个变量(win)进行数据判断。

void Minesweeper(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//确认棋盘中非‘#’‘*’位置的SPACE空间数量以判断输赢
	
	while (win < row * col - MINE)
	{
		printf("请玩家输入坐标:>");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 10 && y>0 && y < 10 && show[x][y] == '*' || show[x][y] == '#')
		{
			if (mine[x][y] == '#')//如果是雷
			{
				printf("BOOM!!很遗憾,你被炸死了,下图为MINE示意图\n");
				printf("------------------------------------------------------\n");
				Displayboard(mine, ROW, COL);
				printf("------------------------------------------------------\n");
				break;
			}
			else//如果不是雷
			{
				MinesweeperII(mine, show, row, col, x, y);  //先进行展开判断
				Displayboard(show, ROW, COL);
				
				FLAG(mine, show);  //标志判断
				CFLAG(mine, show);  //去除标志判断
				printf("------------------------------------------------------\n");

				int i = 0;
				int j = 0;

				for (win = 0, i = 1; i <= ROW; i++)//实现每一次改变棋盘后计算win以判断是否胜利
				{
					for (j = 1; j <= COL; j++)
					{
						if (show[i][j] != '*' && show[i][j] != '#')
						{
							win++;
						}
					}
				}
			}
		}
		else
		{
			printf("非法坐标,请重新输入\n");
		}
	}
	if (win == row * col - MINE)
	{
		printf("------------------------------------------------------\n");
		printf("BINGO!! 恭喜你排雷成功!!游戏胜利\n");
		printf("------------------------------------------------------\n");

	}
}

7、实现判断函数

                  此处构建 CheckMine 和 MinesweeperII函数,进行判断。
                  CheckMine函数主要直接实现单个坐标周边8个位置是否含有雷,且含有数量。
                  MinesweeperII函数目的为了进行循环和展开判断,实现上述拓展中的做法,首先对需要判断的X和Y进行限制,不可让其越界访问产生错误或死递归。其次为了减少计算量,重复判断的坐标因被更改为“ ”(space),所以可以不用再次判断。最后满足条件的,我们逐一对这个八个坐标及进行MinesweeperII函数调用,以递归的方式实现一次排查一大片,同时使得无雷空间变为“ ”(space),极大方便我们游玩

int CheckMine(char mine[ROWS][COLS],int x,int y)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for(j = -1;j <= 1; j++)
		{
			if (mine[x + i][y + j] == '#')
			{
				count++;
			}
		}
	}
	return count;
}

void MinesweeperII(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
	if (x<1 || x>row || y<1 || y>col)//坐标非法=
	{
		return;
	}
	if (show[x][y] != '*')//已被排查
	{
		return;
	}
	int count = CheckMine(mine, x, y);
	if (count != 0)//周围有雷,显示雷数量
	{
		show[x][y] = count + '0';
		return;
	}
	else if (count == 0)//周围没有雷
	{
		show[x][y] = ' ';//先变换为' ',美观
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				MinesweeperII(mine, show, ROW, COL, i, j);
			}
		}
	}
}

8、拓展:对于插旗标志和撤旗标志的操作

                  此处实现相对容易,仅需要WHILE和SWITCH的合理分配即可。且逻辑相似。

void FLAG(char mine[ROWS][COLS], char show[ROWS][COLS])
{
	int i = 0;
	int x = 0;
	int y = 0;
	int a = 0;//跳出循环标志符
	while (1)
	{
		printf("是否进行插旗操作(1/0):>");
		scanf("%d", &i);
		switch (i)
		{
		case 1:
			printf("请输入要插旗的坐标:> ");
			scanf("%d %d", &x, &y);
			getchar();
			if (show[x][y] == '*')
			{
				show[x][y] = '#';
				Displayboard(show, ROW, COL);
				a++;
				break;
			}
			else
			{
				printf("坐标已被填充,请重新填写\n");
				break;
			}
		case 0:
			return;
		default:
			printf("非法坐标,请重新输入\n");
			break;
		}
		if (a == 1)
		{
			break;
		}
	}
	
}

void CFLAG(char mine[ROWS][COLS], char show[ROWS][COLS])
{
	int i = 0;
	int x = 0;
	int y = 0;
	int a = 0;//跳出循环标志符
	while (1)
	{
		printf("是否进行拔旗操作(1/0):>");
		scanf("%d", &i);
		switch (i)
		{
		case 1:
			printf("请输入要插旗的坐标:> ");
			scanf("%d %d", &x, &y);
			getchar();
			if (show[x][y] == '#')
			{
				show[x][y] = '*';
				Displayboard(show, ROW, COL);
				a++;
				break;
			}
			else
			{
				printf("坐标已被填充,请重新填写\n");
				break;
			}
		case 0:
			return;
		default:
			printf("非法坐标,请重新输入\n");
			break;
		}
		if (a == 1)
		{
			break;
		}
	}
}

四、完整代码:

#define _CRT_SECURE_NO_WARNINGS
#pragma once

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

#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 10



//棋盘初始化
void Initboard(char board[ROWS][COLS], int row, int col, char set);

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

//埋雷
void SetMINE(char board[ROWS][COLS], int row, int col);

//扫雷
void Minesweeper(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

void MinesweeperII(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y);

//插旗
void FLAG(char mine[ROWS][COLS], char show[ROWS][COLS]);

//拔旗
void CFLAG(char mine[ROWS][COLS], char show[ROWS][COLS]);

//头文件.h
//---------------------------------------------------------------------------------------------

#define _CRT_SECURE_NO_WARNINGS

#include "game.h"



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

		}
	}

}

void Displayboard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int z = 0;
	for (i = 0; i < row; i++)
	{
		//打印横坐标
		if (z != 10)
		{
			for (z = 0; z <= col; z++)
			{
				printf(" %d ", z);
				if (z < col)
				{
					printf("|");
				}
			}
			printf("\n");
		}
		//打印分割线
		if (i < row )
		{
			for (j = 0; j <= col; j++)
			{
				printf("---");
				if (j < col)
				{
					printf("|");
				}
			}
			printf("\n");
		}
		//打印纵坐标
		printf(" %d ", i + 1);
		printf("|");
		
		//打印数组
		for (j = 1; j <= col; j++)
		{
			printf(" %C ",board[i+1][j]);
			if (j < col)
			{
				printf("|");
			}
		}
		printf("\n");
	
	}

}

void SetMINE(char board[ROWS][COLS], int row, int col)
{
	int x = 1;
	int y = 1;
	int count = 0;
	while (count < MINE)
	{
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (board[x][y] == ' ' && board[x][y] != '#')
		{
			board[x][y] = '#';
			count++;
		}
	}
}

int CheckMine(char mine[ROWS][COLS],int x,int y)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for(j = -1;j <= 1; j++)
		{
			if (mine[x + i][y + j] == '#')
			{
				count++;
			}
		}
	}
	return count;
}

void FLAG(char mine[ROWS][COLS], char show[ROWS][COLS])
{
	int i = 0;
	int x = 0;
	int y = 0;
	int a = 0;//跳出循环标志符
	while (1)
	{
		printf("是否进行插旗操作(1/0):>");
		scanf("%d", &i);
		switch (i)
		{
		case 1:
			printf("请输入要插旗的坐标:> ");
			scanf("%d %d", &x, &y);
			getchar();
			if (show[x][y] == '*')
			{
				show[x][y] = '#';
				Displayboard(show, ROW, COL);
				a++;
				break;
			}
			else
			{
				printf("坐标已被填充,请重新填写\n");
				break;
			}
		case 0:
			return;
		default:
			printf("非法坐标,请重新输入\n");
			break;
		}
		if (a == 1)
		{
			break;
		}
	}
	
}

void CFLAG(char mine[ROWS][COLS], char show[ROWS][COLS])
{
	int i = 0;
	int x = 0;
	int y = 0;
	int a = 0;//跳出循环标志符
	while (1)
	{
		printf("是否进行拔旗操作(1/0):>");
		scanf("%d", &i);
		switch (i)
		{
		case 1:
			printf("请输入要插旗的坐标:> ");
			scanf("%d %d", &x, &y);
			getchar();
			if (show[x][y] == '#')
			{
				show[x][y] = '*';
				Displayboard(show, ROW, COL);
				a++;
				break;
			}
			else
			{
				printf("坐标已被填充,请重新填写\n");
				break;
			}
		case 0:
			return;
		default:
			printf("非法坐标,请重新输入\n");
			break;
		}
		if (a == 1)
		{
			break;
		}
	}
}

void MinesweeperII(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y)
{
	if (x<1 || x>row || y<1 || y>col)//坐标非法
	{
		return;
	}
	if (show[x][y] != '*')//已被排查
	{
		return;
	}
	int count = CheckMine(mine, x, y);
	if (count != 0)//周围有雷
	{
		show[x][y] = count + '0';
		return;
	}
	else if (count == 0)//周围没有雷
	{
		show[x][y] = ' ';//填充为space
		for (int i = x - 1; i <= x + 1; i++)
		{
			for (int j = y - 1; j <= y + 1; j++)
			{
				MinesweeperII(mine, show, ROW, COL, i, j);//递归实现外展
			}
		}
	}
}



void Minesweeper(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;//确认棋盘中非‘#’‘*’位置的SPACE空间数量以判断输赢
	
	while (win < row * col - MINE)
	{
		printf("请玩家输入坐标:>");
		scanf("%d %d", &x, &y);
		if (x > 0 && x < 10 && y>0 && y < 10 && show[x][y] == '*' || show[x][y] == '#')
		{
			if (mine[x][y] == '#')//如果是雷
			{
				printf("BOOM!!很遗憾,你被炸死了,下图为MINE示意图\n");
				printf("------------------------------------------------------\n");
				Displayboard(mine, ROW, COL);
				printf("------------------------------------------------------\n");
				break;
			}
			else//如果不是雷
			{
				MinesweeperII(mine, show, row, col, x, y);  //先进行展开判断
				Displayboard(show, ROW, COL);
				
				FLAG(mine, show);  //标志判断
				CFLAG(mine, show);  //去除标志判断
				printf("------------------------------------------------------\n");

				int i = 0;
				int j = 0;

				for (win = 0, i = 1; i <= ROW; i++)//实现每一次改变棋盘后计算win以判断是否胜利
				{
					for (j = 1; j <= COL; j++)
					{
						if (show[i][j] != '*' && show[i][j] != '#')
						{
							win++;
						}
					}
				}
			}
		}
		else
		{
			printf("非法坐标,请重新输入\n");
		}
	}
	if (win == row * col - MINE)
	{
		printf("------------------------------------------------------\n");
		printf("BINGO!! 恭喜你排雷成功!!游戏胜利\n");
		printf("------------------------------------------------------\n");

	}
}

//game.c
//---------------------------------------------------------------------------------------------

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"

//项目:扫雷
//构建开始游戏体系 建立难度选择机制
//建立标志雷体系
//建立排查雷体系


void game()
{
	//难度选择
	//DifficultySelect();

	
	//建立炸弹盘 //建立排查盘
	char mine[ROWS][COLS] = { '0' };//放置雷的信息
	char show[ROWS][COLS] = { '0' };//排查雷的信息
	
	//棋盘初始化
	Initboard(mine, ROWS, COLS,' ');
	Initboard(show, ROWS, COLS,'*');
	

	//生成MINE
	SetMINE(mine, ROW, COL);
	
	//棋盘打印
	Displayboard(mine, ROW, COL);
	/*Displayboard(show,ROW,COL);*/

	//扫雷(包含一次展开一片)
	Minesweeper(mine, show, ROW, COL);



	
	


}

void menu()
{
	printf("-----------------------------------\n");
	printf("--------   扫  雷  游  戏   -------\n");
	printf("--------   1. 开 始 游 戏   -------\n");
	printf("--------   0. 退 出 游 戏   -------\n");
	printf("-----------------------------------\n");

}

int main()
{
	int input = 0;
	
	srand((unsigned int)time(NULL));
	do 
	{
		menu();
		printf("请输入数字以决定是否开始游戏:>(输入1/0)");
		scanf("%d", &input);
		switch (input) 
		{
			case 1:
				printf("好的,开始游戏\n");
				game();
				Sleep(4000);
				system("cls");
				break;
			case 0:
				printf("好的,退出程序\n");
				break;
			default:
				printf("输入非法,请重新输入\n");
				break;
		}
	} while (input);


}

//test.c
//---------------------------------------------------------------------------------------------

五、总结

                  其实我感觉写这个还好的,对于初学者而言,实现基本思路其实是相对重要的,跟随鹏哥学习的过程中能激发很多思考,比如这里对于扫雷的两个拓展。同时也遇到了很多很多困难,毕竟我的知识量并不算多。所幸的是我坚持下来了,并完成了除了难度选择的其余拓展(因为最初选择宏定义的方式进行书写,而宏定义在我的认知中是不能修改的,这里想到了最蠢的办法,多次定义宏,EG:ROW ROW1然后重复多次书写已有代码,但是感觉相对愚笨,故不再尝试,待有朝一日学成归来,定将此处遗漏的点补上),我自知上述材料还有很多不足,欢迎大家批评指正。
                  最后,希望此篇不辜负那个初出茅庐的自己叭。

  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值