配色: 字号:
C++编写贪吃蛇小游戏
2020-08-14 | 阅:  转:  |  分享 
  
C++编写贪吃蛇小游戏快速入门刚学完C++。一时兴起,就花几天时间手动做了个贪吃蛇,后来觉得不过瘾,于是又加入了AI功能。希望大家Enjoy
It.效果图示AI模式演示imageimage整体规划+原理image大体上可以分为图上所示的几个类。不过……怎么看都有点强行面
向对象的味道在里面。。[哭笑][哭笑][哭笑]。不管了……代码写得可能有点凌乱,下面我会为大家一一讲解。整个程序设计的原理就是:主
函数死循环,不断刷新打印贪吃蛇和食物。这样每循环一次,就类似电影里面的一帧,最终显示的效果就是蛇会动起来。01初始化工作-游戏设
置游戏设置和相关初始化放在了一个类里面,并进行了静态声明。主要设置了游戏窗口的长和款。并在GameInit()函数里面设置了窗口大
小,隐藏光标,初始化随机数种子等。代码如下://游戏设置相关模块,把函数都放到一个类里面了。函数定义为static静态成员,不生成
实体也可以直接调用classGameSetting{public://游戏窗口的长宽staticconstintwin
dow_height=40;staticconstintwindow_width=80;public:stati
cvoidGameInit(){//设置游戏窗口大小charbuffer[32];sprintf_s(buffer,
"modeconcols=%dlines=%d",window_width,window_height);system
(buffer);//隐藏光标HANDLEhandle=GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFOCursorInfo;GetConsoleCursorInfo(handle,&Cu
rsorInfo);//获取控制台光标信息CursorInfo.bVisible=false;//隐藏控制台光标SetC
onsoleCursorInfo(handle,&CursorInfo);//设置控制台光标状态//初始化随机数种子sran
d((unsignedint)time(0));}};用到了几个相关的WindowsAPI,本文不做过多介绍,大家百度即可。
02打印信息类该类主要是用来打印一些游戏相关信息的。该类大体如下:image下面挑几个重点的来讲:2.1画地图边界这个函数主要
是根据上面所给的游戏窗口长宽来打印地图边界的。其中还划分了几个区域,主要用来放不同的信息的。//画地图边界staticvoid
DrawMap(){system("cls");inti,j;for(i=0;iwindow_width;i++)cout<<"#";cout<eSetting::window_height-2;i++){for(j=0;jndow_width;j++){if(i==13&&j>=GameSetting::window_width
-29){cout<<"#";continue;}if(j==0||j==GameSetting::
window_width-29||j==GameSetting::window_width-1){cout<<
"#";}elsecout<<"";}cout<etting::window_width;i++)cout<<"#";}划分区域如下图,#就是边框了:image2.2
画出分数和模式该函数主要是在右上角画出成绩和游戏模式的,在绘制之前会进行刷新处理。先清除,再重新打印。用到了一个gotoxy()函
数。这个函数主要是移动光标到(x,y)坐标处的。关于(x,y)的位置,根据实际情况调整即可。//画分数staticvoid
DrawScore(intscore){gotoxy(GameSetting::window_width-22+14,6
);cout<<"";gotoxy(GameSetting::window_width-22+14,4);cou
t<<"";gotoxy(GameSetting::window_width-22,6);cout<<"当前玩
家分数:"<);cout<<"当前游戏速度:"<<10-speed/25<坐标,随机生成规则,和画出食物等一系列操作。其中食物坐标我们用了一个结构体:typedefstruct{intx;int
y;}COORDINATE;该结构体两个成员,分别保存坐标的(x,y)。蛇身的坐标也会用到这个结构体。有关食物类的大体如下:im
age下面我们还是挑几个重点来讲。3.1随机生成食物随机生成食物,原则上不允许食物出现在蛇身的位置上,如果有。我们重新生成。注意
地图的范围,就是区域左边一块。实际情况根据自身的地图范围来调整食物坐标的范围,注意不要越界。用rand()函数获得随机坐标。代码如
下:voidRandomXY(vector&coord){m_coordinate.x=ran
d()%(GameSetting::window_width-30)+1;m_coordinate.y=rand
()%(GameSetting::window_height-2)+1;unsignedinti;//原则上不
允许食物出现在蛇的位置上,如果有,重新生成for(i=0;i在蛇身的位置上。重新生成if(coord[i].x==m_coordinate.x&&coord[i].y==m_
coordinate.y){m_coordinate.x=rand()%(GameSetting::window_wi
dth-30)+1;m_coordinate.y=rand()%(GameSetting::window_hei
ght-2)+1;i=0;}}}然后,在构造函数里面传入蛇身的坐标。即可生成食物。3.2画出食物画出食物比较简
单了,gotoxy到随机生成的坐标之后,cout就行。我们在这还设置了一个食物颜色为红色。代码如下:voidDrawFood()
{setColor(12,0);gotoxy(m_coordinate.x,m_coordinate.y);cout<
<"@";setColor(7,0);}04贪吃蛇类定义贪吃蛇的移动,打印,吃食物等等。这节课我们暂时不讨论AI功能,先把
手动操作的贪吃蛇做了跑起来,下节课再做AI功能的介绍。该类大体如下:image4.1成员变量成员变量m_direction记录每
次移动的方向。m_is_alive记录贪吃蛇是否还活着。m_coordinate则是贪吃蛇身体坐标的记录。贪吃蛇是一节一节的,整条
蛇必然是由许多节组成的。因此用了一个vector来存储蛇身,每节类型是COORDINATE结构体的。4.2默认构造函数默认构造函
数Snake()里面主要是做了初始贪吃蛇的生成,以及移动方向的定义等。初始的蛇为3节。在中间位置,向上移动。代码如下://移动方
向向上m_direction=1;m_is_alive=true;COORDINATEsnake_head;//
蛇头生成位置snake_head.x=GameSetting::window_width/2-15;snake_h
ead.y=GameSetting::window_height/2;this->m_coordinate.push_b
ack(snake_head);snake_head.y++;this->m_coordinate.push_back(sna
ke_head);snake_head.y++;this->m_coordinate.push_back(snake_head
);//初始蛇身长度三节4.3监听键盘监听键盘用了C里面的一个库函数。_kbhit()非阻塞函数,可以不断监听键盘的情况从而不
产生阻塞。有键盘按下的时候,就获取按下的键盘是哪个。然后做出相应的变化,其实是方向的调整。需要注意的是,当我们的蛇往上走的时候,按
下方向的键,我们是不做处理的。其它方向一样。还有一个调整游戏速度的,speed是休眠时间,speed越小,速度越快。反之速度越慢。
//监听键盘voidlisten_key_borad(){charch;if(_kbhit())//kbhit非阻塞
函数{ch=_getch();//使用getch函数获取键盘输入switch(ch){case''w''
:case''W'':if(this->m_direction==DOWN)break;this->m_directi
on=UP;break;case''s'':case''S'':if(this->m_direction==UP)
break;this->m_direction=DOWN;break;case''a'':case''A'':if(t
his->m_direction==RIGHT)break;this->m_direction=LEFT;break
;case''d'':case''D'':if(this->m_direction==LEFT)break;this-
>m_direction=RIGHT;break;case''+'':if(speed>=25){speed-
=25;}break;case''-'':if(speed<250){speed+=25;}break;
}}}4.4移动贪吃蛇移动贪吃蛇,我们用了一个方向变量,在监听键盘的时候获取移动的方向,然后在根据方向移动贪吃蛇的蛇头。这里
的移动我们是这样处理的,首先,贪吃蛇每移动一次,需要改变的只有蛇头和蛇尾两节。我们只需要把新的蛇头插进去,最后再画出来就可以了。至
于蛇尾,如果我们不删除蛇尾的话,蛇会不断变长的。因此我们的做法是:吃到食物的时候插入蛇头而不删除蛇尾,没有吃到食物的时候插入蛇头同
时删除蛇尾。这样就完美搞定了。//移动贪吃蛇voidmove_snake(){//监听键盘listen_key_borad
();//蛇头COORDINATEhead=m_coordinate[0];//direction方向:1上2
下3左4右switch(this->m_direction){caseUP:head.y--;break
;caseDOWN:head.y++;break;caseLEFT:head.x--;break;caseRI
GHT:head.x++;break;}//插入移动后新的蛇头。是否删除蛇尾,在后续吃到食物判断那里做m_coordin
ate.insert(m_coordinate.begin(),head);}4.5是否吃到食物判断是否吃到食物,就是看看蛇头
的坐标等不等于食物的坐标。如果等于,就重新生成食物,不删除蛇尾,蛇变长一节。不等于,就删除蛇尾,蛇长不变。boolis_eat_
food(Food&f){//获取食物坐标COORDINATEfood_coordinate=f.GetFoodCo
ordinate();//吃到食物,食物重新生成,不删除蛇尾if(m_coordinate[HEAD].x==food_
coordinate.x&&m_coordinate[HEAD].y==food_coordinate.y){f.Ra
ndomXY(m_coordinate);returntrue;}else{//没有吃到食物,删除蛇尾m_coord
inate.erase(m_coordinate.end()-1);returnfalse;}}4.6判断蛇是否还存活判
断蛇是否GG,主要是看是否超出边界,是否碰到自己身体其他部分。//判断贪吃蛇死了没boolsnake_is_alive(){i
f(m_coordinate[HEAD].x<=0||m_coordinate[HEAD].x>=GameSetti
ng::window_width-29||m_coordinate[HEAD].y<=0||m_coordinat
e[HEAD].y>=GameSetting::window_height-1){//超出边界m_is_alive
=false;returnm_is_alive;}//和自己碰到一起for(unsignedinti=1;
inate[HEAD].x&&m_coordinate[i].y==m_coordinate[HEAD].y){m_is
_alive=false;returnm_is_alive;}}m_is_alive=true;return
m_is_alive;}4.7画出贪吃蛇画出贪吃蛇比较简单,gotoxy到身体的每一节,然后cout就行。在此之前设置了颜色为浅
绿色。//画出贪吃蛇voiddraw_snake(){//设置颜色为浅绿色setColor(10,0);for(uns
ignedinti=0;im_coordinate.size();i++){gotoxy(m_c
oordinate[i].x,m_coordinate[i].y);cout<<"";}//恢复原来的颜色setC
olor(7,0);}4.8清除屏幕上的贪吃蛇我们是死循环不断刷新打印贪吃蛇的,因此每移动一次,必然会在屏幕上留下上一次贪吃蛇
的痕迹。因此我们每次在画蛇之前,不是添足,而是清理一下上次遗留的蛇身。我们知道,蛇每次移动,变的只有蛇头和蛇尾,因此该函数我们只需
要清理蛇尾就行。gotoxy到蛇尾的坐标,cout<<"";就行。gotoxy(m_coordinate[this->m_coo
rdinate.size()-1].x,m_coordinate[this->m_coordinate.size()-1].
y);cout<<"";05主函数,组装我们的游戏我们的游戏在主函数里面进行组装。然后开始运行。首先我们做游戏相关的初始化
和模式选择。GameSettingsetting;PrintInfoprint_info;Snakesnake;//初始化
游戏setting.GameInit();//游戏模式选择print_info.DrawChoiceInfo();charch
=_getch();switch(ch){case''1'':snake.set_model(true);speed=5
0;break;case''2'':snake.set_model(false);break;default:gotoxy(
GameSetting::window_width/2-10,GameSetting::window_height/
2+3);cout<<"输入错误,Bye!"<0;}gotoxy(GameSetting::window_width/2-10,GameSetting::windo
w_height/2+3);system("pause");然后就是画地图边框,打印游戏相关信息和说明。生成食物了。//画
地图print_info.DrawMap();print_info.DrawGameInfo(snake.GetModel());
//生成食物Foodfood(snake.m_coordinate);最后就是游戏死循环,在死循环里面,我们需要不断移动蛇,画蛇
,判断蛇的状态,判断食物的状态,是否吃到食物等等。具体代码://游戏死循环while(true){//打印成绩print_i
nfo.DrawScore(snake.GetSnakeSize());//画出食物food.DrawFood();//清理
蛇尾,每次画蛇前必做snake.ClearSnake();//判断是否吃到食物snake.is_eat_food(food)
;//根据用户模式选择不同的调度方式if(snake.GetModel()==true){snake.move_snake();}else{snake.AI_find_path(food);snake.AI_move_snake();}//画蛇snake.draw_snake();//判断蛇是否还活着if(!snake.snake_is_alive()){print_info.GameOver(snake.GetSnakeSize());break;}//控制游戏速度Sleep(speed);}最终,我们的代码就可以Run起来了。具体效果放在开头了。界面算不上好看,但是整个程序向大家展示了最基本最核心的功能和代码,大家可以在这个基础上开发自己喜欢的各种美丽的界面哦。06AI部分和完善代码是画了几天间间断断写出来的,水平不算很高,代码也写得乱七八糟的。不过代码会在后期不断优化,尽量做到精简优美。至于AI功能,等下一篇博文写吧。作者:短短的路走走停停被抢注啦链接:https://www.jianshu.com/p/826929875b90来源:简书简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
献花(0)
+1
(本文系摘摘摘丿丿...首藏)