1.前言🚗
在上一篇博客里面我们和大家分享了用C语言实现的三子棋小游戏,那么在这一篇博客,我将会继续和大家分享一个经典的小游戏 —— 扫雷。相较于三子棋而言,扫雷的初始化设置和游戏逻辑会更为困难。我们会从思路构造到实现函数给大家详细拆分整个游戏实现的过程。
2.初步思考🚓
和三子棋一样的是,菜单是任何游戏都必不可少的一部分,所以同样的我们需要一个简易的菜单:
void menu()
{
printf("******************************\n");
printf("******** 1.play *********\n");
printf("******** 0.exit *********\n");
printf("******************************\n");
}
并且我们还是用switch选择语句进入游戏。这样一来我们主函数的大致框架就有了,如下所示:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
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("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
这里大家可以看到和三子棋一样的,我们引入了game.h这个头文件,有了上次的经验,我们自然知道了这是放置函数声明的位置,大家可以先看看我们需要实现的全部函数,先试着自己码起来,如果有困难再继续看下去:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//打印棋盘
void DisPlayBoard(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
3.游戏内容的大体框架🚕
实现代码之前我们还是和大家先讲讲扫雷这个游戏,毕竟不是人人都玩过这个游戏的,知道了基本的规则才更能了解代码的含义,先来看一张图片:
这就是一个扫雷的基本界面了,首先扫雷自然需要有雷区和非雷区,雷的个数我们是可以通过手动来设置的,而位置则需要随机分布了。图中的数字表示的是以数字为中心,和周围八个格子组成的九宫格范围内雷的数量,有几个雷数字就显示几。我们需要通过这一逻辑关系来逐一把雷排除掉。
这样一来我们在游戏内容这一部分大体的框架就有了,如下所示:
void game()
{
char mine[ROWS][COLS] = { 0 };//布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出雷的信息
//初始化棋盘()
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisPlayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//DisPlayBoard(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
4.初始化棋盘🛺
我们首先就来完成初始化棋盘这一部分,首先我们可以回想一下三子棋的棋盘,我们可以发现其实扫雷和三子棋一样,需要的都是一个n×n的一个期盼。所以我们同样可以用一个二维数组来表示棋盘。
在上一部分我们可以看到我们在进行游戏部分的编程时调用了两次的初始化棋盘的函数。这是为什么呢?对于玩家而言,玩家只需要看到排查出的雷的信息就可以了。但是对于系统而言,还需要一个布置好雷的信息的棋盘。所以我们需要的是两个数组,如下:
char mine[ROWS][COLS] = { 0 };//布置好的雷的信息
char show[ROWS][COLS] = { 0 };//存放排查出雷的信息
接下来就是初始化棋盘了,我们这里调用了两次函数:
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
第二次调用函数我们是给玩家展示的界面,第一此调用函数则是存放排查出雷的信息
我们先来看函数的代码实现:
void InitBoard(char board[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++)
{
board[i][j] = set;
}
}
}
这里大家可以注意到,我们传入的参数是ROWS和COLS而不是我们本来计划好的ROW和COL。这里的话大家可以看我们的声明函数那一部分。我们将ROW和COL定义为9,而将ROWS和COLS定义为11。那为什么我们这里需要用到的是ROWS和COLS呢?
其实大家如果理解了扫雷的规则的话应该不难接受这个传参设定。我们知道在扫雷中数字的含义是九宫格内包含的雷数,那如果是位于边角的雷我们要如何统计呢?这时候为了方便我们的代码实现,我们不妨将棋盘扩大为11×11,这样就不会有溢出的现象发生了。
而传入的set参数就是我们需要用到的两个标记符号,分别使用于我们所需要的棋盘。
5.打印棋盘🚑
同样的,本质上也是二维数组的打印,但是在此之前我们希望可以对这个简易的棋盘进行美化。在棋盘左边以及上方加入行号以及列号,更方便玩家的输入,同时将游戏的名称打印出来,如下所示:
void DisPlayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
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");
}
我们可以看看就目前为止代码实现出来的样子:
截至到目前,游戏的页面基础布置就完成了,接下来就是相对难度更大的游戏逻辑的设计,准备好了吗,让我们一起看下去。
6.布置雷的操作🚌
首先雷的数量我们在函数声明部分设定为10个,如果后续想更改的话也很方便。
在布置雷这一操作上我们需要用到一个我们之前讲到的一个知识点:随机数,回忆一下大家应该可以想起来,我们之前在设置随机数的时候运用到了时间戳,这里我们进行同样的操作:
void SetMine(char mine[ROWS][COLS], int row, int col)
{
//布置十个雷
int count = EASY_COUNT;
while (count != 0)
{
//生成随机的下标
int x = rand()%row+1;
int y = rand()%col+1;
if (mine[x][y] = '0')
{
mine[x][y] = '1';
count--;
}
}
}
时间戳部分我们已经在主函数main里面已经设定过了,如下:
srand((unsigned int)time(NULL));
我们布置雷时运用了if循环语句,是将随机产生的x和y组合成一个坐标一个一个放置进去,知道达到我们需要的个数就跳出循环。
7.排查雷的位置🚎
这一部分应该是整个扫雷里面最难的一个部分。首先在排查这个位置是否为雷之前我们还需要考虑一个问题,需要排查的这个坐标是否是合法坐标?也就是说,我们的首要任务是判断坐标的合法性。当坐标合法时我们才可以将其分为是雷或者非雷两种情况。具体的代码实现如下:
void FindMine(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 - EASY_COUNT)
{
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
{
//不是雷 = 统计坐标周围有几个雷 - 存储排查雷的信息到show数组,游戏继续
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
DisPlayBoard(show, row, col);
win++;
}
}
else
{
printf("坐标不合法,请重新输入\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功部分\n");
DisPlayBoard(mine, row, col);
}
}
在这里我们又定义了get_mine_count这一函数,这一函数是用来实现统计该格子九宫格范围内包含雷的数量。
这个函数的大致思路是先找出该坐标附近八个格子的坐标,然后就是判断这几个位置是否为雷。我们前面已经规定了1为雷,所以我们只需要判断该位置是不是1即可。但是要注意的是,我们这里的1指的是字符1,而不是数字1,二者是不同的概念。
那么我们应该如何去解决这个问题呢?我们知道,’ 1 ‘ - ’ 0 ’ = 1,同样的’ 0 ’ - ’ 0 ’ = 0。这一一来,我们就可以让着八个坐标都执行减去字符0这个操作,然后再全部相加,就能得九宫格范围内雷的总数。捋清楚思路后,我们就可以着手实现这个函数:
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';
}
至此,我们的扫雷小游戏已经完成了,运行的结果大家可以自行尝试,后续会继续更新展开一片和插旗子标记雷区的代码实现,希望大家继续关注。