来一起做一个属于自己的扫雷游戏吧,手把手教你如何实现~

前言

        这里的扫雷需要用到循环,数组,函数,递归等知识 ,这些知识点将会在后续文章中详细讲解。本文章中将着重讲述代码的实现思路。
       本扫雷游戏具备功能有:地图自动探索,清屏,做记号(类似插旗)
       如果代码有什么问题,请大佬指出,我会一一改正更新本文章。
        如果想直接要源代码,可以在本文章的最后面自取,我会放出两种文件形式(一份是三文件,一份是单文件)。本文章将以三文件的形式展开。

构建三文件

        首先我们先来构建三份文件(game.h    game.c  test.c)简单介绍一下三份文件的作用,首先game.h是用来放头文件的,宏定义和函数声明等等;game.c是来实现游戏运行,完成函数定义的,test.c是用来存放主函数的,也就是程序启动的入口。

完成test.c

        这个比较简单,就是写个主函数让程序进入到游戏,我就不在这里赘述了。

#include"game.h"

int main()
{
	PlayGame();
	return 0;
}

游戏分析

       首先我们先来设置一下游戏的格子数与地雷数量,这里我是用15*15的大小,雷数是20来进行设计的,这个因人而异,大家可以自由设置难度。我使用到的是利用宏定义来指定上述系数,并且将宏定义放入到game.h中,因为这样也方便我们后续修改。

#define ROW  15
#define COL  15
#define N 20

       接着我们来进一步分析游戏需要用到的参数,既然扫雷游戏是一个矩形图状的,我们自然而然地想到利用二位数组来存放参数,再思考一下游戏在游玩中我们会发现,游戏开始游玩时,玩家看到的是一片格子图,当玩家点开一个格子时,要么打开的是数字,要么是空格,那么我们就需要两个二维数组来分别存放这些内容,一个是字符数组来展示给玩家看的,一个是数字数组用来存放数字。因为着两个数组在整个游戏中基本都要用到,因此我们可以将其设置成全局变量。还是一样,我们放在game.h中。

char show[ROW][COL];
int play[ROW][COL];

       最后我们来思考游戏是怎么运行的,当玩家点开格子时,如果显示的是数字,这个数字代表的是其周围的九宫格有多少颗雷。那我们就要对以其为中心的九宫格进行遍历统计雷的数量,可是当我们要统计边界的雷数时,就可能会产生越界行为,未来避免越界出现,我们就要用到多次判断。那有没有更好的办法呢?当然有,就是将数组扩大。下面来看看吧。
在这里插入图片描述

红色区是玩家游玩区,蓝色区是数组扩大区,这样方便我们区操作数组。
       我们再添加两个新的宏定义,为什么不直接改上面的宏定义呢?因为上面两个宏定义我们可以用来对玩家输入的坐标进行专门的操作(就是在进行遍历棋盘的时候,我们需要的是从1到ROW或者COL的值)。
添加的代码如下:

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

因为数组扩大了,这时我们就需要对数组进行大小修改

char show[ROWS][COLS];
int play[ROWS][COLS];

游戏逻辑实现

1.游戏准备工作

       现在我们来实现扫雷游戏的主逻辑,首先玩家打开程序看到的是菜单面板。那我们先写个菜单函数。

void menu()//菜单
{
	printf("******************************************\n");
	printf("*****        欢迎来到扫雷游戏        *****\n");
	printf("*************   1.开始游玩   *************\n");
	printf("*************   2.退出游戏   *************\n");
	printf("******************************************\n");
}

       接着玩家要输入指令进入游戏,如果游戏结束,我们可以再度呈现面板菜单,让玩家选择是否继续下一把游戏,而不是直接就退出游戏了,那这里我们可以写个循环。

void PlayGame()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入指令:->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			srand((unsigned int)time(NULL));
			InitShow(show);//每次进入游戏都要初始化地图
			SetMine(play);
			SetNum(play);
			Game();
			break;
		case 2:
			printf("退出游戏,欢迎下次游玩!\n");
			break;
		default:
			printf("指令输入错误,请重新输入!\n");
			break;
		}
	} while (input - 2);
}

       我们继续讨论,如果玩家输入1,那就是要进入游戏,那我们的程序就要做好游戏准备,首先地图初始化(将字符数组show 初始化为’*’)接着开始在play 数组中布置地雷和做好统计九宫格内地雷的数字标记。
       首先字符初始化利用循环来写。

void InitShow(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			arr[i][j] = '*';
		}
	}
}

       然后布置地雷,这里我们用到随机数来进行布置,让每次游戏的地雷位置都是随机的,让游戏更有乐趣,把随机数种子放在布置地雷函数之前并且保证每次启动游戏随机数种子都是不一样(即放在case 1:后面并且再 SetMine(play) 这个函数之前就可以了)

void SetMine(int arr[ROWS][COLS])//布置雷
{
	int count = N;
	while (count)
	{
		int row = rand() % ROW + 1;
		int col = rand() % ROW + 1;
		if (arr[row][col] == 0)
		{
			arr[row][col] = -1;//雷设置成-1
			count--;
		}
	}
}

这里我将地雷标记成-1,在九宫格内一个空格最多显示周围有8个地雷,为了避免与其的发生冲突,我们就不会将地雷设置成0到8的数字。
我们进行循环遍历进行对周围九宫格进行地雷数字统计。

void SetNum(int arr[ROWS][COLS])//制作数字
{
	int i = 0, j = 0, x = 0, y = 0;
	int count = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] != -1)
			{
				for (x = i - 1; x < i + 2; x++)
				{
					for (y = j - 1; y < j + 2; y++)
					{
						if (arr[x][y] == -1)
						{
							count++;
						}
					}
				}
				arr[i][j] = count;
				count = 0;
			}
		}
	}
}

2.游戏主逻辑实现

       我们接下来进入到游戏内部,选择我们来一起思考游戏的每一个动作吧,将游戏主逻辑放在Game()函数中,逐渐补充Gmae()函数。
       首先我们游戏开始,打印游戏面板。

void PrintMap(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d ", i);
		for (j = 1; j <= COL; j++)
		{
			printf("%3c", arr[i][j]);
		}
		printf("\n");
	}
	printf("一共有%d个雷!!!\n",N);
}

面板效果如下:
       接着我们需要接收玩家输入的坐标(这时我们要警惕坐标非法访问数组,导致越界访问)因此我们需要保证程序接收的是正确的坐标值。

int row = 0; 
int col = 0;
	PrintMap(show);
	do
	{
		printf("请输入坐标;->");
		scanf("%d%*c%d", &col, &row);//玩家输入和数组有所不同
		if (col<1 || col>COL || row<1 || row>ROW)
		{
			printf("没有该坐标存在,请重新输入\n");
		}
	} while (col<1 || col>COL || row<1 || row>ROW);

要注意了,各位老铁,玩家输入的坐标值一般习惯数学的平面直角坐标系中先输入横坐标,接着输入纵坐标,但是我们的数组和玩家输入的值是相反的,这时我们对scanf接收进行一下小的调整。如果不知道%*c的老铁,可以打开这个链接在2.2scanf如何灵活使用中查看


       这时我们对玩家输入的坐标要进行一下判断,看看玩家是否踩雷了。先来写判断是否踩雷的函数。

int IfMine(int arr[ROWS][COLS], int row, int col)
{
	int flag = 0;
	if (arr[row][col] == -1)
		flag = 1;
	return flag;//1表示踩到地雷
}

接着在Game()函数中用flag来接收是否踩雷函数的返回值。

int flag = 0;
flag = IfMine(play, row, col);
if (flag == 1)
{
	printf("很遗憾,你踩到地雷了\n");
	Print(play);//让玩家看到这个地图
	break;
}

如果玩家踩雷了,我们打印我们的数字数组,让玩家看到整个地雷布置。(毕竟输了也要输得明白)

void Print(int arr[ROWS][COLS])//当游戏失败,让玩家看到这个地图
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d", i);
		for (j = 1; j <= COL; j++)
		{
			if (play[i][j] == -1)
			{
				printf("%3d", -1);
			}
			else
			{
				printf("%3d", 0);
			}
		}
		printf("\n");
	}
}

       此时我们要同时对两个数组进行操作,如果玩家没有踩到地雷,我们进行下一步操作。这时就有两种情况,一种是踩到有地雷统计的数字标志,另外一种是踩到没有地雷标记的0(这时show 数组上对应的位置就要显示空格),这时我们来思考一下,如果踩到的是数字标志的话,那地图就会显示数字,就不会展开空白区域的操作:如果踩到的是0,这时地图在(show数组)此处的’*'就要改成空格并且要向外展开,直到遇到有地雷的数字标记。那这时我们就要用到递归来解决问题。
       每次用到递归之前,我们就要想好递归的结束条件,避免递归成为死递归。现在我们来好好思考递归的结束条件,首先地图向外拓展的话就有可以到达边界,这时要避免越界访问数组;接着地图拓展遇到地雷也应该停止拓展(不然就暴露地雷了);然后,也是比较容易遗漏的,就是遇到空格我们也应该停止拓展(在地图上(show)既然显示空格说明之前我们就以及探索过了,没必要在探索,否则会出现死递归
来看一下代码:

void OpenNull(int play[ROWS][COLS], char show[ROWS][COLS], int row, int col)//展开地图
{
	int i = 0;
	int j = 0;
	if (row<1 || row>ROW || col<1 || col>COL || play[row][col] == -1 || show[row][col] == ' ')//越界访问,雷区访问,空格重复访问,
		return;
	if (play[row][col] == 0)   //递归,遇到空格打开来,遇到数字停止递归并返回该数字
	{
		show[row][col] = ' ';
		for (i = row - 1; i < row + 2; i++)
		{
			for (j = col - 1; j < col + 2; j++)
			{
				if (i == row && j == col)
					continue;
				OpenNull(play,show,i,j);
			}
		}
	}
	else
	{
		show[row][col] = '0' + play[row][col];
	}
}

       在每次地图展开后,我们就要判断玩家是否获胜,那么如何判断是否获胜呢?现在我们手头上有两个数组,一个是数字数组,这个显然无法进行判断,因为它是用来备份雷和标记数的,也就是说它是死的,不能随意修改的,但是我们的字符数组show就不一样了,它会展示出空格,* ,标记数字等等。那么我们就可以对其进行字符统计,当场上的*和后面提到的玩家标记#进行统计,如果数量和雷的数量一致,那么玩家获得胜利,否则继续游戏。
来一起看一下获胜的代码吧。

int IfWin(char arr[ROWS][COLS])//判断是否获胜
{
	int i = 0;
	int j = 0;
	int count = 0;
	int flag = 1;//假设获胜
	for (i = 1; i <= ROW && flag == 1; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] == '*' || arr[i][j] == '#')
				count++;
			if (count > N)
			{
				flag = 0;
				break;
			}
		}
	}
	return flag;
}

       如果玩家没有获胜,游戏就会继续,这时我们可以写一个小小的功能就是标记雷的位置(类似插旗),这里为了方便,我就直接将标记符号设置成 # ,让玩家自己选择要不要标记,并且标记到哪里。

void IfMake(char show[ROWS][COLS])
{
	int sign = -1;
	printf("是否需要做记号,如果要做记号,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
		do
		{
			scanf("%d", &sign);
			int x = 0;
			int y = 0;
			switch (sign)
			{
			case 0:
				goto next;
			case 1://要标记
				printf("请输入要标记的坐标:->");
				do
				{
					scanf("%d%*c%d", &x, &y);
					if ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '*')//避免越界访问或这标记到别处,导致程序不好运行
					{
						printf("输入有误或者此处不给标记,请重新输入:->\n");
					}
					else
					{
						MakeSign(show, y, x);
						PrintMap(show);
					}
				} while ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '#');
				break;
			default:
				printf("输入错误,请重新输入:->");
				break;
			}
			printf("是否要继续标记,如果需要,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
		} while (sign);
next:
	return;
}

void MakeSign(char arr[ROWS][COLS], int row, int col)//给玩家做记号
{
	arr[row][col] = '#';
}

这里我用了一个goto 语句,也可以不用,当时我写的时候有点头晕眼花,怕没跳出循环就用了goto 语句.
我来解释一下下面的一段代码:

	if ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '*')//避免越界访问或这标记到别处,导致程序不好运行

首先越界访问数组,我们是绝对不会允许玩家输入这样的非法坐标,那为什么连这条语句 (show[y][x] != ‘*’)也要限制呢?
上面我们提到获胜函数时统计#和“ * ”的个数是否和雷的个数相同,如果玩家在其他地方做了标记,就会导致获胜函数发生bug,这样我们又要改获胜函数了,与其花精力去改一个获胜函数,还不如将这个标记函数做个限制,将不合适的坐标值直接扼杀在摇篮里。


       接着我们来想一想如果按照上面程序走会不会显得程序显示的东西太多了,这时我们可以写一行清屏代码,将上面的全部删除。

  system("cls");//清屏

所使用到的头文件为stdlib,h


最后根据上面的思路,我们就可以补充完整的Game()函数了。
代码如下:

void Game()//游戏主逻辑
{
	
	int flag = 0;
	int row = 0; 
	int col = 0;
	do
	{
		PrintMap(show);
		do
		{
			printf("请输入坐标;->");
			scanf("%d%*c%d", &col, &row);//玩家输入和数组有所不同
			if (col<1 || col>COL || row<1 || row>ROW)
			{
				printf("没有该坐标存在,请重新输入\n");
			}
		} while (col<1 || col>COL || row<1 || row>ROW);
		
		flag = IfMine(play, row, col);
		if (flag == 1)
		{
			printf("很遗憾,你踩到地雷了\n");
			Print(play);//让玩家看到这个地图
			break;
		}
		else
		{
			OpenNull(play, show, row, col);
			int ret = IfWin(show);
			if (ret == 1)
			{
				printf("恭喜你,获得游戏胜利,祝你在生活中天天快乐!!!\n");
				PrintMap(show);
				break;
			}
			else
			{
				PrintMap(show);
				IfMake(show);
			}
		}
		system("cls");//清屏
	} while (flag - 1);
}

大家记得将函数声明放在game.h中哦~~

3.游戏测试小代码

       我这里用来一段代码用来测试play生成是否正确。避免布置雷和统计雷周围的数字发生错误,影响玩家体验。

void PrintMap2(int arr[ROWS][COLS])//测试专用
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			printf("%2d",arr[i][j]);
		}
		printf("\n");
	}
}

结语

       感谢你阅读到最后,希望这些内容能帮到你。制作十分不易,如果可以,能给个赞支持一下吗?我会在本专栏上持续推出作品,关注我,不迷路哦~
       下面时三文件和单文件形式的代码,有需要自取哦~


最终代码实现

三文件形式

game.h

#pragma once

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

#define ROW  15
#define COL  15
#define ROWS ROW+2
#define COLS COL+2
#define N 20

char show[ROWS][COLS];
int play[ROWS][COLS];

void PrintMap(char arr[ROWS][COLS]);//打印地图

void InitShow(char arr[ROWS][COLS]);//将地图初始化

void PrintMap2(int arr[ROWS][COLS]);//测试专用

void SetMine(int arr[ROWS][COLS]);//布置雷

void SetNum(int arr[ROWS][COLS]);//制作数字

void PlayGame();//游戏开始

void menu();//菜单

void Game();//游戏主逻辑

int IfMine(int arr[ROWS][COLS],int row,int col);//是否炸雷

void Print(int arr[ROWS][COLS]);//让玩家看到这个地图

void OpenNull(int play[ROWS][COLS], char show[ROWS][COLS], int row, int col);//展开地图

int IfWin(char arr[ROWS][COLS]);//判断是否获胜

void MakeSign(char arr[ROWS][COLS], int row, int col);//给玩家做记号

void IfMake(char show[ROWS][COLS]);//玩家是否做记号

game.c

#include"game.h"

void PrintMap2(int arr[ROWS][COLS])//测试专用
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			printf("%2d",arr[i][j]);
		}
		printf("\n");
	}
}

void InitShow(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			arr[i][j] = '*';
		}
	}
}

void PrintMap(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d ", i);
		for (j = 1; j <= COL; j++)
		{
			printf("%3c", arr[i][j]);
		}
		printf("\n");
	}
	printf("一共有%d个雷!!!\n",N);
}

void SetMine(int arr[ROWS][COLS])//布置雷
{
	int count = N;
	while (count)
	{
		int row = rand() % ROW + 1;
		int col = rand() % ROW + 1;
		if (arr[row][col] == 0)
		{
			arr[row][col] = -1;//雷设置成-1
			count--;
		}
	}
}

void SetNum(int arr[ROWS][COLS])//制作数字
{
	int i = 0, j = 0, x = 0, y = 0;
	int count = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] != -1)
			{
				for (x = i - 1; x < i + 2; x++)
				{
					for (y = j - 1; y < j + 2; y++)
					{
						if (arr[x][y] == -1)
						{
							count++;
						}
					}
				}
				arr[i][j] = count;
				count = 0;
			}
		}
	}
}

void PlayGame()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入指令:->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			srand((unsigned int)time(NULL));
			InitShow(show);//每次进入游戏都要初始化地图
			SetMine(play);
			SetNum(play);
			Game();
			break;
		case 2:
			printf("退出游戏,欢迎下次游玩!\n");
			break;
		default:
			printf("指令输入错误,请重新输入!\n");
			break;
		}
	} while (input - 2);
}

void menu()//菜单
{
	printf("******************************************\n");
	printf("*****        欢迎来到扫雷游戏        *****\n");
	printf("*************   1.开始游玩   *************\n");
	printf("*************   2.退出游戏   *************\n");
	printf("******************************************\n");
}

void Game()//游戏主逻辑
{
	
	int flag = 0;
	int row = 0; 
	int col = 0;
	do
	{
		PrintMap(show);
		do
		{
			printf("请输入坐标;->");
			scanf("%d%*c%d", &col, &row);//玩家输入和数组有所不同
			if (col<1 || col>COL || row<1 || row>ROW)
			{
				printf("没有该坐标存在,请重新输入\n");
			}
		} while (col<1 || col>COL || row<1 || row>ROW);
		
		flag = IfMine(play, row, col);
		if (flag == 1)
		{
			printf("很遗憾,你踩到地雷了\n");
			Print(play);//让玩家看到这个地图
			break;
		}
		else
		{
			OpenNull(play, show, row, col);
			int ret = IfWin(show);
			if (ret == 1)
			{
				printf("恭喜你,获得游戏胜利,祝你在生活中天天快乐!!!\n");
				PrintMap(show);
				break;
			}
			else
			{
				PrintMap(show);
				IfMake(show);
			}
		}
		system("cls");//清屏
	} while (flag - 1);
}

int IfMine(int arr[ROWS][COLS], int row, int col)
{
	int flag = 0;
	if (arr[row][col] == -1)
		flag = 1;
	return flag;//1表示踩到地雷
}

void Print(int arr[ROWS][COLS])//当游戏失败,让玩家看到这个地图
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d", i);
		for (j = 1; j <= COL; j++)
		{
			if (play[i][j] == -1)
			{
				printf("%3d", -1);
			}
			else
			{
				printf("%3d", 0);
			}
		}
		printf("\n");
	}
}

void OpenNull(int play[ROWS][COLS], char show[ROWS][COLS], int row, int col)//展开地图
{
	int i = 0;
	int j = 0;
	if (row<1 || row>ROW || col<1 || col>COL || play[row][col] == -1 || show[row][col] == ' ')//越界访问,雷区访问,空格重复访问,
		return;
	if (play[row][col] == 0)   //递归,遇到空格打开来,遇到数字停止递归并返回该数字
	{
		show[row][col] = ' ';
		for (i = row - 1; i < row + 2; i++)
		{
			for (j = col - 1; j < col + 2; j++)
			{
				if (i == row && j == col)
					continue;
				OpenNull(play,show,i,j);
			}
		}
	}
	else
	{
		show[row][col] = '0' + play[row][col];
	}
}

int IfWin(char arr[ROWS][COLS])//判断是否获胜
{
	int i = 0;
	int j = 0;
	int count = 0;
	int flag = 1;//假设获胜
	for (i = 1; i <= ROW && flag == 1; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] == '*' || arr[i][j] == '#')
				count++;
			if (count > N)
			{
				flag = 0;
				break;
			}
		}
	}
	return flag;
}

void IfMake(char show[ROWS][COLS])
{
	int sign = -1;
	printf("是否需要做记号,如果要做记号,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
		do
		{
			scanf("%d", &sign);
			int x = 0;
			int y = 0;
			switch (sign)
			{
			case 0:
				goto next;
			case 1://要标记
				printf("请输入要标记的坐标:->");
				do
				{
					scanf("%d%*c%d", &x, &y);
					if ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '*')//避免越界访问或这标记到别处,导致程序不好运行
					{
						printf("输入有误或者此处不给标记,请重新输入:->\n");
					}
					else
					{
						MakeSign(show, y, x);
						PrintMap(show);
					}
				} while ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '#');
				break;
			default:
				printf("输入错误,请重新输入:->");
				break;
			}
			printf("是否要继续标记,如果需要,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
		} while (sign);
next:
	return;
}

void MakeSign(char arr[ROWS][COLS], int row, int col)//给玩家做记号
{
	arr[row][col] = '#';
}

test.c

#include"game.h"

int main()
{
	PlayGame();
	return 0;
}

单文件形式

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

#define ROW  15
#define COL  15
#define ROWS ROW+2
#define COLS COL+2
#define N 20

char show[ROWS][COLS];
int play[ROWS][COLS];

void PrintMap(char arr[ROWS][COLS]);//打印地图

void InitShow(char arr[ROWS][COLS]);//将地图初始化

void PrintMap2(int arr[ROWS][COLS]);//测试专用

void SetMine(int arr[ROWS][COLS]);//布置雷

void SetNum(int arr[ROWS][COLS]);//制作数字

void PlayGame();//游戏开始

void menu();//菜单

void Game();//游戏主逻辑

int IfMine(int arr[ROWS][COLS], int row, int col);//是否炸雷

void Print(int arr[ROWS][COLS]);//让玩家看到这个地图

void OpenNull(int play[ROWS][COLS], char show[ROWS][COLS], int row, int col);//展开地图

int IfWin(char arr[ROWS][COLS]);//判断是否获胜

void MakeSign(char arr[ROWS][COLS], int row, int col);//给玩家做记号

void IfMake(char show[ROWS][COLS]);//玩家是否做记号

int main()
{
	PlayGame();
	return 0;
}

void PrintMap2(int arr[ROWS][COLS])//测试专用
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			printf("%2d", arr[i][j]);
		}
		printf("\n");
	}
}

void InitShow(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			arr[i][j] = '*';
		}
	}
}

void PrintMap(char arr[ROWS][COLS])
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d ", i);
		for (j = 1; j <= COL; j++)
		{
			printf("%3c", arr[i][j]);
		}
		printf("\n");
	}
	printf("一共有%d个雷!!!\n", N);
}

void SetMine(int arr[ROWS][COLS])//布置雷
{
	int count = N;
	while (count)
	{
		int row = rand() % ROW + 1;
		int col = rand() % ROW + 1;
		if (arr[row][col] == 0)
		{
			arr[row][col] = -1;//雷设置成-1
			count--;
		}
	}
}

void SetNum(int arr[ROWS][COLS])//制作数字
{
	int i = 0, j = 0, x = 0, y = 0;
	int count = 0;
	for (i = 1; i <= ROW; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] != -1)
			{
				for (x = i - 1; x < i + 2; x++)
				{
					for (y = j - 1; y < j + 2; y++)
					{
						if (arr[x][y] == -1)
						{
							count++;
						}
					}
				}
				arr[i][j] = count;
				count = 0;
			}
		}
	}
}

void PlayGame()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入指令:->");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			srand((unsigned int)time(NULL));
			InitShow(show);//每次进入游戏都要初始化地图
			SetMine(play);
			SetNum(play);
			Game();
			break;
		case 2:
			printf("退出游戏,欢迎下次游玩!\n");
			break;
		default:
			printf("指令输入错误,请重新输入!\n");
			break;
		}
	} while (input - 2);
}

void menu()//菜单
{
	printf("******************************************\n");
	printf("*****        欢迎来到扫雷游戏        *****\n");
	printf("*************   1.开始游玩   *************\n");
	printf("*************   2.退出游戏   *************\n");
	printf("******************************************\n");
}

void Game()//游戏主逻辑
{

	int flag = 0;
	int row = 0;
	int col = 0;
	do
	{
		PrintMap(show);
		do
		{
			printf("请输入坐标;->");
			scanf("%d%*c%d", &col, &row);//玩家输入和数组有所不同
			if (col<1 || col>COL || row<1 || row>ROW)
			{
				printf("没有该坐标存在,请重新输入\n");
			}
		} while (col<1 || col>COL || row<1 || row>ROW);

		flag = IfMine(play, row, col);
		if (flag == 1)
		{
			printf("很遗憾,你踩到地雷了\n");
			Print(play);//让玩家看到这个地图
			break;
		}
		else
		{
			OpenNull(play, show, row, col);
			int ret = IfWin(show);
			if (ret == 1)
			{
				printf("恭喜你,获得游戏胜利,祝你在生活中天天快乐!!!\n");
				PrintMap(show);
				break;
			}
			else
			{
				PrintMap(show);
				IfMake(show);
			}
		}
		system("cls");//清屏
	} while (flag - 1);
}

int IfMine(int arr[ROWS][COLS], int row, int col)
{
	int flag = 0;
	if (arr[row][col] == -1)
		flag = 1;
	return flag;//1表示踩到地雷
}

void Print(int arr[ROWS][COLS])//当游戏失败,让玩家看到这个地图
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= ROW; i++)
		printf("%3d", i);
	printf("\n");
	for (i = 1; i <= ROW; i++)
	{
		printf("%2d", i);
		for (j = 1; j <= COL; j++)
		{
			if (play[i][j] == -1)
			{
				printf("%3d", -1);
			}
			else
			{
				printf("%3d", 0);
			}
		}
		printf("\n");
	}
}

void OpenNull(int play[ROWS][COLS], char show[ROWS][COLS], int row, int col)//展开地图
{
	int i = 0;
	int j = 0;
	if (row<1 || row>ROW || col<1 || col>COL || play[row][col] == -1 || show[row][col] == ' ')//越界访问,雷区访问,空格重复访问,
		return;
	if (play[row][col] == 0)   //递归,遇到空格打开来,遇到数字停止递归并返回该数字
	{
		show[row][col] = ' ';
		for (i = row - 1; i < row + 2; i++)
		{
			for (j = col - 1; j < col + 2; j++)
			{
				if (i == row && j == col)
					continue;
				OpenNull(play, show, i, j);
			}
		}
	}
	else
	{
		show[row][col] = '0' + play[row][col];
	}
}

int IfWin(char arr[ROWS][COLS])//判断是否获胜
{
	int i = 0;
	int j = 0;
	int count = 0;
	int flag = 1;//假设获胜
	for (i = 1; i <= ROW && flag == 1; i++)
	{
		for (j = 1; j <= COL; j++)
		{
			if (arr[i][j] == '*' || arr[i][j] == '#')
				count++;
			if (count > N)
			{
				flag = 0;
				break;
			}
		}
	}
	return flag;
}

void IfMake(char show[ROWS][COLS])
{
	int sign = -1;
	printf("是否需要做记号,如果要做记号,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
	do
	{
		scanf("%d", &sign);
		int x = 0;
		int y = 0;
		switch (sign)
		{
		case 0:
			goto next;
		case 1://要标记
			printf("请输入要标记的坐标:->");
			do
			{
				scanf("%d%*c%d", &x, &y);
				if ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '*')//避免越界访问或这标记到别处,导致程序不好运行
				{
					printf("输入有误或者此处不给标记,请重新输入:->\n");
				}
				else
				{
					MakeSign(show, y, x);
					PrintMap(show);
				}
			} while ((x<1 || x>COL || y<1 || y>ROW) || show[y][x] != '#');
			break;
		default:
			printf("输入错误,请重新输入:->");
			break;
		}
		printf("是否要继续标记,如果需要,请输入 1 \n如果不需要,清输入 0 \n请输入:->");
	} while (sign);
next:
	return;
}

void MakeSign(char arr[ROWS][COLS], int row, int col)//给玩家做记号
{
	arr[row][col] = '#';
}
  • 31
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值