一、初级版扫雷
1.1 游戏功能
初级版只具备最基础的两个功能:
1、显示当前输入坐标周围雷的数目
2、排雷错误,则游戏结束
3、当排除所有的非雷区域后,取得胜利
1.2 设计思路
我们以9*9的格子(我们称为棋盘)为例。
首先,我们需要两个数组,其中一个用来布置地雷的位置(这个数组不对玩家显示),另一个用来显示排雷的信息。
假设我们现在要统计坐标(1,1)和(3,4)周围雷的数目,坐标(3,4)需要统计周围8个位置是否有雷,坐标(1,1)只用统计周围三个位置是否有雷,在棋盘中,大多数格子都被8个格子所包围,而边界位置旁边有3或5个格子,如果分别进行判断的话,需要讨论多种情况,因此我们将棋盘扩大为11 *11。有效区域只有中间的9 *9部分,这样对于每一个位置,都是统计周围8个格子是否有雷。
运行效果:
在游戏开始前,我们打印一个游戏菜单供玩家进行选择,当玩家选择了开始游戏后,我们进入游戏模块。
void menu()
{
printf("**********************************************\n");
printf("********请选择-> 1:开始游戏 0:结束游戏****\n");
printf("**********************************************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); //初始化种子
do
{
menu(); //打印菜单
scanf("%d",&input);
switch (input)
{
case 1:
printf("扫雷游戏开始:\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
当玩家选择开始游戏后,我们要先创建两个数组,并将其初始化。存放地雷的数组,先将其全部初始化为字符’0’;存放排雷信息的数组先全部初始化为字符 ’ * '。
游戏模块:
void game()
{
char Myboard[ROWS][COLS] = { 0 }; //用来存放地雷
char ShowBoard[ROWS][COLS] = { 0 }; //用来显示排雷的信息
InitBoard(Myboard, ROWS, COLS, '0'); //将地雷棋盘全部初始化为0
InitBoard(ShowBoard, ROWS, COLS, '*'); //将显示棋盘全部初始化为*
SetBoard(Myboard, ROW, COL); //布雷
PrintBoard(ShowBoard, ROWS, COLS); //打印棋盘
FindBoard(Myboard,ShowBoard, ROWS, COLS); //排雷
}
初始化模块:
void InitBoard(char board[ROWS][COLS], int row, int col, char c) //初始化棋盘
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
board[i][j] = c;
}
}
然后我们使用srand和rand函数随机布置地雷的位置(srand函数要放在主函数的循环体外)因为棋盘的有效区域只有中间的9 *9部分,因此我们要确保rand产生的随机数在1-9这个范围内。
void SetBoard(char board[ROWS][COLS], int row, int col) //布雷
{
for (int cnt = 0; cnt < COUNT;)
{
int x = rand() % row + 1; //随机产生横纵坐标
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
cnt++;
}
}
}
当布置好雷之后,我们打印显示棋盘,在打印的同时打印上横纵序号,方便玩家确定坐标
void PrintBoard(char board[ROWS][COLS], int row, int col) //打印棋盘
{
printf(" ");
for (int i = 1; i < row - 1; i++) //打印列坐标
printf("%d ", i);
printf("\n");
for (int i = 1; i < row - 1; i++)
{
printf("%d ", i); //打印横坐标
for (int j = 1; j < col - 1; j++)
printf("%c ", board[i][j]);
printf("\n");
}
}
最后是游戏的核心部分:排雷
当玩家输入的坐标不是雷,统计周围8个位置雷的数目,可以用8个if语句或者循环语句来统计,此处我将每个位置的字符相加,最后减去8个’0’,也是雷的数目。
int Num(char board[ROWS][COLS], int x, int y) //统计当前位置周围有几个雷
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] +
board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
//如果是雷,则对应的坐标位置是字符 '1',否则是字符'0',将周围八个位置的字符全部相加最后减去8个字符'0',就是周围雷的数目
//'1' - '0' = 1(数字1) '0' - '0' = 0(数字0)
}
void FindBoard(char board[ROWS][COLS], char ShowBoard[ROWS][COLS], int row, int col) //排雷
{
int x = 0;
int y = 0;
int cnt = ROW * COL - COUNT; //cnt表示非雷的数目,即棋盘大小减去地雷数
while (1)
{
printf("请输入坐标\n");
scanf("%d%d", &x, &y);
if (x < 1 || x >row || y < 1 || y > col)
{
printf("坐标非法,请重新输入\n");
}
else
{
if (board[x][y] == '1') //当前坐标位置是雷,游戏结束
{
printf("你失败了,游戏结束\n");
PrintBoard(board, row, col);
break;
}
else if (board[x][y] == '0') //当前坐标不是雷,统计周围雷的数目
{
if (0 == Num(board, x, y)) //如果周围没有雷,则当其置为空
{
ShowBoard[x][y] = ' ';
}
else ShowBoard[x][y] = Num(board, x, y) + '0'; //周围有雷,将其置为雷的个数
//(因为我们打印的是字符型变量,因此要加上'0'才是所对应的数字字符)
cnt--; //每排完一个雷,非雷的数目减1
PrintBoard(ShowBoard, row, col); //打印当前显示棋盘的信息
}
}
if (cnt == 0)
{
printf("恭喜你,成功排雷\n");
PrintBoard(board, row, col);
break;
}
}
}
我们将代码分别封装在main.c和game.c两个文件中,并在game.h头文件中对函数进行声明
main.c
#include"game.h"
void menu()
{
printf("**********************************************\n");
printf("********请选择-> 1:开始游戏 0:结束游戏****\n");
printf("**********************************************\n");
}
void game()
{
char Myboard[ROWS][COLS] = { 0 }; //用来存放地雷
char ShowBoard[ROWS][COLS] = { 0 }; //用来显示排雷的信息
InitBoard(Myboard, ROWS, COLS, '0'); //将地雷棋盘全部初始化为0
InitBoard(ShowBoard, ROWS, COLS, '*'); //将显示棋盘全部初始化为*
SetBoard(Myboard, ROW, COL); //布雷
PrintBoard(ShowBoard, ROWS, COLS);
FindBoard(Myboard,ShowBoard, ROWS, COLS); //排雷
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL)); //初始化种子
do
{
menu(); //打印菜单
scanf("%d",&input);
switch (input)
{
case 1:
printf("扫雷游戏开始:\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
game.c
void InitBoard(char board[ROWS][COLS], int row, int col, char c) //初始化棋盘
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
board[i][j] = c;
}
}
void PrintBoard(char board[ROWS][COLS], int row, int col) //打印棋盘
{
printf(" ");
for (int i = 1; i < row - 1; i++) //打印列坐标
printf("%d ", i);
printf("\n");
for (int i = 1; i < row - 1; i++)
{
printf("%d ", i); //打印横坐标
for (int j = 1; j < col - 1; j++)
printf("%c ", board[i][j]);
printf("\n");
}
}
void SetBoard(char board[ROWS][COLS], int row, int col) //布雷
{
for (int cnt = 0; cnt < COUNT;)
{
int x = rand() % row + 1; //随机产生横纵坐标
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
cnt++;
}
}
}
int Num(char board[ROWS][COLS],int x,int y) //统计当前位置周围有几个雷
{
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] +
board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}
void FindBoard(char board[ROWS][COLS], char ShowBoard[ROWS][COLS], int row, int col) //排雷
{
int x = 0;
int y = 0;
int cnt = ROW * COL - COUNT; //cnt表示非雷的数目,即棋盘大小减去地雷数
while (1)
{
printf("请输入坐标\n");
scanf("%d%d", &x, &y);
if (x < 1 || x >row || y < 1 || y > col)
{
printf("坐标非法,请重新输入\n");
}
else
{
if (board[x][y] == '1') //当前坐标位置是雷,游戏结束
{
printf("你失败了,游戏结束\n");
PrintBoard(board, row, col);
break;
}
else if (board[x][y] == '0') //当前坐标不是雷,统计周围雷的数目
{
if (0 == Num(board, x, y)) //如果周围没有雷,则当其置为空
{
ShowBoard[x][y] = ' ';
}
else ShowBoard[x][y] = Num(board, x, y) + '0'; //周围有雷,将其置为雷的个数
//(因为我们打印的是字符型变量,因此要加上'0'才是所对应的数字字符)
cnt--; //每排完一个雷,非雷的数目减1
PrintBoard(ShowBoard, row, col); //打印当前显示棋盘的信息
}
}
if (cnt == 0) //所有非雷位置均排查完
{
printf("恭喜你,成功排雷\n");
PrintBoard(board, row, col);
break;
}
}
}
二、优化版扫雷
2.1 优化功能
在初级版扫雷中,我们必须一个一个地将所有非雷位置全部排查完才能完成游戏的胜利,提示的信息特别少,并且如果运气不好,第一次就可能排到雷的位置。在优化版扫雷中,增加了自动展开、防止第一次就排到雷的情况同时增加了标记功能。
2.2 设计思路
1、展开功能
当输入一个坐标,如果它的周围没有雷,则将其周围不是雷的区域展开,如果它周围的格子周围也没有雷,则继续展开。
同时为了防止数组越界,在统计雷的数目函数里增加一个if语句来判断。同时为了记录在展开函数中,展开了多少个格子,我们将展开函数返回值设为int,用来记录展开了多少格子。
int Num(char board[ROWS][COLS],int x,int y) //统计当前位置周围有几个雷
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) //防止数组越界
return board[x - 1][y - 1] + board[x - 1][y] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] +
board[x + 1][y - 1] + board[x + 1][y] + board[x + 1][y + 1] - 8 * '0';
}
int Blank(char board[ROWS][COLS], char ShowBoard[ROWS][COLS], int x, int y) //判断当前位置周围是不是没有雷
{
int count = 0; //展开的格子数
for (int i = x - 1; i <= x + 1 && i >=0 && i <= ROW; i++)
{
for (int j = y - 1; j <= y + 1 && j >=0 && j <=COL; j++)
{
if (i == x && j == y) //跳过坐标为(x,y)的位置
;
else if (ShowBoard[i][j] == '*' && board[i][j] != '1') //如果坐标(i,j)处没有被初始化并且不为雷,判断其周围有没有雷
{ //如果周围也没有雷,将其展开
int cnt = Num(board, i, j);
if (cnt == 0)
{
ShowBoard[i][j] = ' ';
count += Blank(board, ShowBoard, i, j); //继续判断这个坐标周围的位置是否需要展开
}
else ShowBoard[i][j] = cnt + '0';
if (i >= 1 && i <= ROW && j >= 1 && j <= COL) //当展开的格子在有效棋盘范围内时,count++
count++;
}
}
}
return count; //总共展开了多少个格子
}
2、防止第一次就排到雷
如果运气不好,第一次就排到雷,则重置这个雷的位置。
3、在排雷过程中,让玩家选择排雷和标记,当选择标记,将输入的坐标置为’#’
void FindBoard(char board[ROWS][COLS],char ShowBoard[ROWS][COLS],int row, int col) //排雷
{
int cnt = ROW * COL - COUNT;
int time = 1; //time为1时表示是第一步
int over = 0;
//PrintBoard(board, row, col);
while (1)
{
printf("请选择-> \n1、排雷 2、标记\n");
int choose = 0;
scanf("%d", &choose);
switch (choose)
{
case 1:
while (1) //排雷循环
{
printf("请输入排雷的坐标\n");
int x = 0;
int y = 0;
scanf("%d%d", &x, &y);
if (x < 1 || x >row || y < 1 || y > col)
{
printf("坐标非法,请重新输入\n");
}
else
{
if (board[x][y] == '1')
{
if (time) //如果第一次就遇见雷,则重置这个雷的位置
{
board[x][y] = '0';
while (1) //重置第一次雷的位置
{
int x1 = rand() % row + 1;
int y1 = rand() % col + 1;
if (board[x1][y1] == '0')
{
board[x1][y1] = '1';
break; //跳出重置第一次雷的循环
}
}
time = 0; //time置为0表示不是第一次
if (0 == Num(board, x, y))
{
ShowBoard[x][y] = ' ';
int count = Blank(board, ShowBoard, x, y); //如果重置之后,周围没有雷,展开这个坐标
cnt -= count;
}
else ShowBoard[x][y] = Num(board, x, y) + '0';
cnt--;
PrintBoard(ShowBoard, row, col);
goto end; //第一次排到雷,判断是否排完
}
else //不是第一次
{
over = 1;
break; //跳出排雷循环
}
}
else if (board[x][y] == '0')
{
time = 0;
if (0 == Num(board, x, y))
{
ShowBoard[x][y] = ' ';
int count = Blank(board, ShowBoard, x, y); //如果周围没有雷,进行展开
cnt -= count;
}
else ShowBoard[x][y] = Num(board, x, y) + '0';
cnt--;
PrintBoard(ShowBoard, row, col);
break; //跳出排雷循环
}
}
}
break; //跳出switch
case 2:
printf("请输入标记的坐标\n");
while (1) //标记循环
{
int x1 = 0;
int y1 = 0;
scanf("%d%d", &x1, &y1);
if (x1< 1 || x1>row || y1<1 || y1>col)
printf("坐标越界,请重新输入\n");
else if (ShowBoard[x1][y1] != '*')
printf("此处坐标已排查过,请重新输入\n");
else if (ShowBoard[x1][y1] == '*')
{
ShowBoard[x1][y1] = '#';
PrintBoard(ShowBoard, row, col);
break; //跳出标记循环
}
}
break; //跳出switch
default:
printf("输入错误,请重新输入\n");
break; //跳出switch
}
end :
if (over)
{
printf("你失败了,游戏结束\n");
PrintBoard(board, row, col);
break; //游戏结束
}
if (cnt == 0)
{
printf("恭喜你,成功排雷\n");
PrintBoard(board, row, col);
break; //游戏结束
}
}
}
运行结果:雷数为10
雷数为1:
雷数为2:
雷数为79(这里我们打印出雷的布局,方便我们测试):
雷数为80(随便输入一个坐标都会取得胜利):