@TOC
本游戏程序和上一篇三子棋小程序的制作思路相仿,实现基础都是二维数组的运用
游戏主函数
首先,我们设计三子棋时,要设计选择进入游戏,退出游戏的选项,以及输入错误内容时,重新选择的程序。
将游戏标题函数设置为
menu()
将游戏主题函数设置为
game()
若在选择时,输入内容不在程序设定范围内,则要求使用者重新输入。所以程序此处应有一个循环。
因此主函数程序设置如下:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏/n");
input -= 2;
break;
default:
printf("输入错误,请重新选择/n");
break;
}
} while (input);
return 0;
}
当输入1时,进入case 1:运行game()函数;
当输入2时,进入case 2:打印“退出游戏”并跳出switch循环,在dowhile结尾进行检测时,检测结果为0,跳出循环,退出程序;
当输入其他字符时,进入default,打印‘“输入错误,请重新选择”。因为input非零,所以继续进行dowhile循环。
代码实现的初步工作
首先我们创建游戏程序文件==game.c==,和头文件==game.h==
为了提高系统的可操作性,我们先将程序中的行与列定义为==ROW==和==COL==
所以头文件中预处理指令如下:
#define _CRT_SECURE_NO_WARNINGS 1 //scanf函数防止报错
#include <stdio.h> //printf函数
#include <time.h> //time函数,产生随机值(后面讲)
//例如9×9的棋盘,周边多一圈,这样便于进行周围雷数量的判断。周边一圈不进行打印,不设置雷
#define ROW 9 //行
#define COL 9 //列
#define ROWS ROW+2 //棋盘外围拓展一圈,便于后续的程序制作,这里后面再讲
#define COLS COL+2
//设定雷的数量
#define NUM 10
//这里将游戏中用到的棋盘尺寸,雷的数量提前设置好,提高程序整体的可变性,便于后续修改
然后在==game.c==和主函数所在文件==TigerMinesweeper.c==中引用头文件:
#include "game.h"
接着设计主函数中的==menu()==函数。因为打印游戏菜单不需要返回值,所以函数类型void即可:
void menu()
{
printf("********扫雷游戏*********/n");
printf("******** 1.play ********/n");
printf("******** 2.exit ********/n");
printf("************************/n");
}
根据主函数的设计,我们继续设计game()函数:
同理不需要返回值,所以函数类型为void:
void game()
{
char mine[ROWS][COLS] = { 0 }; //创建雷的位置的 数组
char show[ROWS][COLS] = { 0 }; //创建展示给玩家的页面 的数组
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//布置雷
SetMine(mine, ROW, COL);
//打印棋盘
DisplayBoard(show, ROW, COL);
//开始逐步排查雷
FindMine(mine, show, ROW, COL);
}
如上文所示,我们先整理出了一个大致的代码设计方向。
游戏实现原理与程序优化
初始化棋盘
首先我们需要将数组初始化
mine数组为雷组,“1”为有雷,“0”为无雷
show数组展示给玩家,“*”为初始展示界面,数字代表周围八个格子中雷的数量,0则说明无雷
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set; //set对应传进来的参数,这样两个数组可以共用一个函数
}
}
}
然后再在头文件==game.h==中声明,并做好注释:
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
打印棋盘
因为我们周围的一圈不算入游戏,只是为了程序进行时便于判断,故打印棋盘时,只打印1~9,0和10不打印(在行列都为9时)
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("-----扫雷 游戏-----/n");
int i = 0;
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("/n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("/n");
}
}
然后我们同样在头文件==game.h==中声明此函数,并做好注释:
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
这样此函数既可用于打印显示给玩家的棋盘,也可以打印雷的分布图,便于后续程序的调试
布置雷
此处依然采用time函数,讲随机值取模后再+1后获得1~9的随机数。
依此方法获取雷的随机坐标,将二维数组中的0赋值为1(0为无雷,1为有雷)
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = NUM;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] != '1')
{
mine[x][y] = '1';
count--;
}
}
}
同样,在头文件==game.h==中声明此函数,并做好注释:
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
排查雷
排查雷的时候,是游戏的最重要部分,即玩家的操作部分。
所以我们要先理清游戏流程,以此着手制作
//1输入坐标
//2检查是否为雷
//2.1若是雷,GG
//2.2若不是雷,统计坐标周围有几个雷,存储数据到二维数组show中,游戏继续
所以我们可以这样制作出一串代码:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入排查雷的坐标:/n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1') //是雷,GG
{
printf("你被炸死了,GG/n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//不是雷,获取坐标周边的累个数
}
}
else
{
printf("坐标不存在,请重新输入");
}
}
}
然后我们再思考如何获取玩家输入坐标周围的雷个数,这里设定一个函数来获取周围雷的数量:
//获取周边雷数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
}
此处我有两种思路:
①因为ASCII表的0~9的是连续排列的,所以:
//获取周边雷数量
int GetMineCount(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');
}
②既然要获取周边的雷数量,那么我们首先知道的是,玩家所选坐标是没有雷的。
这么说,我们可以统计玩家所选坐标周围,总共九格中雷的数量。所以我们采用两重嵌套的for循环:
//获取周边雷数量
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
int i = 0;
int j = 0;
int count = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}
return count;
}
然后我们就可以完善先前的==排查雷==的函数了。
当游戏中所有雷被排除,记所有非雷的位置被玩家选择后,剩下未选择的都是雷,那么玩家胜利。
以此我们可以设置变量win,当玩家正确输入次数达到要求时,游戏结束,玩家胜利
如下:
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 - NUM)
{
printf("请输入排查雷的坐标:/n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1') //是雷,GG
{
printf("你被炸死了,GG/n");
DisplayBoard(mine, ROW, COL);
break;
}
else //不是雷,获取坐标周边的累个数
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("坐标不存在,请重新输入");
}
}
if (win == row * col - NUM)
{
printf("恭喜你,排雷成功/n");
DisplayBoard(mine, ROW, COL);
}
}
但是你以为结束了吗?并没有
我发现,如果玩家输入同一坐标多次,那win会不停地增长,在win达到目标后玩家胜利。而事实上,玩家并没有将所有非雷的空选择(并没有把雷排除完)
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
//1输入坐标
//2检查是否为雷
//2.1若是雷,GG
//2.2若不是雷,统计坐标周围有几个雷,存储数据到二维数组show中,游戏继续
int x = 0;
int y = 0;
int win = 0;
while (win < row*col - NUM)
{
printf("请输入排查雷的坐标:/n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1') //是雷,GG
{
printf("你被炸死了,GG/n");
DisplayBoard(mine, ROW, COL);
break;
}
else //不是雷,获取坐标周边的累个数
{
int count = GetMineCount(mine, x, y);
if (show[x][y] == '*') //这里增加一次判断,若玩家已经排过雷,则不计入总次数
{
win++;
}
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("坐标不存在,请重新输入");
}
}
if (win == row * col - NUM)
{
printf("恭喜你,排雷成功/n");
DisplayBoard(mine, ROW, COL);
}
}
下面是传统艺能,挖坑。
以上程序,如果执行,可以很明显地发现,和以前玩电脑时的扫雷有很大区别。
至少两个坑吧
1.在周围都没有雷的时候,自动将周围的空白摊开,直到数字不为0(优化玩家体验)
2.不是简单的输入坐标,而是能使用鼠标光标进行游戏
原创文章,作者:kepupublish,如若转载,请注明出处:https://blog.ytso.com/147244.html