c语言数组简单实现童年回忆——扫雷
Posted invictusQAQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言数组简单实现童年回忆——扫雷相关的知识,希望对你有一定的参考价值。
目录
1.扫雷简介
不知道大家再次看见这张图有什么感受,自从win10更新后将扫雷移除,似乎这个我们的童年回忆正与我们渐行渐远。而今天我们要做的就是利用C语言简单实现这个我们的童年回忆。
先简单介绍一下扫雷的规则:
1、点击方格(能力有限这里我们暂时用输入坐标的方式),如果是地雷,游戏失败,找到所有地雷游戏胜利。
2、刚开始需要碰运气,只要选择一个区域,就可以正式开始了。
3、根据现有情况,判断出一定有雷的位置。(在选择一个无雷区域后会显示周围雷的数量)
4、重复上述操作直到全部雷被排查。、
2.游戏实现思路
根据上述对游戏规则的介绍我们大致可以得出以下思路
注:本文会涉及到一些有关模块化设计思想,具体可以去这里查看https://blog.csdn.net/weixin_60778429/article/details/121193665
1.最基本也是最重要的玩家交互界面(参考上个链接)
2.初始化雷区
3.打印棋盘
4.随机布雷
5玩家排雷(循环)
6.统计所排区域四周雷的数量
7.递归实现空白拓展(详解)
3.代码详细分析
1.最基本的菜单界面之前提过这里就简单放一下代码供大家参考就好了
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请做出你的选择\\n");
scanf("%d",&input);
switch (input)
{
case 1:
printf("新的征程\\n");
game();
break;
case 0:
printf("结束冒险\\n");
break;
default:
printf("输入错误,请重新输入\\n");
break;
}
} while (input);
return 0;
}
2.接下来我们需要初始化棋盘
初始棋盘需要两个二维数组,一个用于存放雷(mine),实现各种功能,另一个用于展示给玩家目前操作状况,便于玩家判断局势(show)。
因为我们在后续判断已排区域周围雷数量时,为方便统计边缘区域我们的数组会比实际大两行两列。代码如下。
void IntiBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;//初始化的参数
}
}
}
这里需要注意set参数为我们设定棋盘的初始化值,这里我们默认mine数组全部初始化为‘0’,show数组初始化为‘*’。(这里采用字符初始化的原因时2为了实现两个数组类型的统一便于代码实现)
3.打印棋盘
这里我们需要打印一个棋盘(show数组)供玩家观察目前状况以做出下一步判断,为便于玩家游戏,这里我们将棋盘的行与列一同打印出来。代码如下。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
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.既然现在棋盘已经有了,那么接下来我们要实现的就是在雷区布雷(这里用‘1’来代表雷),这里我们还是采用随机数布雷的方式,但是要注意随机数的范围不要越界,rand随机值用法可参照下文。那么直接放代码c语言实现简单猜数字游戏(rand/do while的基本使用)_invictusQAQ的博客-CSDN博客
void SetMine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;//控制坐标范围
int y = rand() % col + 1;
if (mine[x][y] == '0')//确保不重复布雷
{
mine[x][y] = '1';
count--;//控制布雷数量
}
}
}
5.既然雷池已经布置完毕,那么接下来就是我们聪明勇敢的玩家的排雷show了,这里我们采用循环的方式让玩家去排雷,直到雷区清空或者玩家阵亡而退出循环。代码如下。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win<row*col-EASY_COUNT)//判断是否已经排完雷
{
printf("请输入排查坐标\\n");
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
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//显示四周雷的数量
Space(show, mine, x, y);//空白递归
DisplayBoard(show, row, col);//展示玩家排查后的状况
win++;//排雷成功数+1
}
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\\n");
DisplayBoard(mine, row, col);
}
}
6.扫雷过程中有一个非常核心的功能,那就是显示出已排查区域四周雷的数量,便于玩家做出下一步判断,是让扫雷不成为一个运气游戏的关键,下面我们着手来实现他。
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x][y - 1] +//这里采用暴力枚举的方式排查周围的八格
mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x - 1][y + 1] +
mine[x][y + 1] +
mine[x + 1][y + 1] - 8 * '0';//字符-‘0’=数字
}
这段代码可能有些同学不理解,这里稍微解释一下。由于我们之前布置雷区时采用‘1’来标记雷,而字符数字-字符0就会得到原本数字的ASCII值,而我们非雷区域是用‘0’标记的。因此用周围八个坐标之和-8*‘0’便可以得到周围实际的地雷数量。
7.大家玩过扫雷的相信一定遇到过突然拓展出一片空白的现象,这是游戏设计者为了提升玩家游戏体验而想出的方法,毕竟真让玩家将每个空格都去排查一遍属实有些折磨人。而触发拓展空白的条件就是当统计到坐标处周围雷的个数为0时,依次检查周围八个位置的周围雷个数,如果统计到任一位置的周围雷个数为0,则继续扩展其周围,如此反复直到把附近位置排查完。
有了大致思路后,还有两点细节是我们需要注意的
1.检查过的位置,拓展时不用重复检查
2.检查的坐标不能是雷
而既然提到了递归,我就需要先大致了解一下什么是递归?
程序调用自身的编程技巧称为递归( recursion)。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
使用递归的条件
- 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
- 每次递归调用之后都越来越接近这个限制条件。
而这里的限制条件便是将所有周围雷数为0的区域找出递归结束。代码如下。
void Space(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//空白递归
{
int i = 0;
if (get_mine_count(mine, x, y) == 0)//判断周围无雷
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
int j=0;
for (j = y - 1; j <= y + 1; j++)//遍历周围是否满足递归条件
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && mine[i][j] != '1' && show[i][j] == '*')
{
Space(show, mine, i, j);//满足条件递归
}
}
}
}
else//不满足条件则执行常规操作
show[x][y] = '0' + get_mine_count(mine, x, y);
}
到此我们的核心部分已经完成,接下来我将放出剩余模块化相关的代码供大家参考,分为3个文件
test.c逻辑测试文件
#include"game.h"
void menu()
{
printf("****************************************\\n");
printf("********** 1.play **********\\n");
printf("********** 0.exit **********\\n");
printf("****************************************\\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };
char show[ROWS][COLS] = { 0 };
IntiBoard(mine, ROWS, COLS,'0');
IntiBoard(show, ROWS, COLS,'*');
DisplayBoard(show, ROW, COL);
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROW, COL);
FindMine(mine, show, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请做出你的选择\\n");
scanf("%d",&input);
switch (input)
{
case 1:
printf("新的征程\\n");
game();
break;
case 0:
printf("结束冒险\\n");
break;
default:
printf("输入错误,请重新输入\\n");
break;
}
} while (input);
return 0;
}
game.c函数核心功能实现
#include"game.h"
void IntiBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
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 mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mine[x][y] == '0')
{
mine[x][y] = '1';
count--;
}
}
}
static int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x][y - 1] +
mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x - 1][y + 1] +
mine[x][y + 1] +
mine[x + 1][y + 1] - 8 * '0';//字符-‘0’=数字
}
void Space(char show[ROWS][COLS], char mine[ROWS][COLS], int x, int y)//空白递归
{
int i = 0;
if (get_mine_count(mine, x, y) == 0)
{
show[x][y] = ' ';
for (i = x - 1; i <= x + 1; i++)
{
int j=0;
for (j = y - 1; j <= y + 1; j++)
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && mine[i][j] != '1' && show[i][j] == '*')
{
Space(show, mine, i, j);
}
}
}
}
else
show[x][y] = '0' + get_mine_count(mine, x, y);
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win<row*col-EASY_COUNT)
{
printf("请输入排查坐标\\n");
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
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
Space(show, mine, x, y);
DisplayBoard(show, row, col);
win++;
}
}
}
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\\n");
DisplayBoard(mine, row, col);
}
}
game.h函数声明文件
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
void IntiBoard(char board[ROWS][COLS], int rows, int cols,char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char mine[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
最后感谢大家的观看,如果有什么好的见解可在评论区下方提出。
以上是关于c语言数组简单实现童年回忆——扫雷的主要内容,如果未能解决你的问题,请参考以下文章