文章目录
- 前言
- 一、扫雷游戏的分析和设计
- 1.1 扫雷游戏的功能说明
- 1.2游戏的分析和设计
- 1.2.1 数据结构的分析
- 1.2.2 文件结构的设计
- 二、扫雷游戏的代码实现
- 2.1 test.c的游戏逻辑的初步设计
- 2.2 game()函数
- 2.3 game.h
- 2.4 game.c
- 三、源码及扫雷游戏的扩展
- 3.1源码
- 3.2 扩展
- 总结
前言
这一期我们将用C语言实现一个简单的游戏扫雷,它将让我们通过对函数和数组的综合实践来巩固我们这部分的知识.
一、扫雷游戏的分析和设计
1.1 扫雷游戏的功能说明
使用控制台实现扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9 * 9的格子
• 默认随机布置10个雷
• 可以排查雷
◦ 如果位置不是雷,就显示周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有非雷都找出来,排雷成功,游戏结束
初始界面:
排雷界面:
排雷失败界面:
1.2游戏的分析和设计
1.2.1 数据结构的分析
在扫雷的过程中,布置雷的信息和排查的的信息都需要存储,因此我们需要选取合适的数据结构来存储这样的信息.
考虑到这是一个n * n的棋盘,我们首先会想到我们学过的二维数组.
棋盘的模型想好了.
下面我们就要来考虑下面几个问题:
- 我们要用什么样的符号来表示有雷或是没有雷呢?
- 设想我们所玩过的扫雷游戏,我们进入界面后看到的一定是加密的显示,看不到雷的情况,我们又该用什么养的符号来表示这种状态?
- 当我们排查雷时如果这个位置没有雷,将会显示周围8个方格的雷的个数,这又该怎么实现呢?
带着上面的问题我们先去尝试:由于我们排查玩雷之后要显示周围雷的个数,那么有雷如果用数字1,没有雷用数字0表示,统计周围方格的雷将会变得简单.
但是这是放在实现者的角度去看,展示给用户的仍然还是一张加密的棋盘,倘若我们用 * 来表示加密,那么:
所以我们考虑设计两个一模一样的棋盘,一个mine棋盘用于存放布置雷的信息,另一个show棋盘为加密状态,用于显示给用户,当排查雷时做修改展示给用户.为了后续操作的统一接口,我们将上述谈到的雷信息数字1和0换做字符’1’和’0’,与char类型的 * 统一.
排查雷,假如我排查坐标为(7,4),那么:
对应(7,4)坐标的show棋盘位置就该修改为字符’2’:
如果我们排查坐标(2,3),我们发现这个地方是雷,那将提示"很遗憾,你被炸死了的"信息并打印一次真正的雷信息mine棋盘给用户,然后游戏结束.
上面的逻辑都很清晰,我们来看看下面的排查:
倘若我要排查坐标(1,1):
我们统计棋盘边界周围雷的信息时发现我们统计了棋盘外面的的信息,这将造成数组越界访问,那该怎么解决这个问题呢?
其实根本原因就是我们越界访问,那么只要我们统计的信息不是越界就行,我们将我们的棋盘"扩大"一些,在边界外面加上一圈,底层实现时得到的是11 * 11的棋盘,但呈现给用户的仍然是9 * 9棋盘.
上面的操作中我们还涉及到了统计雷数,我们说雷的信息我们用字符’1’和’0’来表示,那么字符的相加实质上是ASCII码值的运算,很明显这不是我们想要的,我们如何让字符’1’真正表示我们想要的数
字效果1呢?很简单,只需要 ‘1’-‘0’ 就可以了,同理统计的过程也一样.
除此之外,程序设计中还会涉及到布置雷,布置雷的过程实际上是一个随机的过程,因此我们需要使用到 rand() 函数生成随机数以及 srand() 函数 time() 函数设置随机数种子,但对于得到的随机数我们需要进行处理,因为我们想要随机的数据是布置雷的坐标,那么它的范围一定是在1~9范围内的,所以根据之前猜数字游戏中的公式,可以这样处理: 1+rand()%9.
细节上,我们希望可以做到不将一个游戏写固定死,可以用#define宏定义一些数据,例如棋盘大小,游戏难度(雷的个数).
讲到这里,棋盘操作的大致的框架思路我们就有了,让我们来实践操作一下!
1.2.2 文件结构的设计
扫雷游戏使用C语言实现必定是一个"大项目",如果我们把所有的代码都放在一个源文件之下,它将变得非常的庞大,并且不便于阅读或修改.所以我们可以把实现游戏的程序分为几个部分,分在几个文件中去写.
分成以下三个文件:
test.c 游戏的逻辑实现部分
game.c 游戏中函数的实现部分
game.h 游戏所需要的数据类型和函数声明等等
二、扫雷游戏的代码实现
2.1 test.c的游戏逻辑的初步设计
test.c实现的是游戏大致框架(逻辑),类似猜数字游戏,它会有一个菜单,并且可以进入游戏(程序)后看到菜单,然后选择玩游戏或是退出游戏.玩游戏则进入游戏内部,游戏结束后还会再次弹出菜单退出游戏则提示退出游戏然后程序结束.
以下是这个框架的实现
test.c文件
#include "game.h"
void menu()
{
printf("***************扫雷游戏*****************\n");
printf("************ 1.paly ****************\n");
printf("************ 0.exit ****************\n");
printf("****************************************\n");
}
int main()
{
int input = 1;
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;
}
2.2 game()函数
根据菜单,我们如果选择了1将进入到游戏内部,这里我们给一个game()函数,来封装游戏内部逻辑,下面是代码:
//完成整个游戏的过程
void game()
{
char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
char show[ROWS][COLS] = {0};//存放排查出的雷的信息用于显示
//初始化棋盘
boradInit(mine, ROWS, COLS, '0');
boradInit(show, ROWS, COLS, '*');
//布置雷
setMine(mine, ROW, COL);
//打印棋盘
boardPrint(show, ROW, COL);
//排查雷
findMine(mine, show, ROW, COL);
}
这个game()函数属于游戏框架,我们把它放在test.c文件中.
2.3 game.h
game.h文件中存放的是我们游戏过程中使用到的各种方法声明,数据类型等等,类似于书籍目录,下面是代码:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//设置游戏难度(雷的个数)
#define EASYCOUNT 10
//定义棋盘大小
#define ROW 9
#define COL 9
//以下是实现过程底层的棋盘大小
#define ROWS ROW + 2
#define COLS COL + 2
//初始化棋盘
void boradInit(char board[ROWS][COLS], int rows, int cols, char set);//注意形参与实参大小写的区分
//打印棋盘
void boardPrint(char board[ROWS][COLS], int row, int col);
//布置雷
void setMine(char board[ROWS][COLS], int row, int col);
//排查雷
void findMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计坐标周围雷的个数
int getMineCount(char mine[ROWS][COLS], int x, int y);
2.4 game.c
这个文件中写的涉及到的各种方法.函数的具体实现,代码如下:
#include "game.h"
void boradInit(char board[ROWS][COLS], int rows, int cols,char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void boardPrint(char board[ROWS][COLS], int row, int col)
{
printf("\n");
printf("--------扫雷--------\n");
//打印行列号
for (int x = 0; x <= row; x++)
{
printf("%d ", x);
}
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 setMine(char board[ROWS][COLS], int row, int col)
{
int count = EASYCOUNT;//游戏难度
while (count)
{
int x = rand() % row + 1;//随机产生行坐标1~9
int y = rand() % col + 1;//随机产生行坐标1~9
if (board[x][y] == '0')//排除掉已经被布置的情况
{
board[x][y] = '1';//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 = 0;
int y = 0;
int win = 0;
while (win < row*col -EASYCOUNT)
{
printf("请输入你要排查的坐标:<");
scanf("%d%d", &x, &y);
//判断坐标合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!\n\n");
boardPrint(mine, row, col);
break;
}
else
{
if (show[x][y] != '*')
{
printf("该坐标已经被排查过了,无需排查\n\n");
}
else
{
//不是雷,统计周围八个坐标处的雷的个数,并赋值给show棋盘对应位置
int count = getMineCount(mine, x, y);
show[x][y] = count + '0';
boardPrint(show, row, col);
win++;
}
}
}
else
{
printf("输入的坐标非法,请重新输入!\n");
}
}
if(win == row * col - EASYCOUNT)
{
printf("恭喜你,排雷成功!\n");
boardPrint(mine, row, col);
}
}
以上就是我们所有的程序设计.
三、源码及扫雷游戏的扩展
3.1源码
///game.h文件
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//设置游戏难度(雷的个数)
#define EASYCOUNT 10
//定义棋盘大小
#define ROW 9
#define COL 9
//以下是实现过程底层的棋盘大小
#define ROWS ROW + 2
#define COLS COL + 2
//初始化棋盘
void boradInit(char board[ROWS][COLS], int rows, int cols, char set);//注意形参与实参大小写的区分
//打印棋盘
void boardPrint(char board[ROWS][COLS], int row, int col);
//布置雷
void setMine(char board[ROWS][COLS], int row, int col);
//排查雷
void findMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//统计坐标周围雷的个数
int getMineCount(char mine[ROWS][COLS], int x, int y);
///game.c文件
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void boradInit(char board[ROWS][COLS], int rows, int cols,char set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void boardPrint(char board[ROWS][COLS], int row, int col)
{
printf("\n");
printf("--------扫雷--------\n");
//打印行列号
for (int x = 0; x <= row; x++)
{
printf("%d ", x);
}
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 setMine(char board[ROWS][COLS], int row, int col)
{
int count = EASYCOUNT;//游戏难度
while (count)
{
int x = rand() % row + 1;//随机产生行坐标1~9
int y = rand() % col + 1;//随机产生行坐标1~9
if (board[x][y] == '0')//排除掉已经被布置的情况
{
board[x][y] = '1';//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 = 0;
int y = 0;
int win = 0;
while (win < row*col -EASYCOUNT)
{
printf("请输入你要排查的坐标:<");
scanf("%d%d", &x, &y);
//判断坐标合法性
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("很遗憾,你被炸死了!\n\n");
boardPrint(mine, row, col);
break;
}
else
{
if (show[x][y] != '*')
{
printf("该坐标已经被排查过了,无需排查\n\n");
}
else
{
//不是雷,统计周围八个坐标处的雷的个数,并赋值给show棋盘对应位置
int count = getMineCount(mine, x, y);
show[x][y] = count + '0';
boardPrint(show, row, col);
win++;
}
}
}
else
{
printf("输入的坐标非法,请重新输入!\n");
}
}
if(win == row * col - EASYCOUNT)
{
printf("恭喜你,排雷成功!\n");
boardPrint(mine, row, col);
}
}
///test.c文件
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
//完成整个游戏的过程
void game()
{
char mine[ROWS][COLS] = {0};//存放布置好的雷的信息
char show[ROWS][COLS] = {0};//存放排查出的雷的信息用于显示
//初始化棋盘
boradInit(mine, ROWS, COLS, '0');
boradInit(show, ROWS, COLS, '*');
setMine(mine, ROW, COL);
boardPrint(show, ROW, COL);
findMine(mine, show, ROW, COL);
}
//菜单
void menu();
int main()
{
int input = 1;
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;
}
void menu()
{
printf("***************扫雷游戏*****************\n");
printf("************ 1.paly ****************\n");
printf("************ 0.exit ****************\n");
printf("****************************************\n");
}
3.2 扩展
对于上面我们所实现的扫雷游戏其实并不是很完美,它仅仅实现了一个扫雷游戏最基本的功能,对比我们所玩过接触过的扫雷游戏,它其实还存在一定的差距.
思考:
• 是否可以选择游戏难度?
◦ 简单 9 * 9 棋盘,10个雷
◦ 中等 16 * 16棋盘,40个雷
◦ 困难 30 * 16棋盘,99个雷
• 如果排查位置不是雷,周围也没有雷,怎么可以展开周围的⼀片
• 是否可以标记雷?
• 是否可以加上排雷的时间显示?
总结
本片文章,介绍了用C语言实现一个扫雷游戏,巩固了我们所学过的函数和数组的知识,有机会我会带来扫雷游戏的优化扩展,敬请期待!