贪吃蛇小游戏(c语言实现)解决bug

   首先在这里非常非常非常感谢博主 2021dragon 的贪吃蛇游戏程序让我学到了很多。

   如果以下代码看不懂,一定要到博主那里学习(博主那有详细完美的解析),然后再看我的就可     以了 

   原文链接:[引用内容](贪吃蛇(C语言实现)_贪吃蛇c语言代码-CSDN博客)

   看不懂没关系多思考几遍,上机按照博主的思路写一遍(就是抄,但是要有思考的抄) 

   我就是这样明白的,后面再不断思考,也是花了几天时间,就明白了

《在博主 2021dragon 贪吃蛇(c语言实现)bug:“长按一个方向键,蛇就会暂停的问题进行探讨”》

1.我在基于博主代码上增加了游戏的初始界面:开始游戏,更多设置(选择颜色,难度)

2.并使蛇可以加速移动(也决绝了博主留下的bug)

3.并且优化我喜欢的代码顺序

4.置于颜色,难度选择,后续我应该可以用指针解决或者别的方法。以及后续对程序再优化(过几天...)

下面是我的代码,解释Bug在后面。

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <time.h>
#include <conio.h>

#define ROW 22 //游戏区行数
#define COL 42 //游戏区列数

#define KONG 0 //标记空(什么也没有)
#define WALL 1 //标记墙
#define FOOD 2 //标记食物
#define HEAD 3 //标记蛇头
#define BODY 4 //标记蛇身

#define UP 72 //方向键:上
#define DOWN 80 //方向键:下
#define LEFT 75 //方向键:左
#define RIGHT 77 //方向键:右
#define SPACE 32 //暂停
#define ESC 27 //退出
//
//蛇头
struct Snake
{
	int len; //记录蛇身长度
	int x; //蛇头横坐标
	int y; //蛇头纵坐标
}snake;

//蛇身
struct Body
{
	int x; //蛇身横坐标
	int y; //蛇身纵坐标
}body[ROW * COL]; //开辟足以存储蛇身的结构体数组

int face[ROW][COL]; //标记游戏区各个位置的状态

void HideCursor();//隐藏光标
void CursorJump(int x, int y);///光标跳转
void InitInterface(); // 初始化界面
void color(int c);//颜色设置
void InitSnake();//初始化蛇
void RandFood();//随机生成食物
void JudgeFunc(int x, int y);//判断得分与结束
void DrawSnake(int flag);//打印蛇与覆盖蛇
void MoveSnake(int x, int y);//移动蛇
void run(int x, int y);//执行按键
void Game();//游戏主体逻辑函数

void mainn();
void tcs(); //第二界面在界面这块我就用到了函数嵌套(函数一层套一层),该贪吃蛇游戏不只是界面,后续蛇的运动也是用到函数的嵌套
void w();//3.1界面
void ww();//3.2界面
void www();//3.3界面
void Jiem2();//返回第二界面(通用)

int grade; //全局变量
int main()
{
	char ch;
	printf("开始游戏(请输入Y  or N)\n");
	printf("\n");
	printf("更多设置(请输入W)\n");
	printf("\n");
	scanf_s("%c", &ch);
	if (ch == 'Y' || ch == 'y')
	{
		printf("准备进入游戏...");
		Sleep(1000);
		mainn();
	}
	else if (ch == 'N' || ch == 'n')
	{
		printf("正在退出程序中,请等待...");
		Sleep(500);
		system("cls");
	}
	else if (ch == 'W' || ch == 'w')
	{
		system("cls");
		tcs();
	}
	else
	{
		printf("输入错误,请重新输入");
		Sleep(300);
		system("cls");
		main();
	}
	return 0;
}


//十三:第二界面
void tcs()
{
	printf("选择蛇的颜色(按1)\n");
	printf("\n");
	printf("设置难度(按2)\n");
	printf("\n");

	int ch;
	scanf_s("%d", &ch);
	if (ch == 1)
	{
		system("cls");
		w();//3.1函数
	}
	else if (ch == 2)
	{
		system("cls");
		ww();//3.2函数
	}
	else
	{
		system("cls");
		printf("输入错误\n\n返回第一界面(1)\n\n退出游戏(按0)\n\n");
		www();//3.3函数
	}

}

//3.1界面
void w()
{
	printf("蓝色,绿色,红色(输入1 2 3进行选择)\n");
	int ch;
	int a;//先这样,后面在搞指针指向颜色
			//我之所以定义个int a,是为了验证我代码输入,输出是否正确。后续有时间了我在进行优化,加入颜色和难度)
	scanf_s("%d", &ch);
	if (ch == 1)
	{
		a = 1;
		printf("%d", a);
	}
	else if (ch == 2)
	{
		a = 2;
		printf("%d", a);
	}
	else if (ch == 3)
	{
		a = 3;
		printf("%d", a);
	}
	else
	{
		printf("输入错误,请重新输入\n");
		printf("返回第二界面(Y)\n退出游戏(N)");
		Jiem2();
	}
}

//3.2界面
void ww()
{
	printf("难度:1 2 3   (输入1 2 3进行选择)\n");
	int ch;
	int a;//先这样,后面在搞指针指向void run()里面的t
	scanf_s("%d", &ch);
	if (ch == 1)
	{
		a = 1;
		printf("%d", a);
	}
	else if (ch == 2)
	{
		a = 2;
		printf("%d", a);
	}
	else if (ch == 3)
	{
		a = 3;
		printf("%d", a);
	}
	else
	{
		printf("输入错误,请重新输入\n");
		printf("返回第二界面(Y)\n退出游戏(N)");
		Jiem2();
	}
}
//3.3界面
void www()
{
	int ch;
	scanf_s("%d", &ch);
	if (ch == 1)
	{
		main();
	}
	else if (ch == 0)
	{
		printf("正在退出程序中,请等待...");
		Sleep(500);
		system("cls");
	}
	else
	{
		printf("输入错误,请重新输入");
		printf("返回第二界面(Y)\n退出游戏(N)");
		Jiem2();
	}
}

//返回第二界面(通用)
void Jiem2()
{
	printf("返回第二界面(Y)\n退出游戏(N)");
	char ch;
	scanf_s("%c", &ch);
	if (ch == 'Y' || ch == 'y')
	{
		tcs();
	}
	else if (ch == 'N' || ch == 'n')
	{
		printf("正在退出程序中,请等待...");
		Sleep(500);
		system("cls");
	}
	else
	{
		printf("输入错误,请重新输入");
		Sleep(300);
		system("cls");
		Jiem2();
	}
}


//十二:第一界面
void mainn()
{
#pragma warning(disable:4996)//消除警告
	grade = 0;
	system("title 贪吃蛇");//cmd窗口名字
	system("mode con cols=84 lines=23 ");//cmd窗口大小
	HideCursor();//一
	InitInterface();//三
	InitSnake();//五
	srand((unsigned int)time(NULL)); //设置随机数生成起点好像可以去除
	RandFood();//六
	DrawSnake(1);//七
	Game();//九


}
//一隐藏光标
void HideCursor()
{
	CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
	curInfo.dwSize = 1; //如果没赋值的话,光标隐藏无效
	curInfo.bVisible = FALSE; //将光标设置为不可见
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
	SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
//二:光标跳转
void CursorJump(int x, int y)
{
	COORD pos; //定义光标位置的结构体变量
	pos.X = x; //横坐标
	pos.Y = y; //纵坐标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
	SetConsoleCursorPosition(handle, pos); //设置光标位置
}
//三:初始化界面
void InitInterface()
{
	color(6); //颜色设置为土黄色
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (j == 0 || j == COL - 1)
			{
				face[i][j] = WALL; //标记该位置为墙
				CursorJump(2 * j, i);
				printf("■");
			}
			else if (i == 0 || i == ROW - 1)
			{
				face[i][j] = WALL; //标记该位置为墙
				CursorJump(2 * j, i);
				printf("■");
			}
			else
			{
				face[i][j] = KONG; //标记该位置为空
			}
		}
	}
	color(7); //颜色设置为白色
	CursorJump(0, ROW);
	printf("当前得分:%d", grade);
}
//颜色设置
void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//注:SetConsoleTextAttribute是一个API(应用程序编程接口)
}
//五:初始化蛇
void InitSnake()
{
	snake.len = 2; //蛇的身体长度初始化为2
	snake.x = COL / 2; //蛇头位置的横坐标
	snake.y = ROW / 2; //蛇头位置的纵坐标
	//蛇身坐标的初始化
	body[0].x = COL / 2 - 1;
	body[0].y = ROW / 2;
	body[1].x = COL / 2 - 2;
	body[1].y = ROW / 2;
	//将蛇头和蛇身位置进行标记
	face[snake.y][snake.x] = HEAD;
	face[body[0].y][body[0].x] = BODY;
	face[body[1].y][body[1].x] = BODY;
}
//六:食物生成(随机)
void RandFood()
{
	int i, j;
	do
	{
		//随机生成食物的横纵坐标
		i = rand() % ROW;
		j = rand() % COL;
	} while (face[i][j] != KONG); //确保生成食物的位置为空,若不为空则重新生成
	face[i][j] = FOOD; //将食物位置进行标记
	color(12); //颜色设置为红色
	CursorJump(2 * j, i); //光标跳转到生成的随机位置处
	printf("●"); //打印食物●
}

//七:打印蛇体(头,身),与覆盖
void DrawSnake(int flag)
{
	if (flag == 1) //打印蛇
	{
		color(9); //颜色设置为蓝色
		CursorJump(2 * snake.x, snake.y); 
		printf("■"); //打印蛇头
		for (int i = 0; i < snake.len; i++)
		{
			CursorJump(2 * body[i].x, body[i].y);
			printf("□"); //打印蛇身
		}
	}
	else //覆盖蛇
	{
		if (body[snake.len - 1].x != 0) //防止len++后将(0, 0)位置的墙覆盖
		{
			//将蛇尾覆盖为空格即可
			CursorJump(2 * body[snake.len - 1].x, body[snake.len - 1].y);
			printf(" ");
		}
	}
}
//八:移动蛇//,然后再打印移动后的蛇
void MoveSnake(int x, int y)
{
	DrawSnake(0); //先覆盖当前所显示的蛇
	face[body[snake.len - 1].y][body[snake.len - 1].x] = KONG; //蛇移动后蛇尾重新标记为空*************** 否则蛇再次碰到原来路径结束游戏
	face[snake.y][snake.x] = BODY; //蛇移动后蛇头的位置变为蛇身
	//蛇移动后各个蛇身位置坐标需要更新
	for (int i = snake.len - 1; i > 0; i--)
	{
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	//蛇移动后蛇头位置信息变为第0个蛇身的位置信息
	body[0].x = snake.x;
	body[0].y = snake.y;
	//蛇头的位置更改
	snake.x = snake.x + x;
	snake.y = snake.y + y;
	DrawSnake(1); //打印移动后的蛇
}

//九:游戏主体逻辑函数
void Game()
{
	int n = RIGHT; //开始游戏时,默认向后移动
	int tmp = 0; //记录蛇的移动方向
	goto first; //第一次进入循环先向默认方向前进
	while (1)
	{
		n = getch(); //读取键值
		//在执行前,需要对所读取的按键进行调整
		n = getch(); //读取键值
		//在执行前,需要对所读取的按键进行调整
		switch (n)
		{
		case UP:
			if (tmp != LEFT && tmp != RIGHT) //并且上一次蛇的移动方向不是“左”或“右”
			{
				if (tmp == DOWN)//如果上一次蛇的移动方向是“下”
				{
					n = DOWN; //那么下一次蛇的移动方向设置为“下”
						JudgeFunc(0, 1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, 1); //移动蛇
				}
				else
				{
					n = UP;
					JudgeFunc(0, -1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, -1); //移动蛇
				}
			}
			break;
		case DOWN: //如果敲击的是“上”
			if (tmp != LEFT && tmp != RIGHT)  //并且上一次蛇的移动方向不是“左”或“右”
			{
				if (tmp == UP)//那么下一次蛇的移动方向设置为“上”
				{
					n = UP;
					JudgeFunc(0, -1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, -1); //移动蛇
				}
				else
				{
					n = DOWN;
					JudgeFunc(0, 1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, 1); //移动蛇
				}
			}
			break;
		case LEFT:
			if (tmp != UP && tmp != DOWN)
			{
				if (tmp == RIGHT)
				{
					n = RIGHT;
					JudgeFunc(1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(1, 0); //移动蛇
				}
				else
				{
					n = LEFT;
					JudgeFunc(-1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(-1, 0); //移动蛇
				}
			}
			break;
		case RIGHT:
			if (tmp != UP && tmp != DOWN)
			{
				if (tmp == LEFT)
				{
					n = LEFT;
					JudgeFunc(-1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(-1, 0); //移动蛇
				}
				else
				{
					n = RIGHT;
					JudgeFunc(1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(1, 0); //移动蛇
				}
			}
			break;
		case SPACE:
		case ESC:
		case 'r':
		case 'R':
			break; //这四个无需调整
		default:
			n = tmp; //其他键无效,默认为上一次蛇移动的方向
			break;
		}
	first: //第一次进入循环先向默认方向前进
		switch (n)
		{
		case UP: //方向键:上
			run(0, -1); //向上移动(横坐标偏移为0,纵坐标偏移为-1)
			tmp = UP; //记录当前蛇的移动方向
			break;
		case DOWN: //方向键:下
			run(0, 1); //向下移动(横坐标偏移为0,纵坐标偏移为1)
			tmp = DOWN; //记录当前蛇的移动方向
			break;
		case LEFT: //方向键:左
			run(-1, 0); //向左移动(横坐标偏移为-1,纵坐标偏移为0)
			tmp = LEFT; //记录当前蛇的移动方向
			break;
		case RIGHT: //方向键:右
			run(1, 0); //向右移动(横坐标偏移为1,纵坐标偏移为0)
			tmp = RIGHT; //记录当前蛇的移动方向
			break;
		case SPACE: //暂停
			system("pause>nul"); //暂停后按任意键继续
			break;
		case ESC: //退出
			system("cls"); //清空屏幕
			color(7); //颜色设置为白色
			CursorJump(COL - 8, ROW / 2);
			printf("  游戏结束  ");
			CursorJump(COL - 8, ROW / 2 + 2);
			exit(0);
		case 'r':
		case 'R': //重新开始
			system("cls"); //清空屏幕
			main(); //重新执行主函数
		}
	}
}
//十判断得分与结束
void JudgeFunc(int x, int y)
{
	//若蛇头即将到达的位置是食物,则得分
	if (face[snake.y + y][snake.x + x] == FOOD)
	{
		snake.len++; //蛇身加长
		grade += 10; //更新当前得分
		color(7); //颜色设置为白色
		CursorJump(0, ROW);
		printf("当前得分:%d", grade); //重新打印当前得分
		RandFood(); //重新随机生成食物
	}
	//若蛇头即将到达的位置是墙或者蛇身,则游戏结束
	else if (face[snake.y + y][snake.x + x] == WALL || face[snake.y + y][snake.x + x] == BODY)
	{
		Sleep(1000); //留给玩家反应时间
		system("cls"); //清空屏幕
		color(7); //颜色设置为白色
		CursorJump(2 * (COL / 3), ROW / 2);
		printf("GAME OVER");
		while (1) //询问玩家是否再来一局
		{
			char ch;
			CursorJump(2 * (COL / 3), ROW / 2 + 3);
			printf("再来一局?(y/n):");
			scanf("%c", &ch);
			if (ch == 'y' || ch == 'Y')
			{
				system("cls");
				mainn();
			}
			else if (ch == 'n' || ch == 'N')
			{
				CursorJump(2 * (COL / 3), ROW / 2 + 5);
				exit(0);
			}
		}
	}
}
//十一:自动移动
void run(int x, int y)
{
	int t = 0;
	while (1)
	{
		if (t == 0)
			t = 3000; //这里t越小,速度越快
						  //解释 : 因为下面的while(--t),当t=5000,自减到0,系统也是要点时间,并不是瞬间完成。
					   	 // 故我们可以利用t来设置游戏难度。(根据大一的学习内容,应该使用指针来解决,同理,界面文字的颜色也可以用指针)
		while (--t)
		{
			if (kbhit() != 0) //若键盘被敲击,则退出循环
				break;
		}
		if (t == 0) //键盘未被敲击
		{
			JudgeFunc(x, y); //判断到达该位置后,是否得分与游戏结束
			MoveSnake(x, y); //移动蛇
		}
		else //键盘被敲击
		{
			break; //返回Game函数读取键值
		}
	}
}

 bug:

	while (1)
	{
		n = getch(); //读取键值
		//在执行前,需要对所读取的按键进行调整
		switch (n)
		{
		case UP:
		case DOWN: //如果敲击的是“上”或“下”
			if (tmp != LEFT&&tmp != RIGHT) //并且上一次蛇的移动方向不是“左”或“右”
			{
				n = tmp; //那么下一次蛇的移动方向设置为上一次蛇的移动方向
			}
			break;
		case LEFT:
		case RIGHT: //如果敲击的是“左”或“右”
			if (tmp != UP&&tmp != DOWN) //并且上一次蛇的移动方向不是“上”或“下”
			{
				n = tmp; //那么下一次蛇的移动方向设置为上一次蛇的移动方向
			}
		case SPACE:
		case ESC:
		case 'r':
		case 'R':
			break; //这四个无需调整
		default:
			n = tmp; //其他键无效,默认为上一次蛇移动的方向
			break;
		}
————————————————
版权声明:本文为CSDN博主「2021dragon」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chenlong_cxy/article/details/119765659

 bug解决:

解决思路:

为什么我们长按方向键会停止呢?

首先明白为什么不按方向键蛇可以自动移动?

就在于 run(int x,int y)函数

当键盘未敲击时,反复进行   if(t==0)语句,JudgeFunc(x, y)和MoveSnake(x, y)函数,

MoveSnake(x, y)就是移动蛇,从而蛇自动移动

当键盘敲击时,就返回Game函数。这时如果我们长按方向键,我们只反复进行Game 和run函数

并没有进行MoveSnake(x, y)函数,故蛇不会移动。 

所以我只要在进行Game函数时加入个MoveSnake(x, y)函数即可解决bug。

为什么长按会加速,我觉得是kbhit()的原因,长按方向键系统就快速反复运行Game 和run函数(相较于间断按)(比喻:我们键盘是输入A,长按1s——AAAAAAAAAAA,间断按1s——AAAA)

(这时有的人会问为什么我长按后会加速一段时间才停止,而不是立即停止以及蛇为什么会加速

对于此,我没有解决,我只解决了长按方向键会停止的bug)

void run(int x, int y)
{
	int t = 0;
	while (1)
	{
		if (t == 0)
			t = 3000; //这里t越小,蛇移动速度越快(可以根据次设置游戏难度)
		while (--t)
		{
			if (kbhit() != 0) //若键盘被敲击,则退出循环
				break;
		}
		if (t == 0) //键盘未被敲击
		{
			JudgeFunc(x, y); //判断到达该位置后,是否得分与游戏结束
			MoveSnake(x, y); //移动蛇
		}
		else //键盘被敲击
		{
			break; //返回Game函数读取键值
		}
	}
}

 Bug解决代码:

		n = getch(); //读取键值
		//在执行前,需要对所读取的按键进行调整
		switch (n)
		{
		case UP:
			if (tmp != LEFT && tmp != RIGHT) //并且上一次蛇的移动方向不是“左”或“右”
			{
				if (tmp == DOWN)//如果上一次蛇的移动方向是“下”
				{
					n = DOWN;//那么下一次蛇的移动方向设置为“下”
					JudgeFunc(0, 1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, 1); //移动蛇
				}
				else
				{
					n = UP;
					JudgeFunc(0, -1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, -1); //移动蛇
				}
			}
			break;
		case DOWN: //如果敲击的是“上”
			if (tmp != LEFT && tmp != RIGHT)  //并且上一次蛇的移动方向不是“左”或“右”
			{
				if (tmp == UP)那么下一次蛇的移动方向设置为“上”
				{
					n = UP;
					JudgeFunc(0, -1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, -1); //移动蛇
				}
				else
				{
					n = DOWN;
					JudgeFunc(0, 1); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(0, 1); //移动蛇
				}
			}
			break;
		case LEFT:
			if (tmp != UP && tmp != DOWN) 
			{
				if (tmp == RIGHT)
				{
					n = RIGHT;
					JudgeFunc(1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(1, 0); //移动蛇
				}
				else
				{
					n = LEFT;
					JudgeFunc(-1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(-1, 0); //移动蛇
				}
			}
			break;
		case RIGHT: 
			if (tmp != UP && tmp != DOWN) 
			{
				if (tmp == LEFT)
				{
					n = LEFT;
					JudgeFunc(-1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(-1, 0); //移动蛇
				}
				else
				{
					n = RIGHT;
					JudgeFunc(1, 0); //判断到达该位置后,是否得分与游戏结束
					MoveSnake(1, 0); //移动蛇
				}
			}
		}
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值