大家好!这篇博客要讲的是,用c语言实现一个简单游戏——扫雷。
实现扫雷的过程,其实与三字棋大同小异。也许这对初学者来说,仍是一个工程量较大的课题,但我们不妨参照上一个游戏三子棋,先弄清楚扫雷是怎样一个游戏,然后再把相关信息整理成一条流程,依照流程来开展课题。
(参照详见【c语言】保姆级教程实现小游戏 —— 三字棋)
所用知识:函数、数组、选择结构语句,循环结构语句等。
所用编译器:VS 2019
一、扫雷游戏怎么玩
由图,
开局,系统会布置一个“雷区”,并展示给玩家看。每一次“扫雷”,就是点击相应的方格。
若玩家点击的方格里没有埋雷,方格处就会显示它周围一圈方格(去心的3x3九宫格)的雷的数量,而当周围一圈方格里都没有埋雷,系统就会对更大的无雷范围进行清扫;
若玩家点击的方格里埋了雷,则游戏失败。
由上,我们可以得到以下信息:
游戏开始,系统会布置好一个雷区并展示给玩家
玩家排雷的方式是点击雷区内某一方格
玩家点击某一方格后,系统会判定此处是否有雷
无雷则游戏继续,有雷则游戏结束
排雷直到整个雷区清空,则玩家胜利,游戏结束
而稍作整理,我们不难得到以下流程:
创建和初始化雷区
布置好雷区并展示给玩家看
玩家扫雷
下面,我们分别来看如何具体实现。
二、扫雷游戏的实现
0.准备工作
参照三子棋,我们仍然用创建一个test函数来实现游戏刚开始的流程,并在游戏开始时,打印一个游戏菜单,以便给玩家操作提示。我们创建一个menu函数来实现菜单。
void menu()
{
printf("**********************\n");
printf("****** 1.play ******\n");
printf("****** 0.exit ******\n");
printf("**********************\n");
}
void test()
{
int input = 0;
do {
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("开始游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
当玩家依照提示输入“1”,则进入游戏。我们创建一个game函数来实现游戏的流程。
1.创建和初始化雷区
不难想到,我们其实是需要两个雷区的,一个用来真正地布置雷,而另一个表面上没有雷。玩家在展示的雷区中选择扫雷的位置,而对扫雷的判断只需要核对另一个雷区的相应位置上是否有雷。
我们分别创建两个相同大小的9x9的二维数组当作雷区,并规定:在二维数组中字符“ * ”代表待排区域,字符“ 0 ”代表非雷,字符“ 1 ”代表雷。
//宏定义雷区(二维数组)大小,以便后续按需修改
#define ROW 9
#define COL 9
//以下宏定义以防数组越界,原因见后
#define ROWS ROW+2
#define COLS COL+2
char mine[ROWS][COLS] = { 0 }; //真正布置雷的雷区
char show[ROWS][COLS] = { 0 }; //表面上没有雷的雷区
我们创建一个Init_board函数来对两个雷区进行初始化。
void Init_board(char arr[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
//返回类型为void
//参数为雷区(二维数组)、预设的行、预设的列、初始化的内容
2.布置好雷区并展示给玩家看
我们创建一个set_mine函数来布置雷区。默认雷区中一共有十颗雷。
#define mine_count 10//宏定义雷的数量,以便后续按需修改
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = mine_count;
int x = 0;int y = 0;
while (count)
{
x = rand() % row + 1;//用rand函数生成随机数(详解见文首三子棋博客内)
y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//返回类型为void
//参数为雷区(二维数组)、预设的行、预设的列
然后,我们创建一个print__board函数来打印雷区。
void print_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;int j = 0;
int a = 0;
for (a = 0; a <= col; a++)
{
printf("%d ", a);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%C ", arr[i][j]);
}
printf("\n");
}
}
//返回类型为void
//参数为雷区(二维数组)、预设的行、预设的列
3.玩家扫雷
玩家扫雷的过程又可以细分为:
玩家输入要在雷区中扫雷的坐标;
系统判断坐标处是否有雷;
有雷则游戏结束;无雷,则显示,周围清空=雷的数量。
我们创建一个find_mine函数来实现扫雷。同时,创建一个mine_count函数来计算雷的数量,再创建一个spread_mine函数用来对无雷范围进行清扫,并在find_mine函数内部调用。
int mine_count(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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
}
//返回类型为int,便于下面代码中的count变量接收结果
//参数为雷区(二维数组)、所排查坐标的行、所排查坐标的列
void spread_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int n = mine_count(mine, x, y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y] == '*')
{
if (n == 0)
{
show[x][y] = '0';
spread_mine(mine, show, x - 1, y);
spread_mine(mine, show, x - 1, y - 1);
spread_mine(mine, show, x, y - 1);
spread_mine(mine, show, x + 1, y - 1);
spread_mine(mine, show, x + 1, y);
spread_mine(mine, show, x + 1, y + 1);
spread_mine(mine, show, x, y + 1);
spread_mine(mine, show, x - 1, y + 1);
}
else
{
show[x][y] = n + '0';
}
}
}
}
//返回类型为void
//参数为真正布置雷的雷区(二维数组)、表面上没有雷的雷区(二维数组)、所排查坐标的行、所排查坐标的列
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_COUNT))
{
printf("请输入扫雷的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该坐标非法,请重新输入!\n");
continue;
}
if (mine[x][y] == '0')
{
spread_mine(mine, show, x, y);
int n = mine_count(mine, x, y);
show[x][y] = n + '0';
print_board(show, ROW, COL);
win++;
}
else
{
printf("雷爆炸了,你输了!\n");
print_board(mine, ROW, COL);
break;
}
}
else
{
printf("该坐标非法,请重新输入!\n");
}
}
if (win == (row * col - MINE_COUNT))
{
printf("恭喜你!扫雷成功!\n");
}
}
//返回类型为void
//参数为真正布置雷的雷区(二维数组)、表面上没有雷的雷区(二维数组)、预设的行、预设的列
三、代码汇总
test.c文件 - 存放游戏的主体代码
#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 };//表面上没有雷的雷区
//初始化真正布置雷的雷区
Init_board(mine, ROWS, COLS, '0');
//初始化表面上没有雷的雷区
Init_board(show, ROWS, COLS, '*');
//布置雷区
set_mine(mine, ROW, COL);
//打印雷区
print_board(show, ROW, COL);
//扫雷
find_mine(mine, show, ROW, COL);
}
//游戏开始时的必要流程的实现
void test()
{
int input = 0;
do {
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
printf("开始游戏\n");
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
}
} while (input);
}
int main()
{
test();
return 0;
}
game.h文件 - 存放所有头文件、宏定义和函数的声明
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE_COUNT 10
//初始化雷区
void Init_board(char arr[ROWS][COLS], int rows, int cols, char set);
//打印雷区
void print_board(char arr[ROWS][COLS], int row, int col);
//布置雷区
void set_mine(char mine[ROWS][COLS], int row, int col);
//扫雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c文件 - 存放所有函数的本体
#include"game.h"
//初始化雷区
void Init_board(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
//打印雷区
void print_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
int a = 0;
for (a = 0; a <= col; a++)
{
printf("%d ", a);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%C ", arr[i][j]);
}
printf("\n");
}
}
//布置雷区
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = MINE_COUNT;
int x = 0; int y = 0;
while (count)
{
x = rand() % row + 1;
y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
//计数雷
int mine_count(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][y + 1] + mine[x + 1][y - 1] +
mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0');
}
//清空区域
void spread_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int n = mine_count(mine, x, y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
{
if (show[x][y] == '*')
{
if (n == 0)
{
show[x][y] = '0';
spread_mine(mine, show, x - 1, y);
spread_mine(mine, show, x - 1, y - 1);
spread_mine(mine, show, x, y - 1);
spread_mine(mine, show, x + 1, y - 1);
spread_mine(mine, show, x + 1, y);
spread_mine(mine, show, x + 1, y + 1);
spread_mine(mine, show, x, y + 1);
spread_mine(mine, show, x - 1, y + 1);
}
else
{
show[x][y] = n + '0';
}
}
}
}
//扫雷
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_COUNT))
{
printf("请输入扫雷的坐标:");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (show[x][y] != '*')
{
printf("该坐标非法,请重新输入!\n");
continue;
}
if (mine[x][y] == '0')
{
spread_mine(mine, show, x, y);
int n = mine_count(mine, x, y);
show[x][y] = n + '0';
print_board(show, ROW, COL);
win++;
}
else
{
printf("雷爆炸了,你输了!\n");
print_board(mine, ROW, COL);
break;
}
}
else
{
printf("该坐标非法,请重新输入!\n");
}
}
if (win == (row * col - MINE_COUNT))
{
printf("恭喜你!扫雷成功!\n");
}
}
四、运行效果
以上。