欢迎光临
我们一直在努力

扫雷游戏 模拟实现

在了解了数组与函数相关知识后,我们可以运用这些知识,来模拟实现扫雷游戏。


1 · 扫雷游戏分析

1 – 1 · 扫雷游戏功能说明

使用控制台实现扫雷游戏

游戏可以通过菜单实现继续玩或者退出游戏

扫雷的棋盘是9*9的格子,可调整

默认随机布置10个雷,可调整

可以排查雷:

如果位置不是雷,就显示周围有几个雷

并且如果该位置周围没有雷,将持续向周围开辟,直到排查到显示周围有几个雷数字的地块。

如果位置是雷,就炸死游戏结束

把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束


1 – 2 · 游戏分析

扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。

因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们首先想到的就是创建⼀个9*9的数组来存放信息。

那如果这个位置布置雷,我们就存放1,没有布置雷就存放0。

如果这样设计,我们就可以很轻松的得出某个地块周围雷的个数,只需要将周围一圈的数据相加便是雷的个数。

但是随之而来的还有一个问题,在9 x 9 棋盘靠中心的位置可以这样做,但是一旦我们排查最边缘一圈的地块,我们用这样的方法计算周围雷的个数,显然是越界了的。

因此,在设计的时候给数组扩⼤⼀圈,雷还是布置在中间的9*9的坐标上,周围⼀圈不去布置雷就行,这样就解决了越界的问题。所以我们将存放数据的数组创建成11*11比较合适。

再继续分析

我们在棋盘上布置了雷,棋盘上雷的信息(1)和非雷的信息(0),假设我们排查了某⼀个位置后,这个坐标处不是雷,这个坐标的周围有1个雷,那我们需要将排查出的雷的数量信息记录存储,并打印出来,作为排雷的重要参考信息的。那这个雷的个数信息存放在哪里呢?如果存放在布

置雷的数组中,这样雷的信息和雷的个数信息就可能或产⽣混淆和打印上的困难。

针对这个问题

我们可以创建两个二维数组,一个用来展示,一个用来存雷和判断周围雷的个数,为了保持神秘感,可以将展示的数组全部赋值一个符号 * 。


2 · 游戏实现

接下来,我将一步步实现扫雷游戏


2 – 1 · 文件格式

上一篇文章我们提到了:多文件的形式对函数的声明和定义,这⾥我们实践⼀下,我们设计三个文件:

MineSweeping.h

用于存放函数的声明与使用库函数所需头文件的包含

MineSweeping.c

用于定义头文件中声明的函数

game.c

用于启动游戏


2 – 2 · MineSweeping.h

我们先看看我们的头文件,然后再逐个讲解定义的函数

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

#define ROW 9//游戏实际操作的行数
#define COL 9//游戏实际操作的列数

#define ROWS ROW+2//总的行数,防止计算排雷数字越界
#define COLS COL+2//总的列数,防止计算排雷数字越界

#define MINE 10//游戏总雷数

void Menu();

void CreatBoard(char arr[ROWS][COLS],int row,int col,char set);//棋盘初始化

void ShowBoard(char arr[ROWS][COLS]);//展示游戏界面

void SetMine(char arr[ROWS][COLS], int mine);//设置雷

void MineSweeping(char mine[ROWS][COLS], char show[ROWS][COLS]);//扫雷
char ScanMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col);//扫描给定坐标周围的雷

//如果查找到周围无雷,开辟至出现显示周围有雷
void SearchExpand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

void SetSign(char mine[ROWS][COLS], char show[ROWS][COLS]);//设置标记

void DelSign(char mine[ROWS][COLS], char show[ROWS][COLS]);//删除标记

我们在自定义头文件包含了一些库函数所需的头文件,这样的话,我们其余两个文件仅需包含我们这个头文件即可。

随后我们使用了 #define 

#define A B

我们这里是用 #define 定义符号(常量),功能是将代码中的A 替换为 B

这样写方便我们对游戏难度做出修改,比如改变行数,改变列数,改变雷数,正常我们需要去代码中一个个修改值,而现在我们只需要在头文件的 #define 这里修改即可。

接下来是一个个函数


2 – 3 · Menu

void Menu()
{
printf("****** 扫雷游戏 ******\\n");
printf("**********************\\n");
printf("**** 1 : start ******\\n");
printf("**** 0 : exit ******\\n");
printf("**********************\\n");
}

对开始的菜单的打印,与我们之前写的猜数字游戏类似。


2 – 4 · CreatBoard

void CreatBoard(char arr[ROWS][COLS],int row,int col,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = set;
}
}
}

用于初始化棋盘,由于我们要创建两个数组,一个一个创建会出现重复代码,那么我们就可以封装一个函数用于初始化,初始化只需要调用该函数。


2 – 5 · ShowBoard

void ShowBoard(char arr[ROWS][COLS])
{
int i = 0;
int j = 0;

//打印
printf("——扫雷游戏—–\\n");
//打印列标
for (i = 0; i <= ROW; 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");
}
}

首先我们打印了标题,为了美观。

随后,为了更方便的让玩家进行排查地块,我们需要将行号与列号打印出来

效果如下:


2 – 6 · SetMine

void SetMine(char arr[ROWS][COLS], int mine)
{
int row = 0;
int col = 0;

//设置雷
while(mine)
{
row = rand() % ROW + 1;//生成雷的行号,1~9
col = rand() % COL + 1;//生成雷的列号,1~9
if (arr[row][col] == '0')
{
arr[row][col] = '1';
mine–;
}
}
}

我们在猜数字游戏那篇中提到了使用 rand 生成随机数,我们随机在 1 ~ 9 数字中分别生成一个随机数,用来放雷,当然,放雷的前提是该位置不是雷,否则会出现在同一个位置重复放雷而导致雷数少了的情况。

我们的Srand只需要使用一次,我们在game.c文件中使用即可。


2 – 7 · ScanMine

char ScanMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
int x = 0;
int y = 0;

char sum = 0;
for (x = row – 1; x <= row + 1; x++)
{
for (y = col – 1; y <= col + 1; y++)
{
sum += mine[x][y] – '0';
}
}
return sum;
}

用于扫描给定地块周围一圈雷的个数,并返回雷的个数的数值。


2 – 8 · SearchExpand

void SearchExpand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
char sum = 0;

for (i = row – 1; i <= row + 1; i++)
{
for (j = col – 1; j <= col + 1; j++)
{
//检查坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '*')
{
//分两种情况,扩展到0和扩展到非0
sum = ScanMine(mine, show, i, j);
show[i][j] = sum + '0';
sum = 0;
//只需考虑扩展到0情况
if (show[i][j] == '0')
{
SearchExpand(mine, show, i, j);
}
}
}
}
}

用于当我们探查到了一个周围无雷的地块,我们向周围开辟,直到探查到周围有雷的地块,也就是显示了数字,节省玩家时间。

效果如下:

我们只输入了 1 1 进行对 坐标 (1,1)地块的排查。


2 – 9 · MineSweeping

void MineSweeping(char mine[ROWS][COLS], char show[ROWS][COLS])
{
int i = 0;
int j = 0;
char sum = 0;
int count = ROW * COL;//胜利条件,当棋盘上*个数 与#个数之和与总雷数相等
int exit = ROW + 10;

while (count – MINE)
{
ShowBoard(show);
/*ShowBoard(mine);*/
printf("如果想要退出排查模式,请输入%d %d\\n", exit, exit);
printf("请输入要排查的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//确认坐标合法性
if (i == exit && j == exit)
{
break;
}
else if (1 <= i && i <= ROW && 1 <= j && j <= COL)
{
//开始排查
//踩到雷了
if (mine[i][j] == '1')
{
printf("很遗憾,踩到雷了,游戏结束\\n");
printf("雷图如下\\n");
ShowBoard(mine);
printf("\\n");
break;
}
else
{
//计算周围雷的个数
sum = ScanMine(mine, show, i, j);
//显示周围雷个数的数字
show[i][j] = sum + '0';
sum = 0;
if (show[i][j] == '0')
{
SearchExpand(mine, show, i, j);
}

//判断场上剩余的*
count = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (show[i][j] == '*' || show[i][j] == '#')
{
count++;
}
}
}
}
}
else
{
printf("输入的坐标有误,请重新输入\\n");
}
}
//胜利
if (count – MINE == 0)
{
ShowBoard(show);
printf("恭喜,游戏胜利\\n");
}
}

实现探查功能,游戏主要功能的实现。

对于显示周围雷个数的数字,由于我们的 show 数组放的是字符 * 是char 类型,所以如果想要得到char 类型的数字,最简单快速的办法就是加上一个字符0 即 '0'。

对于胜利条件,当场上 未知地块与标记地块之和  等于总雷数,即为胜利

system("cls") 功能是清除打印在屏幕上的信息

使用需要包含头文件 stdlib.h

这里为了美观而使用

退出排查模式是因为我们准备用 switch来进入 排查,标记,删除标记这三个功能,为了玩家体验,排查模式会持续进行,手动退出。


2 – 10 · SetSign

void SetSign(char mine[ROWS][COLS], char show[ROWS][COLS])
{
ShowBoard(show);
int i = 0;
int j = 0;
printf("请输入要标记的地块的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//判断坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '*')
{
show[i][j] = '#';
}
else
{
printf("坐标非法,可能选择了无效地块或已探查地块,请重新选择\\n");
}
}

用于设置标记。

system("cls") 功能是清除打印在屏幕上的信息


2 – 11 · DelSign

void DelSign(char mine[ROWS][COLS], char show[ROWS][COLS])
{
ShowBoard(show);
int i = 0;
int j = 0;
printf("请输入要删除的标记的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//判断坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '#')
{
show[i][j] = '*';
}
else
{
printf("坐标非法,可能选择了无效地块或已探查过的坐标\\n");
}
}

用于删除设置的标记

system("cls") 功能是清除打印在屏幕上的信息


2 – 12 · Game

void Game()
{
//初始化棋盘
char show[ROWS][COLS] = { 0 };//游戏实际的棋盘
char boom[ROWS][COLS] = { 0 };//用来计算数字的棋盘
CreatBoard(show,ROWS,COLS,'*');
CreatBoard(boom, ROWS, COLS, '0');

//设置雷
SetMine(boom, MINE);

//开始游戏
int input = 0;
do
{
//展示棋盘
ShowBoard(show);

//用switch,来进入排查,标记,删除标记三个模式
printf("1:进行持续排查 2:进行一次标记 \\n3:进行一次删除标记 0:退出本局\\n");
printf("请选择你想要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
MineSweeping(boom, show);
break;
case 2:
system("cls");
SetSign(boom, show);
break;
case 3:
system("cls");
DelSign(boom, show);
break;
case 0:
system("cls");
printf("成功退出本局游戏\\n");
printf("\\n");
break;
default:
system("cls");
printf("输入有误,请重新选择\\n");
printf("\\n");
break;
}
} while (input);

}

游戏的实现。


2 – 13 · main

int main()
{
int input = 0;
srand((unsigned int)time(NULL));

do
{
Menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
Game();
break;
case 0:
printf("成功退出游戏");
break;
default:
printf("选择错误,请重新选择\\n");
break;
}
} while (input);

return 0;
}

与之前猜数字类似。

Srand 只需使用一次即可。


3 · 扫雷游戏代码展示

3 – 1 · MineSweeping.h

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

#define ROW 9//游戏实际操作的行数
#define COL 9//游戏实际操作的列数

#define ROWS ROW+2//总的行数,防止计算排雷数字越界
#define COLS COL+2//总的列数,防止计算排雷数字越界

#define MINE 10//游戏总雷数

void Menu();

void CreatBoard(char arr[ROWS][COLS],int row,int col,char set);//棋盘初始化

void ShowBoard(char arr[ROWS][COLS]);//展示游戏界面

void SetMine(char arr[ROWS][COLS], int mine);//设置雷

void MineSweeping(char mine[ROWS][COLS], char show[ROWS][COLS]);//扫雷
char ScanMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col);//扫描给定坐标周围的雷

//如果查找到周围无雷,开辟至出现显示周围有雷
void SearchExpand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

void SetSign(char mine[ROWS][COLS], char show[ROWS][COLS]);//设置标记

void DelSign(char mine[ROWS][COLS], char show[ROWS][COLS]);//删除标记


3 – 2 · MineSweeping.c

#include "MineSweeping.h"

void Menu()
{
printf("****** 扫雷游戏 ******\\n");
printf("**********************\\n");
printf("**** 1 : start ******\\n");
printf("**** 0 : exit ******\\n");
printf("**********************\\n");
}

void CreatBoard(char arr[ROWS][COLS],int row,int col,char set)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = set;
}
}
}

void ShowBoard(char arr[ROWS][COLS])
{
int i = 0;
int j = 0;

//打印
printf("——扫雷游戏—–\\n");
//打印列标
for (i = 0; i <= ROW; 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");
}
}

void SetMine(char arr[ROWS][COLS], int mine)
{
int row = 0;
int col = 0;

//设置雷
while(mine)
{
row = rand() % ROW + 1;//生成雷的行号,1~9
col = rand() % COL + 1;//生成雷的列号,1~9
if (arr[row][col] == '0')
{
arr[row][col] = '1';
mine–;
}
}
}

char ScanMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
int x = 0;
int y = 0;

char sum = 0;
for (x = row – 1; x <= row + 1; x++)
{
for (y = col – 1; y <= col + 1; y++)
{
sum += mine[x][y] – '0';
}
}
return sum;
}

void SearchExpand(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
char sum = 0;

for (i = row – 1; i <= row + 1; i++)
{
for (j = col – 1; j <= col + 1; j++)
{
//检查坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '*')
{
//分两种情况,扩展到0和扩展到非0
sum = ScanMine(mine, show, i, j);
show[i][j] = sum + '0';
sum = 0;
//只需考虑扩展到0情况
if (show[i][j] == '0')
{
SearchExpand(mine, show, i, j);
}
}
}
}
}

void SetSign(char mine[ROWS][COLS], char show[ROWS][COLS])
{
ShowBoard(show);
int i = 0;
int j = 0;
printf("请输入要标记的地块的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//判断坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '*')
{
show[i][j] = '#';
}
else
{
printf("坐标非法,可能选择了无效地块或已探查地块,请重新选择\\n");
}
}

void DelSign(char mine[ROWS][COLS], char show[ROWS][COLS])
{
ShowBoard(show);
int i = 0;
int j = 0;
printf("请输入要删除的标记的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//判断坐标合法性
if (1 <= i && i <= ROW && 1 <= j && j <= COL && show[i][j] == '#')
{
show[i][j] = '*';
}
else
{
printf("坐标非法,可能选择了无效地块或已探查过的坐标\\n");
}
}

void MineSweeping(char mine[ROWS][COLS], char show[ROWS][COLS])
{
int i = 0;
int j = 0;
char sum = 0;
int count = ROW * COL;//胜利条件,当棋盘上*个数 与#个数之和与总雷数相等
int exit = ROW + 10;

while (count – MINE)
{
ShowBoard(show);
/*ShowBoard(mine);*/
printf("如果想要退出排查模式,请输入%d %d\\n", exit, exit);
printf("请输入要排查的坐标:>");
scanf("%d %d", &i, &j);
system("cls");
//确认坐标合法性
if (i == exit && j == exit)
{
break;
}
else if (1 <= i && i <= ROW && 1 <= j && j <= COL)
{
//开始排查
//踩到雷了
if (mine[i][j] == '1')
{
printf("很遗憾,踩到雷了,游戏结束\\n");
printf("雷图如下\\n");
ShowBoard(mine);
printf("\\n");
break;
}
else
{
//计算周围雷的个数
sum = ScanMine(mine, show, i, j);
//显示周围雷个数的数字
show[i][j] = sum + '0';
sum = 0;
if (show[i][j] == '0')
{
SearchExpand(mine, show, i, j);
}

//判断场上剩余的*
count = 0;
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (show[i][j] == '*' || show[i][j] == '#')
{
count++;
}
}
}
}
}
else
{
printf("输入的坐标有误,请重新输入\\n");
}
}
//胜利
if (count – MINE == 0)
{
ShowBoard(show);
printf("恭喜,游戏胜利\\n");
}
}


3 – 3 · game.c

#include "MineSweeping.h"

void Game()
{
//初始化棋盘
char show[ROWS][COLS] = { 0 };//游戏实际的棋盘
char boom[ROWS][COLS] = { 0 };//用来计算数字的棋盘
CreatBoard(show,ROWS,COLS,'*');
CreatBoard(boom, ROWS, COLS, '0');

//设置雷
SetMine(boom, MINE);

//开始游戏
int input = 0;
do
{
//展示棋盘
ShowBoard(show);

//用switch,来进入排查,标记,删除标记三个模式
printf("1:进行持续排查 2:进行一次标记 \\n3:进行一次删除标记 0:退出本局\\n");
printf("请选择你想要进行的操作:>");
scanf("%d", &input);
switch (input)
{
case 1:
system("cls");
MineSweeping(boom, show);
break;
case 2:
system("cls");
SetSign(boom, show);
break;
case 3:
system("cls");
DelSign(boom, show);
break;
case 0:
system("cls");
printf("成功退出本局游戏\\n");
printf("\\n");
break;
default:
system("cls");
printf("输入有误,请重新选择\\n");
printf("\\n");
break;
}
} while (input);

}

int main()
{
int input = 0;
srand((unsigned int)time(NULL));

do
{
Menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
Game();
break;
case 0:
printf("成功退出游戏");
break;
default:
printf("选择错误,请重新选择\\n");
break;
}
} while (input);

return 0;
}


以上介绍了扫雷游戏的模拟实现

以上内容如有错误或不准确之处,欢迎指出,或者你有更好的想法,也欢迎交流。

赞(0)
未经允许不得转载:171主机测评 » 扫雷游戏 模拟实现
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址