扫雷是一款经典的小游戏,今天带大家用C语言实现这款小游戏。
游戏规则
扫雷规则如下:在一个X*Y的棋盘上,分布着N颗雷,找出N颗雷且不踩雷便胜利。
棋盘如图1所示:
图1 扫雷棋盘
当我们点击某一坐标时,存在两种可能,有雷或无雷,有雷判定为输,无雷着继续游戏,并且会标记出该坐标附近3*3的单元格内有几颗雷,如图2所示。
图2 扫雷示意图
以上为游戏的基本规则,以下开始相关编程介绍。
扫雷程序编写
1、整体思路
我们计划编写一个棋盘大小为9*9,总共10颗雷的扫雷游戏。因此我们需要一个二维数组来充当我们的棋盘,我们可以输入数组对应的下标完成对棋盘的操作。
整体的逻辑如下:
1、初始化棋盘,即初始化二维数组,保证我们每一次游玩都不会受先前的影响。
2、在棋盘上设置10颗雷。
3、显示棋盘。
4、用户输入。
5、根据用户输入坐标判断该处是否有雷,有雷则游戏结束,无雷则判断周围8个坐标雷的个数,将雷的个数打印在该坐标出,如图2所示。
6、找出全部雷后,判定用户胜利。
2、初始化棋盘
思路理清后,我们就可以开始编程了。首先是初始化棋盘。
在我们上面的思考中,可以发现如果埋雷,判断,输入坐标,显示雷等一系列操作都在一个二维数组中完成的话,未免有些太麻烦了,因此我们可以建立两个大小相同的二维数组,一个数组用来埋雷,一个数组用来显示。如下所示:
char mine[ROWS][COLS] = {0};
char surface[ROWS][COLS] = { 0 };
而考虑到我们每输入一个坐标,就会像图2那样判断坐标周围雷的个数,如果出现图3的情况,会发生越界,因此,我们需要将棋盘的每一条边长度加一,防止越界。
图3 当坐标位于棋盘边缘时,发生越界
因此我们设定:
#define ROW 9
#define COL 9
#define ROWS (ROW+2)
#define COLS (COL+2)
在完成以上思考后,终于可以实现棋盘的初始化了。为了方便我们下一步操作,在雷区数组全部写入“ 0 ”,在显示数组全部写入“ * ”,程序如下:
void InitMineBoard(char mine[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
mine[i][j] = '0';
}
}
}
void InitSurBoard(char surface[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
surface[i][j] = '*';
}
}
}
3、显示棋盘
该部分较为简单,直接打印我们想让用户看到的部分数组即可,代码如下:
void DisplayBoard(char Board[ROWS][COLS], int rows, int cols)
{
int i = 0;
int j = 0;
for (i = 0; i < rows - 1; i++)//打印列号,方便观察
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i < rows-1; i++)
{
printf("%d ", i);//打印行号,方便观察
{
for (j = 1; j < cols-1; j++)
{
printf("%c ", Board[i][j]);
}
printf("\n");
}
printf("\n");
}
4、设置地雷
接下来,我们需要在雷区数组内设置10颗雷,即横纵坐标在1~9范围内随机设置10颗雷。这里我们可以采用随机数对9取余再加一的方式来设置雷,每设定好一颗雷后计数器加一,直到等于10后退出循环,完成设置。程序如下:
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int n = 0;
while (n!=10)
{
x = 1 + rand() % 9;
y = 1 + rand() % 9;
if (mine[x][y] != '1')
{
mine[x][y] = '1';
n++;
}
}
}
5、玩家操作
玩家的操作方式为输入对应坐标判断是否有雷。如果输入坐标正确且没有下过子,则输入成功。程序判断是否有雷,有雷则游戏结束,无雷则游戏继续并统计周围雷的个数。程序如下:
int PlayerMove(char surface[ROWS][COLS], char mine[ROWS][COLS], int rows, int cols)
{
int x = 0;
int y = 0;
printf("请输入坐标:");
while (1)
{
int n = 0;
scanf("%d %d", &x, &y);
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
n = JudgeMine(surface,mine,x,y);
if (n != 9)
{
if (surface[x][y] == '*')
{
/*surface[x][y] = n+'0';*/
SpaceGen(surface, mine, x, y);
return 1;
}
if (surface[x][y] != '*')
{
printf("已有坐标,请重新输入:");
}
}
if (n == 9)
{
printf("你输了!\n");
return 0;
}
}
else
printf("输入非法,请重新输入:");
}
}
6、判断是否有雷
首先根据输入的坐标,在雷区数组中判断该坐标是否有雷,有则返回9,无则跟据周围雷的个数返回0~8.程序如下:
int JudgeMine(char surface[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//判断选定点周围雷的个数
{
int n = 0;
int flag = 0;
int i = 0;
int j = 0;
if (mine[x][y] == '1')//找到雷后,返回9,因为周围最多有8个雷
{
flag = 9;
return flag;
}
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
{
n++;
}
}
}
return n;
}
7、输赢判断
输游戏很简单,踩中一颗雷就输,赢游戏很难,需要找出全部的雷。因此,胜利往往是在已经找完了整个棋盘之后才会有。
因此,我们可以知道,在找完了整个棋盘且没有踩雷的情况下,只有10个“ * ”存在于棋盘中,通过计算剩余“ * ”的个数就可以判断用户是否胜利。程序如下:
int IsWin(char surface[ROWS][COLS], char mine[ROWS][COLS], int rows, int cols)//当只剩下10个" * "时,表明雷已全部找出
{
int i = 0;
int j = 0;
int n = 0;
for (i = 1; i < 9; i++)
{
for (j = 1; j < 9; j++)
{
if (surface[i][j] == '*')//判断已经找出雷的个数
{
n++;
}
}
}
if (n == 10)
return 1;//结束游戏
else
return 2;//继续
}
8、总结
自此,我们的扫雷程序已经编写完毕,根据总体思路,将各子程序写入主程序中,主程序如下:
#include <stdio.h>
#include "game.h"
void menu()
{
printf("********************\n");
printf("****** 1 play ******\n");
printf("****** 0 exit ******\n");
printf("********************\n");
}
void game()
{
int flag = 0;
char mine[ROWS][COLS] = {0};
char surface[ROWS][COLS] = { 0 };
InitMineBoard(mine, ROWS, COLS);
InitSurBoard(surface,ROWS,COLS);
SetMine(mine, ROWS, COLS);
DisplayBoard(mine, ROWS, COLS);
while (1)
{
DisplayBoard(surface, ROWS, COLS);
flag = PlayerMove(surface, mine, ROWS, COLS);
if (flag == 0)
break;
flag = IsWin(surface, mine, ROWS, COLS);
if (flag == 1)
{
printf("你赢了!\n");
DisplayBoard(surface, ROWS, COLS);
DisplayBoard(mine, ROWS, COLS);
break;
}
}
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
printf("game start\n");
game();
break;
case 0:
printf("game end\n");
break;
default:
printf("非法输入,请重新输入\n");
break;
}
} while (input);
return 0;
}
进阶相关
在以上程序的编写中,我们实现了扫雷的基本功能,但是一个一个的去点未免太麻烦了,一个9*9大小的棋盘需要71步操作。而在我们常见的扫雷中,经常由如图4一样的,点一下出来一大片的情况。该方式极大地方便了我们继续游戏。
图4
而要实现该功能,我们可以采用递归的方式完成。递归的结束条件有两个:
1、判断的坐标接触到边界
2、判断的坐标周围有雷
在明确了以上两个条件以后,我们就可以开始递归了,原理如下:
如果判断的坐标周围没有雷,则向下一个坐标移动,判断下一个坐标周围是否有雷。
程序如下:
void SpaceGen(char surface[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
if (x == 0 || y == 0 || x == ROWS - 1 || y == COLS - 1)//判断是否越界
return;
if (surface[x][y] != '*')
return;
int n = JudgeMine(surface, mine, x, y);
if (n > 0)
{
surface[x][y] = n + '0';
return;
}
if (n == 0)
{
surface[x][y] = ' ';
SpaceGen(surface, mine, x - 1, y - 1);
SpaceGen(surface, mine, x - 1, y);
SpaceGen(surface, mine, x, y + 1);
SpaceGen(surface, mine, x, y - 1);
SpaceGen(surface, mine, x, y + 1);
SpaceGen(surface, mine, x + 1, y - 1);
SpaceGen(surface, mine, x + 1, y);
SpaceGen(surface, mine, x + 1, y + 1);
}
}
总结
至此,扫雷程序的编写完成。效果如图5所示,该程序比较简单,但是值得思考的地方很多,包括防止数组越界,胜负判断等等,希望这篇博文对大家有所帮助!
图5 效果演示