丐中丐版扫雷游戏

提示:这是一个基于c语言编写的丐中丐版扫雷游戏。


前言

首先来了解一下扫雷这个游戏

《扫雷》是一款大众类的益智小游戏,于1992年发行。如下图所示,游戏目标是根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷就输掉游戏。
扫雷游戏
简单了解了扫雷游戏长什么样子,现在我们来观察一下其构成。
首先,扫雷游戏是一个正方形的棋盘,这里的简单模式所展示的是一个 9 × 9 大小的棋盘,并且点击一个格子后,会显示周围地雷的个数。由此我们来编写其程序。


以下是本篇文章正文内容

一、扫雷游戏的菜单显示与选择

创建一个源文件test.c
上来先来一段主函数,将代表测试运行的函数任意命名,这里命名的为“test”。

int main()
{
	test();//
	return 0;
}

既然有了函数名,那就继续编写这个函数,因为这个函数不需要返回值,这里使用 void
然后接着就开始写这个扫雷游戏。
任何游戏刚开始都是需要一个开始菜单的,所以我们先写一段开始菜单的内容。
于是我们写一个开始菜单的函数,命名为“menu()”。

menu()
{
	printf("******************\n");
	printf("*****  扫雷  *****\n");
	printf("***** 1.play *****\n");
	printf("*****        *****\n");
	printf("***** 0.exit *****\n");
	printf("******************\n");
	printf("输入数字开始游戏:>");
}

这里有菜单的显示的名字(扫雷),以及游玩(1.play)、退出(0.exit)的选项,然后还要告诉玩家输入选项开头的数字才能开始游戏。
接着我们将菜单 menu()函数放进负责测试运行的test()函数里,这里我们发现,我们需要菜单一开始就打印在屏幕上,并且要在玩家选择后重新显示菜单,所以这里使用了do…while循环语句。
紧接着玩家使用scanf输入数字才能开始游戏,简单命名输入的值叫“input”。

void test()
{
	int input = 0;
	do 
	{
		menu();
		scanf("%d", &input);
	} while ();
}

然后展现出来的是这样:
扫雷菜单

我们需要考虑输入的值为1或者为0,如果为1进入游戏,如果为0,结束游戏,
所以我们需要一个分支语句,这里使用switch()分支语句刚好合适:

  • 给一个case 1:进入游戏,这里就需要一个运行游戏的函数,命名其为“game()”。
  • 给一个case 0:退出游戏,并且在退出游戏时可以结束这段循环,那就将do...while 中的判断条件改成 input
  • 我们还需要考虑玩家不按常理出牌,乱输入值,所以switch()语句中给上一个default,告诉玩家不要调皮。

这样实现后我们发现,这会在菜单上一直显示刚才显示过的内容,所以我们加入一个system("cls"),用于清除屏幕。

void test()
{
	int input = 0;
	do 
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//开始游玩
			game();
			break;
		case 0:
			system("cls");
			printf("退出游戏\n");
			break;
		default :
			system("cls");
			printf("输入错误\n");
			break;
		}
	} while (input);
}

输入0:
输入0
退出游戏
调皮乱输:
调皮
输入错误

以上库函数需要头文件:

  • #include <stdio.h>

接下来就进入编写游戏的环节了。

二、扫雷游戏的内部结构

1.game函数

在写game()函数之前,我们需要知道,这个扫雷游戏该怎么显示出来。
首先,这就是个 9 × 9 的棋盘,那么我们就需要打印一个这么大小的棋盘出来。
那么棋盘中又要分为两个部分:

  • 一是给玩家展示的外部棋盘,这里用“ * ”来表示,能和菜单界面统一样式。
  • 二是外部棋盘下面隐藏地雷的棋盘,我们用 ‘1’ 来表示雷,用 ‘0’ 来表示安全的格子。

接着,玩家点击棋盘的格子后,需要知道这个格子的 3 × 3 范围内是否有雷。那么,在我们定义棋盘的时候,就需要让真正的棋盘比显示出来的棋盘大一圈,为什么要这样做呢?
因为,如果当玩家选择了棋盘最边缘的格子,那么在游戏检测这个格子周围 3 × 3 范围内是否有雷时,会扫描到棋盘之外,那么就越界了,可能扫描的不准确,扫描出来的是随机值。
有了以上的思路之后就可先来定义这个棋盘的大小了。
首先我们需要真实的棋盘的行和列的大小,这里用小写xy来表示。

#define x 11
#define y 11

接着我们需要显示给玩家的棋盘大小,这里用大写XY来表示。

#define X 9
#define Y 9

紧接着我们在game()函数中定义这个真实棋盘的值:

  • 这里创建一个game数组,用于充当布置地雷的棋盘,它需要隐藏地雷。
  • 然后创建一个show数组,用于展示整个棋盘的具体内容,它用于显示地雷。
void game()
{
	char game[x][y] = { 0 };
	char show[x][y] = { 0 };
}	

现在的代码似乎有些长了,看起来有些复杂麻烦,为了逻辑清晰,就需要使代码模块化,于是再创建一个源文件game,c吧。

2.初始化棋盘

我们需要模块化的代码,那么game.c文件中的代码就可以专门分割出来,用于实现game()函数中的内容。
插入一个头文件
这个时候我们发现,现在有两个源文件,两个源文件的库函数都需要同样的头文件,那么我们再创建一个头文件的外部文件,将所有的头文件都集中在一起,这样两个源文件中使用头文件时就不需要长篇大论了。
将这个头文件命名为game.h

#pragma once

#include <stdio.h>

#define X 9
#define Y 9
#define x 11
#define y 11

这时候将test.cgame.c文件中的头文件都改成game.h就好了。

#include "game.h"

我们刚才定义了game和show数组,那么接下来就是准备初始化把这两个数组的内容。
所以我们需要写一个函数,将其命名为i_pan
这里的“i”是MBTI中i人的意思,用来代表内部真实棋盘。
首先我们需要定义这个函数i_pan
于是在game.h头文件中先定义这个函数。

void i_pan(char pan[x][y], int a ,int b, char set);

紧接着在game.c文件中编写其内容。

void i_pan(char pan[x][y], int a ,int b, char set)
{
	for (int i = 0;i < a; i++)
	{
		for (int j = 0;j < b; j++)
		{
			pan[i][j] = set;
		}
	}
}

然后我们需要在test.c文件中调用这个函数,再在game()函数中将参数传给i_pan函数。
我们先将布置地雷的game数组中塞满 ‘0’,在将展示给玩家的棋盘中塞满 ‘*’。

	i_pan(game, x, y, '0');
	i_pan(show, x, y, '*');

3.打印棋盘

然后我们将其打印出来。定义一个p_pan函数用于打印棋盘。同i_pan函数一样。
这里的“p”是MBTI中p人的意思,用来代表外显的棋盘。

void p_pan(char pan[x][y], int a, int b);

因为这里打印出的棋盘是给玩家看的,所以我们只需要在11× 11的真实棋盘中打印出9 × 9 大小的棋盘,行列1~9。
并且我们需要在棋盘上告诉玩家,有几行几列,于是棋盘的第一行第一列作为棋盘格子的序号打印出来。

	printf("--------扫雷-------\n");
	for (int i = 0;i <= b; i++)
	{
		printf("%d|", i);
	}
	printf("\n");

再将9 × 9的棋盘打印出来:

	for (int i = 1;i <= a;i++)
	{
		printf("%d|", i);
		for (int j = 1;j <= b;j++)
		{
			printf("%c|", pan[i][j]);
		}
		printf("\n");
	}
	printf("\n");

最后需要展示一次棋盘就将屏幕清除一次,于是使用system("cls")呈现为:

void p_pan(char pan[x][y], int a, int b)
{
	system("cls");
	printf("--------扫雷-------\n");
	for (int i = 0;i <= b; i++)
	{
		printf("%d|", i);
	}
	printf("\n");

	for (int i = 1;i <= a;i++)
	{
		printf("%d|", i);
		for (int j = 1;j <= b;j++)
		{
			printf("%c|", pan[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

然后在game()函数中传参给p_pan函数:

	p_pan(game, X, Y);
	p_pan(show, X, Y);

注意这里使用的参数是大写的XY。
最后展现出这样:
内部
外部

4.布置地雷

怎么布置地雷呢?首先我们要h知道布置几个地雷,初级扫雷是10个地雷,那么我们就布置10个。那么就定义一个easy_boom,赋值10。

#define easy_boom 10

接着怎么让地雷随机出现在棋盘上呢,那这里就要使用了随机值。
首先我们来定义一个函数set_boom,用来表示布置地雷。

void set_boom(char game[x][y], int a, int b);

接着我们让这个函数内部使用随机值来布置地雷,这里用AB来表示布置地雷的game数组中的行列,只要检测到这个坐标不为地雷,就布置一个地雷。这里用‘1’来表示地雷。当布置了10个地雷,就跳出循环进入下一步。

void set_boom(char game[x][y], int a, int b)
{
	int c = easy_boom;
	int A = 0;
	int B = 0;
	while (c)
	{
		A = rand() % a + 1;
		B = rand() % b + 1;
		if (game[A][B] != '1')
		{
			game[A][B] = '1';
			c--;
		}
	}
}

然后我们用了rand库函数,需要配合scand库函数来完成随机的效果。

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

这里需要头文件stdlib.htime.h
这里我们用p_pan(game, X, Y);;检查一下布置的效果。

set_boom(game, X, Y);
p_pan(game, X, Y);

运行一下:
布置10个地雷
可以清楚看到一共有 10个 地雷在棋盘中。
那么下一步就可以扫雷了。

5.找到地雷

怎么找到地雷呢?我们发现,当玩家选择一个格子时,我们需要检测这个格子周围 3 × 3 的范围,而这8个格子各自的坐标都与中间格的坐标至少有一个不同,并且相差1格。

x-1,y-1   |x,y-1  |  x+1,y-1 |
x-1,y     |x,y    |  x+1,y   |
x-1,y+1   |x,y+1  |  x+1,y+1 |

于是我们可以用分支语句来判断是否踩到地雷了。
game[][]数组的值是‘1’时就是踩到地雷,然后告诉玩家 炸了 。如果不是就判断周围3 ×3范围内有几个地雷。

if (game[A][B] == '1')
{
	p_pan(game, X, Y);
	printf("***** BOOM!!!! *****\n");
	printf("\n");
	break;
}

那么怎么判断3 × 3范围内有几个雷呢?
这里我们知道‘1’的ASCII码值是49,而‘0’48,那么我们就可以用‘1’-‘0’来表示1(49-48==1)。
这样我们就可以在九宫格内循环判断有多少个 1 ,并将其相加起来,最后在中间格中显示周围有多少个地雷。
那我们就需要先写一个判断地雷的个数的函数booms(),这里需要其返回值,所以用int类型。

int booms(char game[x][y], int a, int b)
{
	int z = 0;
	for (int i = -1;i <= 1;i++)
	{
		for (int j = -1;j <= 1;j++)
		{
			z = z + (game[a + i][b + j] - '0');
		}
	}
	return z;
}

然后我们将其返回值赋给 H ,让其通过show[][]数组展示出有多少地雷,然后打印出来。

else
{
	int H = booms(game, A, B);
	show[A][B] = H + '0';
	p_pan(show, X, Y);
}

注意这里 H是数字,需要加上字符‘0’,转换成对于的字符。

这里可以找地雷了,那当我们找完地雷,怎么判断通关呢?
那我们就可以用整体的格子数量减去布置地雷的数量,最后这个差值就是判断过关的条件。
那么就定义一个数,让其每找到一个非地雷格子时就+1,直到其值等于这个差值。

	if (C == X * Y - easy_boom)
	{
		printf("过关!\n");
		p_pan(game, X, Y);
	}

ok,现在我们可以判断是否过关了。那么我们需要玩家输入坐标来游玩游戏,并且判断玩家输入的坐标是否在棋盘内。
首先定义一个找地雷的函数find_boom

void find_boom(char game[x][y],char show[x][y] ,int a, int b);

然后将上诉找地雷的代码结合起来:

void find_boom(char game[x][y],char show[x][y], int a, int b)
{
	int A = 0;
	int B = 0;
	int C = 0;
	while (C < a * b - easy_boom)
	{
		printf("输入坐标排雷:>");
		scanf("%d %d", &A, &B);
		if (A >= 1 && A <= 9 && B >= 1 && B <= 9)
		{
			if (game[A][B] == '1')
			{
				p_pan(game, X, Y);
				printf("***** BOOM!!!! *****\n");
				printf("\n");
				break;
			}
			else
			{
				int H = booms(game, A, B);
				show[A][B] = H + '0';
				p_pan(show, X, Y);
				C++;
			}
		}
		else
		{
			printf("输入错误!\n");
		}

	}
	if (C == X * Y - easy_boom)
	{
		printf("过关!\n");
		p_pan(game, X, Y);
	}
}

最后,将game函数中的值传参给find_boom

恭喜你,我们完成了整个丐中丐版扫雷的编写!!快去运行看看吧!!

展示整体代码

game.h

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

#define X 9
#define Y 9
#define x 11
#define y 11
#define easy_boom 10

void i_pan(char pan[x][y], int a ,int b, char set);
void p_pan(char pan[x][y], int a, int b);
void set_boom(char game[x][y], int a, int b);
void find_boom(char game[x][y],char show[x][y] ,int a, int b);

game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

void i_pan(char pan[x][y], int a ,int b, char set)
{
	for (int i = 0;i < a; i++)
	{
		for (int j = 0;j < b; j++)
		{
			pan[i][j] = set;
		}
	}
}

void p_pan(char pan[x][y], int a, int b)
{
	system("cls");
	printf("--------扫雷-------\n");
	for (int i = 0;i <= b; i++)
	{
		printf("%d|", i);
	}
	printf("\n");

	for (int i = 1;i <= a;i++)
	{
		printf("%d|", i);
		for (int j = 1;j <= b;j++)
		{
			printf("%c|", pan[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

void set_boom(char game[x][y], int a, int b)
{
	int c = easy_boom;
	int A = 0;
	int B = 0;
	while (c)
	{
		A = rand() % a + 1;
		B = rand() % b + 1;
		if (game[A][B] != '1')
		{
			game[A][B] = '1';
			c--;
		}
	}
}

int booms(char game[x][y], int a, int b)
{
	int z = 0;
	for (int i = -1;i <= 1;i++)
	{
		for (int j = -1;j <= 1;j++)
		{
			z = z + (game[a + i][b + j] - '0');
		}
	}
	return z;
}

void find_boom(char game[x][y],char show[x][y], int a, int b)
{
	int A = 0;
	int B = 0;
	int C = 0;
	while (C < a * b - easy_boom)
	{
		printf("输入坐标排雷:>");
		scanf("%d %d", &A, &B);
		if (A >= 1 && A <= 9 && B >= 1 && B <= 9)
		{
			if (game[A][B] == '1')
			{
				p_pan(game, X, Y);
				printf("***** BOOM!!!! *****\n");
				printf("\n");
				break;
			}
			else
			{
				int H = booms(game, A, B);
				show[A][B] = H + '0';
				p_pan(show, X, Y);
				C++;
			}
		}
		else
		{
			printf("输入错误!\n");
		}

	}
	if (C == X * Y - easy_boom)
	{
		printf("过关!\n");
		p_pan(game, X, Y);
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"

menu()
{
	printf("******************\n");
	printf("*****  扫雷  *****\n");
	printf("***** 1.play *****\n");
	printf("*****        *****\n");
	printf("***** 0.exit *****\n");
	printf("******************\n");
	printf("输入数字:>");
}

void game()
{

	char game[x][y] = { 0 };
	char show[x][y] = { 0 };

	i_pan(game, x, y, '0');
	i_pan(show, x, y, '*');

	//p_pan(game, X, Y);
	p_pan(show, X, Y);

	set_boom(game, X, Y);
	p_pan(game, X, Y);

	find_boom(game, show, X, Y);

}	
	

void test()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do 
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			//开始游玩
			game();
			break;
		case 0:
			system("cls");
			printf("退出游戏\n");
			break;
		default :
			system("cls");
			printf("输入错误\n");
			break;
		}
	} while (input);
}

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

  • 16
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值