链队列的应用——贪吃蛇游戏设计
1, 贪吃蛇游戏设计描述
链队列这种数据结构的特点是数据先进先出,利用这个特点我们可以用来模拟贪吃蛇,来进行贪吃蛇游戏的开发。蛇的游动插入头节点,删除尾结点。游戏界面的围墙,可以控制控制台的光标在界面打印一个长方形的围墙表示游戏围墙。
2,链队列表的ADT描述及贪吃蛇相关函数功能模块
- 光标的移动: Goto(x,y)x,y表示界面要移动光标的位置
- 围墙的显示: Print_map()
- 随机创造食物:creat_food()
- 初始化蛇的身体:Insert_snack()
- 蛇的移动: Move_snack(before_x,before_y)
Operate_order()
3, 贪吃蛇相关函数功能的伪代码
struct Snack
{
int x;
int y;
struct Snack* next;
};
struct Food
{
int x;
int y;
};
struct Snack* snack;//声明蛇的头部
struct Snack* front, * rear;//分别表示蛇头,蛇尾
struct Food food;//声明食物
int Width = 40, Length = 40,judge1=0,score=0;//依次表示围墙的宽,长度,是否吃到食物的标志,得分//光标的移动,控制围墙的打印,每次移动蛇头的打印
void Goto(x, y)//x,y代表界面的光标的坐标
{
COORD coord;
coord.X = x, coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);//将光标移动到指定位置
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };//游标信息
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//设置控制台光标信息,隐藏光标
}//打印地图和出身蛇的身体,goto(x,y)函数,将界面的光标移动到指定位置打印方块围墙
void Print_map()
{
for (i = 0; i < Length/2; i++)cout << "■";//Length表示围墙的长度,由于方块占界面两个位置,Length要除2
for (i = 1; i < Width/2-1; i++)//Width表示围墙的宽
{
Goto(0,i);//移动光标
cout << "■";//打印围墙
}
for (i = 1; i < Width/2 - 1; i++)
{
Goto(Length-2, i);
cout << "■";
}
Goto(0,Width/2-1);
for (i = 0; i < Length / 2; i++)cout << "■";
Goto(snack->x, snack->y);//把光标移动到蛇头的位置
cout << "●";//打印蛇头
p = front->next;
while (p != NULL)Goto(p->x, p->y), cout << "◇", p = p->next;//打印蛇身
}
//随机创造食物
void creat_food()
{
p = front;//创建辅助变量
while (1)
{
srand((int)time(0));//建立随机数种子
food.x = rand() % 35 + 2, food.y = rand() % 18 + 1;//创建食物坐标,确保坐标在围墙内
//由于食物图像也占两个坐标,而蛇头在x轴上的移动是每两个进行移动,所以创建的食物坐标在x轴上需为偶数,否则蛇永远吃不到食物
if (food.x % 2 != 0)food.x++;
while (p!=NULL&&food.x != p->x && food.y != p->y)p = p->next;//判断创建的食物是否与蛇身重合
if (p==NULL)//如果不重合,则p==NULL,跳出循环
{
break;
}
}
Goto(food.x, food.y);//打印食物
cout << "★";//星号代表食物
}
//初始化蛇的身体
void Insert_snack()
{
snack->x = 20, snack->y = 10, snack->next = NULL;//初始化蛇头
front = snack;
p = (struct Snack*)malloc(sizeof(struct Snack));//以下初始化蛇身
snack->next = p, p->x = 22, p->y = 10;
p->next= (struct Snack*)malloc(sizeof(struct Snack));
p = p->next, p->x = 24, p->y = 10;
p->next= (struct Snack*)malloc(sizeof(struct Snack));
p = p->next, p->x = 26, p->y = 10, p->next = NULL, rear = p;
}
}//判断蛇头的每一次移动是否撞到墙壁
void crash_wall()
{
if (front->y == 0 || front->y == 19 || front->x <= 1 || front->x >= 38)//由于围墙的每一个方块在x轴上占两个坐标,所以要小于等于1,大于等于38
{
Goto(22, 22);
cout << "游戏结束";
exit(0);
return ;
}
}
//蛇身的移动
void move_snack()
{
Goto(front->x, front->y);//将光标移动到最新的蛇头
cout << "●";//打印蛇头
Goto(front->next->x, front->next->y);//原来的蛇头变成蛇身
cout << "◇";
Goto(45, 9);
cout << "当前得分:" << score;//打印当前得分
if (judge1 ==1)//如果吃到食物,则重新创造食物,此函数结束,否则,将蛇尾删除
{
creat_food();
return;
}
Goto(rear->x,rear->y);
p=front; //做辅助变量
while (p->next!= rear)p = p->next;
rear = p;
free(p->next);//释放蛇尾的内存,删除蛇尾
rear->next = NULL;
cout << " ";
}
//判断是否吃到食物
void eat_food()
{
if (front->x == food.x && front->y == food.y)//如果蛇头坐标等于食物坐标,则吃到食物
{
judge1 = 1;//标志吃到食物
score++;//分数加一
}
}
//接受键盘指令,指挥蛇的移动,键盘上的←↓↑→进行控制
void Operate_order()
{
next_step->x = front->x, next_step->y = front->y; //创建下一步的结点
if (GetAsyncKeyState(VK_UP))//如果指令为↑,蛇头的y坐标减一
{
next_step->y--, next_step->x = front->x;
}
else if (GetAsyncKeyState(VK_DOWN))//如果指令为↓,蛇头的y坐标加一
{
next_step->y++, next_step->x = front->x;
}
else if (GetAsyncKeyState(VK_LEFT))//如果指令为←,蛇头的x坐标减二,由于方块图形占x轴两个位置,所以减二
{
next_step->x -= 2, next_step->y = front->y;
}
else if (GetAsyncKeyState(VK_RIGHT))//如果指令为→,蛇头的x坐标加二,由于方块图形占x轴两个位置,所以加二
{
next_step->x += 2, next_step->y = front->y;
}
else
{
if (front->x == front->next->x)//如果没有接受移动指令,则沿当前方向移动
{
if (front->y > front->next->y)
{
next_step->y++, next_step->x = front->x;
}
else
{
next_step->y--, next_step->x = front->x;
}
}
else if (front->x > front->next->x)
{
next_step->x += 2, next_step->y = front->y;
}
else
{
next_step->x -= 2, next_step->y = front->y;
}
}
next_step->next = front, front = next_step;
eat_food();//如果吃到食物,插入一个结点,judge1置为1,标志吃到食物
move_snack();//移动蛇身
crash_wall();//判断是否撞墙,撞墙就结束游戏
judge1 = 0;//每一次蛇移动,是否吃到食物标志置为0
}
3,程序运行结果及截图
游戏刚开始界面
游戏中途界面
游戏结束界面
4,学习心得
在编写时,比较苦难的地方在于,如何呈现围墙游戏界面,除了采用数组之外,我们可以采用SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord)这个函数,将界面数组化,提高程序运行的效率,减少游戏运行时的卡顿问题。除了采用这个函数,我们也可以采用清屏函数,但是做出来的游戏体验极差,蛇每走一步,图面就必须重新打印一边。
此外,如何呈现蛇的移动这个问题,我们可以采用,蛇头移动一步,就打印,原来的蛇头打印成身体,末尾打印成空格,这样程序就可以呈现出蛇在移动的效果
5,附源代码(无)
#include<iostream>
#include<conio.h>
#include<ctime>
#include<Windows.h>
using namespace std;
struct Snack
{
int x;
int y;
struct Snack* next;
};
struct Food
{
int x;
int y;
};
struct Snack* snack;//声明蛇的头部
struct Snack* front, * rear;//分别表示蛇头,蛇尾
struct Food food;//声明食物
int Width = 40, Length = 40,judge1=0,score=0;//依次表示围墙的宽,长度,是否吃到食物的标志,得分
//光标的移动,控制围墙的打印,每次移动蛇头的打印
void Goto(int x, int y)//x,y代表界面的光标的坐标
{
COORD coord;
coord.X = x, coord.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);//将光标移动到指定位置
CONSOLE_CURSOR_INFO cursor_info = { 1,0 };//游标信息
SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursor_info);//设置控制台光标信息,隐藏光标
}
//随机创造食物
void creat_food()
{
struct Snack* p = front;//创建辅助变量
while (1)
{
srand((int)time(0));//建立随机数种子
food.x = rand() % 35 + 2, food.y = rand() % 18 + 1;//创建食物坐标,确保坐标在围墙内
//由于食物图像也占两个坐标,而蛇头在x轴上的移动是每两个进行移动,所以创建的食物坐标在x轴上需为偶数,否则蛇永远吃不到食物
if (food.x % 2 != 0)food.x++;
while (p!=NULL&&food.x != p->x && food.y != p->y)p = p->next;//判断创建的食物是否与蛇身重合
if (p==NULL)//如果不重合,则p==NULL,跳出循环
{
break;
}
}
Goto(food.x, food.y);//打印食物
cout << "★";//星号代表食物
}
//判断是否吃到食物
void eat_food()
{
if (front->x == food.x && front->y == food.y)//如果蛇头坐标等于食物坐标,则吃到食物
{
judge1 = 1;//标志吃到食物
score++;//分数加一
}
}
//初始化蛇的身体
void Insert_snack()
{
snack->x = 20, snack->y = 10, snack->next = NULL;//初始化蛇头
front = snack;
struct Snack* p;
p = (struct Snack*)malloc(sizeof(struct Snack));//以下初始化蛇身
snack->next = p, p->x = 22, p->y = 10;
p->next= (struct Snack*)malloc(sizeof(struct Snack));
p = p->next, p->x = 24, p->y = 10;
p->next= (struct Snack*)malloc(sizeof(struct Snack));
p = p->next, p->x = 26, p->y = 10, p->next = NULL, rear = p;
}
//打印地图和出身蛇的身体,goto(x,y)函数,将界面的光标移动到指定位置打印方块围墙
void Print_map()
{
for (int i = 0; i < Length/2; i++)cout << "■";//Length表示围墙的长度,由于方块占界面两个位置,Length要除2
for (int i = 1; i < Width/2-1; i++)//Width表示围墙的宽
{
Goto(0,i);//移动光标
cout << "■";//打印围墙
}
for (int i = 1; i < Width/2 - 1; i++)
{
Goto(Length-2, i);
cout << "■";
}
Goto(0,Width/2-1);
for (int i = 0; i < Length / 2; i++)cout << "■";
Goto(snack->x, snack->y);//把光标移动到蛇头的位置
cout << "●";//打印蛇头
struct Snack* p = front->next;
while (p != NULL)Goto(p->x, p->y), cout << "◇", p = p->next;//打印蛇身
}
//蛇身的移动
void move_snack()
{
Goto(front->x, front->y);//将光标移动到最新的蛇头
cout << "●";//打印蛇头
Goto(front->next->x, front->next->y);//原来的蛇头变成蛇身
cout << "◇";
Goto(45, 9);
cout << "当前得分:" << score;//打印当前得分
if (judge1 ==1)//如果吃到食物,则重新创造食物,此函数结束,否则,将蛇尾删除
{
creat_food();
return;
}
Goto(rear->x,rear->y);
struct Snack*p=front;
while (p->next!= rear)p = p->next;
rear = p;
free(p->next);//释放蛇尾的内存
rear->next = NULL;
cout << " ";
}
//判断蛇头的每一次移动是否撞到墙壁
void crash_wall()
{
if (front->y == 0 || front->y == 19 || front->x <= 1 || front->x >= 38)//由于围墙的每一个方块在x轴上占两个坐标,所以要小于等于1,大于等于38
{
Goto(45, 15);
cout << "游戏结束";
Goto(2, 25);
exit(0);
return ;
}
}
//接受键盘指令,指挥蛇的移动,键盘上的←↓↑→进行控制
void Operate_order()
{
struct Snack* next_step = (struct Snack*)malloc(sizeof(struct Snack));//创建下一步的结点
next_step->x = front->x, next_step->y = front->y;
if (GetAsyncKeyState(VK_UP))//如果指令为↑,蛇头的y坐标减一
{
next_step->y--, next_step->x = front->x;
}
else if (GetAsyncKeyState(VK_DOWN))//如果指令为↓,蛇头的y坐标加一
{
next_step->y++, next_step->x = front->x;
}
else if (GetAsyncKeyState(VK_LEFT))//如果指令为←,蛇头的x坐标减二,由于方块图形占x轴两个位置,所以减二
{
next_step->x -= 2, next_step->y = front->y;
}
else if (GetAsyncKeyState(VK_RIGHT))//如果指令为→,蛇头的x坐标加二,由于方块图形占x轴两个位置,所以加二
{
next_step->x += 2, next_step->y = front->y;
}
else
{
if (front->x == front->next->x)//如果没有接受移动指令,则沿当前方向移动
{
if (front->y > front->next->y)
{
next_step->y++, next_step->x = front->x;
}
else
{
next_step->y--, next_step->x = front->x;
}
}
else if (front->x > front->next->x)
{
next_step->x += 2, next_step->y = front->y;
}
else
{
next_step->x -= 2, next_step->y = front->y;
}
}
next_step->next = front, front = next_step;
eat_food();//如果吃到食物,插入一个结点,judge1置为1,标志吃到食物
move_snack();//移动蛇身
crash_wall();//判断是否撞墙,撞墙就结束游戏
judge1 = 0;//每一次蛇移动,吃到食物标志置为0
}
void stop()
{
int a = 0;
if (GetAsyncKeyState(VK_SPACE))
{
a = 1;
}
while (a)
{
if (GetAsyncKeyState(VK_SPACE))
{
a = 0;
break;
}
}
}
int main()
{
snack = (struct Snack*)malloc(sizeof(struct Snack));
Goto(45, 6);
cout << "键盘上←↑→↓控制游戏,点击空格开始游戏";
while (1)
{
if (GetAsyncKeyState(VK_SPACE))
{
break;
}
}
Goto(0,0);
Insert_snack();
Print_map();
creat_food();
while (1)
{
Operate_order();
Sleep(250);
stop();//按空格暂停游戏,再次按空格开始游戏
}
}