首先,我们对扫雷这个项目进行划分,划分为几个模块:主界面、游戏界面(棋盘)、结算界面。然后对这几个模块再进行划分,划分为几个函数用来实现此部分的功能。我们对游戏的逻辑进行分析:
1. 展示主界面
2. 玩家选择玩游戏或者退出
(分支)3.1. 进行游戏 / 3.2 退出。
由上面这个逻辑结构我们可以写出主函数的代码:
using namespace std;
int main() {
int choice = 0;
do {
menu();
cin >> choice;
switch (choice)
{
case 1:
game();
break;
case 2:
cout << "退出游戏" << endl;
break;
default:
cout << "选择错误,重新选择" << endl;
break;
}
} while (choice);
return 0 ^ 0;
}
我们将主界面封装为一个函数,用来向玩家展示信息,然后让玩家选择:
void menu() {
printf("*************************\n");
printf("******* 1.play ******\n");
printf("******* 0.exit ******\n");
printf("*************************\n");
}
将游戏内部的逻辑封装在一个函数里并在主函数中进行调用,即game(),然后我们对游戏内部的逻辑进行分析
1.创建棋盘并初始化
2.随机布置雷
3.打印一个初始棋盘
4.玩家选择排雷的坐标
(分支)5.1不是雷,返回3 / 5.2是雷,寄了。
(判断)5.1.1雷全部排完,胜利
由以上逻辑:
void game() {
srand((unsigned int)time(NULL));//为rand提供种子
int num = EZ_MINE;//更改游戏难度
char mine[ROWS][COLS] = { 0 };//棋盘mine用于存放雷的坐标信息
//ROWS=ROW+2;COLS=COL+2;这是为了防止检查某坐标的附近坐标时数组越界访问
init_board(mine, '0');
setMine(mine, num);//随机布置雷
char show[ROWS][COLS] = { 0 };//棋盘show用于向玩家展示
init_board(show, '*');//初始化棋盘
while (win) {//win是一个全局变量,定义:int win = ROW * COL - EZ_MINE;
//win用于记录非雷并且未排查的坐标数目
int x = 0, y = 0;
// printBoard(mine);//用于测试
printBoard(show);//打印棋盘
printf( "请输入要排查的坐标:");
scanf("%d%d", &x, &y);
if (mine[x][y] != '1') {//判断此坐标是否已经排查
if (show[x][y] != '*') {
printf("已经排查过了\n");
continue;
}
check(mine, show,x, y); //判断是不是雷
}
else {
printBoard(mine);
printf("你寄了\n");
break;
}
}
if (win == 0){
printBoard(mine);
printf("你赢了\n");
}
}
然后我们再对game函数的子函数进行逻辑分析,首先是初始化棋盘init_board(),我们的目标是把每个坐标全都初始化为一个指定字符,易得出:
void init_board(char mine[][COLS], char fill) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
mine[i][j] = fill;
}
}
}
然后是setMine,随机产生多个不重复坐标,并把mine中这几个坐标的值设为1:
void setMine(char mine[][COLS], int num) {
int cnt = 0;
while (cnt<num) {
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (mine[x][y] != '1') { mine[x][y] = '1'; cnt++; }
else continue;
}
}
再是printBoard,只需遍历然后打印即可:
void printBoard(char show[][COLS]) {
printf("--------------------------------\n");
for (int j = 0; j <= COL; j++)printf("%d |", j);//方便玩家获得横坐标
printf("\n");
for (int i = 1; i <= ROW; i++) {
printf("%d |", i);//方便玩家获得纵坐标
for (int j = 1; j <= COL; j++) {
printf("%c ", show[i][j]);
}
printf("\n");
}
printf("--------------------------------\n");
}
最后是重头戏check,他应该接收一个坐标然后返回这个坐标附近8个坐标的雷的数目,然后把show中此坐标的值设为返回值。这是一种简单的实现方法,但是如果我们想要实现游戏中一点出现一片的效果,单是这样是不够的,我们需要再进行分析:
如果这个坐标不是雷,并且它附近的坐标没有被检查过,并且这个附近坐标的值应为0,我们就对这个附近坐标再进行check操作,以此类推,(类似于DFS)直到这片check区域的边缘的坐标值大于0:
void check(char mine[][COLS], char show[][COLS], int x, int y) {
int cnt= (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');
show[x][y] = cnt+'0';
win--;
if (cnt == 0 && show[x - 1][y - 1] == '*'&&mine[x-1][y-1]!='1'&&x-1>=1&&x-1<=ROW&&y-1>=1&&y-1<=COL)check(mine, show, x - 1, y - 1);
if (cnt == 0 && show[x - 1][y ] == '*' && mine[x - 1][y ] != '1' && x-1 >= 1 && x-1 <= ROW && y >= 1 && y <= COL)check(mine, show, x - 1, y );
if (cnt == 0 && show[x - 1][y + 1] == '*' && mine[x - 1][y + 1] != '1' && x-1 >= 1 && x-1 <= ROW && y+1 >= 1 && y+1 <= COL)check(mine, show, x - 1, y + 1);
if (cnt == 0 && show[x ][y - 1] == '*' && mine[x ][y - 1] != '1' && x >= 1 && x <= ROW && y-1 >= 1 && y-1 <= COL)check(mine, show, x , y - 1);
if (cnt == 0 && show[x ][y + 1] == '*' && mine[x ][y + 1] != '1' && x >= 1 && x <= ROW && y+1 >= 1 && y+1 <= COL)check(mine, show, x , y + 1);
if (cnt == 0 && show[x + 1][y - 1] == '*' && mine[x + 1][y - 1] != '1' && x+1 >= 1 && x+1 <= ROW && y-1 >= 1 && y-1 <= COL)check(mine, show, x +1, y - 1);
if (cnt == 0 && show[x + 1][y ] == '*' && mine[x + 1][y ] != '1' && x+1 >= 1 && x+1 <= ROW && y >= 1 && y <= COL)check(mine, show, x +1, y );
if (cnt == 0 && show[x +1][y + 1] == '*' && mine[x + 1][y + 1] != '1' && x+1 >= 1 && x+1 <= ROW && y+1 >= 1 && y+1 <= COL)check(mine, show, x +1, y + 1);
return;
}
然后再包含一下头文件,就大功告成了!
图1.check效果
图2.游戏效果(EZ_MINE=3)