1. 前言
1.1 目的
以前对STL的知识不怎么关心,最近在工作中觉得工程代码用到STL的地方太多了,没看懂,再不补一补就快秃头了,于是秉承着学习的态度,重温了C++ STL,然后就想到了可以写个贪吃蛇来现学现卖,加深映像。
1.2 建议
建议在阅读本文之前,对STL陌生的同学可以先温习下STL。
1.3 开发环境
语言:C++
环境:Visual Stdio 2022 、 Windows
2. 成果展示
不墨迹,先展示成果。
3. 项目需求
1.初始化游戏,生成地图,蛇,食物
2.蛇吃到食物身体变长
3.蛇不能反向移动
4.食物被吃后立即生产新的
5.记录吃到的食物个数(得分)
6.食物不能生成在蛇身
7.蛇不能撞自己
8.设置游戏难度
9.蛇如果撞墙,直接游戏结束
4. 详细设计
4.1 概览
前面介绍了,是用的面向对象的思想来写的,那必然要生成蛇类、食物类、点类,为啥不是地图类,因为我是先用的画点的方法,然后用点来画方形地图。而且还可以用点类来画蛇,画食物。
蛇类:
食物类:
点类:
4.2 初始化
4.2.1 地图
在介绍怎么初始化地图之前,我们先了解下,怎么在终端用代码输出一个小方块。
没错,就是cout打印出来就可以了,但是我想在一个已知的坐标打印出来呢?
那就需要用到光标定位:
void gotoxy(int x, int y) //光标定位
{
COORD pos = { x,y };
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}
然后再输出方块就行,用某狗输入法里面的符号大全:
然后就可以在特定的坐标打印方块,那么也就可以继续实现打印出自定义宽高的地图。
需要强调一点,终端的光标的高 = 2 * 宽,所以我们方块的坐标想在终端输出(4,4)的位置,就需要在代码中用(8,4)来代替。
4.2.1 蛇和食物
那我们继续了解下怎么初始化蛇和食物,初始化肯定是需要用随机坐标来产生,这就需要用到rand()这个函数,然后可以产生一个【a,b】区间的整数:
randNum = rand() % (b - a + 1) + a;
那么久可以简单的在终端上随机生产一个蛇头和食物了。
整条蛇我是用容器 vector< pair< int, int > >
来存储坐标的,食物我是用 pair< int, int >
4.3 蛇吃到食物身体变长
首先需要实现蛇的移动逻辑:初始化蛇向右移动,假设蛇头坐标(x , y)那把这个坐标(x-1 , y) 添加到 vector的尾元素,然后删除头元素,及 "插尾删头"
。
这里我们还需要定义一个oldSnack vector来打印空格,来抹去上一次蛇的身体,不然蛇是会一直覆盖,你会发现,蛇尾看起来没有被删除。
然后就可以实现蛇吃到食物身体变长,这个逻辑是通过添加食物坐标到vector的尾元素,且不删除头元素,及“只插尾”
。
4.4 蛇不能反向移动
也就是,假如蛇移动方向是右,我不小心按了A键(左),蛇是不能后退的。
需要定义一个变量oldDirection,记录上一次的移动方向,来和当前获取的方向进行判断。
4.5 食物被吃后刷新
刷新很简单,重新生成一个就好,但是怎么判断食物被吃呢,可以当前蛇头的坐标是否等于食物坐标,
如果不等,说明没吃到,那就继续移动蛇身,如果吃到了,重新产生食物。
记录吃到的食物个数,也在这一块儿,上面截图里的myCount,如果想做个分数,自己乘以一个食物分数系数就可以实现。
4.6 食物不能生成在蛇身
这里需要先重新生成,然后再进行判断
这里把判断食物是否产生在蛇身封装了一下,用STL算法里的find():
bool IsFoodEqualSnack(vector<pair<int, int>> vec, pair<int, int> food)
{
if (!vec.empty())
{
auto result = find(begin(vec), end(vec), food);
if ( result != vec.end() )
{
return true;
}
else
{
return false;
}
}
else
{
cout << "vector is empty" << endl;
return false;
}
cout << endl;
}
4.7 设置游戏难度
目前是通过Sleep() 来控制移动速度的,逻辑是当前蛇长度减初始化时候的长度,然后对2取余,也就是每吃到两个食物,移动速度增加一档。
void setSpeed(Snack objSnack)
{
Sleep( 1000 / speedLevel );
if ( (objSnack.mV.size() - 3 != 0))
{
if ( (oldspeed != (objSnack.mV.size() - 3) % 2) &&
((objSnack.mV.size() - 3) % 2 == 0) )
{
speedLevel++;
}
}
oldspeed = (objSnack.mV.size() - 3) % 2;
}
4.8 蛇不能撞自己
这里我们需要的逻辑是,蛇身vector中不能中重复的元素,这怎么实现呢?
很简单,利用set容器的唯一特性,我们把蛇身vector赋值给一个set容器,判断set的size和蛇身vector的size是否相同,是则不重复,就是没有撞到自己,反之则是装到自己了,GameOver。
bool Snack::isHitItself()
{
mSet = { mV.begin(),mV.end() };
if (mSet.size() != mV.size())//蛇头撞到了蛇身
{
return true;
}
else
{
return false;
}
}
4.8 蛇不能撞墙
这个很简单,没啥好说的。
bool Snack::isHitWall()
{
if ( (mV.end()-1)->first == MAP_X *2 ||
(mV.end() - 1)->first == 0 ||
(mV.end() - 1)->second == MAP_Y ||
(mV.end() - 1)->second == 0)
{
return true;
}
else
{
return false;
}
}
5. 流程图
6. 代码
环境对了就绝对能正常运行,开发过程中已经解决了99.99%的bug,已经没有bug了。
代码下载
7. 结语
本文内容完全原创,不允许转载!!!