目录
引言
扫雷这个游戏想必大家都不陌生。如果你知道扫雷游戏的规则,那么你可以跳过这一段,直接到main函数的整体逻辑这里开始。如果你还不知道扫雷怎么玩,我也给你复制好了游戏规则。
胜利条件:
在一个9*9方块矩阵中随机布置一定量的地雷10个。由玩家逐个翻开方块,以找出所有地雷为最终游戏目标。如果玩家翻开的方块有地雷,则游戏结束。
游戏主区域由很多个方格组成。使用鼠标左键随机点击一个方格,方格即被打开并显示出方格中的数字;方格中数字则表示其周围的8个方格隐藏了几颗雷。
在判断出不是雷的方块上按下左键(我们的是输入坐标),可以打开该方块。如果方块上出现数字,则该数字表示其周围3×3区域中的地雷数,一般为8个格子,对于边块为5个格子,对于角块为3个格子。所以扫雷中最大的数字为8;如果方块上为空(相当于0),则可以递归地打开与空相邻的方块;如果不幸触雷,则游戏结束。
main函数的整体逻辑
int main()
{
srand((unsigned int)time(NULL));//暂时不管
int choice = 0;
do
{
printf(" 游戏介绍: \n");
printf("输入 1 为开始游戏,输入 0 为退出游戏\n");
printf("游戏最终的胜利即为把全部的雷标记出来\n");
menu();
printf("请选择\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
{
game();
break;
}
case 0:
{
printf("游戏退出\n");
break;
}
default:
{
printf("选择错误,重新选择\n");
break;
}
}
} while (choice);
return 0;
}
这里的逻辑与上一篇三子棋【编程之路(006)三子棋游戏】(C语言实现)_p_fly的博客-优快云博客一样,也很简单,如果不太明白,可以点击链接进去看看。接下来我们便实现游戏各个函数的功能。
菜单的打印
void menu()
{
printf("************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("************************\n");
}
初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = ch;
}
}
}
这里要注意的是我们扫雷游戏其实是有两个棋盘(二维数组实现)的。一个是玩家玩的棋盘界面;另一个是我们布置雷区的棋盘。有了整体的把控,我们对于游戏的实现便有了大致的方向。
由于扫雷要探索周围一圈的区域,也就是八个格子,对于中间的格子还好,我们可以很好的表示,但是对于边缘和四角的的格子,我们探索周围就会出现越界的情况(如下图)。解决方法是直接定义比棋盘规模多两行的行列。如9*9的规模我们便定义成(9+2)*(9+2)的规模。先说明ROWS和COLS是9+2的规模,ROW和COL是9的规模。那么为什么还要多次一举定义出ROW和COL呢,那是因为虽然我们的规模大,但还是有很多操作是需要用到9*9的。接下来其他功能的实现便可以很直观的看到。
棋盘的打印
void Display(char board[ROWS][COLS], int row, int col)
{
printf("-------------------\n");
int i = 0, j = 0;
for (j = 0; j <= col; j++)//这个for循环是打印棋盘的列坐标
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);//这一行语句是打印棋盘的行坐标
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
这里所打印出来的棋盘是展示棋盘,而不是雷区棋盘。我们在函数传参的时候把'*'传个展示棋盘,让其保持待探索的感觉。效果如下
布置雷区
void SetMine(char board[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int count = AMOUNT;//AMOUNT是我们定义的雷的数量
while (count)
{
x = rand() % row + 1;//(0~8)+1==1~9
y = rand() % col + 1;//(0~8)+1==1~9
if (board[x][y] == '0')
{
board[x][y] = '1';//雷我们用字符'1'来表示,因为形参的类型是char
count--;
}
}
}
rand()函数和main()函数中的srand()函数随机生成了AMOUNT个雷。
对于rand函数和srand函数不懂的可以参考这一篇文章
rand()和srand()函数的用法_diluosixu的博客-优快云博客_srand和rand函数怎么用
确定周围雷数量值
int AmountMine(char board[ROWS][COLS], int x, int y)
{
return (board[x - 1][y] +
board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] - 8 * '0');
//减去8*'0':因为我们的雷区中都是字符型的'0'//非雷'1'//雷。
//所以如果有n个雷,就会产生n*'1'-n*'0'的整数。('1'-'0'=1)
}
对于玩家排查的一个(x,y)的格子,我们应该返回周围一圈有多少雷,如下图
标记雷
void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
show[x][y] = '!';
}
}
我们把标记的雷在展示数组上用'!'标记。
取消标记
void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
if (show[x][y] == '!')
{
show[x][y] = '*';
}
}
我们想把原来标记的取消,直接在改成'*'即可。
递归展开空白安全区
void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int around_x = 0;
int around_y = 0;
int count = 0;
//坐标合法
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
//遍历周围坐标
for (around_x = -1; around_x <= 1; around_x++)
{
for (around_y = -1; around_y <= 1; around_y++)
{
//如果这个坐标不是雷
if (mine[x + around_x][y + around_y] == '0')
{
//统计周围雷的个数
count = AmountMine(mine, x + around_x, y + around_y);
if (count == 0)
{
if (show[x + around_x][y + around_y] == '*')
{
show[x + around_x][y + around_y] = ' ';
Spread(mine, show, x + around_x, y + around_y);
}
}
//如果是雷就直接在展示棋盘上显示周围有几个雷的数字
else
{
show[x + around_x][y + around_y] = count + '0';
}
}
}
}
}
}
统计被正确标记的雷数
int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
int end = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (mine[i][j] == '1' && show[i][j] == '!')
//在雷区棋盘上是雷并且在展示棋盘上是'!'才是被标记的正确的雷
end++;
}
}
return end;
}
如果玩家把所有的雷标记出来,那么就直接胜利了。
扫雷的具体过程
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int n = 0, x = 0, y = 0;
int win = 0;//统计被标记的个数(可能被标记的都是雷,也有可能标错)
int flag = 0;//falg为0代表未取消标记,为1代表被取消标记//这是我为了方便展示棋盘而设置的
int fail = 0;//fail为0代表没有被炸死,为1代表被炸死
again:
while (win < AMOUNT)
{
printf("功能 1:排查雷 2:标记雷 3:取消标记\n");
printf("请选择功能和排查的坐标(功能 横 纵)\n");
scanf("%d %d %d", &n, &x, &y);
switch (n)
{
case 1:
break;
case 2:
{
FlagMine(show, mine, row, col, x, y);
win++;
break;
}
case 3:
{
FlagCancel(show, mine, row, col, x, y);
flag = 1;
win--;
break;
}
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1' && show[x][y] == '*' && flag == 0)
{
printf("你被炸死了,下面是雷区分布图 1为雷 0非雷\n");
Display(mine, ROW, COL);
fail = 1;
break;
}
else if (show[x][y] == '!')
Display(show, ROW, COL);
else if (flag == 1)
Display(show, ROW, COL);
else
{
int count = AmountMine(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';
Spread(mine, show, x, y);
Display(show, ROW, COL);
}
else
{
show[x][y] = count + '0';
Display(show, ROW, COL);
}
}
}
else
{
printf("输入错误,请重新输入");
}
}
if (AMOUNT == ClearMine(mine,show,row,col))//如果雷的数量都被标记出来,就成功。
{
printf("恭喜你,扫雷成功\n");
}
else if(fail == 1)//如果触发到fail为1,则说明爆炸过了,就失败了。
{
printf("请选择是否再来一把\n");
}
else
{
printf("标记的雷有误,请仔细检查,重新标记\n");
//到这里就是标记了至少一个安全区域,需要重新排雷
goto again;//goto语句直接跳到重新排雷的地方
}
}
源码
该部分代码为game.h文件中的
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define AMOUNT 10
//菜单
void menu();
//棋盘初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch);
//展示棋盘
void Display(char board[ROWS][COLS], int row, int col);
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col);
//选择坐标区域雷的数量
int AmountMine(char board[ROWS][COLS], int row, int col);
//扫雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//标记雷
void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y);
//取消标记
void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y);
//排查选择坐标区域周围是否安全
void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
//返回被标记的雷的个数
int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
该部分代码为game.c文件中的
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("************************\n");
}
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch)
{
int i = 0, j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = ch;
}
}
}
void Display(char board[ROWS][COLS], int row, int col)
{
printf("-------------------\n");
int i = 0, j = 0;
for (j = 0; j <= col; j++)//这个for循环是打印棋盘的列坐标
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);//这一行语句是打印棋盘的行坐标
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int count = AMOUNT;
while (count)
{
x = rand() % row + 1;//(0~8)+1==1~9
y = rand() % col + 1;//(0~8)+1==1~9
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int AmountMine(char board[ROWS][COLS], int x, int y)
{
return (board[x - 1][y] +
board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] - 8 * '0');
}
void FlagMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
show[x][y] = '!';
}
}
void FlagCancel(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col, int x, int y)
{
if (show[x][y] == '!')
{
show[x][y] = '*';
}
}
void Spread(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
int around_x = 0;
int around_y = 0;
int count = 0;
//坐标合法
if (x >= 1 && x <= 9 && y >= 1 && y <= 9)
{
//遍历周围坐标
for (around_x = -1; around_x <= 1; around_x++)
{
for (around_y = -1; around_y <= 1; around_y++)
{
//如果这个坐标不是雷
if (mine[x + around_x][y + around_y] == '0')
{
//统计周围雷的个数
count = AmountMine(mine, x + around_x, y + around_y);
if (count == 0)
{
if (show[x + around_x][y + around_y] == '*')
{
show[x + around_x][y + around_y] = ' ';
Spread(mine, show, x + around_x, y + around_y);
}
}
else
{
show[x + around_x][y + around_y] = count + '0';
}
}
}
}
}
}
int ClearMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int i = 0, j = 0;
int end = 0;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (mine[i][j] == '1' && show[i][j] == '!')
end++;
}
}
return end;
}
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int row, int col)
{
int n = 0, x = 0, y = 0;
int win = 0;
int flag = 0;
int fail = 0;
again:
while (win < AMOUNT)
{
printf("功能 1:排查雷 2:标记雷 3:取消标记\n");
printf("请选择功能和排查的坐标(功能 横 纵)\n");
scanf("%d %d %d", &n, &x, &y);
switch (n)
{
case 1:
break;
case 2:
{
FlagMine(show, mine, row, col, x, y);
win++;
break;
}
case 3:
{
FlagCancel(show, mine, row, col, x, y);
flag = 1;
win--;
break;
}
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1' && show[x][y] == '*' && flag == 0)
{
printf("你被炸死了,下面是雷区分布图 1为雷 0非雷\n");
Display(mine, ROW, COL);
fail = 1;
break;
}
else if (show[x][y] == '!')
Display(show, ROW, COL);
else if (flag == 1)
Display(show, ROW, COL);
else
{
int count = AmountMine(mine, x, y);
if (count == 0)
{
show[x][y] = ' ';
Spread(mine, show, x, y);
Display(show, ROW, COL);
}
else
{
show[x][y] = count + '0';
Display(show, ROW, COL);
}
}
}
else
{
printf("输入错误,请重新输入");
}
}
if (AMOUNT == ClearMine(mine,show,row,col))
{
printf("恭喜你,扫雷成功\n");
}
else if(fail == 1)
{
printf("请选择是否再来一把\n");
}
else
{
printf("标记的雷有误,请仔细检查,重新标记\n");
goto again;
}
}
该部分代码为test.c文件中的
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void game()
{
//存放雷的棋盘 雷为'1',非雷为'0' 这里是字符1,0
char mine[ROWS][COLS] = { 0 };
//展示排查后的棋盘 未排查的区域为'*',排查后显示周围雷的数字(也是字符)
char show[ROWS][COLS] = { 0 };
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
SetMine(mine, ROW, COL);
Display(show, ROW, COL);
//Display(mine, ROW, COL);
FindMine(show, mine, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int choice = 0;
do
{
printf(" 游戏介绍: \n");
printf("输入 1 为开始游戏,输入 0 为退出游戏\n");
printf("游戏最终的胜利即为把全部的雷标记出来\n");
menu();
printf("请选择\n");
scanf("%d", &choice);
switch (choice)
{
case 1:
{
game();
break;
}
case 0:
{
printf("游戏退出\n");
break;
}
default:
{
printf("选择错误,重新选择\n");
break;
}
}
} while (choice);
return 0;
}
希望这个扫雷游戏可以帮到你。有什么错误和建议也希望大家可以在评论区发出来。