扫雷
1.新建源文件
test.c中存放主函数;
game.h中存放使用的函数的声明;
game.c中存放使用的函数的定义;
2.建立主函数
需求:
1.玩家选择是否进行游戏;
2.要有反复游玩的功能;
主函数代码如下:
int main()
{
int input;
srand((unsigned int )time(NULL));
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n\n");
break;
}
} while (input);
return 0;
}
生成的游戏菜单
玩家输入1
,进入game函数,进行游戏;
玩家输入0
,结束游戏;
玩家输入其他字符
,提示输入错误,重新输入;
3.game函数设计思路
思路:定义界面->初始化->打印->埋雷->找雷
1.游戏界面
扫雷游戏的界面是一个方盘
我们很容易联想到建立一个二维数组来作为游戏界面,其中需要存放地雷的位置信息,同时玩家面对的二维数组则是一片未知区域,只有点下某一点位时才出现周围雷区的信息。于是我们建立两个二维数组,mine
数组存放地雷信息,show
数组则是面对玩家实际操作。
首先利用宏定义行数与列数
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
char mine[ROWS][COLS],show[ROWS][COLS];
这里不得不提到一点:因为需要统计坐标周围8个位置雷的个数,假如要统计边界坐标周围雷的个数,那么就会造成数组越界,如果单独讨论就十分繁琐。如果我们定义了9* 9的边界,也就要定义11*11的数组元素,最外边一圈元素我们不要打印出来,如下图
地雷存放在橙色区域中,以及玩家面对的游戏界面也为橙色区域。行的范围1~ROW,列的范围1 ~ COL。
—————————————————————————
2.初始化 Init
既然数组已经定义好了,我们就要初始化这两个二维数组。
我们规定地雷用字符'1'
表示,安全区域用字符'0'
表示,而玩家的show数组则全部初始化为字符'*'
。
初始化函数声明:
void Init(char board[ROWS][COLS], int rows, int cols, char sign);
—————————————————————————
3.打印函数 Displayboard
验证是否初始化成功;
以及每次玩家点击某个坐标后,我们都要将结果打印出来。
打印函数声明:
void Displayboard(char board[ROWS][COLS], int row, int col)
—————————————————————————
4.设置雷的位置 SetMine
首先利用宏定义雷的数量
#define bomb 10
然后利用函数rand随机在上述橙色区域中布置地雷。
设置地雷函数声明:
void SetMine(char board[ROWS][COLS], int row, int col);
—————————————————————————
5.排查雷 FindMine
需求:
1.防止第一步踩雷
2.如果踩到雷,提示玩家踩中地雷,该轮游戏结束。
3.如果没有踩雷,则在坐标中标记周围地雷数,如果标记为0,那么展开。
4.找出所有地雷,提示玩家排雷成功,该轮游戏结束。
排查雷函数声明:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
4.函数定义 源码分析
初始化,打印和设置地雷函数都比较简单,直接上源码。扫雷的核心在于排查地雷。
4.1 初始化函数 Init
void Init(char board[ROWS][COLS], int rows, int cols, char sign)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = sign;
}
}
}
4.2 打印函数 Displayboard
void Displayboard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("------------------------------------------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
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");
}
4.3 设置地雷函数 SetMine
void SetMine(char board[ROWS][COLS], int row, int col)
{
int i=bomb;
while(i)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
i--;
}
}
}
4.4 排查地雷函数FindMine
排查雷一定是一个循环的过程,直到出结果才能终止循环。我们可以声明变量win
为安全地区的数量,当win==ROW*COL-bomb时,则排完所有地雷。
而且要求输入的坐标必须在上述的橙色区域内,否则要求玩家重新输入。
那我们就有了一个基本的框架:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int win=0;//win为排雷数
while (1)
{
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//进入排雷.......
}
else
{
printf("越界 重新输入\n");
}
}
}
4.4.1 防止第一步踩雷
好的,现在我们假设输入的坐标正确,终于能玩游戏了,但是有些时候很倒霉,一上来就踩中了地雷,那我们如何避免呢?
这里就要先设计一个函数,避免你的霉运——Prevent_jinx
将你第一步脚下的地雷挪到没雷的位置去。
//防止第一步就踩中地雷
void Prevent_jinx(char mine[ROWS][COLS],int x,int y)
{
int m, n;
while (1)
{
m = rand() % ROW + 1;
n = rand() % COL + 1;
if (mine[m][n] != '1')
{
mine[m][n] = '1';
mine[x][y] = '0';
break;
}
}
}
那我们怎么触发这个函数呢?
一定是在第一步的时候——即此刻排查出的安全区的数量win==0
,而且你选择的坐标正好下面埋着一颗雷——mine[x][y] == '1'
于是我们的框架得到了更新:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int win = 0;//win为安全数 如果win=总数-地雷数 则成功
while (1)
{
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (win == 0 && mine[x][y] == '1')//如果一上来就踩雷
{
Prevent_jinx(mine, x, y);
}
//正式进入排雷.......
}
else
{
printf("越界 重新输入\n");
}
}
}
趟过了第一步便正式进入了排雷,下面我们将判断之后的每一步是否踩中了地雷。
假设踩中了地雷,那么我们就需要提示玩家踩中地雷,而且需要打印实际地雷位置信息,好让玩家‘死’个明白,然后退出概该轮游戏。
于是我们的框架得到了更新:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int win = 0;//win为安全地区数 如果win=总数-地雷数 则成功
while (1)
{
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (win == 0 && mine[x][y] == '1')//如果一上来就踩雷
{
Prevent_jinx(mine, x, y);
}
if (mine[x][y] == '1' ) //踩中雷
{
printf("踩到雷啦!\n");
Displayboard(mine, ROW, COL);
break;
}
else //未踩中雷
{
.....
}
}
else
{
printf("越界 重新输入\n");
}
}
}
4.4.2 展开
如果没有踩中地雷,那么我们需要在show[x][y] 处标记周围八个方向的地雷个数,如果个数为0则需要展开
。
展开:如果(x,y)处为0,则周围8个点依次向外找雷,如果周围找到了地雷则标记个数,否则继续向外找雷。
实现定义找周围的雷数函数——Getbombcount
:
int Getbombcount(char mine[ROWS][COLS], int x, int y)
{
int i, j,count=0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
}
我们定义递归函数OpenMine
来执行**展开
**操作:
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL && mine[x][y]!='1') //x,y在合法范围内,而且不是雷区
{
int count = Getbombcount(mine, x, y);
if (count == 0) //如果count=0,则需要继续扩展找雷
{
show[x][y] = '0';
int i, j;
//依次遍历周围八个点
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] != '*') continue; //如果已被标记过则不用进入递归,避免重复查找
OpenMine(mine, show, i, j);
}
}
}
else //count不为0 停止扩展找雷
{
show[x][y] = count + '0';
}
}
else return;
}
这样的依次展开就可以排查掉许多安全区域
现在我们需要一个函数Countshow
来统计以下安全区域的数量
int Countshow(char board[ROWS][COLS], int row, int col, char sign)
{
int count = 0,i,j;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (board[i][j] == sign)
count++;
}
}
return count;
}
于是我们的FindMine函数就完全写出来了:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int win = 0;//win为排雷数 如果win=总数-地雷数 则成功
while (1)
{
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (win == 0 && mine[x][y] == '1')//如果一上来就踩雷
{
Prevent_jinx(mine, x, y);
}
if (mine[x][y] == '1' )
{
printf("踩到雷啦!\n");
Displayboard(mine, ROW, COL);
break;
}
else //未踩中雷
{
OpenMine(mine, show, x, y);
win = ROW*COL-Countshow(show, ROW, COL, '*');
if (Countshow(show, ROW, COL, '*')== bomb)
{
Displayboard(show, ROW, COL);
printf("排雷成功\n");
break;
}
Displayboard(show, ROW, COL);
}
}
else
{
printf("越界 重新输入\n");
}
}
}
5.函数声明 game.h 源码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
//定义宏
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define bomb 3
//初始化
void Init(char board[ROWS][COLS], int rows, int cols, char sign);
//打印游戏界面
void Displayboard(char board[ROWS][COLS], int row, int col);
//布置地雷
void SetMine(char board[ROWS][COLS], int row, int col);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
6.main函数 test.h 源码
#include "game.h"
void menu()
{
printf("*******************\n");
printf("***0.exit 1.play***\n");
printf("*******************\n");
}
void game()
{
//定义存放地雷的数组 和 玩家游玩的数组
char mine[ROWS][COLS],show[ROWS][COLS];
//初始化数组
Init(mine,ROWS,COLS,'0');
Init(show, ROWS, COLS, '*');
//打印
//Displayboard(mine,ROW,COL);
//Displayboard(show, ROW, COL);
//布置地雷
SetMine(mine,ROW,COL);
Displayboard(mine, ROW, COL);
Displayboard(show, ROW, COL);
//排查雷
FindMine(mine,show,ROW,COL);
}
int main()
{
int input;
srand((unsigned int )time(NULL));
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n\n");
break;
}
} while (input);
return 0;
}
7 函数定义 game.c 源码
#include "game.h"
//初始化
void Init(char board[ROWS][COLS], int rows, int cols, char sign)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = sign;
}
}
}
//打印游戏界面
void Displayboard(char board[ROWS][COLS], int row, int col)
{
int i, j;
printf("------------------------------------------\n");
for (i = 0; i <= row; i++)
{
printf("%d ", i);
}
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 i=bomb;
while(i)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] != '1')
{
board[x][y] = '1';
i--;
}
}
}
//获取坐标周围地雷数目
int Getbombcount(char mine[ROWS][COLS], int x, int y)
{
int i, j,count=0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
count++;
}
}
return count;
}
//防止第一步就踩中地雷
void Prevent_jinx(char mine[ROWS][COLS],int x,int y)
{
int m, n;
while (1)
{
m = rand() % ROW + 1;
n = rand() % COL + 1;
if (mine[m][n] != '1')
{
mine[m][n] = '1';
mine[x][y] = '0';
break;
}
}
}
void OpenMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
if (x >= 1 && x <= ROW && y >= 1 && y <= COL && mine[x][y]!='1') //x,y在合法范围内,而且不是雷区
{
int count = Getbombcount(mine, x, y);
if (count == 0) //如果count=0,则需要继续扩展
{
show[x][y] = '0';
int i, j;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
if (show[i][j] != '*') continue; //如果已被排雷过则不用递归
OpenMine(mine, show, i, j);
}
}
}
else //count不为0 停止扩展
{
show[x][y] = count + '0';
}
}
else return;
}
int Countshow(char board[ROWS][COLS], int row, int col, char sign)
{
int count = 0,i,j;
for (i = 1; i <= row; i++)
{
for (j = 1; j <= col; j++)
{
if (board[i][j] == sign)
count++;
}
}
return count;
}
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x, y;
int win = 0;//win为排雷数 如果win=总数-地雷数 则成功
while (1)
{
printf("输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (win == 0 && mine[x][y] == '1')//如果一上来就踩雷
{
Prevent_jinx(mine, x, y);
}
if (mine[x][y] == '1' )
{
printf("踩到雷啦!\n");
Displayboard(mine, ROW, COL);
break;
}
else //未踩中雷
{
OpenMine(mine, show, x, y);
win = ROW*COL-Countshow(show, ROW, COL, '*');
if (Countshow(show, ROW, COL, '*')== bomb)
{
Displayboard(show, ROW, COL);
printf("排雷成功\n");
break;
}
Displayboard(show, ROW, COL);
}
}
else
{
printf("越界 重新输入\n");
}
}
}
8.实际游玩效果
第一把 :设置bomb为2
第二把:设置bomb为4