扫雷游戏的基本实现实际上还是比较简单的,没有用到很深的知识,只需要我们理解和掌握数组和函数的使用。
接下来开始带大家慢慢来用c语言来实现扫雷游戏的运行代码。
1.扫雷游戏的分析
1.1扫雷游戏的功能
•用控制台实现
•菜单界面,玩家选择进入游戏或者退出游戏
•游戏界面,玩家开始一步步的扫雷
在扫雷游戏过程中,我们要实现扫雷棋盘的大小,棋盘中雷的个数,以及当玩家扫过的位置不是雷的时候,周围一圈雷的信息
2.扫雷游戏的代码实现分析过程
2.1游戏菜单界面
首先我们可以设置游戏的界面,界面的设置也十分的简单,为了使我们的思路更加清晰,我们可以用一个函数来实现。
代码如下:(按照自己喜欢的来就行)
#include <stdio.h>
void menu() //menu函数,向玩家展示游戏界面
{
printf("********************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("********************\n");
}
int main()
{
menu();
return 0;
}
2.2玩家的选择
接着实现玩家的输入,玩家首先要看到菜单界面,并且因为玩家可能想再次玩游戏或者输入错误,所以我们可以用do while循环来实现多次输入,用switch语句来实现玩家的不同选择。
代码如下:(代码无法运行,因为play函数(扫雷游戏实现)的代码我们还没有写)
#define _CRT_SECURE_NO_WARNINGS //防止scanf报错
#include <stdio.h>
int main()
{
int input = 0; //do while循环执行一次后要while判断是否再次进入循环,所以input要在循环外面定义
do
{
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
play(); //进入扫雷游戏
break;
case 0:
printf("exit\n");
break;
default:
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
2.3扫雷游戏界面
以9* 9的棋盘为例,我们要在棋盘上布置雷和排查雷,很容易想到用二维数组来存放这些信息。
布置雷的位置存放1,其余位置存放0。
但是我们玩过扫雷的都知道,当我们排查雷的时候,如果当前位置没有雷,则该位置会统计显示周围1圈8个位置雷的总数,当我们排查最外圈的雷的时候,周围一圈可能会发生越界的情况,所以我们可以在创建数组时候,给数组扩大一圈,周围一圈全都不布置雷,即可解决越界的问题,所以我们在创建时可以创建11* 11的数组棋盘。
在创建完数组后,我们会发现布置雷显示的1(红色部分的1)可能会和统计雷个数的1(图中周围8个都为黄色的格子)信息重合,数组太杂乱,这样不方便我们调整和观察,有的人肯定会想,那为什么埋雷的时候用0/1表示,不用其他的符号来表示呢,但是如果真的将埋雷的信息和统计雷个数的信息放在1个数组里面不利于我们思考和实现,很可能代码写着写着弄混淆了,也可能代码写完后十分的复杂也不利于我们修改,所以我们可以用两个数组来实现,1个数组用来存放埋雷的信息,0表示该处没有雷,1表示该处有雷,用另外一个数组用来存放统计周围雷的个数的信息,将数组全部用表示。
为了后面能够将两个数组用同一个函数解决,我们可以将它们的元素统一成字符(因为是字符,0和1可以转变为字符)。
为了使我们能够方便的改变棋盘的大小,我们可以将行和列都宏定义一个常量,这样之后我们想要改变棋盘的大小,只需要改宏定义的值即可。
代码如下:
#define ROW 9 //宏定义行为9
#define COL 9 //列为9
#define ROWS ROW+2 //将行和列扩大一圈,行和列都要加2
#define COLS COL+2
void play()
{
char mine[ROWS][COLS]; //布置好的雷的信息
char show[ROWS][COLS]; //排查处的雷的信息
}
接下来就要开始实现扫雷过程。
思路:
(1)初始化棋盘。可以用InitBoard函数实现。
(2)打印埋雷信息的棋盘,向玩家展示。可以用DisplayBoard函数实现。
(3)向棋盘中布置一定数量的雷。可以用SetMine函数实现。
(4)玩家开始排查雷。可以用FindMine函数实现。
(1)初始化棋盘
初始化二维数组,可以用双循环来实现,把行和列作为实参传给函数,因为我们想把两个数字的初始化用一个函数来实现,但是两个数组的初始化的内容不同,所以我们可以定义一个变量set,把我们想要初始化的内容作为实参传递给set来实现。
代码如下:
#include <stdio.h>
#define ROW 9 //宏定义行为9
#define COL 9 //列为9
#define ROWS ROW+2 //将行和列扩大一圈,行和列都要加2
#define COLS COL+2
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void play()
{
char mine[ROWS][COLS]; //布置好的雷的信息
char show[ROWS][COLS]; //排查处的雷的信息
InitBoard(mine,ROWS,COLS,'0'); //(1)初始化棋盘。
InitBoard(show, ROWS, COLS, '*');
}
(2)打印布置雷信息的棋盘
和初始化棋盘类似,可以在在第0行和第一列加对应的数字表示对应的行,方便玩家输入坐标。
代码如下:
#include <stdio.h>
#define ROW 9 //宏定义行为9
#define COL 9 //列为9
#define ROWS ROW+2 //将行和列扩大一圈,行和列都要加2
#define COLS COL+2
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
for (i = 0; i <= col; i++)
{
printf("%d ", i); //在每一列最上面打印该列为第几列,方便玩家的输入
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //在每一行最左边打印该行为第几列,方便玩家的输入
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]); //打印初始化后的字符数组
}
printf("\n");
}
}
void play()
{
char mine[ROWS][COLS]; //布置好的雷的信息
char show[ROWS][COLS]; //排查处的雷的信息
DisplayBoard(show,ROW,COL); //(2)打印埋雷信息的棋盘
}
结果如图:
(3)向棋盘中布置一定数量的雷
因为每次要在不同位置随机生成雷,所以我们需要用到rand函数来提供随机数,在主函数中添加srand((unsigned int)time(NULL));
并声明头文件:
#include <stdlib.h>
#include <time.h>(原因在猜数字游戏中有详细的解释)
我们可以将雷的个数设定成一个可以改变的常量,用MineNumber实现,方便我们之后更改,每次随机的位置如果不是雷,就布置雷,并且把需要布置雷的次数减1,一直重复操作,直至雷全部布置完,用循环实现布置雷。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9 //宏定义行为9
#define COL 9 //列为9
#define ROWS ROW+2 //将行和列扩大一圈,行和列都要加2
#define COLS COL+2
#define MineNumber 10 //定义需要设置雷的个数,方便更改
void SetMine(char mine[ROWS][COLS],int row,int col)
{
int count = MineNumber;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1; //每次进入循环,生成不同的随机数,控制生成数的范围在棋盘内
if (mine[x][y] == '0')
{
mine[x][y] = '1'; //如果x,y处不是雷,那么就在此处设置雷
count--;
}
}
}
void play()
{
char mine[ROWS][COLS]; //布置好的雷的信息
char show[ROWS][COLS]; //排查处的雷的信息
SetMine(mine, ROW, COL); //(3)向棋盘中布置一定数量的雷。
}
(4)玩家开始排查雷
首先需要玩家输入排雷的坐标x和y,然后进行判断,如果该处是雷,游戏就失败了,如果不是雷,就要向玩家反馈x,y处周围雷的总数,可以用 GetMineCount函数实现。
周围一圈雷的坐标如下:
代码如下:
#include <stdio.h>
#define ROW 9 //宏定义行为9
#define COL 9 //列为9
#define ROWS ROW+2 //将行和列扩大一圈,行和列都要加2
#define COLS COL+2
#define MineNumber 10
int GetMineCount(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');
//因为创建的是字符数组,我们要得到雷的个数需要转换,字符减去字符0即可得到对应的数字,周围8个字符总共需减去8个字符0
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int total = 0; //玩家排雷的次数
while (total < row * col - MineNumber)
{
printf("请输入需要排查的位置:");
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); //玩家失败后,向玩家展示所有雷布置的位置
break;
}
else
{
int count = GetMineCount(mine, x, y); //此处我们需要设计函数,来返回玩家查找的位置周围一圈有多少雷
show[x][y] = count + '0'; //函数返回的是整形,但是我们创建的是字符数组,所以要把整形转变为字符,再替代当前x,y位置的值
DisplayBoard(show, ROW, COL);
total++;
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
if (total == row * col - MineNumber)
{
printf("恭喜你,挑战成功!");
}
}
void play()
{
char mine[ROWS][COLS] = { 0 }; //布置好的雷的信息
char show[ROWS][COLS] = { 0 }; //排查处的雷的信息
FindMine(mine, show, ROW, COL); //(4)玩家开始排查雷
}
3.基本扫雷游戏的完整代码及拓展
稍微调整一下后完整代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MineNumber 10
void InitBoard(char board[ROWS][COLS],int rows,int cols,char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("\n");
printf("******扫雷游戏******\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("******扫雷游戏******\n");
printf("\n");
}
void SetMine(char mine[ROWS][COLS],int row,int col)
{
int count = MineNumber;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
int GetMineCount(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 FindMine(char mine[ROWS][COLS],char show[ROWS][COLS],int row,int col)
{
int x, y;
int total = 0;
while (total < row * col-MineNumber)
{
printf("请输入需要排查的位置:");
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);
break;
}
else
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
total++;
}
}
else
{
printf("输入错误,请重新输入!\n");
}
}
if (total == row * col - MineNumber)
{
printf("恭喜你,挑战成功!");
}
}
void play()
{
char mine[ROWS][COLS];
char show[ROWS][COLS];
printf("开始游戏\n");
InitBoard(mine,ROWS,COLS,'0');
InitBoard(show,ROWS,COLS,'*');
DisplayBoard(show,ROW,COL);
SetMine(mine,ROW,COL);
FindMine(mine,show,ROW,COL);
}
void menu()
{
printf("********************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("********************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case 1:
play();
break;
case 0:
printf("exit\n");
break;
default:
printf("输入错误,请重新输入!\n");
}
} while (input);
return 0;
}
可以发现,当代码数量越来越多时,把代码全部放在一个文件里不利于我们思考,所以我们可以设计多个文件,用多⽂件的形式对函数的声明和定义。
我们实践⼀下,设计三个⽂件:
test.c //文件中写游戏的测试逻辑
game.c //文件中写游戏中函数的实现等
game.h //文件中写游戏需要的数据类型和函数声明等
如图所示:
test.c文件和game.c文件需要用到声明可以用#include "game.h"来调用。
当然,我们还可以拓展更多的内容,例如实际上我们玩的扫雷,如果排查的位置不是雷,周围一圈也没有雷,就可以展开一大片,可以标记我们认为是雷的位置,还有排雷的时间显示,需要用到后面的学习知识,这里就不一一介绍了,希望能帮助大家更好的了解最基本的扫雷游戏的原理。