🚩纸上得来终觉浅, 绝知此事要躬行。
🌟主页:June-Frost
🚀专栏:C语言
🔥该篇将运用数组来实现 扫雷游戏。
目录:
🌟思路框架
测试
通过迭代保证每次玩完游戏后可以再来一局或者退出。
游戏
说明:
1 . 实现扫雷,首先需要布置雷,通过一个二维数组就可以很容易的将雷的信息存储下来。只需要将二维数组的元素赋予两个不同的值,例如,'1' ------雷,'0' ------无雷,或者 '#' ------雷,' * ' ------无雷,只不过这两种在后续排查雷中操作不同(通过一个循环前者可以加起来计算出周围有几个雷,后者可以通过比较来判定有多少雷)。
2.如上图,对于 排查雷(周围有多少雷),如果只有一个数组,那么在周围如果只有一个雷的情况下('1'------表示雷,'0'------表示无雷),该位置的数组元素就会被赋值为1,在最后展示棋盘的时候,这个1就会造成歧义,就会不知道这个'1'究竟是雷,还是周围雷的个数。
所以,通过建立另一个数组,来记录周围雷的数量。
如图:
3.
但是我们可以将其逻辑统一:
4. 为了简便操作,我们将两个数组规定为同类型,同大小(两个都扩充一圈)。
- 优点1:可以方便数组操作,下标一样,不需要重新计算对应数组元素的下标。
- 优点2:同类型意味着可以使用同一个函数,不需要一个功能写两次。
每个部分都是单独的函数体,下面将会单独对每个函数体进行实现。
🌟测试部分函数实现
- 选择是否玩游戏
与三子棋一样,该板块的循环部分将会使用do while 循环来实现(保证一开始可以选择,运行完游戏部分后还可以继续选择)。
c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
int main()
{
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()函数中。
前提:
c
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
定义两种,可以在后续分别用于不同的函数。
- 游戏逻辑
c
void game()
{
char mine[ROWS][COLS] = { 0 };//存放雷
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
//初始化
InitBoard(mine, ROWS, COLS, '0');//都先初始化为无雷
InitBoard(show, ROWS, COLS, '*');
//布置雷
SetMine(mine, ROW, COL);
//打印第二个棋盘
DisplayBoard(show, ROW, COL);
//排查雷
FineMine(mine, show, ROW, COL);
}
- 初始化
由于想使用一个函数就可以将两个棋盘初始化,所以在参数上需要增加一个变量用于接收初始化的值。
c
void InitBoard(char board[ROWS][COLS], int row, int col, char set)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
- 布置雷
前提:#define Set_Mine 10;//需要布置雷的数量
布置雷是随机的,所以我们采用伪随机数,将下标在合法范围内随机,如果该坐标下是无雷的,就赋值'1',定为有雷。
c
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = Set_Mine;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;//布置成功,雷数减少1
}
}
}
}
- 打印棋盘
为了方便玩家定位坐标,在打印棋盘的时候,可以将行和列打印出来。并且展示出来的,是中间的区域(不展示扩容区域)
c
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("--------扫雷游戏-------\n");
int i = 0;
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");
}
}
- 排查雷
计数:
c
int GetMineCount(char board[ROWS][COLS], int x, int y)
{
int i = x - 1;
int j = y - 1;
int ret = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
ret += board[i][j];
}
}
return ret - 9 * '0';
}
排雷:
c
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
int win = 0;//如果要赢 需要的有检查的次数,例如:9*9棋盘,有10个雷,检查出71个地区不是雷,就意味着获胜。
while (win<ROW*COL-Set_Mine)
{
int x = 0;
int y = 0;
printf("请输入坐标,中间用空格:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法
{
//踩到雷
if (mine[x][y] == '1')
{
printf("很遗憾,已踩到雷\n");
DisplayBoard(mine, ROW, COL);
break;
}
//没猜到雷
else
{
Unfold(mine,show, ROW, COL, x, y,&win);//递归展开
DisplayBoard(show, ROW, COL);//展示棋盘
}
}
else
{
printf("输入非法,需要重新输入\n");
}
}
if (win == ROW * COL - Set_Mine)
{
printf("游戏获胜\n");
DisplayBoard(mine, ROW, COL);
}
}
展开:
如何实现上面的展开效果呢?
当我们点击一个坐标后,看是否满足3个条件:
1.这个坐标不是雷(如果按照博主的排雷逻辑,这个条件不需要判断,因为坐标下不是雷的时候才会调用展开函数)
2.周围没有雷
3.没有被递归过(防止死递归)
这时被点击的地方就可以被赋值为空,并对周围的8个区块看是否符合这样的条件,8个区块也按照这个逻辑,如果满足三个条件,被赋空并且遍历周围,如果不满足就显示周围有多少雷,或者不做任何反应,按照这样的逻辑一直延续下去。这种情况我们可以使用递归去解决。
c
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col,int x,int y, int* count_w)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)//判断合法
{
if (!GetMineCount(mine, x, y) && show[x][y] != ' ')//周围没有雷并且该位置没有被递归过
{
show[x][y] = ' ';
int i = 0;
int j =0;
int ret = 0;
//直接遍历9个,至于中间的那个坐标,最后一个else if 会处理
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, row,col,i, j, count_w);
(* count_w)++;
}
}
}
else if (GetMineCount(mine, x, y))//周围有雷
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
(* count_w)++;
}
else if (show[x][y] == ' ')//被递归过
{
;//不需要操作
}
}
//不合法的函数不需要任何处理
}
这里需要判断合法性,因为扩容出去的那一圈的元素不可以递归,而且也不可以计算 (不然按照GetMineCount的逻辑会越界访问,而且就会可以计算出,也不会打印这个区域,所以可以直接不用管,递归这些地方的时候,函数会什么都不处理)。
🌟完整的代码
game .h
c
#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 Set_Mine 10//需要布置雷的数量
//游戏逻辑
void game();
//初始化
void InitBoard(char board[ROWS][COLS], int row, int col, char set);
//埋雷
void SetMine(char board[ROWS][COLS], int row, int col);
//展示棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);
//排雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game .c
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 row, int col, char set)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = set;
}
}
}
//布置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = Set_Mine;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;//布置成功,雷数减少1
}
}
}
}
//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
printf("--------扫雷游戏-------\n");
int i = 0;
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");
}
}
//计数
int GetMineCount(char board[ROWS][COLS], int x, int y)
{
int i = x - 1;
int j = y - 1;
int ret = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
ret += board[i][j];
}
}
return ret - 9 * '0';
}
//展开
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col,int x,int y, int* count_w)
{
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (!GetMineCount(mine, x, y) && show[x][y] != ' ')//周围没有雷并且该位置没有被递归过
{
show[x][y] = ' ';
int i = 0;
int j =0;
int ret = 0;
for (i = x - 1; i <= x + 1; i++)
{
for (j = y - 1; j <= y + 1; j++)
{
Unfold(mine, show, row,col,i, j, count_w);
(* count_w)++;
}
}
}
else if (GetMineCount(mine, x, y))//周围有雷
{
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
(* count_w)++;
}
else if (show[x][y] == ' ')//被递归过
{
;
}
}
}
//排雷
void FineMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col)
{
int win = 0;
while (win<ROW*COL-Set_Mine)
{
int x = 0;
int y = 0;
printf("请输入坐标,中间用空格:> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//踩到雷
if (mine[x][y] == '1')
{
printf("很遗憾,已踩到雷\n");
DisplayBoard(mine, ROW, COL);
break;
}
//没猜到雷
else
{
Unfold(mine,show, ROW, COL, x, y,&win);//递归展开
DisplayBoard(show, ROW, COL);//展示棋盘
}
}
else
{
printf("输入非法,需要重新输入\n");
}
}
if (win == ROW * COL - Set_Mine)
{
printf("游戏获胜\n");
DisplayBoard(mine, ROW, COL);
}
}
//游戏逻辑
void game()
{
char mine[ROWS][COLS] = { 0 };//存放雷
char show[ROWS][COLS] = { 0 };//存放排查出的雷的信息
//初始化
InitBoard(mine, ROWS, COLS, '0');//都先初始化为无雷
InitBoard(show, ROWS, COLS, '*');
//布置雷
SetMine(mine, ROW, COL);
//打印第二个棋盘
DisplayBoard(show, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排查雷
FineMine(mine, show, ROW, COL);
}
test.c
c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
int main()
{
int input = 0;
srand((unsigned)time(NULL));
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;
}
❤️ 结语
文章到这里就结束了,如果对你有帮助,你的点赞将会是我的最大动力,如果大家有什么问题或者不同的见解,欢迎大家的留言~