贪吃蛇中的小技术
程序设计思路
- 构造游戏地图,用矩阵实现,每一个元素就是一个点
- 构造蛇对象,有长度,身体,移动等属性
- 游戏时,蛇一直在移动,获取到键盘输入的消息后改变方向,继续移动
- 构建食物对象,位置,以及被蛇吃掉后的位置更新
- 程序终止条件,也就是蛇不能撞到自己或者边界,也就是输了;当然为了游戏效果,设置一个最大值,当蛇的长度超过这个值,就判定为win
关键点
由于是控制台程序,因此实现贪吃蛇的关键点在于将蛇移动后的界面重绘并展示出来,并随时获取到键盘输入的消息,以及食物被吃后的重新设定。
将每次移动后的画面重现,初步构想是把每次界面中各元素的位置记录下来,然后清楚屏幕内容,重新输出。(MFC中易于实现,主要是除了redraw window,不知道还有什么方法可以去除画过的痕迹)但实际上并没有必要,改变的只是蛇头和蛇尾的位置,只需要改两个点的位置的内容就好。
随时获取键盘消息,在MFC中很方便可以实现(虽然亲自试验并没有起作用),但在控制台程序里,还需要引用一些库中的函数。
食物的重新设定,主要是位置不要出界,不要落在蛇身在的位置。
引用头文件
标准C++程序开头,除了常见的,还需要
#include <iostream>
#include <ctime> //需要随机设置食物的位置,时间作为随机种子
#include <conio.h> //响应键盘函数
#include <windows.h> //控制台界面的控制
关键点的实现
void Snake::Move()
{
gotoxy(body[len - 1].x, body[len - 1].y);
cout <<' ';
for (int i = len - 1; i > 0; --i) {
this->body[i].x = this->body[i - 1].x;
this->body[i].y = this->body[i - 1].y;
}
if (this->dir == 1) {
this->body[0].x--;
}
else if (this->dir == 2) {
this->body[0].y++;
}
else if (this->dir == 3) {
this->body[0].x++;
}
else if (this->dir == 4) {
this->body[0].y--;
}
gotoxy(this->body[0].x, this->body[0].y);
cout <<'O';
}
这个是蛇的移动方法,同时表现了在界面里的展示方法
if(kbhit()) //kbhit 非阻塞函数
{
ch=getch(); //使用 getch 函数获取键盘输入
switch (ch){
case 'A':
case 'a':
snake.Left();
break;
case 's':
case 'S':
snake.Down();
break;
case 'd':
case 'D':
snake.Right();
break;
case 'w':
case 'W':
snake.Up();
break;
}
}
这里的 kbhit() 函数就是可以实现随时响应键盘输入的函数,效果比较好
void setFood(Snake &s){
food.x = rand() % 28 + 2;
food.y = rand() % 78 + 2;
int i;
int flag = 1;
while(flag){
for (i = 0; i < s.len; ++i){
if(s.body[i].x==food.x && s.body[i].y == food.y){
break;
}
}
if(i==s.len){
flag = 0;
gotoxy(food.x, food.y);
cout << '$';
}else{
food.x = rand() % 28 + 2;
food.y = rand() % 78 + 2;
}
}
return;
}
这个就是食物设置的方法,这个的28和78是边界大小
问题探讨
目前程序实现的基本还有一个蛇身重合,也就是自己会撞自己没有加上去(懒得改了)。其他的没有问题。
但对于键盘消息响应,一直耿耿于怀,查到了还有一种方法是
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME)&0x8000)?1:0)
if(KEY_DOWN('A')){
······
}else if(){
······
}······
但是和kbhit()比起来,效果比较卡。在实现双击加速时,使用了条件嵌套,结果kbhit()效果也逊色不少,所以想着应该有更好的办法,还是说在MFC等封装好的图形界面设计里,优化效果会好点?这个目前还没有找到答案。