扫雷游戏(初级)
背景介绍
扫雷,这个游戏想必绝大多数人都听过或者玩过,其游戏流程就是选择扫雷点,然后逐步探索雷区,当在没有触发地雷的情况下探索玩整个区域,就算游戏通关
那么,要怎么利用C语言来简单实现扫雷游戏呢?
接下来,请先浏览扫雷的基本网格图
该项目介绍的是:利用初阶C语言实现扫雷游戏
在这之前我们需要先回顾以下扫雷游戏的规则以及流程特色:
奥,在这之前,还请您对这个表格保留一个影响,因为接下来会涉及到两个网格,或者说是两个二维数组,一个数组用于执行游戏设定操作,另一个数组用于呈现游戏效果
它们分别是:
游戏操作数组 put_mines[][] ,顾名思义,对吧
效果呈现数组 show_mines[][] , 呈现游戏效果
规则:
踩到雷,游戏失败
把除了雷区以外的地方都探索完毕,游戏通关
流程:因为只涉及到了初阶C语言知识,所以所有的地区都需要通过每一次输入坐标来实现
//在C进阶甚至C++中,将会介绍更加高效的游玩方式以及更加丰富有趣的游戏设计!
一,项目分析环节
//这个环节就是介绍这个扫雷项目是怎么实现的,它是一个怎样的思维方式?是怎么一步步的去呈现这个项目的
1,游戏流程
如下图,选取一个地址,进行探索
我们选取... 这个位置 <4,5>
程序反馈如下:
可以发现, 这个位置是非雷区,而且,其周围3X3范围内都是非雷区,因为在该项目中,我们会通过创建一个侦测器,把探索坐标周围3X3地区的地雷数量统计出来,并反馈到我们所探索的地区。
接着,我们探索 和 这两个区域 <6,6> 和 <6,7>
很不幸,我们在 <6,7> 这个位置踩雷了,游戏结束!
2,设计思路
①,创建 ” 棋盘 “
在游戏流程中我们以及大致了解了游戏的玩法以及部分效果展示,接下来,我们来梳理一下整体的设计思路!
首先,在初阶C语言中,我们需要通过二维数组来实现棋盘的组建,在此基础上,进行一系列擦欧总和编译
所以我们首先要创建一个大小合适的二维数组。
这里就称之为 arr[9][9]
因为在二维数组中,我们是使用 ‘ * ’ 来表示为揭秘区域,所以也用 ‘ 1 ’ 来表示地雷区域,所以,使用 char 来定义数组
可以发现,9X9就算地雷所放置的全部范围,但是我们还要实现选取一个坐标,自动探索其周围3X3空间的地雷情况,所以9X9的网格是不够用的。应该采用11X11,并且,为了便于观察和选取坐标,我们可以把行数和列数重新调整,这就得到了完整的游戏棋盘
如图我们创建了一个11X11的棋盘,途中标出的只是游玩时候将会打印到屏幕上的内容,虽然创建了11X11的棋盘,但是我们实际上只是在9X9范围内操作,所以就需要定义两套变量,为了方便使用,我们可以采用全局宏定义
我们定义 11X11 的行和列为 ROWS 和 COLS
定义9X9 的行和列为 ROW 和 COL 。它们之间关系为
ROW = COL = 9
ROWS = ROW + 2
COLS = COL + 2
②,初始化棋盘
在①中我们进行了棋盘的创建,自定义一个函数,用于初始化棋盘,其大致内容,就是见谅两层循环,外层行循环,内层列循环,行每执行一次,列执行11次,如此就能得到一个11X11 的棋盘了,创建完成后,我们需要在创建一个自定义函数来打印棋盘,而打印钱,只需要打印9X9范围就行
因为我们是在这个范围内尽心扫雷游戏,11X11的创建只是为了程序在搜索探索雷区的时候不发生越界访问
其中的红色方框,模拟了某一次排查地雷的时候,勘测器工作的范围
侦测器,就是说,我们最后需要对我们的地雷进行排查,而这个时候需要使用侦测器来帮助我们探索所选择区域周围3X3区域范围内的地雷数量,以帮助我们顺利完成地雷的排查工作
以下是初始化程序的参考模型:
③,打印棋盘
棋盘的打印,并非只是打印9X9的雷区,实际上我们还需要再用一行和一列来实现行号和列号的打印,这样打印出来的棋盘比较美观
以下是打印棋盘程序的参考模型:
那么现在我们就能够打印出这样的东西:
④,埋下地雷
在②和③中,我们成功创建了一个棋盘,尽管它看上去确实很简陋,关于这点我们将会在更全面版本的迅雷游戏中去进一步优化它
在创建好了棋盘后,我们要开始设计一些挑战因素,通过随机数rand,在9X9棋盘内随机生成一定数量的地雷,可以采用生成的随机数 余上 9,或者更加包容的设定,我们让随机数 余上 行数 和 列数, 这样,当行数和列数都为9,就能得到 0 ~ 8 的随机数,把所得到的随机数都进行 +1 输出,就能得到 1 ~ 9 的随机数,通过行得到的随机数设为x,通过列得到的随机数设为y,这样就得到了坐平面直角坐标系中的x和y。这样程序就能在9X9范围内随机放置地雷
当然,为了放置城西偶然的生成了两个相同的坐标,我们需要对每一个地雷的坐标进行判断,确保不会出现少雷的情况
参考程序如下:
如此,我们就通过while循环把雷成功埋进了棋盘,这里我们进行一次小小的测试:
以上是某一次游戏中,程序随机生成的地雷情况
⑤,排查地雷
我们在④中成功布置了地雷,现在让我们设计程序帮助我们把这些随机的地雷都找出来
对于地雷的排查,其实很简单,我们只需要随便选取一个坐标,只要不是运气太差,是不会在第一步就结束游戏的。
假如我们输入了坐标
那么,在放置了地雷的棋盘中,这里我们称之为put_mines[ROWS][COLS]
需要对这个坐标进行核实,看看这个位置上是否已经有了地雷,如果有地雷,则触发游戏失败的程序,如果没有,就继续进行下一步探索。
之前也提到过,我们还需要在某个已探索的3X3区域周围进行地雷侦测,将发现的地雷数量返回到我们所探索的那个地址
所以这里就需要创建一个自定义函数来计算地雷的数量,从而实现地雷的侦测
侦测地雷
那么,我们创建的是char字符数组,所以可以通过ASCII 码 的值雷进行地雷数量的确定
我们设定的非雷区就是 ‘ 0 ’ ,而雷区就是 ‘ 1 ’ ,
所以,只需要把 ‘ 1 ’ - ‘ 0 ’ ,就能得到1,再把这个1赋值给整型的一个计数变量,就能完成一个地雷的计数操作,那么我需要再了解已探索区域 周围3X3区域的内容:
所以我们只需要对着八个区域进行勘探就行了,可以参考以下计算模型来得出地雷数量:
计算原理,就是把八个区域的字符总大小求出来,再减去8个区域下 ‘ 0 ’ 字符的大小综合,也就是说,把勘探区域的字符大小,检测数来,减去八个区域再非雷情况下的字符大小,就能得到地雷数量。
⑥,设定输赢
游戏总的有个结局,输或者赢,都需要一个东西去结束它,在① ~ ⑤ 中,我们进行了棋盘的布置,地雷的布置,还进行了地雷的排查,现在是时候设定胜利条件了
我们可以在排查雷的时候设置一个变量 win, 每当我们进行一次成功的地雷排查,这个变量就自增一次,在这样的条件下,我们只需要设定,如果 变量 win 的数值大小,等于棋盘总区域数量减去雷区数量的数值大小,就说明我们已经顺利完成了所有地区的勘探任务,任务圆满完成,游戏成功通关!
可以参考以下程序来实现:
//以下就是我们对于扫雷游戏初阶的全部介绍环节,下面将会展示程序原码,并对其进行必要性的解释
二,项目原码
在这个项目中,我们将程序分类存放,以便于更改和编译,避免在一个源文件下编译,造成的混乱
我们将会设定两个 .c 源文件 , 一个 .h 头文件
它们分别是:
主函数测试文件 —— Minesweep_01_test.c
用于编译主函数,也就是主程序所运行的函数
自定义函数程序文件 —— Minesweep_01.c
用于编译我们自定义的函数
项目函数依托头文件 —— Minesweep_01.h
把我们定义的函数在这个文件中声明,然后再著文件中直接 #include 应用就行了
那么我们就从头文件开始
1,项目函数依托头文件 —— Minesweep_01.h
#pragma once
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <time.h>
//设定侦测器工作范围,以及地雷的埋设范围
#define ROW 9
#define COL 9
//设定基础数组的行和列,建立基本的工作网格
#define ROWS ROW + 2
#define COLS COL + 2
//设定难度,及埋设地雷的数量
#define Easy_mod 10
//这里多定义了两个常量,将用于后续版本的优化使用
#define Normal_mod 15
#define Hard_mod 20
//这里设置该源文件为自定义函数的头文件
//初始化棋盘,当我们创建了一个11X11的工作棋盘,需要自定义一个函数来对其元素进行初始化
void InitBoard(char Board[ROWS][COLS], int rows, int cols, char set);
//初始化数组初始化11X11,这里只需要9X9棋盘,所以只打印9X9
void DispalyBoard(char Board[ROWS][COLS], int row, int col);
//布置地雷
void SetMine(char mines[ROWS][COLS], int row, int col);
//排查地雷
void FindMine(char put_mine[ROWS][COLS], char show_mine[ROWS][COLS], int row, int col);
2,自定义函数程序文件 —— Minesweep_01.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Minesweep_01.h"
//自定义实现初始化数组
void InitBoard(char Board[ROWS][COLS], int rows, int cols, int set)
{
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
Board[i][j] = set;
}
}
}
void DispalyBoard(char Board[ROWS][COLS], int row, int col)
{
printf("---------* 扫雷游戏 *----------\n");
//吧行和列号打印出来
//列号
for (int i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
//定义外层循环,每循环一次,就是一行
for (int i = 1; i <= row; i++)
{
//行号
printf("%d ", i);
for (int j = 1; j <= col; j++)
{
//定义内层循环,每外层循环一次,就是再一行内循环完一个列
printf("%c ", Board[i][j]);
}
printf("\n");
}
printf("---------* 扫雷游戏 *----------\n");
}
void SetMine(char mines[ROWS][COLS], int row, int col)
{
//设置雷数
int count = Easy_mod;
//通过随机数产生地雷的坐标
//随机数会很小或者很大,所以余上行或列就能得到0~8
//0~8分别都 + 1 就能得到 0~9
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (mines[x][y] == '0')
{
//为了避免系统产生了重复的坐标
mines[x][y] = '1';
count--;
}
}
}
//这个函数只是用来统计地雷数量,只再这个源文件中使用,所以用static修饰避免问题
static int get_mines_count(char put_mine[ROWS][COLS], int x, int y)
{
return put_mine[x - 1][y - 1] +
put_mine[x - 1][y] +
put_mine[x - 1][y + 1] +
put_mine[x][y - 1] +
put_mine[x][y + 1] +
put_mine[x + 1][y - 1] +
put_mine[x + 1][y] +
put_mine[x + 1][y + 1] - 8 * '0';
}
void FindMine(char put_mine[ROWS][COLS], char show_mine[ROWS][COLS], int row, int col)
{
//首先选择排查坐标
printf("请输入你要排查的坐标:> ");
int x = 0, y = 0;
int win = 0;
while (win<row*col-Easy_mod)
{
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//如果输入的坐标是地雷坐标一样
if (put_mine[x][y] == '1')
{
printf("很遗憾,你踩雷了,游戏失败!\n");
//游戏失败以后,将布置了地雷的数组打印出来
DispalyBoard(put_mine, row, col);
printf("正在回到菜单,请稍后......\n");
//让玩家有足够的时间观察
Sleep(9000);
system("cls");
break;
}
//如果没有踩雷
//就吧周围3X3的空间中的地雷数量统计并返回输出
else
{
//把侦测器统计的地雷个数赋值给int类型的count
int count = get_mines_count(put_mine, x, y);
//将这个结果加上 ' 0 ',转换成字符,再赋值给字符数组中的勘探坐标处
show_mine[x][y] = count + '0';
//每进行一次成功的排查,就把胜利条件自增,以满足后续胜利条件
win++;
system("cls;");
//显示排查信息
DispalyBoard(show_mine, row, col);
}
}
else
{
printf("输入的地址无效,请重新输入!\n");
}
}
if (win == row * col - Easy_mod)
{
printf("\n");
printf("恭喜你成功通关!\n");
printf("\n");
DispalyBoard(show_mine, row, col);
//让系统休眠5秒,这样给玩家足够的时间回顾上局游戏
Sleep(5000);
system("cls");
}
}
3, 主函数测试文件 —— Minesweep_01_test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Minesweep_01.h"
void menu()
{
printf("******************************\n");
printf("**** 欢迎来到游戏:扫雷 ****\n");
printf("**** 1. 开始游戏 ****\n");
printf("**** 0. 退出游戏 ****\n");
printf("******************************\n");
printf("\n");
}
void game()
{
//创建一个数组用于存放地雷
char put_mine[ROWS][COLS];
//创建一个数组用于显示地雷以及后续效果
char show_mine[ROWS][COLS];
//初始化数组
InitBoard(put_mine, ROWS, COLS, '0');
InitBoard(show_mine, ROWS, COLS, '*');
//打印数组
//为了使得游戏有可玩性,所以要把布置地雷的数组隐藏
//DispalyBoard(put_mine, ROW, COL);
DispalyBoard(show_mine, ROW, COL);
//开始布置地雷
SetMine(put_mine, ROW, COL);
//为了确定是否正确产生了随机的地雷,这里编写打印布置地雷后的数组
DispalyBoard(put_mine, ROW, COL);
//
//排查地雷
FindMine(put_mine, show_mine, ROW, COL);
}
int main()
{
int choose = 0;
//随着事件产生随机数
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择:\n");
scanf("%d", &choose);
switch (choose)
{
case 1:
{
printf("游戏即将开始!\n");
game();
break;
}
case 0:
{
printf("即将推出游戏!\n");
break;
}
default:
{
printf("无效的输入,请重新输入!\n");
break;;
}
}
} while (choose);
return 0;
}
----------以上就是本项目的全部原码----------
三,不足及期望
这个程序只是使用了C语言中最简单,最基础的部分来实现扫雷游戏,最后的结果并不是很能令人满意,这是因为初阶的程序是不足以构建复杂项目的,存在构建的可能性,但是这样做将会是费时费力的,所以本项目最大的期望,就是希望看到这里的你,在遇到这一类问题的时候有一个参考方向,有一个大致的规划
原本是准备加入难度选择环节和棋盘大小自定义环节,但是对于初阶C语言来说有些勉强了,关于以上的实现,将会在下一个版本的扫雷游戏中得以实现
扫雷游戏1.0版本不足:
难度单一,不能自动探索连贯的非雷区,每次探索都需要输入坐标,效率低下,且棋盘没有再一个表格框架中,显得很简陋,而且不利于观察和游戏
后续版本将会在以后进行优化,目前来说未来可能会解决的问题分别是:
1,增加自定义难度环节,可以让玩家自定义地雷数量
2,增加自定义棋盘环节,可以让玩家自定义棋盘大小
以上两点实现了玩家可以自定义游戏难度和流程长度
3,增加自动勘探功能,如果某一个已探索区域附近都是非雷区,那么将会把该坐标周围3X3范围内都清空
4,将会对于美观问题进一步优化