• 前言
在我们学习了数组和函数等知识,我们可以自己实现一个扫雷游戏的逻辑。
不仅可以巩固知识,也可以提高自己的代码能力。
在这个过程中如果有什么问题,欢迎各位大佬指点,我会虚心听教改正!!
• 逻辑分析
首先我们来分析扫雷游戏逻辑。
• 界面
- 首先来看扫雷游戏的界面,是一个二维的界面
在这里我们很容易想到用二维数组来表示这个界面。
• 游戏核心逻辑
- 我们再来看游戏过程
• 系统随机生成10个雷在这个棋盘的某些位置
当我们选中一个位置的时候(如下图)
结果如下:
• 如果选中的位置是地雷,则游戏失败(游戏结束);
• 如果不是:
这里会统计周围8个位置雷的数量
如果为 0 会向四周散开直到遇到不会 0 的地方(这里就是递归的核心)
在这里我们需要思考:如果仅仅是只有一个二维数组,能满足我们的界面要求和统计雷的要求吗?
这显然是不可能的。
所以这里我们就可以用两个二维数组(A.showboard B. mineboard)
然后逐步排查,直到最后只剩下雷就胜利了
• 实现逻辑
通过上面的分析,我们需要:
-
创建两个二维数组,一个展示,一个存雷;
-
需要展示棋盘(打印函数);
-
对这个两个数组初始化(初始化函数);
• 对于存雷的二维数组来说,我们还需要随机在棋盘进行埋雷(埋雷的函数); -
然后就是排查雷的过程(排查雷的函数):
(1) 输入一个点的坐标;
(2) 判断这个点是不是雷:
• 是雷,游戏结束
• 不是雷:
1. 如果周围没有雷,那么向四周展开,直到遇到不为0的位置(延展函数);
2. 如果周围有雷,那么显示周围雷的个数(统计函数); -
最后只剩下雷的位置,就获得胜利(判断函数);
但是 这里有很多潜在的问题:
例如:
当我们选择最边上的一圈的坐标的时候,统计周围的个数的时候,会发生什么问题呢?
很显然数组越界……
很显然这样的一个位置就有5个位置导致数组越界
• 为了解决这个问题: 我们决定在我们想要的这个棋盘下再扩大一圈。
!! 在这扩大一圈的位置,我们不能将地雷埋在这里,相反也需要将这里初始化,后面逻辑需要注意。
• 实现过程
在这里我并不提倡初学者一上来就打印菜单
究其原因就是再写的过程中难免会有逻辑错误,如果一上来就写菜单的话,极其不方便我们调试。
所以在这里,我们提倡最后打印菜单。
文件创建
首先看到这个过程还是较为复杂的,我们就采用多文件的形式:
方便我们进行合理分配。
game.h文件放函数声明(记得定义了的函数就要放在里面声明);
game.c文件放核心函数定义;
test.c 文件放主函数
创建准备
在test.c文件中创建一个game函数,实现代码核心逻辑;在test.c文化中主函数中调用game函数即可:
创建两个数组:
为了方便后续修改整个工程,我们这里采用定义宏变量来实现,方便我们修改棋盘大小。
就在头文件game.h中声明:
这里我们选择9 * 9的棋盘;
在game函数中创建(记得包含本地头文件)
初始化
然后进行初始化
• show:
这里我们选择用 “ * ” 来代替展示的空格;
• mine:
对于是雷的位置我们采用字符 “ 1 ” ;
对于不是雷的位置,我们就使用字符“ 0 ”;
为了方便是实现我们统计雷的个数
定义初始化函数:
- 传的参数: 二维数组、棋盘的行号和列号、和初始化的内容;
- 遍历整个二维数组,给每个位置都赋值(对于埋雷的,都需要对扩充的位置置为字符“ 0 ”)
打印棋盘
!!!特别注意:
我们在这里要打印的是二维数组的1 ~ 9行和列
因为我们在这个二维数组扩了一圈
为了美观我们就添加行号和列号,方便我们输入坐标
运行看看赋值成功没(一定要边写边测试,方便找出问题):
埋雷逻辑
在这里我们要随机埋雷(我们这里取10,在game.h中定义宏变量,方便后面修改)
函数创建:
- 传参:mine数组,行号和列号;
- 随机生成一个坐标,把当前位置改为字符“ 1 ”,循环进行,直到10个埋完
判断 – 生成坐标: x: 1 ~ 9; y: 1 ~ 9 且 坐标不能重复
(随机数生成在猜数字游戏1中已经详细介绍过了,这里就不做详解)
运行看看埋雷成功没:(这里也可以调整Count的值来看成功没)
排查地雷
重点就是找雷逻辑;
函数创建:
- 传参我们要统计雷,也要输入过后展示,那么我们的 mine 和 show 都需要传过去;输入坐标需要在范围内 —— 行号和列号;
- 我们需要重复找地雷,直到胜利或踩雷
- 输入一个坐标,判断是否在合理范围内,然后判断是否是雷;
踩雷的逻辑
这个比较简单,我们就略过(附上图片)
没有踩雷的逻辑
!!这里就是重中之中我们需要递归展开
下面我做图解释:
接下来就是函数实现:
-
首先来实现统计周围雷的个数:
-
传参:mine、坐标
-
返回类型 整型(这里采用整型我们后面通过字符“ 0 ”的加法可以转化为对应字符)
-
采用循环遍历这9个位置,因为是字符所以我们通过减字符“ 0 ”得到数字
实现:
-
-
递归函数的实现:
-
传参: show、mine、坐标;
-
判断,给位置赋值
实现:
-
胜利条件
就是遍历整个show,统计是否有10个“ * ”就可以实现了。
至此所有逻辑已经完成:
打印菜单
这个也比较简单我们省略(附上代码)
• 结语
对于学到函数的初学者来说:
这个肯定是有一定的难度的,但自己实现成功过后又不失有一种成就感!!
希望大家多多练习,练习逻辑思维!!
源代码
game.h
#pragma once
#include<stdio.h>
#define ROWS 11
#define COLS 11
#define ROW 9
#define COL 9
void IntiBoard(char board[ROWS][COLS], int x, int y, char c);
void PrintBoard(char board[ROWS][COLS], int x, int y);
#include<stdlib.h> // -- rand/srand头文件
#include<time.h> // time 时间戳
#define Count 10 //埋雷个数
void GetMine(char board[ROWS][COLS], int x, int y);
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int a, int b);
int Countmine(char board[ROWS][COLS], int x, int y);
void ExpdShow(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y);
int WinBoard(char board[ROWS][COLS], int x, int y);
game.c
#include"game.h"
void IntiBoard(char board[ROWS][COLS], int x, int y, char c)
{
int i = 0; //行
for (i = 0; i < x; i++)
{
int j = 0; //列
for (j = 0; j < y; j++)
{
board[i][j] = c;
}
}
}
void PrintBoard(char board[ROWS][COLS], int x, int y)
{
printf("------扫雷开始------\n");
//美化列号:
int i = 0;
for (i = 0; i <= x; i++)
printf("%d ", i);
printf("\n");
for (i = 1; i <= x; i++) //每一行的打印:
{
int j = 0;
printf("%d ", i);
for (j = 1; j <= y; j++)
{
printf("%c ", board[i][j]);
}
printf("\n"); //每行打印完换行
}
printf("-------------------\n");
}
void GetMine(char board[ROWS][COLS], int x, int y)
{
int count = Count;
while (count)
{
int a = rand() % 9 + 1; // 下标1~9
int b = rand() % 9 + 1;
if (0 < a && a <= x && 0 < b && b <= y)
{
if (board[a][b] != '1')
{
board[a][b] = '1';
count--;
}
}
}
}
int Countmine(char board[ROWS][COLS], int x, int y)
{
int ret = 0;
int i = 0;
for (i = -1; i < 2; i++)
{
int j = 0;
for (j = -1; j < 2; j++)
{
ret += board[x + i][y + j] - '0'; // 统计数量
}
}
return ret;
}
//递归展开:在此之前需要一个统计周围雷数的函数
void ExpdShow(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
int n = Countmine(mine, x, y); //得到周围的雷数
if (n == 0 && 0 < x && x <= ROW && 0 < y && y <= COL && show[x][y] != '0')
{
show[x][y] = '0';
ExpdShow(show, mine, x - 1, y);
ExpdShow(show, mine, x + 1, y);
ExpdShow(show, mine, x, y + 1);
ExpdShow(show, mine, x, y - 1);
}
else if (n != 0)
{
show[x][y] = n + '0';
}
}
int WinBoard(char board[ROWS][COLS], int x, int y) //胜利条件判断
{
int i = 0;
int count = 0; //记数
for (i = 1; i <= x; i++)
{
int j = 0;
for (j = 1; j <= y; j++)
{
if (board[i][j] == '*')
count++;
}
}
if (count == 10)
return 1;
else
return 0;
}
void FindMine(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)
{
//一直查找,直到满足胜利条件:
while (1)
{
int a = 0; //接受行号
int b = 0; //接受列号
printf("请输入你要排查的坐标(行 列):> ");
scanf("%d%d", &a, &b);
//判断坐标是否合法:
if (0 < a && a <= x && 0 < b && b <= y)
{
//判断是否踩雷
if (mine[a][b] == '1')
{
printf("Boom!你已经踩雷,游戏失败\n\n");
PrintBoard(mine, ROW, COL); //死得明白
break; //结束找雷
}
else
{
//不是雷的逻辑;
//递归展开:
ExpdShow(show, mine, a, b);
PrintBoard(show, ROW, COL);
if (WinBoard(show, ROW, COL))
{
printf("恭喜你获得胜利!!\n\n");
printf("请看埋雷点:\n");
PrintBoard(mine, ROW, COL);
break;
}
}
}
else
{
printf("输入不合法\n请输入1~9\n");
}
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void game()
{
//设置两个棋盘:一个显示,一个埋雷:
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
//1.初始化棋盘:
IntiBoard(mine, ROWS, COLS, '0');
IntiBoard(show, ROWS, COLS, '*');
//2.打印出棋盘:
// 美化给棋盘标上序号,只需要打印下标 1~9
PrintBoard(show, ROW, COL);
//PrintBoard(mine, ROW, COL);
srand((unsigned int)time(NULL));
//3.埋雷:
//随机数的生产,rand() -- 搭配时间戳
GetMine(mine, ROW, COL);
//PrintBoard(mine, ROW, COL);
//4.查找雷!!(重点)
FindMine(show, mine, ROW, COL);
}
void menu()
{
printf("****************\n");
printf("*****0.exit*****\n");
printf("*****1.play*****\n");
printf("****************\n");
printf("请选择:>");
}
void test()
{
int input = 0;
do
{
//打印菜单:
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("请退出\n\n");
break;
default:
printf("请重新选择\n\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
标签:ROWS,递归,show,int,mine,COLS,char,详解,扫雷
From: https://blog.csdn.net/2403_88316985/article/details/144181291