说起扫雷游戏,这应该是很多人童年的回忆吧,中小学电脑课最常玩的必有扫雷游戏,那么大家知道它是如何开发出来的吗,扫雷游戏背后的原理是什么呢?今天就让我们一探究竟!
扫雷游戏介绍
如下图,简单版本的扫雷游戏,就是在一个9*9的方格阵中,埋藏了10颗地雷,而玩家就是根据这个方阵的反馈,进行10颗地雷位置的排查。
具体的游戏规则如下,玩家随机选择一个位置,如果选取位置不是雷,则显示数字。对于边缘的数字,则显示图中数字周围临近的5个方格中含有的地雷个数,例如下图红色圈起来的数字2,则说明红框之中必有两个地雷,即黑色×处。再看绿色框出来的数字2,它处于方格阵的中间位置,则看它周围临近的8个方格,含有2个地雷,也是黑色×处。
玩家可以根据数字的提示,筛选出10颗雷的位置,最终获得游戏胜利。反之,如果不幸选取到雷(即踩雷),则游戏失败。
扫雷游戏设计方案
1. 创建两个两个二维数组,一个作为埋雷地图,另一个作为玩家视角的排雷视图。
2. 设计一个9*9的扫雷游戏,但为了防止在统计坐标周围雷的个数的时候越界,设定数组的大小为11*11。
3. 数组均为字符数组.
4. 此次游戏实现同样采用多文件的形式设计。
test.c —— 测试游戏功能是否完好的代码
game.c —— 实现游戏逻辑的核心代码
game.h —— 游戏变量及函数的声明
扫雷游戏具体实现
(一)game.h文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define EASY_MODEL 10
void InitGame(char board[ROWS][COLS], int row, int col, char set);//初始化棋盘
void DisPlayBoard(char board[ROWS][COLS], int row, int col);//展示棋盘
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//初始化地雷
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetMine(char mine[ROWS][COLS], int row, int col);
//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int x, int y,int count);
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num);
(二)test.c文件
1.main函数
在实现游戏功能的时候,一般都是在main函数中写出大体的框架,当遇到需要实现一些功能时,我们再去设计函数完成对应功能。
这里设定游戏可以玩多局,即当游戏结束是不用退出可以继续选择是否进行下一局游戏,所以这里使用循环。同时,在第一局开始前询问玩家是否开始游戏,则采用do....while()循环。
这里设置一个菜单来提醒玩家是否进行游戏。同时,玩家选择1时表示开始游戏,选择0时表示游戏结束,退出程序,选择其他时要提示玩家输入非法,并重新进行选择,所以这里需要一个switch选择语句。同时设置随机种子,方便每次开始扫雷游戏时,地雷都能随机位置。
int main()
{
int input = 0;
srand((unsigned int)(time(NULL)));//设置随机种子
do {
menu();
scanf("%d", &input);
switch (input)
{
default:
printf("输入错误嗷,请重新输入!\n");
break;
case 1:
printf("扫雷游戏启动....\n");
game();
break;
case 0:
printf("扫雷游戏结束咯....\n");
break;
}
} while (input);
return 0;
}
2.menu函数
void menu()
{
printf("**************************\n");
printf("******** 1. play *******\n");
printf("******** 0. exit *******\n");
printf("**************************\n");
}
3.game函数
当玩家开始玩游戏时,game函数就是用来实现游戏整体框架,并调用其他实现具体功能的函数,具体代码如下。
void game()
{
char mine[ROWS][COLS]; //埋雷地图
char show[ROWS][COLS]; //玩家窗口
InitGame(mine, ROW, COL, '0');
InitGame(show, ROW, COL, '*');//初始化棋盘
InitMine(mine, show, ROW, COL);//埋雷
DisPlayBoard(show, ROW, COL);
StartGame(mine, show, ROW, COL);
}
其中mine数组时用来存储埋雷信息,show数组是用来存放排查出雷的信息。首先将mine数组的内容全都初始化为字符0,show数组将内容初始化为'*'表示此时位置还未被排雷。初始化棋盘后开始埋雷,将雷设置为1(具体后面会说)。mine数组的视图仅在程序开发时给程序员看,方便进行游戏调试。当正式游戏时,mine数组应该不给玩家看。
(三) game.c文件
1.InitGame函数(初始化棋盘)
此为初始化棋盘函数,由于需要确保函数的通用性,并且考虑到有一个mine视图和一个show视图需要同时初始化,而这两个视图初始化的内容也不同,所以设定一个set参数,每次调用函数的时候可以设置不同的初始化内容。
void InitGame(char board[ROWS][COLS], int row, int col, char set)
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
2.DisPlayBoard函数(显示棋盘 )
在实现DisPlayBoard函数时,不仅仅需要把数组内容给打印出来,还要考虑到玩家在玩游戏时输入地雷坐标方便,需要将整个方阵的横纵坐标给显示出来便于玩家判断。同时,还要提示玩家此局游戏中一共埋藏了多少颗雷。而雷的个数我们用一个全局变量EASY_COUNT来代替,这样也方便后续修改雷的个数。打印代码具体如下:
void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
printf("------------------------------\n");
printf("本场游戏,一共有%d颗地雷\n", EASY_MODEL);
for (int i = 0; i <= col; i++)
{
printf("%d ", i);//输出列号
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);//输出行号
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
3.InitMine函数(埋雷)
本游戏设计10个雷,即EASY_MODEL的值设置为10。test.c的main函数中设计了随机种子,让雷的位置是随机的。使用rand函数来随机生成雷的位置的坐标,同时需要保证10颗雷的位置不能重复。因此采用while循环,如果成功布置雷就将count-1,直至count的值为0时结束循环。
最后,我们将放置雷的位置上的信息改为字符'1',而非雷的位置信息依旧为字符'0'(即修改mine数组中对应位置的内容),修改为1是为了方便后续统计一个坐标周围的8个位置中有多少颗雷。需要注意的是:由于数组的下标是从0开始,而玩家所看到的方格的坐标是从1开始,因此我们在随机生成的雷的坐标中需要对x,y进行+1操作。代码如下:
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//初始化雷区
int mine_num = EASY_MODEL;
while (mine_num)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
mine_num--;
}
}
}
4.GetMine函数(统计方格四周雷的个数)
统计一个坐标为(x,y)位置周围雷的个数,就是统计其周围8个位置的内容有多少个字符'1'。其余8个位置的坐标如下图:
由于字符1和字符0的差距就是1,所以我们统计八个位置的累计地雷数,就是减8个字符0的ASCII值即可,具体代码如下:
int GetMine(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] + mine[x + 1][y] + mine[x][y + 1] + mine[x][y - 1] +
mine[x - 1][y - 1] + mine[x + 1][y + 1] + mine[x - 1][y + 1] + mine[x + 1][y - 1] - 8 * '0';
}
5.StartGame函数(开始扫雷游戏,排查雷)
如果玩家未将所有雷排查完,则需要继续排查,因此必然需要一个循环,且当排查的个数=总数 - 雷的个数时,循环结束。
每次需要判断玩家输入的雷坐标是否合法,不合法则需重新输入,合法才继续判断。需要判断两个条件。(1)当前位置是否为雷,是雷则直接结束游戏。(2)若当前位置非雷,但是排查过了,需要提示玩家并重新输入排雷位置。(3)只有当前位置非雷,并且排查过,才可以。需要注意的是:输入的非法坐标不计入排查的次数中。具体代码如下:(本代码实现了大片排雷)
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int num = EASY_MODEL;
int count = 0;
while (count < row * col - num)
{
printf("请输入你要扫雷的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0' && show[x][y] != '*')
{
printf("刚才排除此地咯,请重新输入坐标!\n");
}
else if (mine[x][y] == '1')
{
printf("你被炸了,游戏结束!\n");
break;
}
else
{
//count++;
SearchMine(mine,show,row,col,x,y,&count);
//count = SearchMine(mine, show, row, col, x, y, count);
DisPlayBoard(show, 9, 9);
}
}
else
{
printf("你输入的坐标不对嗷,请重新输入!\n");
}
}
if (count == row * col - num) {
printf("恭喜你获胜啦!\n");
}
}
6.SearchMine函数(排查雷,实现大片扫雷)
使用GetMine函数获取周围8个坐标中雷的数量:
(1)如果是0,即周围8个坐标没有雷,那么就将中间设置为空白,在棋盘范围内,对该八个坐标再次进行SearchMine(递归);递归时排除已经是空格的坐标,防止陷入死循环。直至遇到一个坐标,其范围为内有雷。
(2)如果不是零,将该坐标设置为雷的数量(注意是字符类型)
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num)
{
if (x < 1 || x > row || y < 1 || y > col) return;//坐标不合法直接不继续
(*num)++;//num变量传的是排查雷个数变量的位置,*num是取出该变量位置上的具体值
char tmp = GetMine(mine, x, y) + '0';//查看当前位置有几个地雷
if (tmp == '0')
{
show[x][y] = ' ';
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
int nx = x + i;
int ny = y + j;
if (show[nx][ny] == '*')
{
SearchMine(mine, show, row, col, nx, ny, num);
}
}
}
}
else
{
show[x][y] = tmp;
//return;
}
}
实现效果:
完整代码
1.test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("**************************\n");
printf("******** 1. play *******\n");
printf("******** 0. exit *******\n");
printf("**************************\n");
}
void game()
{
char mine[ROWS][COLS]; //埋雷地图
char show[ROWS][COLS]; //玩家窗口
InitGame(mine, ROW, COL, '0');
InitGame(show, ROW, COL, '*');//初始化棋盘
InitMine(mine, show, ROW, COL);//埋雷
DisPlayBoard(mine, ROW, COL);
DisPlayBoard(show, ROW, COL);
StartGame(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)(time(NULL)));
do {
menu();
scanf("%d", &input);
switch (input)
{
default:
printf("输入错误嗷,请重新输入!\n");
break;
case 1:
printf("扫雷游戏启动....\n");
game();
break;
case 0:
printf("扫雷游戏结束咯....\n");
break;
}
} while (input);
return 0;
}
2.geme.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define EASY_MODEL 10
void InitGame(char board[ROWS][COLS], int row, int col, char set);
void DisPlayBoard(char board[ROWS][COLS], int row, int col);
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int GetMine(char mine[ROWS][COLS], int row, int col);
//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col,int x, int y,int count);
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num);
3.game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void InitGame(char board[ROWS][COLS], int row, int col, char set)
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
printf("------------------------------\n");
printf("本场游戏,一共有%d颗地雷\n", EASY_MODEL);
for (int i = 0; i <= col; i++)
{
printf("%d ", i);//输出列号
}
printf("\n");
for (int i = 1; i <= row; i++)
{
printf("%d ", i);//输出行号
for (int j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
void InitMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{//初始化雷区
int mine_num = EASY_MODEL;
while (mine_num)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
//show[x][y] = '#';
mine_num--;
}
}
}
int GetMine(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] + mine[x + 1][y] + mine[x][y + 1] + mine[x][y - 1] +
mine[x - 1][y - 1] + mine[x + 1][y + 1] + mine[x - 1][y + 1] + mine[x + 1][y - 1] - 8 * '0';
}
void SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* num)
{
if (x < 1 || x > row || y < 1 || y > col) return;
(*num)++;
char tmp = GetMine(mine, x, y) + '0';
if (tmp == '0')
{
show[x][y] = ' ';
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
int nx = x + i;
int ny = y + j;
if (show[nx][ny] == '*')
{
SearchMine(mine, show, row, col, nx, ny, num);
}
}
}
}
else
{
show[x][y] = tmp;
//return;
}
}
//int SearchMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int count)
//{
// if (x < 1 || x > row || y < 1 || y > col || show[x][y] != '*')
// return count;
//
// int tmp = GetMine(mine, x, y);
// count++;
// show[x][y] = tmp + '0';
//
// if (tmp == 0)
// {
// for (int i = -1; i <= 1; i++)
// {
// for (int j = -1; j <= 1; j++)
// {
// int nx = x + i;
// int ny = y + j;
// if (nx >= 1 && nx <= row && ny >= 1 && ny <= col)
// {
// count = SearchMine(mine, show, row, col, nx, ny, count);
// }
// }
// }
// }
//
// return count;
//}
void StartGame(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int num = EASY_MODEL;
int count = 0;//排查的雷个数
while (count < row * col - num)
{
printf("请输入你要扫雷的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0' && show[x][y] != '*')
{
printf("刚才排除此地咯,请重新输入坐标!\n");
}
else if (mine[x][y] == '1')
{
printf("你被炸了,游戏结束!\n");
break;
}
else
{
//count++;
SearchMine(mine,show,row,col,x,y,&count);
//count = SearchMine(mine, show, row, col, x, y, count);
DisPlayBoard(show, 9, 9);
}
}
else
{
printf("你输入的坐标不对嗷,请重新输入!\n");
}
}
if (count == row * col - num) {
printf("恭喜你获胜啦!\n");
}
}
总结:
程序开发整体上不是流程和逻辑还是比较容易实现的,难点在于实现大片扫雷的功能。大片扫雷功能主要是递归的实现,容易使程序崩溃,还需多多练习递归实现。附上程序运行部分效果图: