要啥有啥?扫雷,我是认真的!

文章目录

  • 要啥有啥?扫雷,我是认真的!
    • 一、游戏规则
    • 二、游戏实现
      • (1)游戏界面
      • (2)游戏ing
        • 初始化数组
        • 打印界面
        • 布置雷
        • 排查雷
          • 展开!
          • 标记!
    • 三、代码总结
      • (1)game.h
      • (2)test.c
      • (3)game.c
    • 四、游戏总结

要啥有啥?扫雷,我是认真的!

扫雷——一款大众类的益智小游戏,一款在小居童年时一直以为是纯靠运气取胜的游戏,导致从没玩赢过。直到高中才晓得真正的游戏规则

要啥有啥?扫雷,我是认真的!_第1张图片

初学C语言的小居开始豪横起来,计划用一些简单的C语言知识来实现一个"要啥有啥"的扫雷,话不多说,讲起!

一、游戏规则

扫雷游戏是在9*9,16*16,16*30或者自定义大小的矩形方阵中实现的,此次就来整一个9*9的扫雷简易版玩玩。

玩家通过选择1(玩游戏)或0(结束游戏)来决定游戏与否。

输入想要扫的雷的坐标。若踩到雷,那就输了;若未踩到雷,显示该坐标上下左右以及四个角上所含雷的个数(如果周围八个都没有雷,则向外展开,直到周围有雷为止),游戏继续。直到矩形方阵中只剩下雷时,恭喜!你赢了。

玩家遇见疑似雷的时候可以选择去标记它,选择1(标记)或0(不标记),输入想要标记的坐标,即可完成标记。

要啥有啥?扫雷,我是认真的!_第2张图片

二、游戏实现

在计算机编程的过程中,为了使代码看起来井然有序,遂将游戏主体分在三个文件里,两个源文件,一个头文件

  • test.c 在该源文件里实现扫雷游戏的主体流程
  • game.c 在该源文件里实现游戏过程中具体的函数
  • game.h 在该头文件里放入各个函数的声明,库函数使用所需的头文件,以及需要定义的常量,如此一来,只需要在其他两个源文件里引这个头文件,就会有包含所有的内容,避免代码的重复冗余

(1)游戏界面

游戏界面的打印以及游戏的进入与结束都放在主函数(main函数)中

代码展示:

int main()
{
     
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
     
		menu();
		printf("请选择:> ");
		scanf("%d", &input);
		switch (input)
		{
     
		case 1:
			system("cls");//执行系统命令-cls-清空屏幕
			game();//选择进入游戏
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
}

说明:

  1. main函数当中,小居运用了do…while语句来实现游戏的进入与结束,保证游戏界面至少进入一次。

  2. do…while语句中首先进行了游戏菜单的打印,1代表玩游戏,0代表退出游戏。

代码展示:

void menu()
//打印菜单
{
     
	printf("**********************************\n");
	printf("**********   1、Play      ********\n");
	printf("**********   0、Exit      ********\n");
	printf("**********************************\n");
}
  1. main函数中定义一个整形变量input来接受玩家的意愿,通过switch分支语句来实现,当玩家选择1时代表想要来次扫雷游戏,就会先进行清屏操作,再执行game函数从而进行游戏,选择0时代表想要退出游戏,如果选择了其他非1或0的数字就会进行提醒“输入错误,请重新输入”。

代码效果:

要啥有啥?扫雷,我是认真的!_第3张图片

(2)游戏ing

当选择数字1进入游戏函数(game函数)后,便进入了游戏的主体,为了使代码分工明确,实现游戏过程中的函数的详细内容将会被放在game.c之中。

代码展示:

void game()
{
     
	char mine[ROWS][COLS] = {
      0 };//存放雷的数组
	char show[ROWS][COLS] = {
      0 };//扫雷的数组
	//初始化两个数组
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印扫雷的界面
	PrintBoard(show, ROW, COL);
	//布置雷的位置
	SetMine(mine, ROW, COL);
	//排查雷
	FindMine(mine, show, ROW, COL);
}

说明:

  1. 进入游戏之后先创建了两个字符型二维数组,一个是mine[ROWS][COLS]用来存放雷的数组,另一个是show[ROWS][COLS]用于进行扫雷,ROWS(11)和COLS(11)代表着行和列,在game.h这个头文件里进行了宏定义,这样未来想要修改行和列只需要在头文件里进行修改即可,方便大大地增加。

    问题1、在这里为啥要创建两个数组呢?

    解:当出现1时,表示的是雷?还是排雷时坐标周围雷的个数1?这种时候就会产生分歧,所以需要创建两个数组来存放信息。

    问题2、既然1会混淆分不清,为何不让雷用@代替,不是雷用#代替呢?

    解:这样会让棋盘上的信息过多,不方便打印。打印的时候只想要打印排查出来的雷的信息,其他位置不显示。如果这样做就需要每个坐标去判断是否为数字,可读性差,难以理解。

    问题3、明明就是9*9的扫雷游戏为啥行和列都是11?

​ 解:在扫雷的过程中有一步需要统计坐标(x,y)(坐标x和y的区间为[1,9])周围8个坐标的雷的个数,如果mine数组的大小刚好是9*9,那坐标方阵最外一圈就没有办法统计全雷的个数,还会出现越界访问现象。show数组的大小也是11*11是为了和mine数组一一对应,方便将mine数组中的内容反映到show数组中。

要啥有啥?扫雷,我是认真的!_第4张图片

  1. 然后进行初始化雷数组和排雷数组,将mine数组中的内容初始化为’0’(字符0),将show数组中的内容初始化为’*’,保持游戏开始时的神秘感,就待玩家解开’*'后的谜底。这里小居将用InitBoard(mine, ROWS, COLS, ‘0’)和
    InitBoard(show, ROWS, COLS, ‘*’)来实现,参数有想要进行初始化的数组,有初始化的行和列,以及初始化的内容,使两个数组的初始化在同一个函数中进行
  2. 接下来就需要打印扫雷的界面,方便看见排雷的过程。
  3. 再然后进行布置雷的操作,想要布置雷的个数也将在game.h这个头文件里进行宏定义,方便调整游戏难易程度。
  4. 最后就是游戏的关键所在排雷,回头将细细道来。

初始化数组

代码展示:

void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set)
{
     
	int i = 0, j = 0;
	for (i = 0; i < rows; i++)
	{
     
		for (j = 0; j < cols; j++)
		{
     
			Board[i][j] = set;
		}
	}
}

说明:

通过两层for循环对数组的内容进行初始化,这里用字符型变量set来接受初始化的内容’0’或’*’,将set赋值给初始化的数组。

打印界面

代码展示:

void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
     
	printf("----------扫雷游戏----------\n");
	int i = 0;
	for (i = 0; i <= col; i++)//列号
	{
     
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)//行号
	{
     
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
     
			printf("%c ", Board[i][j]);//打印数组中的内容
		}
		printf("\n");
	}
	printf("----------扫雷游戏----------\n");
}

说明:

在打印界面最上面和最下面都需要打印分隔线,打印数组中的内容时为了让玩家能够快速识别坐标位置,所以需要打印行号、列号

代码效果:

要啥有啥?扫雷,我是认真的!_第5张图片

布置雷

代码展示:

void SetMine(char Board[ROWS][COLS], int row, int col)
{
     
	int count = NumMine;
	while (count)
	{
     
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (Board[x][y] == '0')
		{
     
			Board[x][y] = '1';//雷为'1'
			count--;
		}
	}
}

说明:

NumMine是整个方阵中雷的个数,这里通过while循环来实现雷的一个一个布置。此时需要让电脑随机生成横纵坐标,运用到了rand函数随机生成1到9(雷布置的区域)的数,该函数的头文件为#include ,使用该函数还需要使用srand函数进行配合

srand((unsigned int)time(NULL));

​ 这是拿时间戳来设置随机数的生成起始点,里面包含time函数,其头文件为#include ,在调用rand函数之前调用一次即可,所以将其放置在了 main函数的循环外即可。

如果生成的坐标先前没有布置过雷(坐标里是’0’),就布置一个雷,雷用’1’表示,每不止一个count- -,直到count等于0,跳出循环,结束布置雷这一个操作。

排查雷

代码展示:

void FindMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col)
{
     
	int win = 0;//排雷区域'*'或'&'个数
	int x = 0, y = 0;
	int a = 0, b = 0;
	while (1)
	{
     
		//PrintBoard(Show, ROW, COL);
		int sign = 0;//标记与否
		printf("请输入想要排雷的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
     
			if (Mine[x][y] == '1')
			{
     
				printf("很遗憾,你被炸死了\n");
				PrintBoard(Mine, row, col);
				break;
			}
			else
			{
     
				//计算该坐标周围有多少雷,并进行递归展开
				Count_Spread(Mine, Show, x, y);
				system("cls");
				PrintBoard(Show, ROW, COL);
				win = To_Win(Show, ROW, COL);
				if (win == NumMine)
				{
     
					printf("恭喜你,扫雷成功!\n");
					PrintBoard(Mine, row, col);
					break;
				}
			}
		}
		else
		{
     
			printf("坐标不合法,请重新输入\n");
			continue;
		}
    }
int To_Win(char Show[ROWS][COLS], int row, int col)
{
     
	int i = 0, j = 0;
	int count = 0;
	for (i = 1; i <= row; i++)
	{
     
		for (j = 1; j <= col; j++)
		{
     
			if (Show[i][j] == '*'|| Show[i][j] == '&')
			{
     
				count++;
			}
		}
	}
	return count;
}

说明:

  1. 定义两个整形变量xy来接受坐标,通过while循环来实现坐标的输入,输入后,先判断坐标的合法性,如果不合法,在提示之后continue,重新进入循环。
  2. 如若坐标合法,就来判断mine数组中的内容,如果是雷(‘1’),就提示玩家输了,并且打印mine数组的内容来展示雷的位置,跳出循环。
  3. 如果不是雷,就该计算该坐标周围有多少雷,并且进行递归展开,打印排雷的数组,新定义的整型变量win来接受show数组中有多少个未排雷的坐标,当win等于NumMine时,就意味着扫雷的成功。
展开!

代码展示:

//x,y坐标周围8个坐标的雷的个数
static int RoundMine(char Mine[ROWS][COLS], int x, int y)
{
     
	return Mine[x - 1][y - 1] +
		Mine[x - 1][y] +
		Mine[x - 1][y + 1] +
		Mine[x][y + 1] +
		Mine[x + 1][y + 1] +
		Mine[x + 1][y] +
		Mine[x + 1][y - 1] +
		Mine[x][y - 1] - 8 * '0';
}
//递归展开
void Count_Spread(char Mine[ROWS][COLS], char Show[ROWS][COLS], int x, int y)
{
     
	int count = RoundMine(Mine, x, y);
	if (count != 0)
	{
     
		Show[x][y] = count + '0';
	}
	else
	{
     
		Show[x][y] = '0';
		if ((x - 1) > 0 && (y - 1) > 0 && Show[x - 1][y - 1] == '*')
			Count_Spread(Mine, Show, x - 1, y - 1);
		if ((x - 1) > 0 && (y + 0) > 0 && Show[x - 1][y + 0] == '*')
			Count_Spread(Mine, Show, x - 1, y + 0);
		if ((x - 1) > 0 && (y + 1) <= COL && Show[x - 1][y + 1] == '*')
			Count_Spread(Mine, Show, x - 1, y + 1);
		if ((x + 0) > 0 && (y + 1) <= COL && Show[x + 0][y + 1] == '*')
			Count_Spread(Mine, Show, x + 0, y + 1);
		if ((x + 1) <= ROW && (y + 1) <= COL && Show[x + 1][y + 1] == '*')
			Count_Spread(Mine, Show, x + 1, y + 1);
		if ((x + 1) <= ROW && (y - 0) > 0 && Show[x + 1][y - 0] == '*')
			Count_Spread(Mine, Show, x + 1, y - 0);
		if ((x + 1) <= ROW && (y - 1) > 0 && Show[x + 1][y - 1] == '*')
			Count_Spread(Mine, Show, x + 1, y - 1);
		if ((x - 0) > 0 && (y - 1) > 0 && Show[x - 0][y - 1] == '*')
			Count_Spread(Mine, Show, x - 0, y - 1);
	}
}

说明:

  1. 这里通过RoundMine函数来计算坐标(x,y)周围8个坐标的雷的个数,由于雷是用字符1来表示的,函数的返回值是整形的,所以需要减去8*‘0’。这里还用了static来修饰函数,是为了让该函数只在本源文件可使用。
  2. 这里通过Count_Spread函数来实现递归展开。先来计算该坐标周围的雷数,如果不等于零,就把该值转换成字符赋值给该坐标。
  3. 如果等于零,就将字符零赋值给该坐标。再在不越界的情况下一一判断该坐标周围的8个坐标是否还是初始值’*’,如果成立,就再一次调用Count_Spread函数,疯狂套娃。
标记!

代码展示:

do
{
     
	printf("标记与否(1表示标记,0表示不标记):>");
	scanf("%d", &sign);
	printf("\n");
	switch (sign)
	{
     
	case 1:
		while (1)
		{
     
			printf("请输入想要标记的坐标:>");
			scanf("%d %d", &a, &b);
			if (a >= 1 && a <= row && b >= 1 && b <= col)
			{
     
				Show[a][b] = '&';
				system("cls");
				PrintBoard(Show, ROW, COL);
				break;
			}
			else
			{
     
				printf("坐标不合法,请重新输入\n");
			}
		}
		break;
	case 0:
		break;
	default:
		printf("输入错误请重新输入\n");
		break;
	}
} while (sign);

说明:

  1. 在排雷的过程中,会遇到自己已经确定是雷的坐标,为了不忘记此处是个雷,会使用标记的方法,这里将用’&'来标记那些内心中判断出的雷。
  2. 在这里标记的方法,和游戏主函数里选择游戏与否以及排雷部分的代码有异曲同工之妙。

代码效果:

要啥有啥?扫雷,我是认真的!_第6张图片

三、代码总结

(1)game.h

#pragma once
#include 
#include 
#include 

#define NumMine 10//布置雷的个数
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2

void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);//初始化
void PrintBoard(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);//排查雷

(2)test.c

#define  _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
     
	printf("****************************\n");
	printf("********   1、Play   *******\n");
	printf("********   0、Exit   *******\n");
	printf("****************************\n");
}
void game()
{
     
	char mine[ROWS][COLS] = {
      0 };//存放雷的数组
	char show[ROWS][COLS] = {
      0 };//扫雷的数组
	//初始化两个数组
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');
	//打印扫雷的界面
	PrintBoard(show, ROW, COL);
	//布置雷的位置
	SetMine(mine, ROW, COL);
	//排查雷
	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:
			system("cls");
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);
}

(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, j = 0;
	for (i = 0; i < rows; i++)
	{
     
		for (j = 0; j < cols; j++)
		{
     
			Board[i][j] = set;
		}
	}
}
void PrintBoard(char Board[ROWS][COLS], int row, int col)
{
     
	printf("----------扫雷游戏----------\n");
	int i = 0;
	for (i = 0; i <= col; i++)
	{
     
		printf("%d ", i);
	}
	printf("\n");
	for (i = 1; i <= row; i++)
	{
     
		int j = 0;
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
     
			printf("%c ", Board[i][j]);
		}
		printf("\n");
	}
	printf("----------扫雷游戏----------\n");
}
void SetMine(char Board[ROWS][COLS], int row, int col)
{
     
	int count = NumMine;
	while (count)
	{
     
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (Board[x][y] == '0')
		{
     
			Board[x][y] = '1';//雷为'1'
			count--;
		}
	}
}
//x,y坐标周围有的雷的个数
static int RoundMine(char Mine[ROWS][COLS], int x, int y)
{
     
	return Mine[x - 1][y - 1] +
		Mine[x - 1][y] +
		Mine[x - 1][y + 1] +
		Mine[x][y + 1] +
		Mine[x + 1][y + 1] +
		Mine[x + 1][y] +
		Mine[x + 1][y - 1] +
		Mine[x][y - 1] - 8 * '0';
}
void Count_Spread(char Mine[ROWS][COLS], char Show[ROWS][COLS], int x, int y)
{
     
	int count = RoundMine(Mine, x, y);
	if (count != 0)
	{
     
		Show[x][y] = count + '0';
	}
	else
	{
     
		Show[x][y] = '0';
		if ((x - 1) > 0 && (y - 1) > 0 && Show[x - 1][y - 1] == '*')
			Count_Spread(Mine, Show, x - 1, y - 1);
		if ((x - 1) > 0 && (y + 0) > 0 && Show[x - 1][y + 0] == '*')
			Count_Spread(Mine, Show, x - 1, y + 0);
		if ((x - 1) > 0 && (y + 1) <= COL && Show[x - 1][y + 1] == '*')
			Count_Spread(Mine, Show, x - 1, y + 1);
		if ((x + 0) > 0 && (y + 1) <= COL && Show[x + 0][y + 1] == '*')
			Count_Spread(Mine, Show, x + 0, y + 1);
		if ((x + 1) <= ROW && (y + 1) <= COL && Show[x + 1][y + 1] == '*')
			Count_Spread(Mine, Show, x + 1, y + 1);
		if ((x + 1) <= ROW && (y - 0) > 0 && Show[x + 1][y - 0] == '*')
			Count_Spread(Mine, Show, x + 1, y - 0);
		if ((x + 1) <= ROW && (y - 1) > 0 && Show[x + 1][y - 1] == '*')
			Count_Spread(Mine, Show, x + 1, y - 1);
		if ((x - 0) > 0 && (y - 1) > 0 && Show[x - 0][y - 1] == '*')
			Count_Spread(Mine, Show, x - 0, y - 1);
	}
}
//通过计算Show[ROWS][COLS]中有多少'*'或'&',该个数等于雷的个数时,获得胜利
int To_Win(char Show[ROWS][COLS], int row, int col)
{
     
	int i = 0, j = 0;
	int count = 0;
	for (i = 1; i <= row; i++)
	{
     
		for (j = 1; j <= col; j++)
		{
     
			if (Show[i][j] == '*'|| Show[i][j] == '&')
			{
     
				count++;
			}
		}
	}
	return count;
}
void FindMine(char Mine[ROWS][COLS], char Show[ROWS][COLS], int row, int col)
{
     
	int win = 0;//排雷区域'*'和'&'的总数
	int x = 0, y = 0;
	int a = 0, b = 0;
	while (1)
	{
     
		int sign = 0;//标记与否
		printf("请输入想要排雷的坐标:>");
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
     
			if (Mine[x][y] == '1')
			{
     
				printf("很遗憾,你被炸死了\n");
				PrintBoard(Mine, row, col);
				break;
			}
			else
			{
     
				//计算该坐标周围有多少雷,并进行递归展开
				Count_Spread(Mine, Show, x, y);
				system("cls");
				PrintBoard(Show, ROW, COL);
				win = To_Win(Show, ROW, COL);
				if (win == NumMine)
				{
     
					printf("恭喜你,扫雷成功!\n");
					PrintBoard(Mine, row, col);
					break;
				}
			}
		}
		else
		{
     
			printf("坐标不合法,请重新输入\n");
			continue;
		}
		do
		{
     
			printf("标记与否(1表示标记,0表示不标记):>");
			scanf("%d", &sign);
			printf("\n");
			switch (sign)
			{
     
			case 1:
				while (1)
				{
     
					printf("请输入想要标记的坐标:>");
					scanf("%d %d", &a, &b);
					if (a >= 1 && a <= row && b >= 1 && b <= col)
					{
     
						Show[a][b] = '&';
						system("cls");
						PrintBoard(Show, ROW, COL);
						break;
					}
					else
					{
     
						printf("坐标不合法,请重新输入\n");
					}
				}
				break;
			case 0:
				break;
			default:
				printf("输入错误请重新输入\n");
				break;
			}
		} while (sign);
	}
}

四、游戏总结

本次的扫雷游戏的实现充满着不易,游戏颜值虽不高,但也算是圆满实现扫雷基本功能,希望技术多多长进,能实现颜值更高,玩起来更加便捷的扫雷游戏…

你可能感兴趣的:(小游戏,c++,游戏开发)