easyx版贪吃蛇

作为一枚小白加萌新,最快乐的事莫过于自己写游戏了。

这次的贪吃蛇包含了

1.Press any key to start 闪烁效果
2.添加了计分板和速度显示。
3.死亡后会有选择继续还是结束的界面。
4.结束界面可以显示得分。
5.解决了反向位移造成死亡的问题
6.速度可以自行调节并且达到上限后再加速或减速不会崩溃(加入了检测代码)
7.等等更多

操作:

wasd移动。
q暂停,之后长按wasd继续移动。
‘,’(逗号)减速 ‘。’(句号)加速。

Bug:(哪位大佬解决了请私我,感激不尽)

1.第一次开始移动会额外多出一节。
2.撞墙会少一节。
3.暂停会有一节隐藏,继续游戏会露出来。

注意点:

1.使用C++和easyx。
2.此程序的核心是蛇的链表结构。需要链表相关的知识。
3.输入法必须调成英文。

上图

easyx版贪吃蛇_第1张图片
easyx版贪吃蛇_第2张图片
easyx版贪吃蛇_第3张图片
easyx版贪吃蛇_第4张图片

代码如下

//贪吃蛇1.0

#include<iostream>
#include<iomanip>
#include<graphics.h>
#include<conio.h>
#include<windows.h>
#include<time.h>
#include<cstdlib>
using namespace std;

const int sideLength = 20;//基础方块边长
const int interval = 8;//基础方块间距
char playerInput = 'm';
char run = 'm';//移动,初始化为wasdq外的任意值
char spe;//调节速度
int speed = 60;
int score = 0;
char c_score[6] = "0";//这个让得分在经过后面的程序处理后可以实时的显示
int level = 10;
char dellevel[6] = "     ";
char c_level[3] = "10";
struct coordinate {//定义一个存储坐标的结构变量类型。coordinate:坐标。
	int x;
	int y;
}food;
coordinate snake_head = { 38,38 };
struct snake {//蛇的结构体.
	int x;
	int y;
	snake *next;
}*head, *tail, *p;//head只是一个snake型的指针,如要让其也能next则要new,不过显然不需要。

#define up 'w'
#define down 's'
#define left 'a'
#define right 'd'
#define wholeLength sideLength + interval
#define fast '.'
#define slow ','
#define pause 'q'

void drawBlock();//生成普通方块
void drawSnake();//生成蛇方块
void deleteBlock();//删除方块(以背景色生成)
void drawFood();//生成食物方块
void insertAthead();//在链表头部插入
void deletetail();//删除链表尾巴
void startpic();//开始界面
void drawMap();//生成初始地图
void drawSnake_0();//生成蛇
void drawDate();//在游戏面板右边显示数据
void initialize();//初始化
void runBody();//蛇身的移动
bool judgeAlive();//判断蛇是否死亡
void clickControl();//监听并获取键盘输入
void createFood();//生成食物
void eatFood();//吃食物
bool retry();//重开游戏
void reSet();//重新设置
void gameOver();//游戏结束生成面
void delSnake();//删除蛇的数据(用new就要delete)

void drawBlock(int x, int y)
{
	setfillcolor(YELLOW);
	solidrectangle(x, y, x + sideLength, y + sideLength);
}
void drawSnake(int x, int y)
{
	setfillcolor(WHITE);
	solidrectangle(x, y, x + sideLength, y + sideLength);
}
void deleteBlock(int x, int y)
{
	setfillcolor(BLACK);
	solidrectangle(x, y, x + sideLength, y + sideLength);
}
void drawFood(int x, int y)
{
	setfillcolor(GREEN);
	solidrectangle(x, y, x + sideLength, y + sideLength);
}
void startpic()
{
	initgraph(1400, 600);
	char str_0[] = "Snake 1.0";
	char tips[] = "Tips:";
	char str_1[] = "Welcome to snake1.0, player!";
	char str_2[] = "·Control:AWSD controls directions.";
	char str_3[] = "·Tip:turnning opposite will not cause dieing.";
	char str_4[] = "·Pause:'p' means pause and click asdw for long to continue.";
	char str_5[] = "Press any key to start.";
	char str_6[] = "·Speed:',' to slow and '.' to fast.";
	char deletestr[] = "                          ";
	char provider[] = "Made by C.N.";
	settextstyle(50, 0, _T("宋体"));
	outtextxy(620, 50, str_0);
	settextstyle(20, 0, _T("宋体"));
	outtextxy(580, 130, str_1);
	outtextxy(580, 180, tips);
	outtextxy(580, 230, str_2);
	outtextxy(580, 280, str_3);
	outtextxy(580, 330, str_4);
	outtextxy(580, 380, str_6);
	settextstyle(50, 0, _T("宋体"));
	outtextxy(580, 430, provider);
	while (1)//实现闪烁效果:while(1),kbjit(),break,三个配合来实现。
		{
		if (_kbhit())
			break;
		    outtextxy(450, 500, str_5);
			Sleep(500);
			outtextxy(450, 500, deletestr);
			Sleep(500);
		}
	_getch();
}
void insertAthead(snake *&head, snake *&tail, coordinate pos)
{
	snake *p = new snake;
	p->x = pos.x;
	p->y = pos.y;
	p->next = NULL;
	if (head == NULL)
	{
		head = p;
		tail = p;
	}
	else {
		p->next = head;
		head = p;
	}
}

void drawMap()
{
	cleardevice();
	coordinate location = { 10,10 };//生成地图用location。
	for (;location.x + wholeLength <= 1200;location.x += wholeLength)
		drawBlock(location.x, location.y);
	for (location.x -= wholeLength;location.y + wholeLength <= 600;location.y += wholeLength)
		drawBlock(location.x, location.y);
	for (location.y -= wholeLength;location.x - wholeLength >= -12;location.x -= wholeLength)
		drawBlock(location.x, location.y);
	for (location.x += wholeLength;location.y - wholeLength >= -12;location.y -= wholeLength)
		drawBlock(location.x, location.y);
}
//难点在于创建一个链表来存储蛇的身体坐标信息。

void drawSnake_0()
{
	head = NULL;//别让任何一个指针变成野指针,养成好习惯。
	//每次连接,插入新的链表节点之前,都必须要先给下一个节点分配存储空间,然后再将当前节点指向下一节点。
	for (int i = 1;i <= 3;i++)
	{
		insertAthead(head, tail, snake_head);
		drawSnake(head->x, head->y);
		snake_head.x += wholeLength;
	}
	snake_head.x -= wholeLength;//最后一次判断多加了一次,要减去。
}
void drawDate()
{
	char s1[] = "PlyerInput:";
	char s2[] = "Score:";
	char s3[] = "Speed Level:";
	char s3_1[] = "";
	settextstyle(30, 0, _T("宋体"));
	outtextxy(1200, 30, s1);
	outtextxy(1200, 70, s2);
	outtextxy(1300, 70, c_score);
	outtextxy(1200, 110, s3);
	outtextxy(1300, 150, c_level);
}
void initialize()
{
	drawMap();
	drawSnake_0();
	drawDate();
	food.x = 318;
	food.y = 178;
	drawFood(food.x, food.y);
}
void runBody()//键盘输入不要放在这个移动蛇的函数里面,否则很难实现连续自动移动。
{
	if (run == up)        
	{//先生成蛇头,至于蛇尾需不需要擦除到吃食物时判断。
		snake_head.y -= wholeLength;
		insertAthead(head, tail, snake_head);
		drawSnake(head->x, head->y);
	}
	if (run == down)
	{
		snake_head.y += wholeLength;
		insertAthead(head, tail, snake_head);
		drawSnake(head->x, head->y);
	}
	if (run == left)
	{
		snake_head.x -= wholeLength;
		insertAthead(head, tail, snake_head);
		drawSnake(head->x, head->y);
	}
	if (run == right)
	{
		snake_head.x += wholeLength;
		insertAthead(head, tail, snake_head);
		drawSnake(head->x, head->y);
	}
	if (spe == fast)
	{
		//此时的调节速度反映到绘制上并不是线性调节,可继续优化。
		if (speed > 5)
		{
			speed -= 5;
			level += 1;
			_ultoa_s(level, c_level, 10);
			outtextxy(1300, 150, dellevel);
			outtextxy(1300, 150, c_level);
			spe = ' ';
		}
	}
	if (spe == slow)
	{
		if (level > 1)
		{
			speed += 5;
			level -= 1;
			_ultoa_s(level, c_level, 10);
			outtextxy(1300, 150, dellevel);
			outtextxy(1300, 150, c_level);
			spe = ' ';
		}
	}
	if (run == pause)
		_getch();//再不断循环的机制下,要长按方向键才能继续移动
	Sleep(speed);
}
bool judgeAlive()//蛇存活游戏才能继续,死亡也就意味着结束
{
	snake*judge = head;//考虑头撞墙的情况
	if (head->x <= 10 || head->x >= 1158 || head->y <= 10 || head->y >= 570)
		return false;
	while (judge->next)//考虑撞到自己的身子(反向移动已经被优化避免了)
	{
		judge = judge->next;
		if (head->x == judge->x&&head->y == judge->y)//遍历蛇的链表一个一个比较
			return false;
	}
	return true;
}
void createFood()
{
	if (food.x == head->x&&food.y == head->y)//吃到了食物才需要进行下一个食物绘制
	{
		snake*judge;
		judge = head;
	point:	srand((unsigned int) time(0));//加上强制转换类型,防止编译警告(虽然警告不影响,但是强迫症表示看着就是很不舒服)
		food.x = 38 + rand() % 40 * 28;
		food.y = 38 + rand() % 19 * 28;
		while (judge->next)//既然有了这个判断条件,那么judge = judge->next;必不可少,不能忘!
		{
			if ((food.x == judge->x) && (food.y == judge->y))//判断食物是否绘制在蛇身上
				goto point;
			judge = judge->next;
		}
		drawFood(food.x, food.y);
	}
}
void eatFood()//擦除蛇尾的同时一定要删除节点,否则我们认为蛇尾被擦掉了,其实只是换了个颜色而已。
{
	if (run == 'a' || run == 'w' || run == 's' || run == 'd')//有效输入才能被识别
	{
		if ((head->x != food.x) || (head->y != food.y))
		{
			deleteBlock(tail->x, tail->y);
			deletetail();
		}
		else 
		{//显示得分
			score++;
			_ultoa_s(score, c_score, 10);
			outtextxy(1300, 70, c_score);
		}
	}
}
void deletetail()//实现将尾巴真正删除一节,释放删掉的那一节的内存并且并且将tail指向最后一节。
{
	snake*p = head;
	while (p->next->next)
		p = p->next;
	tail = p;
	tail->next = NULL;//这句话很重要,不然就是野指针了,不知道指向何处并且放在main()函数里会影响到上面while的判断。
	p = p->next;
	delete p;
}
void clickControl()//利用这个函数结合移动函数实现一个方向连续运动
{
	//此种监测方式带来的麻烦是暂停是会少一节,但是在继续时会再补回来。(小bug小bug)
	
	while (judgeAlive())
	{//利用while循环与_kbhit()相结合来实现对键盘实时输入的读取。
		if (_kbhit())//因为_kbhit()为非阻断函数,所以如果不用循环的话就读取到此就会瞬间赋值0从而跳过if判断,加while是为了保证键入能被读取。
		{
			playerInput = _getch();//牢记必须开英式/美式键盘,搜狗不行,还是要按回车,这是输入法本身的特性决定的。
			if (playerInput == 'a' || playerInput == 'w' || playerInput == 's' || playerInput == 'd' || playerInput == 'q')
				//很重要!!!#####利用这个判断剔除无效输入对游戏的影响并为速度的改变提供了方案。#####
				//再加上一些判断剔除反向位移带来的游戏结束
			{
				if (run == 'd'&&playerInput == 'a')
					continue;
				if (run == 'a'&&playerInput == 'd')
					continue;
				if (run == 's'&&playerInput == 'w')
					continue;
				if (run == 'w'&&playerInput == 's')
					continue;
				run = playerInput;
			}
			if (playerInput == ',' || playerInput == '.')
				spe = playerInput;
			outtextxy(1370, 30, dellevel);//先清空再打印,养成良好习惯。
			outtextxy(1370, 30, playerInput);
		}
		runBody();//先生成蛇身
		eatFood();//再进行食物相关的判断,决定蛇尾与新食物的生成与否
		createFood();
	}
	for (int i = 1;i <= 3;i++)//撞墙或者吃自己后的闪烁效果,代表游戏结束。
	{
		deleteBlock(head->x, head->y);//必须要两个Sleep才能控制出闪烁效果。
		Sleep(500);
		drawSnake(head->x, head->y);
		Sleep(500);
	}
}
bool retry()
{
	cleardevice();
	delSnake();
	char str_1[] = "You are die!!!";
	char str_2[] = "Press 'Y' to retry or 'N' to quit.";
	outtextxy(420, 250, str_1);
	outtextxy(420, 300, str_2);
pos:	char test = _getch();
	if (test == 'n')
		return false;
	else if (test == 'y')
		return true;
	else goto pos;
}
void delSnake()
{
	while (1) {
		snake*p = head;
		while (p->next->next)
			p = p->next;
		tail = p;
		tail->next = NULL;//这句话很重要,不然就是野指针了,不知道指向何处并且放在main()函数里会影响到上面while的判断。
		p = p->next;
		delete p;
		if (tail == head)
		{
			delete head;
			head = NULL;
			tail = NULL;
			break;
		}
	}
}
void reSet()//各项属性值重新调节
{
	snake_head.x = 38;
	snake_head.y = 38;
	run = ' ';
	score = 0;
	_ultoa_s(score, c_score, 10);
	speed = 60;
	level = 10;
	_ultoa_s(level, c_level, 10);
}
void gameOver()
{
	cleardevice();
	char str_1[] = "Thank you for your playing!";
	char str_2[] = "Press any key to quit game.";
	char str_3[] = "At last you got score:";
	settextstyle(50, 0, _T("宋体"));
	outtextxy(420, 150, str_3);
	outtextxy(620, 200, c_score);
	outtextxy(420, 250, str_1);
	outtextxy(420, 400, str_2);
}

int main()//这样主函数看上去就很简洁了
{
	startpic();
begin:
	initialize();
	clickControl();
	if (retry())
	{
		reSet();
		goto begin;
	}
	gameOver();
	_getch();
	closegraph();
	return 0;
}

有什么问题欢迎留言讨论啊~~

你可能感兴趣的:(easyx版贪吃蛇)