Skip to content

Daucloud/SnakeFoP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 

Repository files navigation

SnakeFoP

一、项目运行环境

  • 系统类型:Windows
  • 系统版本:Windows 11
  • 编译器版本:g++ 8.1.0及以上
  • 所需最低C++标准:C++ 11

二、功能实现逻辑

(1) 项目组织方式

  • 本项目使用CMake进行打包和管理,CMake的配置文件 CMakeLists.txtsrc在同一目录下。
  • 项目代码和资源均放置到 src目录下。其中 code目录存放源代码,include目录存放头文件,configmapsrecord目录分别存放配置文件、地图文件和游戏记录文件。另外。last.txt文件保存用户当前的配置和地图信息。
  • code目录下是项目的主体文件。其中,play.cppfunda.cppextension.cpp分别存放游戏入口、基础功能函数定义和周边功能函数定义。
  • include目录下是项目的头文件。其中,data.hfunda.hextension.h中分别存放项目的全局变量和自定义结构体、基础功能函数声明和周边功能函数声明

(2) 游戏主体逻辑

本部分介绍游戏基础功能(地图生成、食物生成、蛇的移动和增长等)的实现逻辑,主要对 funda.cpp文件的内容进行解释。

在游戏的基础部分,主要有三个关键的参与对象:

  1. 地图(stage)
  2. 食物(food)
  3. 蛇(snake)

下面分别介绍三个关键对象的实现逻辑:

1. 地图

地图信息的存储

地图采用二维数组进行存储:

extern int stage[maxn][maxn];

对于地图上的某个位置 stage[i][j]

  • 其值为0表示正常,即蛇允许移动到此处且无事发生;
  • 其值为负表示蛇移动到此处则游戏结束(-1表示该格为蛇自身或者墙壁所占据;-2表示有障碍物,这将在后面的周边功能介绍部分进一步说明);
  • 其值为正表示蛇允许移动到此处,但会"有事发生"(1,2,3分别表示蛇吃到了对应分值的食物并实现增长;5,6,7,8分别表示蛇将穿越虚的上、下、左、右墙)
地图的打印

地图打印通过 void _printstage(int,int)函数进行。在打印墙壁的任务之外,此函数还完成了对墙壁位置的赋值(-1或5~8)和障碍物的打印(具体内容参见后续周边功能介绍部分)。

2. 食物

食物使用 void _printfood(int,int)函数进行生成,两个参数分别表示食物产生区域的高和宽。

食物的分值和位置均采用采用 rand()伪随机函数进行生成,在食物生成后亦对 stage中对应的位置赋予了相应的分值。具体实现参见 funda.cpp文件的源码。

3. 蛇

蛇信息的存储

蛇采用链表形式进行存储:

struct snake {
    coordinate coor;
    snake *next;
};
extern snake *tail,*head;//分别存储蛇尾和蛇头(为了使操作便捷,令蛇尾作为链表头)

/*coordinate是存储坐标信息的结构体
struct coordinate {
    int x,y;
    bool operator==(const coordinate &other){
        return x==other.x&&y==other.y;
    }
};
*/

之所以使用链表存储,是考虑到:

  1. 蛇的位置信息为一维长链结构,较为符合链表这一数据结构形式;
  2. 我们一开始也考虑过使用 coordinate数组存储蛇结构,但数组存储在每次更新蛇位置时需要将整个数组后移一位,时间复杂度为$O(n)$,较为繁琐;而链表只需删除蛇尾并添加蛇头,时间复杂度为$O(1)$,且操作更为便捷。故综合考虑后使用链表存储蛇信息。
蛇的移动

蛇的移动涉及到两个函数:void _refresh()void _getcontrol()。前者用于每隔一个周期刷新蛇的位置,后者用于处理用户输入。

这两个函数通过C++ 11引入的 std::thread 类进行双线程操作,即"刷新蛇的位置"和"读取用户输入"两个函数平行执行。

void _refresh()

此函数每隔一个周期(1/difficulty秒)刷新一次。其实现的功能有:

  1. 更新蛇的移动方向
  2. 根据蛇当前的移动方向判断蛇下一步的行为:是蛇的移动、蛇身增长还是游戏结束
  3. 更新蛇链表的信息
void _getcontrol

此函数为简单处理用户输入:用户输入w/a/s/d则更新 direction1(direction1为暂存的移动方向,此值只有不和 direction方向相反才会在 _refresh中得到更新),为空格则暂停/继续;暂停状态下输入q则游戏退出。

4. 基础功能其他部分的说明

  • void _opposite():对反方向进行标记,方便后续方向的更新
  • void _gotoxy(SHORT,SHORT):移动光标到指定坐标$(x,y)$
  • void _closecursor(bool):关闭光标显示
  • bool _interface():主界面的打印并处理用户输入。其中 other函数处理用户跟周边有关的输入,详见后一部分。
  • void _countdown():进入游戏的简易倒计时。

(3) 游戏周边功能

游戏周边功能主要实现了三项功能:

  • 用户自定义配置:游戏难度、随机种子、食物数量和食物生成概率;
  • 地图配置功能:地图大小、四面墙的虚实、障碍物的数量和障碍物的坐标
  • 游戏记录回放功能:用户保存游戏记录并可在主界面查看回放

本部分主要在extension.cpp文件中予以实现,下面具体说明:

1. 用户自定义配置

实现这部分的核心函数是_config_i_config_u_update_config。辅助函数有_validconfig_i

void _config_i()
  • 首先读取用户的输入配置名(同时对文件名合法性予以判断)
  • 然后读取用户输入的配置,并使用validconfig_i函数判断配置合法性,添加.config后缀后将其存储在../src/config路径下(程序运行文件在和src文件平级的build目录下,下类似)。
void _config_u()
  • 首先读取用户的输入配置名(同时对文件名合法性予以判断)
  • 使用_update_config函数更新与配置有关的全局变量(此函数具体实现见后文)
bool _update_config(const std::string&)

读取指定文件内容(同时对文件的存在性做出判断),并赋值给difficulty、seed、foodquan、foodprob.a、foodprob.b、foodprob.c来配置信息(分别是游戏难度、随机种子、食物数量和一分、二分、三分食物的生成概率)

2. 用户自定义地图

实现这部分的核心函数为_map_n_map_m_update_map。除此之外,还使用了_input_map_printmap_valid_mapsize等辅助函数。

下对其具体实现予以说明:

void _map_n()
  • 首先读取用户输入的地图名(同时对文件名的合法性予以判断)
  • 要求用户输入地图的宽和高,同时使用_valid_mapsize判断用户是否输入是否合法
  • 接下来进行地图的可视化创建功能。用户每次输入一行指令,该指令通过_input_map函数予以读取并处理(该函数的内部实现逻辑见后文),_input_map函数的返回值:
  1. 为负,代表用户的指令不合法,程序会要求用户重新输入
  2. 为1,代表用户的指令合法(o指令、p指令或w/a/s/d指令),之后执行_printmap函数(内部逻辑参见后文),重新打印当前地图;
  3. 为0,代表用户输入了f指令,即退出地图创建过程并保存;
  4. 为2,代表用户输入了q指令,即退出创建过程且不保存
  • 最后,添加后缀.map,并存储文件到../src/maps/路径下。
int _input_map(std::string &str,int w,int h,int&w1,int&s,int&a,int&d,std::vector<coordinate>&obstacle)

对地图创建过程中用户的指令予以判断:

  • 若str是f和q,分别返回0和2
  • 若str中第一位是o,则使用isstreamstring流分割后续输入;若后续输入是合法指令,则在存储障碍物坐标的obstacle数组中添加一个元素;反之,返回-1
  • 若str中第一位是p则使用isstreamstring流分割后续输入;若后续输入是合法指令,则在存储障碍物坐标的obstacle数组中删除指定元素;反之,返回-1
  • 若str中第一位是w/s/a/d,则使用isstreamstring流分割后续输入;若后续输入是合法指令,则改变对应的标记墙壁虚实的变量w1/s/a/d
  • 其他情况,返回-1
void _printmap(int width,int height,int w,int s,int a,int d)

打印创建过程中的函数,基本和print_satge函数相同,只是:

  • 根据对应墙壁的虚实性判断墙壁应打印为绿色还是红色
  • 若某位置标记有障碍物,则打印青色的感叹号
void _map_m()
  • 首先读取用户输入的地图名(同时对文件名合法性予以判断)
  • 使用_update_map函数更新与地图有关的全局变量(此函数具体实现见后文)
bool update_map(const std::string&)

读取指定文件内容(同时对文件的存在性做出判断),并更新与地图有关的全局变量:

  • 更新width、height、ww、ws、wa、wd (分别是地图的宽、高和上下左右墙壁的虚实性)

  • int stage[maxn][maxn]有障碍物的位置标记-2

3. 游戏回放

实现这部分的核心函数为_record_b_record_r。除此之外,还使用_printfood_given_refresh_record_initialize_record_getcontrol_record进行辅助实现

具体实现如下:

记录文件设计

游戏回放部分的核心之一就是记录文件格式的设计。我们采用的思路是:

  • 前两行分别存储配置名称和地图名称
  • 第三行存储游戏一开始生成的食物的坐标
  • 接下来直到文件结束,每行存储四个信息,分别是生成食物的x,y坐标,食物分值(若没有食物生成,这三个值均为0)和当前蛇的移动方向
  • 此外,每局游戏开始的时候,都会打开一个临时文件std::ofstream tempfile("../src/record/temp") ,用于存储本局游戏的信息(格式同上)。游戏结束,并且用户输入b以创建回放记录的情况下,该文件会被保留并被重新命名;在其他情况下(用户不想创建回放;游戏中途退出等),该文件都会被删除
_record_b

用于在用户游戏结束的时候保存游戏记录文件

  • 首先要求用户输入记录名称(并对输入合法性予以判断)
  • temp临时文件重命名为用户输入的名称,并添加后缀.rec后存储在../src/record路径下。同时。如果用户输入的记录名称存在,应当删除原文件。
_record_r

用于回放保存的记录文件

  • 首先要求用户输入回放的名称(同时对该回放是否存在予以判断)
  • 接下来,读取文件信息并生成回放:
  1. 读取配置信息和地图信息并更新数据(如果配置和地图已被删除,会退出回放)
  2. 使用_initialize_record函数初始化回放(具体见后文);并用_print_given函数在指定位置生成指定分值的游戏一开始生成的食物
  3. 新建一个_getcontrol_record线程在后台运行。当用户输入q的时候会退出回放
  4. while不断读取文件输入并存储到变量x,y,v,direction中,并由此生成食物(_printfood_given(x,y,v))和刷新蛇的位置(_refresh_record())。循环当文件无法读取输入的时候结束,并执行结束操作(将蛇变红等)
bool _printfood_given(x,y,v)
  • 用于在指定位置打印食物。实现逻辑基本与基础功能中的该函数相同。

  • 同时,如果x,y,v的值不合法(比如超出边界,分值不是1~3等等),将返回false并指示回放结束

bool _refresh_record()
  • 用于刷新蛇的位置。实现逻辑基本与基础功能的_refresh函数相同
  • 同时,如当前方向direction的值不合法,将会返回false并指示回放结束

其他函数

_changetoint、_changetoint

分别用于将字符串转整数和小数,同时用于在用户新建配置文件等处判断输入的合法性与否(不合法返回负值)

_valid_filename

用于判断用户输入的文件名称是否合法(只能由数字、字母和下划线组成)

_other

用于主界面读取g/q/help之外的其他信息并做出反应(启动周边功能或返回输入不合法)

_save

用于保存用户的配置和地图,并存储到last.txt文件里

_load

用于载入last.txt文件中的配置和地图

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published