前言:
在博客上面已经有好多博主撰写过扫雷相关的游戏代码,而且写的也很全面,在这我就不再详细写出扫雷的代码。
但是对于扫雷中的一个难点就是递归展开,在这我可能会把重点放在递归展开。
完整代码放在结尾了,需要的自拿!!
扫雷棋盘设计和菜单打印:
菜单打印:
游戏菜单的打印在之前讲三子棋的时候已经讲过,在这里将代码给出:
void menu()
{
printf("****************************\n");
printf("*********** 1.play *********\n");
printf("*********** 0.exit *********\n");
printf("****************************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
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;
}
写game()函数:棋盘的设计和打印
这里需要注意我是将用户的棋盘和开发人员的棋盘分开创建。
分别命名为:用户棋盘:player[][] = {};
开发者棋盘:board[][] = {};
void game()
{
char board[ROWS][COLS] = { 0 };
char player[ROWS][COLS] = { 0 };
//设计开发人员棋盘
Creat_Board(board,ROWS,COLS);
//设计玩家所见棋盘
Creat_board(player, ROWS, COLS);
//打印开发人员棋盘
Print_Board(board,ROW,COL);
//打印玩家棋盘
Print_Board(player, ROW, COL);
//设置雷并打印开发棋盘
创建一个game.h的头文件。
#pragma once #include<stdio.h> #include<stdlib.h> #define COLS 11 #define ROWS 11 #define Mine 10
代码如下:
void game()
{
char board[ROWS][COLS] = { 0 };
char player[ROWS][COLS] = { 0 };
//设计开发人员棋盘
Creat_Board(board,ROWS,COLS);
//设计玩家所见棋盘
Creat_board(player, ROWS, COLS);
//打印开发人员棋盘
Print_Board(board,ROW,COL);
//打印玩家棋盘
Print_Board(player, ROW, COL);
}
这里我将打印用户和开发者的棋盘都打印出来,方便布置雷的时候可以看到雷的位置,也方便后期的测试。大家可以后期可以隐藏掉开发者棋盘,只保留用户所棋盘。
开发者棋盘:初始化全‘0’(字符0)
用户棋盘:初始化‘*’
在game.c的源文件写创建棋盘的函数和打印棋盘的函数,后期如果需要就直接调用即可,当然需要注意将这些函数的头文件放在game.h中。
设置雷的位置:
我们在开发者的棋盘中设计雷的位置。
Set_mine(board, ROW, COL);
实现Set_mine(board, ROW, COL)函数:
这里设置10个雷,用字符‘1’代表雷的位置(这里用字符‘1’在后期计算雷的数量的时候是有用的)
void Set_mine(char board[ROWS][COLS], int row, int col)
{
int a = 0;
while (a != Mine)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
a++;
}
}
}
玩家扫雷并显示周围雷的数量:
当雷布置完成后,玩家需要输入坐标
1、如果没有踩中雷,就需要显示它的周围8个位置的雷的数量。
2、如果踩中雷,就需要打印被炸死游戏结束。
3、如果越界输入坐标,就需要显示非法输入,请重新输入坐标。
void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col) { //玩家输入坐标 int x = 0; int y = 0; int mine = row*col-Mine; //scanf("%d %d", &x, &y); while (mine) { printf("请输入坐标<<"); scanf("%d %d", &x, &y); if (board[x][y] == '1') { printf("Oh,No你被炸死了\n"); Print_Board(board, ROW, COL); break; } else if(x>=1&&x<=9&&y>=1&&y<=9) { player[x][y] = Num_mine(board, x, y)+'0'; Print_Board(player, ROW, COL); mine--; } else { printf("非法输入,请重新输入\n"); } } }
每次排完一个坐标的mine就减1,一共是9*9个格子,雷的数量是10个,也就是我们需要排71个格子(亲自点开71个格子)才能取得胜利,这样做和根本不想玩这个游戏了,有些不必要的格子没有必要再点。
设计自动展开递归函数:
如果我们写一个函数 能帮我们把一些不要的区域自动展开,就像这样:自动展开一片
自动展开一片的要求:
1、保证该坐标周围8个坐标没有雷
2、如果选择的坐标周围8个坐标没有雷,就将用户棋盘上相对应的点置为‘ ’,然后向上下左右移一个坐标。
例如选择坐标(x,y)没有雷,就将player[x][y] = ' ' ,之后这个坐标应该变成(x-1,y)、(x+1,y)、(x,y+1)、(x,y-1)
3、移动后的坐标如果周围8个格子还是没有雷那么还是进行2中的操作,不过这里需要注意的是,假设第一个排查的是(x,y)周围8个没有雷,第二个排查的是(x-1,y),第二个周围8个也没有雷,之后坐标要向上下左右移动,但是如果向移动坐标又会变成原先的(x,y),造成了死递归的情况。
4、递归展开不能越界,所以x,y的范围只能是棋盘的范围。
5、当展到的某个坐标周围8个坐标有雷时就不再展了,并且将该格子上面显示周围雷的数量。
图示分析 :
如图所示:
图一为开发者所见棋盘,图二是玩家棋盘
第一步:选定(x,y),并判断周围8个坐标是不是有雷,有的话不递归直接上面显示雷的数量
没有的话玩家棋盘该坐标置为空格,并向四周展开。
第二步,判断展开的x,y有没有越界,如果越界了,就不用展开了,发现不能向下展开,但是向上左右可以展开。之后判断上左右三个点的坐标周围8个坐标有没有雷。
发现左右两个坐标周围没有雷,所以置为空格,但是上面的一个坐标周围有一个雷,所以置为1(这里我用红色代替)
第三步:以刚刚展开的三个坐标各自为中心,然后继续向4个方向展开。
以下三种情况不需要展开:
1、如果是之前展过的坐标就不需要展开。
2、如果越界了就不需要展开。
3、如果自身周围有雷了就不需要再展开。
所以只需要展左右两个坐标,以左右两个坐标为中心,发现:
左边的坐标只能向上展,向左,向下展越界了,向右展发现已将展过了,所以只能向上展。
右边的坐标能向上、右展。
接下来的几步都是重复前面三步,直到没有坐标可以展为止。
最终效果如图所示:
红色表示周围有一个雷;
蓝色表示周围有两个雷:
函数实现:
计算周围雷数量的函数:
int Num_mine(char board[ROWS][COLS],int x,int y)//排查一个坐标周围雷的数量
{
return board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] + board[x + 1][y] + board[x + 1][y - 1]
+ board[x + 1][y + 1]-8*'0';
}
需要注意的是这里返回值我设置成整型,也就是之前为什么把棋盘都初始化为‘0’,雷设计成‘1’的原因,字符‘1’如何变成整型‘1’,只需要减去字符‘0’。
递归函数的设计:
1、保证该坐标周围8个坐标没有雷
2、如果选择的坐标周围8个坐标没有雷,就将用户棋盘上相对应的点置为‘ ’,然后向上下左右移一个坐标。
例如选择坐标(x,y)没有雷,就将player[x][y] = ' ' ,之后这个坐标应该变成(x-1,y)、(x+1,y)、(x,y+1)、(x,y-1)
3、移动后的坐标如果周围8个格子还是没有雷那么还是进行2中的操作,不过这里需要注意的是,假设第一个排查的是(x,y)周围8个没有雷,第二个排查的是(x-1,y),第二个周围8个也没有雷,之后坐标要向上下左右移动,但是如果向移动坐标又会变成原先的(x,y),造成了死递归的情况。
4、递归展开不能越界,所以x,y的范围只能是棋盘的范围。
5、当展到的某个坐标周围8个坐标有雷时就不再展了,并且将该格子上面显示周围雷的数量。
void _mine(char board[ROWS][COLS] ,char player[ROWS][COLS], int x, int y)//展开一片区域
{
int ret = Num_mine(board, x, y);
if (ret == 0 && x > 0&&x<=9 && y > 0&&y<=9 && player[x][y] != ' ')
{
player[x][y] = ' ';
_mine(board,player, x - 1, y);
_mine(board, player, x + 1, y);
_mine(board, player, x, y - 1);
_mine(board, player, x, y + 1);
}
else if (ret != 0)
{
player[x][y] = ret + '0';
}
}
大家可以作为参考,自行设计类似的递归函数。
判断输赢:
有了递归函数,可以自行展开一片,就不需要刚刚的必须要亲自点开71个格子了。
这里们可以想想,我们一直排雷,什么时候算结束?
也就是当棋盘上的81个位置只剩下10个格子没有点开。
所以这里我才用遍历的方式,设计一个函数,如果棋盘上有‘*’就++,直到这个数量等于10的时候就赢了。
当满足条件了就返回1,不满足返回0;
int Iswin(char player[ROWS][COLS], int row, int col) { int sum = 0; int i = 0; int j = 0; for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { if (player[i][j] == '*') sum++; } } if (sum == 10) { return 1; } return 0; }
在Player_mine函数中加入判断语句即可。
if (Iswin(player,ROW,COL)) { printf("恭喜你,扫雷成功\n"); break; }
main.c源文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void game()
{
char board[ROWS][COLS] = { 0 };
char player[ROWS][COLS] = { 0 };
//设计开发人员棋盘
Creat_Board(board,ROWS,COLS);
//设计玩家所见棋盘
Creat_board(player, ROWS, COLS);
//打印开发人员棋盘
//Print_Board(board,ROW,COL);
//打印玩家棋盘
Print_Board(player, ROW, COL);
//设置雷并打印开发棋盘
Set_mine(board, ROW, COL);
//Print_Board(board, ROW, COL);
//玩家扫雷
Player_mine(board,player,ROW,COL);
}
void menu()
{
printf("****************************\n");
printf("*********** 1.play *********\n");
printf("*********** 0.exit *********\n");
printf("****************************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择<<");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("您已退出\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
//char board[ROWS][COLS] = {0};
//char player[ROWS][COLS] = {0};
//设计棋盘
return 0;
}
game.c源文件
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void Creat_Board(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = '0';
}
}
}
void Creat_board(char board[ROWS][COLS], int rows, int cols)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = '*';
}
}
}
void Print_Board(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("————————————扫雷——————————————\n");
for (i = 0; i <= col; 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");
}
}
//void Print_Board(char board[ROWS][COLS], int rows, int cols)
//{
// int i = 0;
// printf("————————————扫雷——————————————\n");
// printf("\n");
// for (i = 0; i < rows; i++)
// {
// int j = 0;
// for (j = 0; j < cols; j++)
// {
// printf("%c", board[i][j]);
// }
// printf("\n");
// }
//}
void Set_mine(char board[ROWS][COLS], int row, int col)
{
int a = 0;
while (a != Mine)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
a++;
}
}
}
int Num_mine(char board[ROWS][COLS],int x,int y)//排查一个坐标周围雷的数量
{
return board[x - 1][y] + board[x - 1][y - 1] + board[x - 1][y + 1] +
board[x][y - 1] + board[x][y + 1] + board[x + 1][y] + board[x + 1][y - 1]
+ board[x + 1][y + 1]-8*'0';
}
void _mine(char board[ROWS][COLS] ,char player[ROWS][COLS], int x, int y)//展开一片区域
{
int ret = Num_mine(board, x, y);
if (ret == 0 && x > 0&&x<=9 && y > 0&&y<=9 && player[x][y] != ' ')
{
player[x][y] = ' ';
_mine(board,player, x - 1, y);
_mine(board, player, x + 1, y);
_mine(board, player, x, y - 1);
_mine(board, player, x, y + 1);
}
else if (ret != 0)
{
player[x][y] = ret + '0';
}
}
int Iswin(char player[ROWS][COLS], int row, int col)
{
int sum = 0;
int i = 0;
int j = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (player[i][j] == '*')
sum++;
}
}
if (sum == 10)
{
return 1;
}
return 0;
}
void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col)
{
//玩家输入坐标
int x = 0;
int y = 0;
//scanf("%d %d", &x, &y);
while (1)
{
printf("请输入坐标<<");
scanf("%d %d", &x, &y);
if (board[x][y] == '1')
{
printf("Oh,No你被炸死了\n");
Print_Board(board, ROW, COL);
break;
}
else if (x >= 1 && x <= row && y >= 1 && y <= col)
{
/*player[x][y] = Num_mine(board, x, y)+'0';
Print_Board(player, ROW, COL);*/
_mine(board, player, x, y);
Print_Board(player, ROW, COL);
if (Iswin(player,ROW,COL))
{
printf("恭喜你,扫雷成功\n");
break;
}
}
else
{
printf("非法输入,请重新输入\n");
}
}
}
game.h头文件
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define COLS 11
#define ROWS 11
#define Mine 10
#define COL COLS-2
#define ROW ROWS-2
void Creat_Board(char board[ROWS][COLS], int rows, int cols);
void Creat_board(char board[ROWS][COLS], int rows, int cols);
void Print_Board(char board[ROWS][COLS], int row, int col);
void Set_mine(char board[ROWS][COLS], int row, int col);
//void Print_Board(char board[ROWS][COLS], int row, int col);
void Player_mine(char board[ROWS][COLS], char player[ROWS][COLS], int row, int col);