18年5月份
学校留了改贪吃蛇程序的bug并拓展功能的大作业
在博客里进行记录;
给的代码创建了class Snack,使用了链表串连每一节蛇。食物也以Snack 对象形式生成。障碍物用一个小结构体表示
/**
贪吃蛇类与障碍体描述
作者:luye@nankai.edu.cn
日期:2018年3月5日
*/
#pragma once
class Snake//蛇类,包含了蛇的基本元素和蛇的所有活动,其中蛇是由链表构成的
{
public://这里没有写构造函数,同学们可以根据需要编写,但要注意指针指向时表发生混乱
//友元函数作用并不明显,可以通过Set来替代,请同学们思考如何实现
friend void creat_food();//生成食物的函数
friend void Initial(); //初始化函数,初始化蛇的身体的初始长度,以及出生位置
int Get_x();
int Get_y();
Snake *Get_next();//获取下一节蛇身指针
bool Bite_self();//判断是否咬到自己
void Can_not_crowall();//不能穿墙,所以碰到强会死
void Move();//蛇的移动
private:
int s_x;//蛇的x,y坐标
int s_y;
Snake *next;//使用链表完成,两个数据域,存放横x纵y坐标。一个指针域,存放链接后续身体节点的地址
};
struct Obstacle//障碍物结构体,内部有x和y两个量
{
int x;//障碍物的横纵坐标
int y;
};
功能主要的实现方式
在此不展现具体实现
/**
贪吃蛇功能函数列表
作者:luye@nankai.edu.cn
日期:2018年3月5日
*/
#pragma once
#include"Snake.h"
#include<iostream>
#include<stdlib.h>//随机函数就包含在这个头文件
#include<time.h>//时间函数time包含在这个头文件
#include<Windows.h>//HANDLE类(句柄类)与COORD类(坐标类)包含在这个头文件
using namespace std;
/**
方向控制
*/
#define UP 1
#define DOWN 2
#define LEFT 4
#define RIGHT 3
extern Snake *head,*food;//全局指针,head是永远指向第一个节点的指针,food是指向食物的指针
extern Snake *q;//遍历整个蛇所用到的指针
extern int condition;//代表按键情况,上述把按键四种情况分别赋了整数值,所以这里的情况也是int 类型
extern int end_condition;//表示结束的情况,=1,撞墙;=2,咬到自己;=3,自己结束;4 撞到障碍物而死
extern int score;
extern int add;//得分和每个食物的分数
extern Obstacle o[30];
inline void Green_color();//字体置绿
inline void Red_color();//字体置红
inline void Yellow_color();//字体置黄
inline void White_color();
void Set_location(int x, int y);//定位函数
void Creat_obs(Obstacle o[], int n);//创建障碍物
void Initial();//初始化函数
void creat_food();//创建食物
void creatMap();//创建地图
void pause();//暂停
void Start();//开始
void Playing();//进行游戏
void Die();//死亡
运行时的全局变量:
Snake *head, *food;//全局指针,head是永远指向第一个节点的指针,food是指向食物的指针
Snake *q;//遍历整个蛇所用到的指针
int condition=5;//代表按键情况,上述把按键四种情况分别赋了整数值,所以这里的情况也是int 类型!!!!最初condition默认为0
int end_condition = 0;//表示结束的情况,=1,撞墙;=2,咬到自己;=3,自己结束;4 撞到障碍物而死
int score = 0, add = 1;//得分和每个食物的分数
Obstacle o[30];//定义一个存放障碍物的结构体数组用来存放每一块障碍物的x,y坐标
//为扩充功能设置的全局变量
int difficulty = 1;
int gamemode = 0;
double timegap = 0;//通过time.h获取两个时间,利用difftime(a,b)获取时间差,每隔一段时间使食物位置刷新
程序最开始主要有三个bug
(1)吃食物时有时会overflowed,而此时吃的食物的数量有随机性。
(2)游戏开始时,蛇头可以直接向身子的方向运动(直接死亡)
(3)吃食物时有时会掉落一截蛇身(“蜕皮”)试验后发现仅在向上运动时吃到食物会导致蛇身掉落。
问题1:判断溢出时程序运行位置应在creat_food处 原creat_food()如下,可见其随机化食物的时候运用递归过多,其实只需随机产生两个坐标即可
void creat_food()//随机生成食物的函数
{
Snake *food_1;//创造出来的食物是先由food_1指向的,等创建好食物之后,再赋值给全局food指针
srand((unsigned)time(NULL));//给srand一个种子,它与rand函数是有区别的,同学们可以查一查这两个函数的区别,以及为什么叫伪随机
food_1 = new Snake;
while ((food_1->s_x % 2) != 0) //保证其为偶数,使得食物能与蛇头对其,这里为偶数是因为“■”横行占2个位置,竖行占1个位置
{
food_1->s_x = (rand() % 52) + 2;//%52表示在0到51的横坐标范围内随机
}
food_1->s_y = (rand() % 24) + 1;//同理
q = head;//q是定义的一个全局指针,用来遍历整个蛇身
while (q->next != NULL)
{
if (q->s_x == food_1->s_x && q->s_y == food_1->s_y) //判断蛇身是否与食物重合
{
delete food_1;//如果创造出来的食物和存在的蛇的身体重合就要删除这个食物重新创造一个食物
creat_food();
}
q = q->next;
}
for (int i = 0; i < 30; i++)
{
if (o[i].x == food_1->s_x && o[i].y == food_1->s_y)
{
delete food_1;//如果食物和地图中的障碍物重合,就要删除食物重新创造食物
creat_food();
}
}
Set_location(food_1->s_x, food_1->s_y);//创造食物成功就要把食物打印出来
food = food_1;
Red_color();//让食物变成红色
cout << "★";//输出食物
}
用一函数生成合法坐标(srand()应该可以放在程序开始)(可能可以用inline优化)
void set(int &x,int &y)//将x,y设置成一个合法坐标
{
srand((unsigned)time(NULL));
x=(rand() % 52) + 2;//具体范围根据设定的命令行窗口大小确定
if(x%2!=0)
x=x+1;
y=(rand() % 24) + 1;
}
改写后结果。用两个flag判断是否与蛇或墙相碰。避免了递归压栈,Snack的创建释放。
void creat_food()//随机生成食物的函数
{
int x=-1,y=-1;
//Snake *food_1;//创造出来的食物是先由food_1指向的,等创建好食物之后,再赋值给全局food指针
srand((unsigned)time(NULL));//给srand一个种子,它与rand函数是有区别的,同学们可以查一查这两个函数的区别,以及为什么叫伪随机
//food_1 = new Snake;
set(x,y);
q = head;//q是定义的一个全局指针,用来遍历整个蛇身
while(1)
{ bool meetsnack=0;
bool meetobs=0;
while (q->next != NULL)
{
if (q->s_x == x && q->s_y == y) //判断蛇身是否与食物重合
{
meetsnack=1;
break;
}
q = q->next;
for (int i = 0; i < 30; i++)//判断是否与障碍物重合
{
if (o[i].x == x && o[i].y == y)
{
meetobs=1;
break;
}
}
}
if(meetsnack!=0||meetobs!=0)
{
set(x,y);
q=head;
continue;
}
else
break;
}
Snake *food_1;
food_1 = new Snake;
food_1->s_x=x;
food_1->s_y=y;
//************************
Set_location(food_1->s_x, food_1->s_y);//创造食物成功就要把食物打印出来
food = food_1;
Red_color();//让食物变成红色
cout << "★";//输出食物
}
问题2:程序在判断转向是否合法,是通过condition的值记录的目前蛇头运动方向,和输入的按键进行比较,看是否相反。而在游戏开始时condition的值未进入判断,所以可以向任意方向运动。讲condition设为0(程序中设置为了5),由于程序默认的开始情况唯一,默认蛇头在第二节右侧,所以在左转处判断条件添加一项即可。
else if (GetAsyncKeyState(VK_LEFT) && condition != RIGHT &&condition != 5)//设置游戏未开始情况为5,使游戏开始时不能向左移动
condition = LEFT;
该部分全部代码为以下。pause()控制按键space的暂停,调用多个windows系统函数。
if (GetAsyncKeyState(VK_UP) && condition != DOWN)//判断按键状态函数
condition = UP;
else if (GetAsyncKeyState(VK_DOWN) && condition != UP)
condition = DOWN;
else if (GetAsyncKeyState(VK_LEFT) && condition != RIGHT &&condition != 5)//设置游戏未开始情况为5,使游戏开始时不能向左移动
condition = LEFT;
else if (GetAsyncKeyState(VK_RIGHT) && condition != LEFT)
condition = RIGHT;
else if (GetAsyncKeyState(VK_SPACE))
{
pause();
if(end_condition==3)
{
system("cls");
break;
}
}
else if (GetAsyncKeyState(VK_ESCAPE))
{
system("cls");
end_condition = 3;
break;
}
问题3:这一处处理很简单,把没有错误的方向的代码复制粘贴改一下就行了。
但是仔细观察时 细思恐极
if (condition == UP)//上的情况
{
nexthead->s_x = head->s_x;//将蛇头移到nexthead,其实就是将蛇头的横纵坐标附给nexthead
nexthead->s_y = head->s_y - 1;//这里要说明蛇的运动规律,.
if (nexthead->s_x == food->s_x && nexthead->s_y == food->s_y)//要运动到下一个位置有食物
{
nexthead->next = head;//将下一个新的,蛇要运动到的节点与现有的头节点链接
head = nexthead;//head移至最前,达到head永远指向头的目的
q = head;//遍历全身的q指针现在从head出发
错误的地方在下面这处while的条件其他位置写的都是while(q=NULL),改成这个形式后运行无问题。
while (q != NULL)//我们假定的原理是:蛇移动靠刷屏然后重新打印整个蛇身,其实同学们可以在实现时采用将蛇尾节点剔除,然后再加到运动方向的蛇头位置的方式。
{
Set_location(q->s_x, q->s_y);
Green_color();
cout << "■";
q = q->next;
}
score += add;//吃到食物,加分
creat_food();
}
但是我们都知道,=作为条件是恒成立的,意味着这段程序根本不起作用。。。直接删除就好了。吃掉食物时,只需将食物印成蛇身即可。
额外拓充了两个小功能
(1)改变难度:通过改变Sleep()的时间
(2)增加游戏模式:隔一段时间刷新食物位置:通过time.h获取时间间隔,每隔一段时间在之前的食物处打印“_”(空格);再重新生成一处食物。
该程序比较简单,但是算是首次进行了一下较大型程序(600行...)的调试和分析,了解了一下windows的系统命令。学习计算机任重道远呀。接下来要经常发布一些可能比较简陋的博文。希望可以获得进步。