贪吃蛇代码实现与剖析(C语言)

这篇具有很好参考价值的文章主要介绍了贪吃蛇代码实现与剖析(C语言)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


首先说明:
1.
这个贪吃蛇代码只有在Windows中执行才会起效果
我用的是Windows系统中的VS2019编译器
2.
我们先给出贪吃蛇的完整代码,是为了让大家提起接下来往后仔细看完这篇博客的热情
3.
这个贪吃蛇代码的前置知识:
1.C语言:函数,结构体,枚举,指针,动态内存管理(free,malloc…),宏
2.数据结构:链表

1.温馨提示

想要执行这个代码,在VS2019中需要调整一下控制台的属性
我们先在VS2019中随意跑一段简单的hello world调出控制台来进行属性的调整

默认情况下:我们的控制台是这个样子的
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
我们需要修改一下这个控制台的属性
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后就会出现这个
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
只有这样,我们才可以更好的实现这个窗口
否则,同样的代码在这个控制台窗口下就会出现这种样子
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
而我们修改了之后的样子是这样的
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
所以我们才要去修改这个控制台窗口的属性

2.最终实现版本的样子

1.游戏开始-欢迎界面

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.游戏运行界面

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.游戏结束界面

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

4.选择是否继续玩

1.选择继续

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
输入Y/y并按下回车即可继续玩
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后回到游戏最开始
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.选择退出游戏

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.完整代码

大家可以先在自己的VS中执行一下玩一玩
1.Snake.h

#pragma once
#include <stdio.h>
#include <Windows.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

#define INIT_X 24
#define INIT_Y 6

typedef struct SnakeNode
{
	struct SnakeNode* next;
	int x;
	int y;
}SNode;

enum Direction
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

enum GameState
{
	OK,
	EXIT_NORMAL,
	KILL_BY_WALL,
	KILL_BY_SELF
};

typedef struct Snake
{
	SNode* _pSnake;//蛇头节点
	SNode* _pFood;//食物
	enum Direction _dir;//蛇移动的方向
	enum GameState _state;//当前游戏状态
	int _score;//当前得分
	int _foodWeight;//每个食物的分数
	int _sleepTime;//蛇的休息时间,影响加速和减速和暂停

}Snake;


void SetPos(short x, short y);
void GameStart(Snake* ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(Snake* ps);
void CreateFood(Snake* ps);

void GameRun(Snake* ps);
void PrintHelpInfo();
void SnakeMove(Snake* ps);
//判断是否撞墙
void IfKillByWall(Snake* ps,int x, int y);
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y);

void EatFood(SNode* pNextNode, Snake* ps);
void NoFood(SNode* pNextNode, Snake* ps);
//暂停函数
void pause();

void GameEnd(Snake* ps);

2.Snake.c

#include "Snake.h"
//设置控制台光标位置的函数
void SetPos(short x, short y)
{
	COORD pos = { x,y };
	HANDLE handle = NULL;
	//获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	//设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的光标信息放在COORD类型的pos中
	//调用SetConsoleCursorPosition函数将光标位置设置到指定的位置
	SetConsoleCursorPosition(handle, pos);
}

//system("mode con cols=120 lines=35");

void WelcomeToGame()
{
	SetPos(45, 12);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(45, 18);
	system("pause");
	system("cls");
	SetPos(45, 12);
	printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速");
	SetPos(45, 13);
	printf("加速能够得到更高的分数");
	SetPos(45, 18);
	system("pause");
	system("cls");
}

void CreateMap()
{
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}

	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25

		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;
		}

		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;
			}
			cur = cur->next;
		}
		//没有跟蛇身重合
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

void GameStart(Snake* ps)
{
	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

void PrintHelpInfo()
{
	SetPos(65, 17);
	printf("不能穿墙,不能咬到自己");
	SetPos(65, 18);
	printf("用↑.↓.←.→ 分别控制蛇的移动");
	SetPos(65, 19);
	printf("F1为加速,F2为减速");
	SetPos(65, 20);
	printf("Esc: 退出游戏  space:暂停游戏");

	SetPos(65, 22);
	printf("编写者:wzs");
}

void EatFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x,cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//释放食物节点
	free(ps->_pFood);
	ps->_pFood = NULL;
	//加分
	ps->_score += ps->_foodWeight;
	//创建新食物
	CreateFood(ps);
}

void NoFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//释放最后一个节点
	SNode* cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

//判断是否撞墙
void IfKillByWall(Snake* ps,int x,int y)
{
	if (x == 0 || x == 56 || y == 0 || y == 26)
	{
		ps->_state = KILL_BY_WALL;
	}
}
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y)
{
	SNode* cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}
//暂停函数
void pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, ps->_foodWeight);
		//调整方向
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		//暂停
		else if (KEY_PRESS(VK_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}

void GameEnd(Snake* ps)
{
	SetPos(65, 24);
	switch (ps->_state)
	{
	case EXIT_NORMAL:
		printf("玩家选择退出,游戏结束");
		break;
	case KILL_BY_WALL:
		printf("很遗憾,撞墙了,游戏结束");
		break;
	case KILL_BY_SELF:
		printf("很遗憾,咬到自己了,游戏结束");
		break;
	default:
		break;
	}
	//释放蛇身的节点
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

3.test.c文件

#include "Snake.h"

//初始化光标信息等
void Init()
{
	HANDLE handle = NULL;
	//获取标准输出的句柄(用来表示不同设备的数值),使用这个句柄可以操作这个设备
	handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;//CONSOLE_CURSOR_INFO 这个结构体包含有关控制台光标的信息
	GetConsoleCursorInfo(handle, &CursorInfo);//检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(handle, &CursorInfo);//设置指定控制台屏幕缓冲区的光标的大小和可见性
}

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	char input = 0;
	do
	{
		Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
		srand((unsigned int)time(NULL));
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(65, 26);
		printf("要在玩一局吗?(Y/N)");
		input = getchar();
		getchar();//清理'\n'
		system("cls");//清屏
		SetPos(45,12);
		if (input == 'n' || input == 'N')
		{
			printf("欢迎再次在玩");
		}
		else if (input == 'Y' || input == 'y')
		{
			printf("游戏即将开始,祝您玩的开心");
			SetPos(45, 14);
			system("pause");
			system("cls");//清屏
		}
	} while (input == 'y' || input == 'Y');
	SetPos(32, 0);
	return 0;
}

一.Win32相关API的介绍

1.首先我们先介绍一下:什么是API?
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
也就是说我们Window系统给我们提供了很多函数,让我们可以通过调用这些函数去完成一些我们目前想要完成的任务
而这些函数服务的对象是应用程序

因此这些函数被称为API

1.控制台程序

1.什么是控制台程序

平常我们运⾏起来的⿊框程序其实就是控制台程序

就是这个Microsoft Visual Studio 调试控制台
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
在我们的Windows系统中,就有一个叫做命令提示符的工具
这个也是控制台程序
我们可以在Window系统中搜索cmd
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后打开,这个命令提示符就是控制台
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
我们要介绍的是:

2.命令提示符中设置控制台窗口的大小

我们可以设置控制台窗口的长宽:
比方说我现在想要让这个命令提示符的行数和列数设置为:
10行,50列

mode con cols=50 lines=10

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
我们输入这个命令,按下回车(就像是在Linux系统中输入命令行相同)
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后发现这个命令提示符变得特别小了
因此这就证明了我们是可以手动设置控制台大小的

3.控制台行和列的注意事项

然后我想让他变成一个正方形呢?
我们输入:让它行和列都变成30吧

mode con cols=30 lines=30

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
为什么不是一个正方形呢?
明明我输的是行30,列30啊
为什么会这样呢?
因为控制台中行的长度的基本单位和列的长度的基本单位不同

其实:
我们可以简单理解为:

控制台中每一行的长度==每一列的长度*2

那么我们想要构建一个正方形就可这样做了:

mode con cols=60 lines=30

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
这就是一个正方形了
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

4.VS2019中设置控制台窗口的大小

比方说我们想要一个15行,30列的一个正方形控制台窗口

mode con cols=30 lines=15

只需要包含Windows.h头文件
并且使用system函数

system("mode con cols=30 lines=15");

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

5.设置控制台名称

我们这个个界面还有一个贪吃蛇的名称
这个怎么设置呢?
跟刚才一样
只需要在VS代码里面加上

system("title 贪吃蛇");

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
不过我这里一开始的时候是无法修改这个控制台的名称
等到我写完贪吃蛇代码之后
控制台的名称就自然而然好了

所以大家如果在这一步无法修改名称的话,请先继续往后看

2.控制台屏幕坐标

COORD是Windows API中定义的一个结构体
它表示一个字符在控制台屏幕上的坐标
这个坐标系是这样的
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

typedef struct _COORD
{
  SHORT X;//X轴上的坐标
  SHORT Y;//Y轴上的坐标
}COORD,*PCOORD;

如果我们想要给这个控制台坐标赋值的话:
比方说我们给它的坐标赋值为:x轴:20,y轴:10
COORD pos = {20,10};
那么pos就是这个控制台上的对应位置的点

我们现在已经清楚了这个控制的坐标系的规则
但是还有一个问题:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
这个控制台上的光标去哪了?
其实这个光标被我们隐藏了
那么我们该怎么样去隐藏这个光标呢?
别急,我们先来介绍一个函数:GetStdHandle

3.GetStdHandle

GetStdHandle是⼀个Windows API函数。它⽤于从⼀个特定的标准设备(标准输⼊、标准输出或标准错误)中取得⼀个句柄(⽤来标识不同设备的数值),使⽤这个句柄可以操作设备。

这是微软官方给的API的使用手册,大家可以看一下
Windows API索引
这个GetStdHandle函数的手册网址:GetStdHandle函数的手册网址
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

4.设置控制台光标状态

1.GetConsoleCursorInfo

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.CONSOLE_CURSOR_INFO

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.SetConsoleCursorInfo

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
也就是说我们想要隐藏光标,需要这样:

#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
int main()
{
	system("mode con cols=60 lines=20");
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//让handle具有能够操作控制台标准输出设备的能力/权限
	CONSOLE_CURSOR_INFO CursorInfo;//这个结构体就是定义光标信息的结构体
	GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标的操作
	SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
	return 0;
}

不要忘了在C语言中使用bool类型的话需要包含stdbool.h头文件
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
这样我们就成功隐藏光标了
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
可是你这个控制台还能够在任意位置打印数据啊,
这肯定是通过设置光标位置做到的,那么如何才能设置光标位置呢?

4.SetConsoleCursorPosition

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
实例:

 COORD pos = {30, 10};
 HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
 //设置标准输出上光标的位置为pos
 SetConsoleCursorPosition(handle, pos);

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
成功在指定位置打印了hello world

5.SetPos函数的实现

那么既然我们需要很多次调整光标位置以便能够在任意位置写入数据
那么我们不妨设计一个函数SetPos来实现调整光标位置的操作呢?
于是我们就可以写出这样的函数

void SetPos(short x, short y)
{
	COORD pos = { x,y };
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(handle, pos);
}

6.GetAsyncKeyState

下面的问题来了:
我们想要玩这个游戏,就一定要能够接收我们的输入
我们既然是在电脑上玩,那就需要使用键盘去玩
那么就一定需要编译器能够在游戏运行的时候获取按键情况

因此微软WIN32API中就给了这么一个函数GetAsyncKeyState
作用是:获取按键情况

SHORT GetAsyncKeyState(
 int vKey
);

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
因此我们就可以让这个返回值跟1进行按位与

如果得出来的值是1:那么就代表这个值的最低位是1,也就是说这个键被按过

如果得出来的值是0:那么就代表这个值的最低位是0,也就是说这个键没有被按过
因此我们可以写出如下的宏

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)

7.打印宽字符的实现

我们现在能够在屏幕上的任意位置打印数据,还能够隐藏光标,还能检测哪些键是否被按过
那不就可以了吗?
我们还漏了一点:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
这个黑色原点:也就是蛇身
这个黑色五角星,也就是食物
这个白色方块:也就是墙体
这个是怎么打印出来的呢?
键盘上也没有啊

我们可以通过
搜狗输入法->输入方式->符号大全->里面就有
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后那不就行了吗?
是不行的
因为这三个字符属于宽字符(一个宽字符占2个字节,一个普通字符占1个字节),在VS的默认情况下我们是无法单独打印这些字符的
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
那么如何才能打印呢?
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
我们的准备工作终于做完了
下面就可以开始我们贪吃蛇游戏的具体实现了

二.贪吃蛇的游戏流程分析

这是我们贪吃蛇的整个游戏流程的分析
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

1.游戏窗口的实现

1.界面的初始化

根据我们刚才API部分的学习,我们已经写出了Init函数
可以用来隐藏屏幕光标
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后我们可以把打印宽字符,设置窗口大小,窗口名称的代码在main函数中去写
我们在这里将控制台的大小设置为宽:35行,列:120列
所以我们就可以在main函数当中这样去写

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	return 0;
}

这样就完成了
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.欢迎界面的打印

我们在前面已经实现了SetPos函数
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
然后我们就可以通过Setpos去定位光标,然后打印欢迎信息
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.窗口布局(地图坐标)

这里要实现的是CreateMap函数
我们在这里实现的是一个27行,58列的棋盘
所以我们就需要去通过SetPos定位光标,然后打印这个墙
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
其次,为了便于打印,我们宏定义了墙,蛇身,食物的字符

#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
注意:这里最后打印下面的墙是因为测试时当我们打印完成之后
程序运行结束就会打印:

C:\Users\23119\Desktop\C++_learn_code\cpp_learn_code\Snake_review\Debug\Snake_review.exe (进程 6000)已退出,代码为 0。
按任意键关闭此窗口. . .

如果我们最后打印的不是下面的墙
那样就会出现这种情况:
因为打印完下面的墙之后又打印了左边和右边的墙
导致程序结束时下面的墙被这句话覆盖了
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
其实我们打印的过程是这样的
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
我们调试看一下过程
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.蛇身结构体的创建与初始化

1.蛇身节点的结构体

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.食物节点的结构体

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.蛇身结构体的创建

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
因此我们就可以定义出下面的结构体
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

4.蛇身的初始化

定义好蛇身节点,食物节点和蛇的结构体之后
下面我们要初始化这条蛇
怎么初始化呢?
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
因此我们就可以写出这样的代码

这两个宏定义是Snake.h文件中的
#define INIT_X 24
#define INIT_Y 6
void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
后面两块是打印蛇身和初始化其他属性的注意事项
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

5.食物的初始化

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
注意:rand()%53生成的随机数的范围是:0~52

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25
		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;//这里是continue while(1){...}这个循环,这次循环不再执行下面的语句,直接跳转到下一次while(1){...}
		}
		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合,重新通过rand函数设置x和y
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;//这里是break出while(cur){...}这个循环
			}
			cur = cur->next;
		}
		//没有跟蛇身重合,就可以创建食物节点了
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

3.GameStart部分的完整代码

1.重点说明一下main函数

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.完整代码

1.Snake.h

#pragma once
#define WALL L'□'
#define BODY L'●'
#define FOOD L'★'
#include <stdio.h>
#include <Windows.h>
#include <stdbool.h>
#include <locale.h>
#include <stdlib.h>
#include <time.h>
#define INIT_X 24
#define INIT_Y 6
typedef struct SnakeNode
{
	struct SnakeNode* next;
	int x;
	int y;
}SNode;

enum Direction
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

enum GameState
{
	OK,
	EXIT_NORMAL,
	KILL_BY_WALL,
	KILL_BY_SELF
};

typedef struct Snake
{
	SNode* _pSnake;//蛇头节点
	SNode* _pFood;//食物
	enum Direction _dir;//蛇移动的方向
	enum GameState _state;//当前游戏状态
	int _score;//当前得分
	int _foodWeight;//每个食物的分数
	int _sleepTime;//蛇的休息时间,影响加速和减速和暂停
}Snake;

void Init();
void SetPos(short x, short y);

void GameStart(Snake* ps);

void WelcomeToGame();
void CreateMap();

void InitSnake(Snake* ps);
void CreateFood(Snake* ps);

2.Snake.c

#include "Snake.h"
void Init()
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//让handle具有能够操作控制台标准输出设备的能力/权限
	CONSOLE_CURSOR_INFO CursorInfo;//这个结构体就是定义光标信息的结构体
	GetConsoleCursorInfo(handle, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标的操作
	SetConsoleCursorInfo(handle, &CursorInfo);//设置控制台光标状态
}

void SetPos(short x, short y)
{
	COORD pos = { x,y };
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleCursorPosition(handle, pos);
}

void WelcomeToGame()
{
	SetPos(45, 12);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(45, 18);
	system("pause");
	system("cls");
	SetPos(45, 12);
	printf("用↑.↓.←.→ 分别控制蛇的移动,F1为加速,F2为减速");
	SetPos(45, 13);
	printf("加速能够得到更高的分数");
	SetPos(45, 18);
	system("pause");
	system("cls");
}

void CreateMap()
{
	//上
	SetPos(0, 0);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	//左
	for (int i = 0; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	//右
	for (int i = 0; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(Snake* ps)
{
	//初始化蛇身
	for (int i = 0; i < 5; i++)
	{
		SNode* newnode = (SNode*)malloc(sizeof(SNode));
		if (newnode == NULL)
		{
			perror("InitSnake():: malloc fail");
			exit(-1);
		}
		newnode->next = NULL;
		newnode->x = INIT_X + 2 * i;
		newnode->y = INIT_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = newnode;
		}
		else
		{
			newnode->next = ps->_pSnake;
			ps->_pSnake = newnode;
		}
	}
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//初始化其他属性
	ps->_dir = RIGHT;
	ps->_state = OK;
	ps->_foodWeight = 10;
	ps->_score = 0;
	ps->_sleepTime = 200;
}

void CreateFood(Snake* ps)
{
	//创建食物
	while (1)
	{
		//保证初始化到墙内
		//x:2~54
		int x = rand() % 53 + 2;//0~52+2  ->  2~54
		//y:1~25
		int y = rand() % 25 + 1;//0~24+1  ->  1~25

		//保证初始化的x必须为偶数
		if (x % 2 != 0)
		{
			continue;
		}

		//保证初始化时不跟蛇身重合
		SNode* cur = ps->_pSnake;
		bool flag = false;
		while (cur)
		{
			//跟蛇身重合
			if (cur->x == x && cur->y == y)
			{
				flag = true;
				break;
			}
			cur = cur->next;
		}
		//没有跟蛇身重合
		if (!flag)
		{
			SNode* newnode = (SNode*)malloc(sizeof(SNode));
			if (newnode == NULL)
			{
				perror("CreateFood():: malloc fail");
				exit(-1);
			}
			newnode->next = NULL;
			newnode->x = x;
			newnode->y = y;
			ps->_pFood = newnode;
			break;
		}
	}
	//打印食物
	SetPos(ps->_pFood->x, ps->_pFood->y);
	wprintf(L"%lc", FOOD);
}

void GameStart(Snake* ps)
{
	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

3.test.c

#include "Snake.h"
//初始化光标信息等
int main()
{
    setlocale(LC_ALL, "");
    system("mode con cols=120 lines=35");
    system("title 贪吃蛇");
    Init();
    Snake snake = { 0 };//将snake结构体变量的内容全都初始化为0
    //(这里主要是为了初始化p_Snake头节点的指针,为了防止头插法创建蛇身链表时出现野指针的非法访问问题)
    //p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
    srand((unsigned int)time(NULL));//设置随机数种子,防止每一次运行rand生成的随机数都是一样的
    GameStart(&snake);
    SetPos(0, 30);//这里我们要定位一下光标,
    //防止最后打印的那条包含:"返回值为0"的语句因为光标最后处于打印食物位置的下一行
    //而导致覆盖我们的墙体
    return 0;
}

3.最终实现情况:

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

4.游戏运行

我们的蛇身结构体和食物都已经初始化好了,游戏的开始工作结束
下面开始实现游戏运行的代码了

1.GameRun函数的整体框架

在这个GameRun函数中我们要实现的整体框架是:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
因此我们可以写出这样的代码框架贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.帮助信息的打印

经过了前面打印欢迎界面之后,这个帮助信息的打印对我们来说就轻而易举了
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.获取按键情况

我们之前在API中提到过这个获取按键情况的宏
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)

如果我们按了这个键,这个宏对应于这个键的值就是1
如果我们没有按这个键,这个宏对应于这个键的值就是0

那么怎么使用这个宏呢?

只需要将键盘上每个键的虚拟键值传递给这个宏,
就可以通过这个宏的返回值来判断是否按下了这个键

这是微软官方提供的虚拟键代码手册,我已经查阅好了相关的按键
大家感兴趣的话,也可以去看一下这个手册
虚拟键代码手册
因此我们就可以写出这样的代码
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

//调整方向
if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
{
	ps->_dir = UP;
}
else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
{
	ps->_dir = DOWN;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
	ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
{
	ps->_dir = RIGHT;
}
//暂停
else if (KEY_PRESS(VK_SPACE))
{
	pause();
}
//Esc退出
else if (KEY_PRESS(VK_ESCAPE))
{
	ps->_state = EXIT_NORMAL;
	break;
}
//加速
else if (KEY_PRESS(VK_F1))
{
	//防止一直加速导致sleepTime<0出现bug
	if (ps->_sleepTime >= 50)
	{
		ps->_sleepTime -= 30;
		ps->_foodWeight += 2;//加速时食物的分值会增加
	}
}
//减速
else if (KEY_PRESS(VK_F2))
{
	//防止一直减速导致程序运行太慢出现卡顿影响用户体验
	if (ps->_sleepTime < 350)
	{
		ps->_sleepTime += 30;
		ps->_foodWeight -= 2;
		//防止太慢时食物得分减为负数
		if (ps->_sleepTime == 350)
		{
			ps->_foodWeight = 1;
		}
	}
}

Sleep是C语言的库函数,可以让程序休息对应的时间
单位是ms
这里的pause是暂停函数:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
所以我们就可以完善一下我们的GameRun函数了

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, ps->_foodWeight);
		//调整方向
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		//暂停
		else if (KEY_PRESS(VK_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}
//暂停函数
void pause()
{
        while (1)
        {
            Sleep(200);
            if (KEY_PRESS(VK_SPACE))
            {
                break;
            }
        }
}

4.蛇身的移动

1.整体框架

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//2.创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//3.判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}

下面我们就要实现一下下面的这4个函数,那么SnakeMove函数就成功完成了

2.EatFood和NoFood函数

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
所以我们就可以写出这样的代码
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

3.IfKillByWall和IfKillBySelf函数

这两个函数的返回值类型可以是void
因为我们可以直接在这两个函数当中修改游戏当前状态
也就是ps->_state
因此我们可以这样写:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

5.GameRun部分的完整代码

这里只写了这一部分的完整代码
需要再加上GameStart部分的完整代码才可以正常运行

1.完整代码

Snake.h

#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)
void pause();
void GameRun(Snake* ps);
void PrintHelpInfo();

void EatFood(SNode* pNextNode, Snake* ps);
void NoFood(SNode* pNextNode, Snake* ps);
void IfKillByWall(Snake* ps, int x, int y);
void IfKillBySelf(Snake* ps, int x, int y);
void SnakeMove(Snake* ps);

Snake.c

//暂停函数
void pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

void GameRun(Snake* ps)
{
	PrintHelpInfo();
	do
	{
		SetPos(65, 10);
		printf("得分: %d , 每个食物得分: %d ", ps->_score, ps->_foodWeight);
		//调整方向
		if (KEY_PRESS(VK_UP) && ps->_dir != DOWN)
		{
			ps->_dir = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->_dir != UP)
		{
			ps->_dir = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		//暂停
		else if (KEY_PRESS(VK_SPACE))
		{
			pause();
		}
		//Esc退出
		else if (KEY_PRESS(VK_ESCAPE))
		{
			ps->_state = EXIT_NORMAL;
			break;
		}
		//加速
		else if (KEY_PRESS(VK_F1))
		{
			//防止一直加速导致sleepTime<0出现bug
			if (ps->_sleepTime >= 50)
			{
				ps->_sleepTime -= 30;
				ps->_foodWeight += 2;//加速时食物的分值会增加
			}
		}
		//减速
		else if (KEY_PRESS(VK_F2))
		{
			//防止一直减速导致程序运行太慢出现卡顿影响用户体验
			if (ps->_sleepTime < 350)
			{
				ps->_sleepTime += 30;
				ps->_foodWeight -= 2;
				//防止太慢时食物得分减为负数
				if (ps->_sleepTime == 350)
				{
					ps->_foodWeight = 1;
				}
			}
		}
		//蛇每次移动都要有一定的休眠时间,时间越短,蛇移动的速度就越快
		Sleep(ps->_sleepTime);
		SnakeMove(ps);
	} while (ps->_state == OK);
}

void PrintHelpInfo()
{
	SetPos(65, 17);
	printf("不能穿墙,不能咬到自己");
	SetPos(65, 18);
	printf("用↑.↓.←.→ 分别控制蛇的移动");
	SetPos(65, 19);
	printf("F1为加速,F2为减速");
	SetPos(65, 20);
	printf("Esc: 退出游戏  space:暂停游戏");

	SetPos(65, 22);
	printf("编写者:wzs");
}

void EatFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//打印蛇身
	SNode* cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	//释放食物节点
	free(ps->_pFood);
	ps->_pFood = NULL;
	//加分
	ps->_score += ps->_foodWeight;
	//创建新食物
	CreateFood(ps);
}

void NoFood(SNode* pNextNode, Snake* ps)
{
	pNextNode->next = ps->_pSnake;
	ps->_pSnake = pNextNode;
	//释放最后一个节点
	SNode* cur = ps->_pSnake;
	while (cur->next->next)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	SetPos(cur->next->x, cur->next->y);
	printf("  ");
	free(cur->next);
	cur->next = NULL;
}

//判断是否撞墙
void IfKillByWall(Snake* ps, int x, int y)
{
	if (x == 0 || x == 56 || y == 0 || y == 26)
	{
		ps->_state = KILL_BY_WALL;
	}
}
//判断是否咬到自己
void IfKillBySelf(Snake* ps, int x, int y)
{
	SNode* cur = ps->_pSnake->next;
	while (cur)
	{
		if (cur->x == ps->_pSnake->x && cur->y == ps->_pSnake->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		cur = cur->next;
	}
}

void SnakeMove(Snake* ps)
{
	//1.根据蛇头的坐标和方向,计算下一个节点的坐标
	int x = ps->_pSnake->x;
	int y = ps->_pSnake->y;
	switch (ps->_dir)
	{
	case UP:
		y--;
		break;
	case DOWN:
		y++;
		break;
	case LEFT:
		x -= 2;
		break;
	case RIGHT:
		x += 2;
		break;
	}

	//2.创建下一个节点
	SNode* pNextNode = (SNode*)malloc(sizeof(SNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove():: malloc fail");
		exit(-1);
	}
	pNextNode->x = x;
	pNextNode->y = y;
	pNextNode->next = NULL;

	//3.判断下一个是不是食物
	if (x == ps->_pFood->x && y == ps->_pFood->y)
	{
		//下一个位置有食物
		EatFood(pNextNode, ps);
	}
	//下一个位置没有食物
	else
	{
		NoFood(pNextNode, ps);
	}
	//判断是否撞墙
	IfKillByWall(ps, x, y);
	//判断是否咬到自己
	IfKillBySelf(ps, x, y);
}

test.c

int main()
{
    setlocale(LC_ALL, "");
    system("mode con cols=120 lines=35");
    system("title 贪吃蛇");
    Init();
    Snake snake = { 0 };//将snake结构体变量的内容全都初始化为0
    //(这里主要是为了初始化p_Snake头节点的指针,为了防止头插法创建蛇身链表时出现野指针的非法访问问题)
    //p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
    srand((unsigned int)time(NULL));//设置随机数种子,防止每一次运行rand生成的随机数都是一样的
    GameStart(&snake);
    GameRun(&snake);
    SetPos(0, 30);//这里我们要定位一下光标,
    //防止最后打印的那条包含:"返回值为0"的语句因为光标最后处于打印食物位置的下一行
    //而导致覆盖我们的墙体
    return 0;
}

2.最终实现情况

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
咬到自己:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构
撞墙:
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

6.游戏结束后的处理

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

1.代码实现

贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

2.Y/N 是否再来一局

int main()
{
	setlocale(LC_ALL, "");
	system("mode con cols=120 lines=35");
	system("title 贪吃蛇");
	Init();
	char input = 0;
	do
	{
		Snake snake = { 0 };//p_Snake=0;而NULL指针的本质就是(void(*)0),在数值上0跟NULL是相等的,这里可以认为p_Snake==NULL
		srand((unsigned int)time(NULL));
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(65, 26);
		printf("要在玩一局吗?(Y/N)");
		input = getchar();
		getchar();//清理'\n'
		system("cls");//清屏
		SetPos(45,12);
		if (input == 'n' || input == 'N')
		{
			printf("欢迎再次在玩");
		}
		else if (input == 'Y' || input == 'y')
		{
			printf("游戏即将开始,祝您玩的开心");
			SetPos(45, 14);
			system("pause");
			system("cls");//清屏
		}
	} while (input == 'y' || input == 'Y');
	SetPos(32, 0);
	return 0;
}

三.总结

上面就是我们贪吃蛇代码的整体分析和梳理
其实我们的整体思路就是这个图片所展现的
我们只需要先把大概的框架全部完成
具体的函数先声明出来
然后我们剩下的任务就只有去把那些函数一一实现即可
贪吃蛇代码实现与剖析(C语言),C语言学习与总结,数据结构与算法,c语言,贪吃蛇游戏实现,WIN32 API,链表的具体应用,数据结构

以上就是贪吃蛇代码实现与剖析(C语言)的全部内容,希望能对大家有所帮助!文章来源地址https://www.toymoban.com/news/detail-736335.html

到了这里,关于贪吃蛇代码实现与剖析(C语言)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 程序员技能与成长:如何学习新的编程语言和代码规范与单元测试

    一名软件工程师的最大挑战就是使自己的技术栈跟得上技术的发展,而在这个技术飞速发展的时代,保证自己不被淘汰的唯一方法就是不断学习。 那么,程序员需要掌握多门编程语言吗?很多初学者都被这个问题所困扰。Google研究总监 Peter Norvig曾就这个问题给出自己的观点

    2024年04月10日
    浏览(47)
  • 入门人工智能 —— 学习一门编程语言 python 基础代码编写和运算符介绍(1)

    随着人工智能技术的快速发展,越来越多的年轻人开始关注这个领域。作为入门者,学习人工智能编程语言至关重要。这里将介绍人工智能编程语言Python的基础知识,帮助初学者更好地理解人工智能领域的基本概念和技术。 下面是一些入门 Python 编程语言的基本知识: 安装

    2024年02月09日
    浏览(66)
  • 【C语言开源库】 一个只有500行代码的开源http服务器:Tinyhttpd学习

    项目搬运,带中文翻译: https://github.com/nengm/Tinyhttpd 在嵌入式中,我们HTTP服务器用得最多的就是boa还有就是goahead,但是这2个代码量比较大,而Tinyhttpd只有几百行,比较有助于我们学习。 直接make之后,所以假如html有执行权限先把它去除了,chmod 600 index.html color.cgi、date.cgi必须

    2024年02月11日
    浏览(42)
  • 机器学习&&深度学习——NLP实战(自然语言推断——注意力机制实现)

    👨‍🎓作者简介:一位即将上大四,正专攻机器学习的保研er 🌌上期文章:机器学习深度学习——NLP实战(自然语言推断——数据集) 📚订阅专栏:机器学习深度学习 希望文章对你们有所帮助 在之前已经介绍了什么是自然语言推断,并且下载并处理了SNLI数据集。由于许

    2024年02月11日
    浏览(39)
  • 嵌入式深度学习语音分离降噪C语言实现

        加我微信hezkz17进数字音频系统研究开发交流答疑 一 深度学习在语音分离中的具体应用? 深度学习在语音分离中有多种具体应用。其中最常见的是使用深度神经网络(DNN)或卷积神经网络(CNN)进行语音分离任务。 1 一种应用是源分离,它旨在从混合语音信号中分离出单

    2024年02月13日
    浏览(73)
  • Go学习圣经:Go语言实现高并发CRUD业务开发

    现在 拿到offer超级难 ,甚至连面试电话,一个都搞不到。 尼恩的技术社群中(50+),很多小伙伴凭借 “左手云原生+右手大数据”的绝活,拿到了offer,并且是非常优质的offer, 据说年终奖都足足18个月 。 第二个案例就是:前段时间,一个2年小伙伴希望涨薪到18K, 尼恩把

    2024年02月11日
    浏览(53)
  • C语言-指针进阶-qsort函数的学习与模拟实现(9.3)

    目录 思维导图: 回调函数 qsort函数介绍 模拟实现qsort 写在最后: 什么是回调函数? 回调函数是一个通过函数指针调用的函数。 将一个函数指针作为参数传递给一个函数,当这个指针被用来调用所指向函数时, 我们就将此称为回调函数。 在举例之前,我们先学习一个C语言

    2024年02月15日
    浏览(55)
  • 【机器学习】R语言实现随机森林、支持向量机、决策树多方法二分类模型

    暑期简单学习了机器学习理论知识,当时跟着B站咕泡老师学的,内容讲得蛮详细,实例代码、资料都比较全面,但是学校Python课程开设在这学期,所以用Python进行数据分析、建模等不是很熟悉,所以决定用之前学过的R语言来实现机器学习。R语言的相关包也都比较完善,所以

    2024年02月04日
    浏览(44)
  • 学习C语言十天了,我实现了仿真自由落体小球『C/C++&图形库EasyX』

    🌸作者简介: 花想云 ,在读本科生一枚,致力于 C/C++、Linux 学习。 🌸 本文收录于 初学C语言必会的20个小游戏专栏 ,本专栏主要内容为利用C/C++与图形库EasyX实现各种有趣的小游戏。 🌸 相关专栏推荐: C语言初阶系列 、 C语言进阶系列 、 数据结构与算法 本文主要内容为

    2023年04月15日
    浏览(61)
  • C/C++|物联网开发入门+项目实战|空间读写|非字符空间|返回值内部实现|嵌入式C语言高级|C语言函数的使用(2)-学习笔记(12)

    参考: 麦子学院-嵌入式C语言高级-C语言函数的使用 空间的读写 void fun(char *p); const char *p 只读空间,只为了看 char *p;该空间可能修改,几乎都要变 strcpy(); 定义:char *strcpy(char *dest,const char *src); sprintf(); 作用 1、修改 int * short * long * 2、空间传递 2.1 子函数看看空间里的情况

    2023年04月22日
    浏览(64)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包