提示:这是一个基于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:
调皮乱输:
以上库函数需要头文件:
- #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.c
和game.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.h
和time.h
。
这里我们用p_pan(game, X, Y);
;检查一下布置的效果。
set_boom(game, X, Y);
p_pan(game, X, Y);
运行一下:
可以清楚看到一共有 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;
}