[C语言] [游戏] 扫雷

相关说明>>>

        众所周知,“扫雷”是个极其经典的游戏,相信大家都玩过。下面开始教你用C语言简单实现扫雷游戏。

        在开始之前推荐大家先去看看我写的 前一篇博客(链接点这里 —>) [C语言] [游戏] 三子棋  写了 8950字  超详细哟 所以在本文中有些重复的细节部分我可能不会说得太详细,不然字数会好多(^ ^ 捂脸.jpg)。

        写之前还是先简单介绍扫雷的游戏规则:玩家输入要排查雷的坐标,如果该位置埋了雷那么游戏结束玩家挑战失败;如果该位置没雷,那么该位置会显示其周围一圈(与其最相邻的共8个位置)内一共含有的雷的个数,就这样一直排雷一直排雷直到所有非雷位置都被排查完,那么恭喜你挑战成功。

              这个扫雷代码,我也同样分为 game.h \ game.c \ test.c 三个文件,分别写自定义函数的声明、定义以及整个程序的实现。然后大家记得一边写一边测试,这样有错误的话改起来不费力。

        为了方便下文说明。我们把“操作面”形象的称为“棋盘”。

扫雷代码的实现思路>>>

        先来分析一下扫雷游戏的组成成分和动作:游戏开始和结束的提示、棋盘、电脑埋雷、打印棋盘、玩家排雷、判断输赢。

游戏开始和结束的提示>>>

        同样的为了方便玩家退出游戏和提高游戏体验感这里需要写一个函数 menu 让玩家选择是否开始游戏

//像这样自定义一个功能单一的函数menu只负责打印选项单
 
void menu()
{
	printf("********************\n");
	printf("****** 1.play ******\n");
	printf("****** 0.exit ******\n");
	printf("********************\n");
}

        接着写出游戏的开始和结束执行代码,先整好最大的一个逻辑,将游戏细节的实现放入game函数里。

//当玩家选择1后开始游戏,进入 game函数

void test()
{
	int i = 0;
	do
	{
		menu();
		scanf("%d", &i);
		if (1 == i)
		{
			printf("开始游戏 >\n"); //好的游戏体验感,
			game();					//给个提示,不会太突然
			break;
		}
		else if(i!=0)  //这里注意美观
			printf("输入错误,请重新输入:\n");

	} while (i);
	printf("退出游戏。\n");
}


//当玩家不想玩后选择0,即结束游戏

        else if 这里应该判断为不等于0,因为如果直接else 那么若玩家输入0后会出现      输入错误,请重新输入:     这句话,接着会打印       退出游戏。       这句话。若没有else if 判断非 0 才打印,那么这两句话都出现就显得不美观,如下面对比图。

(所以就是得注意do while 循环先执行一次了)

else

 else if

 棋盘>>>

       我们先预想一下棋盘的可能(重点)>>>

       我们先从简单的开始,设置一个 9 x 9 的棋盘,定义好行列。

#define ROW 9
#define COL 9

        那么玩家的操作是输入坐标,所以对应的我们也应该使用二维数组。 我们把 0 表示无雷 ,       1 表示 雷 ,‘ * ’  表示未被排查,数字字符表示(已排查)排查出该位置周围一圈内含雷的数量。

        这里把 0 表示无雷 , 1 表示 雷  选择统一使用数字是因为方便,比起使用多种特殊字符,人们更习惯用有顺序 的数字字符进行替换记忆。而且便于计算排查出的雷的信息。

        使用‘ * ’  表示未被排查,是为了保持游戏的神秘感,直观上的视觉冲击强烈暗示我们该位置情况未知。若使用数字则和雷的信息混乱,小白玩家可能会吐槽太丑,然后直接退出游戏。(游戏体验感 游戏体验感)若用空格那么玩家可能会认为没有雷要排,就是没有那种该位置放了东西的感觉。而且将满屏的 ‘ * ’ 都消灭的 成就感 也是玩家需要的。(嘿嘿)

        那么这里还需注意我们得创建两个数组,一个埋雷,一个排雷,是吧。

        这里你可能想问,我只用一个数组,把一个特殊字符当雷,其它位置直接放排查雷 的信息不可以吗?    那么你这么打印给玩家看呢?只有一个数组,那么打印就会把雷也打印出来,但玩家需看到整个棋盘才可以选择坐标。所以我们要创建 mine数组仅给电脑操作(埋雷),对玩家隐藏。show数组负责打印出来给玩家排雷。

        那么show数组必须和mine数组完全一一对应存放对应坐标的排查信息。也就是show数组得和mine数组一样大小。

        想好了两个棋盘,但是棋盘大小呢?真的是你所看到的 9 x 9 吗?

        因为排雷排的是坐标周围一圈,那么那些边缘坐标具体怎么排?是和中间坐标一样直接整个一圈来判断?  那么在 9 x 9 中会判断到这些边缘坐标的边缘(1,5)—> (0,5) 显然没有(0,5)这个坐标,这时候就会出现数组的越界访问,编译器直接报错。  

        若是对这些边缘坐标特殊化判断,那么会有 在行的边缘和列的边缘的坐标,又会出现不同的特殊判断情况,明显更麻烦。

        所以更好的方法就是扩大一圈,上下左右各多一行一列。变成 11 x 11 。这样就可以对原来的(1,5)这样的坐标使用一样的一整圈排查的函数,每个元素用一样的操作进行判断,会精简一些,只要打印的时候注意不显示出这些扩大了的范围就好。

        以上这些都要先想好,不然直接写可能后面需要改的地方会很多了。

        开始写了,开始写了!重要的话再说一遍:推荐大家先看看 我写的 前一篇博客(链接点这里 —>) [C语言] [游戏] 三子棋  写了 8950字  超详细哟 所以在本文中有些重复的细节部分我可能不会说得太详细,不然字数会好多(^ ^ 捂脸.jpg)。

想的差不多了,开始写>>>

        先创建并初识化棋盘(二维数组)。

        由上文可知我们需要使用的棋盘的最大范围是 11 x 11 ,所以我们创建的数组得是 11 x 11。后面使用 9 x 9 时从下标1开始访问就好。像这样先定义好行列

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

         这里需要传 char ret 这个参数是因为,我们要将mine数组和show数组都进行初始化,这两个数组的区别只有初始化内容不同,所以我们把初始化内容作为参数就可以对这两个数组使用同一个函数了。(ret 这个参数表示初始化的内容,mine数组 ret = ‘0’  未埋雷, show数组 ret = ‘*’  未排查

//初始化棋盘
void init_board(char arr[ROWS][COLS], int rows, int cols, char ret)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = ret;
		}
	}
}

        初识化完了以后我们一般会打印出来,看看是否正确。所以接下来先写负责打印的函数。

打印棋盘>>>

         这里函数的参数中,二维数组写的是 11x11 是因为我们定义的数组是 11x11 的,在传递参数时不可能只把其中的 9x9 拎出来传过去,这样编译器不知道你要传的是中间的那个 9x9 。旁边的行列传递 9 就代表了你将操作 9x9 了。

        后面的行列都是传递 9x9 ,当初多余的那一圈只为防止越界访问而存在,所以除访问外,后续不会对它们进行多余的操作。

//打印看看
void show_board(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("-----------扫雷-----------\n");

	for (i = 0; i <= col; i++)//灵活打印列标
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ",arr[i][j]);
		}
		printf("\n");
	}
	printf("-----------扫雷-----------\n");
}

        同样的这里打印了分割线和行列标,要注意游戏体验感很重要。

      (这个打印函数每次排雷前后都要使用。) 等等,打印时记得看清楚打印那个棋盘。

 这里简单看一下初始化结果:(后面有最终效果图)

   

电脑埋雷>>>

        先定义 MINE 表示雷的个数,使用随机数(不确定性大),所以埋的时候得判断该位置是否已被埋

//埋雷
void set_mine(char arr[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int count = MINE;
	while (count)
	{
		x = rand() % row + 1; //1~9
		y = rand() % row + 1;

		if ('0' == arr[x][y])
		{
			arr[x][y] = '1'; //'1'表示雷
			count--;
		}
	}
}

玩家排雷、判断输赢>>>

        这里又到了手滑小白玩家的表演时间了......

        接下来判断游戏结束,要么赢,要么被炸。结束后都要把mine打印出来让玩家看看所有雷的位置。

        被炸简单,直接跳出循环就好。其实赢也一样,赢就是避开雷排完所有位置。如果一直没被炸,排完了怎么跳出循环呢?

        在这里唯一可控的就是排的次数,因为棋盘大小已固定 9x9 减去我们埋好的 10 个雷 最多可以排 row * col - MINE(这里是71)次。所以设置个变量 win ,每排一次加一,直到排到最大次数后循环结束。

        emmmm 不用把雷当做参数却把行列当做参数是因为雷数不变,而行列有9和11会改变,把行列当做参数直观的告诉我们具体要操作那种行列(那种棋盘)。

//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	
	int x = 0;
	int y = 0;
	int win = 0;
	while (win< row * col - MINE)
	{
		printf("请按行列顺序输入要排查的坐标:\n");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if ('1' == mine[x][y])
			{
				printf("是雷!被炸了!!!\n");
				show_board(mine, ROW, COL);				
				break;
			}
			else
			{
				int count = get_mine_count(mine, x, y); //函数返回值是 int 类型
				show[x][y] = count + '0';			//数组存储的是 char 类型
				show_board(show, ROW, COL);
				win++;
			}
		}
		else
			printf("坐标非法,请重新输入:");
	}

//下面因为需要打印这句话,所以得判断赢了才打印

	if (win == row * col - MINE)
	{
		printf("恭喜你,排雷成功\n");
		show_board(mine, ROW, COL);
	}

}

        为了书写方便 ,看起来逻辑清晰,把得出排雷信息写成函数,如下。

//得出排查后雷的信息
int get_mine_count(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{			
			if ('1' == arr[row + i][col + j])
				count++;
		}
	}

	return count;
}

        注意这个函数的返回值是 int 类型,而数组是 char 类型的,所以要把这个 int 类型的信息转化成 char 类型才可以存储到 char 数组里面。 

        根据下面的 ASCII 码表可以看出数字字符 0~9 的 ASCII 码值依次增加。所以我们可以这样写show[x][y] = count + '0';    就把 int 信息转化成了对应的 数字字符。

总代码>>>

//game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 9
#define COL 9

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

#define MINE 10

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//初始化棋盘
void init_board(char arr[ROWS][COLS], int row, int col, char ret);

//打印看看
void show_board(char arr[ROWS][COLS], int row, int col);

//电脑埋雷
void set_mine(char arr[ROWS][COLS], int row, int col);


//玩家排雷    //判断游戏结束
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);

将menu函数和game函数放在test.c里更直观。

//test.c


#include"game.h"

void menu()
{
	printf("***********************\n");
	printf("*******  1.play  ******\n");
	printf("*******  0.exit  ******\n");
	printf("***********************\n");
}
void game()
{
	srand((unsigned int)time(NULL));
	//初始化二维数组
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	//使用的是11*11,调用的是其中的9*9
	
	//初始化棋盘
	init_board(mine, ROWS, COLS, '0');
	init_board(show, ROWS, COLS, '*');

	//打印看看

	//show_board(mine, ROW, COL);
	//注意雷是不打印给玩家看的,
	//但我们写的时候可以打印出来看看是否写对了。

	show_board(show, ROW, COL);

	//埋雷
	set_mine(mine, ROW, COL);
	//show_board(mine, ROW, COL);

	//排查雷
	find_mine(mine, show, ROW, COL);

}
void test()
{
	int i = 0;
	do
	{
		menu();
		scanf("%d", &i);
		if (1 == i)
		{
			printf("开始游戏 >\n"); //好的游戏体验感,
			game();					//给个提示,不会太突然
			break;
		}
		else if(i!=0)  //这里注意美观
			printf("输入错误,请重新输入:\n");

	} while (i);
	printf("退出游戏。\n");
}

int main()
{

	test();

	return 0;
}
//game.c

#include "game.h"

//初始化棋盘
void init_board(char arr[ROWS][COLS], int rows, int cols, char ret)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			arr[i][j] = ret;
		}
	}
}

//打印看看
void show_board(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	printf("-----------扫雷-----------\n");

	for (i = 0; i <= col; i++)//灵活打印列标
	{
		printf("%d ", i);
	}
	printf("\n");

	for (i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (j = 1; j <= col; j++)
		{
			printf("%c ",arr[i][j]);
		}
		printf("\n");
	}
	printf("-----------扫雷-----------\n");
}

//埋雷
void set_mine(char arr[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int count = MINE;
	while (count)
	{
		x = rand() % row + 1; //1~9
		y = rand() % row + 1;

		if ('0' == arr[x][y])
		{
			arr[x][y] = '1'; //'1'表示雷
			count--;
		}
	}
}

//得出排查后雷的信息
int get_mine_count(char arr[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	int count = 0;
	for (i = -1; i <= 1; i++)
	{
		for (j = -1; j <= 1; j++)
		{			
			if ('1' == arr[row + i][col + j])
				count++;
		}
	}

	return count;
}

//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	
	int x = 0;
	int y = 0;
	int win = 0;
	while (win< row * col - MINE)
	{
		printf("请按行列顺序输入要排查的坐标:\n");
		scanf("%d%d", &x, &y);
		if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
		{
			if ('1' == mine[x][y])
			{
				printf("是雷!被炸了!!!\n");
				show_board(mine, ROW, COL);				
				break;
			}
			else
			{
				int count = get_mine_count(mine, x, y); //函数返回值是 int 类型
				show[x][y] = count + '0';			//数组存储的是 char 类型
				show_board(show, ROW, COL);
				win++;
			}
		}
		else
			printf("坐标非法,请重新输入:");
	}
	if (win == row * col - MINE)
	{
		printf("恭喜你,排雷成功\n");
		show_board(mine, ROW, COL);
	}

}

最终效果图>>>

 ......

 就这样,大家都可以写写分享给朋友们,说不定他们会试试你亲手写的小游戏呢!^ ^

写完的思考>>>

        要对将要写的东西有足够的了解,关于它的基本组成成分和操作。把这些预想一下,还要想想可能发生的小意外。就像这个扫雷,如果定义的二维数组是 9x9 那么问题太多,你得想到给它增加一圈却不打印这样的小办法。

        还有我认为游戏最重要的就是体验感,所以写的时候要做的美观一点。

最重要的话>>>

           不理解的记得私信我哦 ^ ^。

        (直接评论区留言也可以^ ^)

猜你喜欢

转载自blog.csdn.net/zoe23333/article/details/123768545