C语言实现扫雷游戏

扫雷游戏的功能说明:

使用控制台实现经典的扫雷游戏
游戏可以通过菜单实现继续玩或者退出游戏
扫雷的棋盘是9*9的格子
默认随机布置10个雷
可以排查雷
    如果位置不是雷,就显示周围有几个雷
    如果位置是雷,就炸死游戏结束
    把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
游戏的界面:

e3009dbb93594a92ba118cba91e52402.png

一、扫雷游戏代码的函数声明

1.格式化输出printf(),格式化输入scanf(),初始化随机数的生成器函数srand(),时间函数time()需要用到下面的三个头文件。
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
2.宏定义棋盘的行数ROW和列数COL,而ROWSCOLS是在原来棋盘行列的基础上再增加一行和一列,这里宏定义的目的是方便后面要对棋盘的行和列数进行修改时,从这里就可以修改。
#define ROW 9
#define COL 9
#define ROWS ROW+1
#define COLS ROW+1
3.宏定义要布置在棋盘当中的雷的数量。(宏定义其实就是对文本内容的替换,如下面宏定义的意思是在预处理程序时,会将程序中所有Mine_num用10来替换)
#define Mine_num 10
4.下面是实现扫雷功能的所有函数的函数声明。
void InitBoard(char arr[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 ClearMine(char Mine[ROWS][COLS],char Show[ROWS][COLS], int row, int col);
上面InitBoard()函数是用来初始化棋盘的,DisplayBoard()函数是用来展示棋盘的,SetMine()是用来对初始化的棋盘进行布雷的函数,最后一个ClearMine()是玩扫雷游戏的函数。我们将函数的声明、函数的定义及玩扫雷游戏的菜单选项分成三个文件来写,上面包含的头文件、宏定义和扫雷功能函数的声明我们统一放在.h的头文件中,命名为game.h,以后在.c文件中就可以包含这个头文件来使用里面的函数声明。

二、扫雷功能所有函数的定义:

1.用来初始化棋盘的函数InitBoard()定义如下:
void InitBoard(char board[ROWS][COLS],int row,int col,char set)
{
	int i = 0;
	for (i = 0; i <row ; i++)
	{
		int j = 0;
		for (j = 0; j < col; j++)
		{
			board[i][j] = set;
		}
	}
}
把给定的二维数组(棋盘)所有的数组元素初始化为自定义的字符,给定的行和列实参为ROWS和COLS。如果是初始化布置雷的棋盘mine(后台隐藏的棋盘),则用字符0初始化棋盘,字符0表示无雷,之后进行布雷时,用字符1表示雷。如果是初始化展示的棋盘show(给玩家看的棋盘),则数组元素全部初始化为字符*,表示还没有扫雷时的一张张方块:

27dbe257b8ba4843aab911bfb30c62d7.png

2.用来展示棋盘的函数DisplayBoard()定义如下:

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	for (i = 0; i <= row; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	int j = 0;
	for (j = 1; j <= row; j++)
	{
		int k = 0;
		printf("%d ", j);
		for (k = 1; k <= col; k++)
		{
			printf("%c ", board[j][k]);
		}
		printf("\n");
	}
}
上面的函数从上往下第一个for循环是用来打印列号在屏幕上;第二个for循环里又嵌套了一个for循环,是用来展示棋盘的信息的,其中外层的for循环实现要打印几行,而且包含了打印行号,内层的for循环实现要打印几列。这样棋盘上有行和列号,就方便我们观察格子的位置信息,并用来输入要扫雷的格子坐标。这里能发现棋盘(二维数组)上扫雷的格子坐标(数组下标)行和列都是从1开始并且小于等于相应的行和列号,这是因为棋盘的大小实际是ROWS*COLS的,而我们要在屏幕上输出的棋盘大小是ROW*COL的,数组的下标是从0开始的,不管是行还是列,我们跳过了棋盘的第一行和第一列,就相当于跳过了下标为0的行和列,而从下标为1的元素开始打印,这样就和真实的坐标相对应起来。所以这里传的行列实参要是ROWCOL
3.用来布置雷的函数SetMine()的定义如下:
void SetMine(char board[ROWS][COLS],int row,int col)
{
	int count = Mine_num;
	while (count)
	{
		int i = rand() % row + 1; //表示数组的行下标
		int j = rand() % col + 1; //表示数组的列下标
		if (board[i][j] != '1')
		{
			board[i][j] = '1';
			count--;
		}
	}
}
当布雷的棋盘被初始化以后,棋盘上全是一样的字符0(非雷),要对其进行布雷,这里是随机的在棋盘上布置雷,所以要用到rand()函数,使用rand函数之前要先调用srand()函数来设置rand函数生成随机数的时候的种子,在整个程序中srand函数只需要被调用一次即可。上面rand()%row (表示rand()函数生成的随机数除以row以后得到的余数) 表示得到的数的范围在0~row-1之间,所以rand()%row+1表示得到的数的范围在1~row之间(包括1和row),这样就生成了一个随机的行坐标i,同样的就可以得到一个随机的列坐标j,范围在1~col之间,然后对这个随机的坐标进行布雷。布了雷以后的格子要显示为字符1(雷),这里我们编写的是一个9*9格的扫雷游戏,布雷数量为Mine_num(10)。上面的程序中while循环的循环条件为count,而count要什么情况下才减小呢?要顺利地布置我们设置的雷数,就要考虑到之后随机生成的坐标会不会和以前随机生成的坐标相同,也就是前面已经在这次生成的坐标中布置了雷,则就不能在相同的坐标上重复布雷,要先判断输入的坐标位置上是否有雷,没有雷才在该坐标上布雷,循环的条件count也才随之减小。这样才能完整的布置我们设置的雷数。

4.玩扫雷游戏的函数ClearMine()的定义如下:

void ClearMine(char Mine[ROWS][COLS],char Show[ROWS][COLS], int row, int col)
{
	int x = 0;//棋盘的横坐标
	int y = 0;//棋盘的纵坐标
	int NotMine_count = 0;
	while (NotMine_count < row * col - Mine_num)
	{
		printf("请输入要排查的坐标(x y):");
	again:
		scanf("%d %d", &x, &y);
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			if (Mine[x][y] == '1')
			{
				printf("很遗憾,你被炸死了!\n");
				DisplayBoard(Mine, row, col);
				printf("\n");
				break;
			}
			else
			{
				if (Show[x][y] == '*')
				{
					Show[x][y] = AroundMine(Mine, x, y) + '0';
					DisplayBoard(Show, row, col);
					NotMine_count++;
				}
				else
				{
					printf("坐标已排查,请重新输入排查坐标(x y):");
					goto again;
				}
			}
		}
		else
		{
			printf("非法坐标,请重新输入:");
			goto again;
		}
		
	}
	if (NotMine_count == row * col - Mine_num)
	{
		printf("恭喜你排雷成功!\n");
		DisplayBoard(Mine, row, col);
		printf("\n");
	}
}
当布雷的棋盘已经准备好以后,接下来就要进入玩扫雷游戏的阶段,首先是你通过show棋盘选择一个坐标,然后在mine棋盘上进行判断,你选的坐标上是否有雷,如果有雷,你就被炸死了,之后展示出布雷的棋盘mine,游戏结束,回到菜单选项;如果不是雷,则就要在mine棋盘上统计你选的坐标周围8格的雷的总数,然后在show棋盘上在你选择的坐标处替换'*'为该坐标周围雷的数量,但要注意这个数量要用字符的数字,不能是整型的数字,因为棋盘是一个二维的字符数组,不能存储整型元素。那要怎么统计你选择的坐标周围的雷的数量呢?我们包装一个函数用来统计坐标周围的雷的数量,函数命名为AroundMine(),通过这个函数返回坐标周围8格雷的数量。这个函数具体怎么实现放到后面来介绍;当返回 你选择的坐标周围雷的数量后,在show棋盘上展示出来,你就可以进行下一步的坐标选择,如果你一直没有扫到雷,那总会把雷排干净,所以要设置游戏胜利的条件,这要根据你布置雷的数量和棋盘的大小,定义变量NotMine_count用来计数你没有扫到雷的次数(格子数),这里设置的棋盘大小为ROW*COL,布雷的数量为Mine_num(10),那么没有雷的格子有ROW*COL-Mine_num个,当你扫雷的次数达到ROW*COL-Mine_num次,说明你已经把所有的雷区排查出来了,排雷成功,然后展示出布雷的棋盘mine,游戏结束,回到菜单选项;所以上面的while循环的循环条件就为NotMine_count<ROW*COL-Mine_num。但是在输入坐标后还需要完善的一点是,你输入的坐标是不是合法的,也就是行列坐标有没有在棋盘的范围以内,所以要提前进行判断是不是在排查的坐标范围以内。还有一个就是如果你多次排查,选到了以前你选过的坐标,那就是重复排查了,这个问题也需要提前进行判断,所以在上面的代码中有三重if else语句的嵌套。

5.AroundMine()函数的定义及补充:

int AroundMine(char board[ROWS][COLS],int x, int y)
{
	int i = 0;
	int MineNum = 0;
	for (i = x - 1; i <= x + 1; i++)
	{
		int j = 0;
		for (j = y - 1; j <= y + 1; j++)
		{
			if (i == x - 1 || i == x + 1)
				MineNum += board[i][j];
			else if (j == y - 1 || j == y + 1)
				MineNum += board[i][j];
		}
	}
	return (MineNum-8*'0');
}
AroundMine函数是用来统计mine棋盘上坐标为(x,y)的格子周围雷的数量,就是除了(x,y)坐标以外,周围8格的雷的数量都要计算。这里有一个整型与字符进行转换的方法,要把一个字符格式的数字转化为整型数字时,可以用该字符数字减去字符0就得到了整型的数字(如char a = '7',int b = a-'0',则b = 7)。因为字符在计算机中以二进制数存储,字符与整型的四则运算也是通过字符的ASCII值与整型进行运算的。从字符0到字符9的十进制ASCII值是连续递增的,所以要得到整型的阿拉伯数字,可以用字符的数字减去字符0就会得到整型的数字,同理,整型的数字加上字符0就会得到字符的数字(如int a = 7,char b = a+'0',b = '7'),所以AroundMine函数的返回值是整型数字,加上一个字符0就得到了字符的数字,这样就可以知道选择的坐标周围有多少颗雷。当然上面还有更直接的方法,不用for循环,直接return(返回)选择的坐标周围8个格子的和减去8*'0'以后的值即可。玩的棋盘大小是ROW*COL的,为什么要增加一行一列变成ROWS*COLS呢?如果棋盘的大小只是ROW*COL的话,如下图,如果我们访问(2,6)这个坐标时,周围一圈8个黄色位置的雷数为2,当我们访问(8,4)这个坐标时,周围一圈8个黄色位置,统计周围雷的个数时,最下面的三个坐标会越界,所以为了防止越界,设计棋盘时,给数组扩大一圈,雷还是布置在中间的ROW*COL的棋盘中,最外围一圈不需要布雷也不需要展示出来全部初始化为字符0即可,这样就不会出现数组越界的问题了。而且还可以发现,扩大了一圈后的棋盘,中间棋盘的坐标跟真实坐标相对应,没有带0的坐标,就是因为数组扩大了一圈,跳过了第一行和第一列。

原棋盘

c4c68d4020bf450bb40248634b0b8e02.png

周围加上一圈后的棋盘

b705d04d5809468fae9676924df9f0c6.png

标准I码

2bca2a19586a4d0f98cd404d2a6e5c5b.png

以上功能函数的定义统一写在一个.c文件中,文件命名为game.c,当然在以上函数的形参中,数组长度使用了宏定义的内容,所以要在game.c文件的开头中包含game.h的头文件。

三、扫雷的菜单选项

在定义完所有扫雷的功能函数以后,将这些功能函数包装成一个完整的游戏逻辑代码,我们将上面的功能函数放在一个名为game()的函数里,在game函数里对上面的功能函数进行调用,即可进行扫雷游戏:
#include "game.h"
void menu();  //菜单函数的声明
void game();  
int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:");
    again:
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            game();
            break;
        case 0:
            printf("退出游戏!\n");
            break;
        default:
            printf("输入错误,请重新选择:");
            goto again;
        }
    } while (input);
    return 0;
}

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'); //初始化存放雷的棋盘为全'0'
    InitBoard(show, ROWS, COLS, '*'); //初始化展示的棋盘为全'*'
    SetMine(mine, ROW, COL);          //对初始化的存放雷的棋盘进行布雷
    //DisplayBoard(mine, ROW, COL);     //展示已经布置雷的棋盘
    DisplayBoard(show, ROW, COL);     //显示棋盘上没有扫中雷时的周围雷的信息
    ClearMine(mine, show, ROW, COL);  //进行扫雷游戏的函数
}
程序运行起来从main函数进入,跳出菜单选项,选择1以后,调用game函数进行扫雷游戏,如果选择0,则退出游戏;要是不小心输入了其他大于1的数,则需要重新输入。最核心的就是调用game函数以后,就开始扫雷游戏了,在game函数中先是创建了两个二维的字符数组(棋盘),一个是用来存放雷的棋盘,另一个是用来展示给玩家看的棋盘,之后就是对两个棋盘进行初始化,然后对初始化后的存放雷的棋盘进行布雷,然后玩家就可以开始扫雷,玩家输入一个坐标,程序对输入的坐标进行逻辑判断。大概的思路框架就是这样。将上面代码写在一个.c文件中,命名为test.c,就是用来写扫雷游戏的测试逻辑的,上面的代码一开头就包含了game.h这个头文件,在game.h这个头文件中包含了我们声明定义的所有函数类型,就是要在这个test.c文件中进行调用。

四、程序的不足

上面只是一个初步的扫雷程序,还可以对此程序进行扩展,如:

是否可以选择游戏难度

    1.简单 9*9 棋盘,10个雷

    2.中等 16*16棋盘,40个雷

    3.困难 30*16棋盘,99个雷

如果排查位置不是雷,周围也没有雷,可以展开周围的一片

是否可以标记雷

是否可以加上排雷的时间显示

后期会在此文件的后面对上面的内容进行扩充。可能文章中还有许多的不足之处,欢迎小伙伴们评论留言,我会在后期进行文章的修改。
下面是扫雷程序的代码:有需要的小伙伴可以访问此gitee拷贝。

Minesweeper · 俺叫熊二/米饭工厂 - 码云 - 开源中国 (gitee.com)

  • 60
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米饭「」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值