“饕餮之路:贪吃蛇编程精粹“

目录

前言

1.写代码前的预准备

1.1修改控制台

1.2相关WinAPI32知识学习

1.2.1WinAPI32介绍

[1.2.2 mode 命令](#1.2.2 mode 命令)

[1.2.3 title 命令](#1.2.3 title 命令)

1.2.4COORD

1.2.5GetStdHandle

1.2.6对控制台光标进行设置

1.2.6.1GetConsolrCursorInfo

[1.2.6.2 CONSOLE_CURSOR_INFO](#1.2.6.2 CONSOLE_CURSOR_INFO)

1.2.6.3SetConsolrCursorInfo

1.2.6.4修改光标大小和可见度代码示例

1.2.7设置光标出现的位置

1.2.7.1SetConsoleCursorPosition

1.2.7.2代码示例

1.2.7.3封装函数

1.2.8GetAsyncKeyState

1.2.9如何修改颜色

1.2.10如何修改控制台背景颜色

1.3将C语言库本地化

1.3.1简单说一下为什么要本地化

1.3.2本地化后打印字符和字符串

1.4控制台窗口的横纵坐标比

2.游戏整体的逻辑框架

3.GameStart()

3.1设置控制台大小和名字

3.2隐藏光标

3.3打印游戏界面和操作介绍

3.4打印游戏地图

3.5创建蛇

3.6创建食物

4.GameRun()

4.1怎么判断游戏一直在运行

4.2打印当前得分

4.3调整蛇的运动方向以及相关按键操作

4.4从当前这个位置到下一个位置的间隔时间

4.5移动到下一个位置SnakeMove()

4.5.1存储下一个位置的坐标

4.5.2判断下一个节点是否是食物NextIsFood()

4.5.3吃掉食物EatFood()

4.5.4下一个位置不是食物

4.5.5判断是否撞到墙

4.5.6判断是否撞到自己

5.GameEnd()

6.程序源代码

6.1代码一

6.2代码二


前言

贪吃蛇是一款很经典的小游戏,也是大家童年里的记忆,当你是一个计算机专业相关的学生,学完了C语言一定和我一样也想自己做一个小游戏在同学面前炫耀一番,证明一波自己的N13之处,接下来看完这篇文章,我相信你可以实现这波N13

效果展示

1.写代码前的预准备

1.1修改控制台

鱼哥使用的编译器是VS2022,但我们在运行程序时,大多数人和我一样弹出的是下面这个界面

这个叫做控制台窗口,我们首先需要修改一下控制台窗口,大家可以按以下操作,修改控制台窗口

当你按照步骤修改,最后再运行一遍程序,出现第六步的窗口时,就算修改成功了

1.2相关WinAPI32知识学习

1.2.1WinAPI32介绍

Windows这个多作业系统除了协调应用程序的执行、分配内存、管理资源之外,它同时也是⼀个很大的服务中心,调用这个服务中心的各种服务(每⼀种服务就是⼀个函数),可以帮应用程序达到开启 视窗、描绘图形、使用周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称API函数。WIN32 API也就是Microsoft Windows 32位平台的应用程序编程接

1.2.2 mode 命令

mode命令用来控制控制台窗口的大小

cols代表例,lines代表行,这些在控制台执行的命令,我们调用C语言中的system函数来执行

1.2.3 title 命令

除了控制控制台的大小,我们还想修改控制台的名字又该怎么做呢?

同样是借助system函数来对控制台进行操作

int main()
{
	system("title 贪吃蛇");
	return 0;
}

当你运行上面的代码时,你会发现控制台的名字好像并没有被修改,这是因为当你运行时程序就已经结束了,所以看不见。

当我们加上下面这行代码时,你就可以看见控制台的名字被修改为贪吃蛇了

int main()
{
	system("title 贪吃蛇");
	system("pause");
	return 0;
}

system("pause")是暂停的意思,等待用户输入信息,不然控制台一闪而过,你来不及看见执行结果

在使用system函数时需要包含头文件stdlib.h

1.2.4COORD

COORD是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕幕缓冲区上的坐标,坐标系 (0,0)的原点位于缓冲区的顶部左侧单元格。

控制台可以看成是一个坐标平面,后面我们可以通过这个坐标将内容打印到对应的位置

给大家你看一下COORD这个结构体长啥样

typedef struct _COORD {
 SHORT X;
 SHORT Y;
} COORD, *PCOORD;

X,Y分别代表横坐标和纵坐标

赋值操作如下:

COORD pos={1,2};

pos的类型是COORD,坐标是(1,2)

1.2.5GetStdHandle

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

类型声明:

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

参数:

|--------------------------------------|------------------------------------|
| 值 | 含义 |
| STD_INPUT_HANDLE ((DWORD)-10) | 标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。 |
| STD_OUTPUT_HANDLE ((DWORD)-11) | 标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$。 |
| STD_ERROR_HANDLE ((DWORD)-12) | 标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$ |

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
houtput = GetStdHandle(STD_OUTPUT_HANDLE);
或者
HANDLE houtput=GetStdHandle(STD_OUTPUT_HANDLE);

1.2.6对控制台光标进行设置

1.2.6.1GetConsolrCursorInfo

检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
);

PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关主机游标

示例:

HANDLE hOutput = NULL;
//获取标准输出的句柄(⽤来标识不同设备的数值) 
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);//获取控制台光标信息 
1.2.6.2 CONSOLE_CURSOR_INFO

CONSOLE_CURSOR_INFO是一个存储光标大小和可见度的结构体

typedef struct _CONSOLE_CURSOR_INFO {
 DWORD dwSize;
 BOOL bVisible;
} CONSOLE_CURSOR_INFO, *PCONSOLE_CURSOR_INFO;

dwsize表示光标的大小,数值位1到100,表示的是百分比

bVisible表示的是光标的可见度,默认是true,隐藏光标需要将bVisible赋值为false

1.2.6.3SetConsolrCursorInfo

设置指定控制台屏幕缓冲区的光标的大小和可见性。

BOOL WINAPI SetConsoleCursorInfo(
 HANDLE hConsoleOutput,
 const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);
1.2.6.4修改光标大小和可见度代码示例

1.修改光标大小

int main()
{
//获取一个输出相关的句柄
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
//定义一个有光标属性的结构体
	CONSOLE_CURSOR_INFO CONCURINFO;
//检索这个光标结构体
	GetConsoleCursorInfo(houtput, &CONCURINFO);
//对结构体进行赋值	
    CONCURINFO.dwSize = 50;
//设置光标的属性
	SetConsoleCursorInfo(houtput, &CONCURINFO);
	return 0;
}

以下是修改前后的效果

2.修改光标的可见度

#include<Windows.h>
#include<stdbool.h>
int main()
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CONCURINFO;
	GetConsoleCursorInfo(houtput, &CONCURINFO);
//将光标的可见度修改为不可见
	CONCURINFO.bVisible = false;
//设置光标属性
	SetConsoleCursorInfo(houtput, &CONCURINFO);
	return 0;
}

在使用WinAPI32的函数时需要包含Windows.h这个头文件,使用false和true时要包含stdbool.h这个头文件,下面是光标隐藏的效果图

1.2.7设置光标出现的位置

1.2.7.1SetConsoleCursorPosition

设置指定控制台屏幕缓冲区中的光标位置,我们将想要设置的坐标信息放在COORD类型的pos中,调用SetConsoleCursorPosition函数将光标位置设置到指定的位置。

BOOL WINAPI SetConsoleCursorPosition(
 HANDLE hConsoleOutput,
 COORD pos
);
1.2.7.2代码示例
int main()
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { 20,20 };
	SetConsoleCursorPosition(houtput, pos);
	return 0;
}

效果展示:

1.2.7.3封装函数

想一想,每次需要修改光标的位置,都需要重新写一遍上面的代码,使代码的可读性和简便性下降,增加了代码的绒余性,我们不妨把设置光标的位置分装成一个函数,提高代码的可读性

void SetPos(short x, short y)
{
 COORD pos = { x, y };
 HANDLE hOutput = NULL;
 //获取标准输出的句柄(⽤来标识不同设备的数值) 
 hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
 //设置标准输出上光标的位置为pos 
 SetConsoleCursorPosition(hOutput, pos);
}

1.2.8GetAsyncKeyState

GetAsyncKeyState是一个获取按键情况的函数

SHORT GetAsyncKeyState(
 int vKey
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态。 GetAsyncKeyState 的返回值是short类型,在上一次调用 GetAsyncKeyState 函数后,如果 返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置为1则说明,该按键被按过,否则为0。 如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

怎么检测按键是否被按过呢?

一个宏就可以解决

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

将GetAsyncKeyState()的返回值按位与上一个1,结果为1则说明按过VK这个键,为0则表示没有按过VK这个键

虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn

1.2.9如何修改颜色

void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
}

直接调用上面这个函数

鱼哥给大家整理了各种颜色对应的数值

|----|-----|----|-----|----|------|
| 数值 | 颜色 | 数值 | 颜色 | 数值 | 颜色 |
| 0 | 黑色 | 1 | 蓝色 | 2 | 绿色 |
| 3 | 湖蓝色 | 4 | 红色 | 5 | 紫色 |
| 6 | 黄色 | 7 | 白色 | 8 | 灰色 |
| 9 | 淡蓝色 | 10 | 淡绿色 | 11 | 淡浅绿色 |
| 12 | 淡红色 | 13 | 淡紫色 | 14 | 淡黄色 |
| 15 | 亮白色 | 16 | 大于15,恢复默认的颜色 |||

代码示例:

1.2.10如何修改控制台背景颜色

1.3将C语言库本地化

1.3.1简单说一下为什么要本地化

C语言最初假定字符都是单字节的。但是这些假定并不是在世界的任何地方都适用。也就是不适合一些非英语的国家,后来为了使C语眼适应国际化,C语眼的标准中不断入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入和输出函数,加入了头文件,其中提供了允许程序员针对特定 地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。如果不本地化,我们就无法显示出地图和蛇。

1.首先包含头文件locale.h

2.在主函数第一行写上setlocale(LC_ALL, "");即完成了本地化

1.3.2本地化后打印字符和字符串

int main()
{
	setlocale(LC_ALL, "");
	wchar_t ch = L'鱼';
	wprintf(L"%lc", ch);
//先定义一个字符后打印
	wprintf(L"%lc", L'哥');
//直接打印
	wprintf(L"%ls", L"好帅");
//打印字符串	
return 0;
}

在定义和打印字符或字符串时,注意添加L

1.4控制台窗口的横纵坐标比

控制台的横纵坐标比是1:2

也就是两个横坐标的长度等于一个纵坐标的长度

每一个坐标不是数学模型上的一个点,而是一个长二宽一的矩形,每一个小矩形可装下一个一个字节的字符,而我们使用的汉字和一些符号是宽字符,也就是两个字节的大小,在控制台所呈现的就是一个正方形的面积大小

2.游戏整体的逻辑框架

test.c文件

#include"Snake.h"
void test()
{
char ch=0;
do{
//创建贪吃蛇
Snake snake = { 0 };
//开始游戏
GameStart(&snake);
//运行游戏
GameRun(&snake);
//结束游戏
GameEnd(&snake);
//将光标移动到指定位置
SetPos(20, 15);
printf("再来一局吗?(Y/N):");
//接受Y或N
ch = getchar();
}while(ch==Y||ch==y);
//考虑到大小写
}

Snake.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>

#define Pos_X 20
#define Pos_Y 9
#define Wall L'◆'//墙体的符号
#define Body L'●'//蛇身每一节的符号
#define Food L'☆'//食物的符号

//蛇的方向
enum DIRECTION
{
	UP=1,向上
	DOWN,向下
	LEFT,向左
	RIGHT向右
};

//蛇的运动状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL//正常退出
};

//蛇身节点
typedef struct SnakeNode
{
	int x;//在控制台上的横坐标
	int y;//在控制台上的纵坐标
	struct SnakeNode* next;//指向下一个节点的指针
}SnakeNode,*pSnakeNode;//重命名结构体和重命名结构体指针


//贪吃蛇完整的信息
typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物的指针
	enum DIRECTION _dir;//方向
	enum GAME_STATUS _status;//状态
	int _food_weight;//食物分数
	int _score;//总分数
	int _sleep_time;//休息时间
}Snake,*pSnake;//重命名结构体和重命名结构体指针


//开始游戏
void GameStart(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//游戏善后
void GameEnd(pSnake ps);

Snake.c

1.开始游戏

//开始游戏
void GameStart(pSnake ps)
{
	system("mode con cols=110 lines=35");//设置窗口大小
	system("title 贪吃蛇");//设置项目名字
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输出流的句柄
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
	//1.打印环境界面
    //2.功能介绍
	WelcomeToGame();
	//3.绘制地图
	CreatMap();
	//4.创建蛇
	InitSnake(ps);
	//5.创建食物
	CreatFood(ps);
	//6.设置蛇的相关信息
}

2.运行游戏

void GameRun(pSnake ps)
{
	PrintfHelpInfo();//打印提示信息
	do {
		SetPos(70, 6);
		printf("总分数:%d", ps->_score);
		SetPos(70, 8);
		printf("当前食物的分数:%2d", ps->_food_weight);

		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_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//退出
			ps->_status = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//F3加速
			if (ps->_sleep_time >= 80)
			{
				ps->_sleep_time -= 10;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//F4减速
			if (ps->_food_weight >= 2)
			{
				ps->_food_weight -= 2;
				ps->_sleep_time += 10;
			}
		}
        Sleep(ps->_sleep_time);
		SnakeMove(ps);//蛇走一步的状态
		
	} while (ps->_status==OK);
}

3.游戏结束

void GameEnd(pSnake ps)
{
	switch (ps->_status)
	{
	case END_NORMAL:
			printf("您主动退出游戏\n");
			break;
	case KILL_BY_SELF:
		printf("您撞到自己了\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到墙了\n");
		break;
	}
	//释放蛇身链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

接下来我们主要实现Snake.c的内容,也会简单分析test.c和Snake.h

3.GameStart()

3.1设置控制台大小和名字

void GameStart(pSnake ps)
{
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
}

注意:设置宽度时需要将宽度设置为偶数,因为我们蛇和墙体还有食物均为宽字符,方便后期对这三者的坐标进行设置,你也可以按自己的想法来,可以通过自己的设计让界面美观一点

控制台大小和名字设置完记得自己调试检查一下是不是自己想要的结果,方便即使调整一下

3.2隐藏光标

HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CURINFO;
GetConsoleCursorInfo(houtput, &CURINFO);
CURINFO.bVisible = false;
SetConsoleCursorInfo(houtput, &CURINFO);

这是一个固定操作,不做过多解释

注意在检索和设置函数那里CURINFO前加上&操作符

3.3打印游戏界面和操作介绍

1.首先我们开始游戏进入一个欢迎来到贪吃蛇的界面

2.直接输出"欢迎来到贪吃蛇",由于光标在(0,0)这个位置,打印出来肯定不美观,所以我们要将光标移动到控制台界面的中间,然后再进行打印

这里我们就用到上面1.2.7.3的封装函数(记得在Snake.h声明,在Snake.c里实现)

void WelcomeToSnake()
{
	SetPos(40, 13);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(40, 25);
	system("pause");
	system("cls");
	SetPos(25, 13);
	wprintf(L"游戏规则:");
	SetPos(40, 10);
	wprintf(L"↑↓←→控制贪吃蛇移动");
	SetPos(40, 12);
	wprintf(L"F3加速,F4减速");
	SetPos(40, 14);
	wprintf(L"加速食物的分数更高,减速食物的分数会降低");
	SetPos(40, 16);
	wprintf(L"撞墙或撞到自己游戏均会结束");
	SetPos(40, 25);
	system("pause");
	system("cls");
}

1.system("pause")是防止程序一下就结束,看不到我们想看到的画面(也可用getchar()替用)

2.system("cls")起到清屏的作用,当打印完一个界面的内容,如果不清屏进入下一个界面,就会将上一个界面的内容遗留下来

下面是一个效果图

如果觉得这个界面字体颜色太枯燥,自己想个性化,换成其他颜色,我们就可以使用1.2.9的那个封装函数就行(同样也是记得声明)

修改控制台背景颜色参考1.2.10

注意:修改输出内容的颜色时,调用void color(int c)时,color下面逻辑下面的在控制台输出的内容会全部修改为这个颜色,想要修改其他内容的颜色,只需要在指定内容的前面加上color函数,并传相应颜色的参数就行了

3.4打印游戏地图

我们打印一个空心矩形将贪吃蛇围住,这个矩形就是我们的地图

void GreatMap()
{
	//上墙
	SetPos(0, 0);
	for (int i = 0; i < 50; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//下墙
	SetPos(0, 26);
	for (int i = 0; i < 50; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//左墙
	for (int i = 1; i < 26; i++)
	{
     SetPos(0, i);
     wprintf(L"%lc", Wall);
	}
	//右墙
	for (int i = 1; i < 26; i++)
	{
		SetPos(98, i);
		wprintf(L"%lc", Wall);
	}
}

这是鱼哥自己设计的一个边框,要注意打印右墙时光标的位置,不要弄错了

下面是我的一个效果图

3.5创建蛇

主要思路,创建一个链表,链表的每个节点存储在该节点的坐标和下一个节点的地址

void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;//定义一个指针,方便后面两个节点之间的连接
	for (i; i < 5; i++)//初始化时我们设置蛇有五个节点
	{
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));//向内存申请一个节点,由cur指向
	if (cur == NULL)//如果申请失败,那么cur将指向空指针
	{
		perror("InitSnake fail");//申请失败我们就将失败原因打印出来,一般都不会申请失败的
		return;//直接返回
	}
    cur->x = Pos_X + i * 2;//Pos_X是在Snake.h里面定义的宏,也就是蛇的尾部节点的横坐标
	cur->y = Pos_Y ;//Pos_Y是尾部节点的纵坐标
	cur->next = NULL;//每申请一个节点我们就将他的下一个节点置为空
	if (ps->pSnakeHead == NULL)//如果蛇头的节点为空,说明这条蛇一个节点也没有,我们现在申请的是第一个节点,我们就将蛇头指向第一个节点
	{
		ps->pSnakeHead = cur;
	}
	else//如果这条蛇已经有节点了,我们对这条蛇进行头插
	{
		cur->next = ps->pSnakeHead;//将新节点的下一个指针指向当前蛇头
		ps->pSnakeHead = cur;//将cur指向的节点作为蛇头
	}
	}
	cur = ps->pSnakeHead;//用cur从蛇头向蛇尾遍历一遍链表
	SetPos(cur->x, cur->y);//找到蛇头节点的坐标
	wprintf(L"%lc", Head);//打印蛇头
	cur = cur->next;
	while (cur)
	{
		SetPos(cur->x, cur->y);//找到相应节点的坐标
		wprintf(L"%lc", Body);//在对应节点打印蛇身
		cur = cur->next;
	}
//将整条蛇的信息设置一下
	ps->_dir = RIGHT;
	ps->_Food_weight = 10;
	ps->_score = 0;
	ps->_state = OK;
	ps->_Sleep_time = 400;//用来控制蛇移动的速度
}

对应的Pos_X,Pos_Y,Head,Body我们在Snake.h里面用#define定义

#define Wall L'卍'
#define Body L'○'
#define Head L'●'
#define Pos_X 48
#define Pos_Y 8

3.6创建食物

创建食物我们是需要食物在控制台窗口并且是在我们的地图里面随机生成的,且食物不能与蛇身重合,不能与墙体重合

void GreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do {
		x = rand() % 97 + 2;//随机生成坐标
		y = rand() % 28 + 1;//因为我们的地图是30行100列的,除去墙体还剩28行96列,且第一列第二列为墙体,第一行也为墙体,所以x要加2,y要加一
	} while (x % 2 != 0);//我们在游戏里面的每个图形都是宽字符,所以横坐标必须为2的倍数,不是2的倍数就重新生成
	pSnakeNode cur = ps->pSnakeHead;//定义一个指针,遍历一遍蛇身,看生成的食物的坐标是否和蛇身重合
	while (cur)
	{
		if (cur->x == x && cur->y == y)//如果生成的食物的坐标和蛇身重合,那么用goto跳回到again的位置,重新生成随机数
		{
			goto again;
		}
		cur = cur->next;
	}
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));//如果食物的坐标可用,我们就申请一个节点
	if (cur == NULL)
	{
		perror("malloc PSnakeNode fail");//节点申请失败,打印一下原因
		return;
	}
	cur->x = x;设置一下食物的坐标
	cur->y = y;
	cur->next = NULL;
	color(1);//设置食物的颜色
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", Food);//打印食物在屏幕上
	ps->pFood = cur;将食物节点的信息存放进蛇的信息里面
}

**补充:**随机数的生成需要包含time.h和stdlib.h两个头文件,在test.c文件中加上srand((unsigned)time(NULL));这是生成随机种子用的

4.GameRun()

运行游戏这一步是整个游戏最难的一部分,它使用了各种各样的函数嵌套,不过没关系,鱼哥会带领大家一步一步攻破难关的

4.1怎么判断游戏一直在运行

怎么判断游戏一直在运行呢?

我们之前创建了一个结构体,在结构体里面有一个枚举类型叫enum STATE,当这个类型为OK的时候,就说明游戏还在运行,是不是只要判断游戏的状态是不是OK就可以了

判断之前是不是得先让游戏运行起来

想运行再判断就会用到do while循环

do{
//游戏的运行逻辑
}while (ps->_status==OK);

这是游戏运行的整体结构

4.2打印当前得分

这部分很简单,只需要将光标设置在自己觉得美观的位置,然后将得分打印出来就行了

color(6);
SetPos(10, 28);
printf("总分:");
color(7);
printf("%d", ps->_score);
color(6);
SetPos(60, 28);
printf("当前食物的分值为:");
color(7);
printf("%d", ps->_Food_weight);

大家根据自己的审美设计

4.3调整蛇的运动方向以及相关按键操作

我们设计的这个游戏主要涉及8个按键,↑↓←→以及space(空格),Esc3,F4

↑↓←→操作贪吃蛇移动,space暂停游戏,Esc退出游戏,F3加速,F4减速

判断对应按键是否按过我们调用1.2.8的宏即可

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)

vk是对应的键位的虚拟键码,不做过多解释,具体参考1.2.8

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_RIGHT) && ps->_dir != LEFT)
{
	ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
	ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_SPACE))//空格暂停键下一段代码会给大家介绍
{
	//暂停
	Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
	//退出
	ps->_state = END_NORMAL;//退出只需要将当前蛇的游戏状态修改为END_NORMAL即可
	break;
}
else if (KEY_PRESS(VK_F3))
{
	//F3加速
	if (ps->_Sleep_time >= 80)//加速和减速主要就是控制屏幕休息的时间
	{
		ps->_Sleep_time -= 10;
		ps->_Food_weight += 2;//休息时间减少,单个食物的分数增加
	}
}
else if (KEY_PRESS(VK_F4))//休息时间有一个下限,食物的分数也有一个下限
{
	//F4减速
	if (ps->_Food_weight >= 2)
	{
		ps->_Food_weight -= 2;
		ps->_Sleep_time+= 10;
	}

**Pause()**函数的实现

void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

Pause()函数的实现很简单,就是使用循环无限让屏幕休息,直到遇到下一个空格键的按下,打破循环,结束暂停

4.4从当前这个位置到下一个位置的间隔时间

间隔时间也就相当于移动速度

间隔时间我们用pSnake->_Sleep_time来存放的

Sleep(ps->_Sleep_time);

这样休息一下就可以了

4.5移动到下一个位置SnakeMove()

4.5.1存储下一个位置的坐标

pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
if (pnext == NULL)
{
	perror("malloc pnext fail");
	return;
}
switch (ps->_dir)
{
case UP:
	pnext->x = ps->pSnakeHead->x;
	pnext->y = ps->pSnakeHead->y - 1;
	break;
case DOWN:
	pnext->x = ps->pSnakeHead->x;
	pnext->y = ps->pSnakeHead->y + 1;
	break;
case RIGHT:
	pnext->x = ps->pSnakeHead->x + 2;
	pnext->y = ps->pSnakeHead->y;
	break;
case LEFT:
	pnext->x = ps->pSnakeHead->x - 2;
	pnext->y = ps->pSnakeHead->y;
	break;
}

思路很简单,根据当前蛇的运动状态,判断下一个位置的坐标应该是多少,申请一个节点存储数据

4.5.2判断下一个节点是否是食物NextIsFood()

下一个位置是不是食物,我们已经找出了下一个位置的坐标,将下一个位置的坐标与食物的坐标作比较,相同则是,否则不是

int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return ((pn->x == ps->pFood->x) && (pn->y == ps->pFood->y));
}

如果是返回非0,否则返回0

4.5.3吃掉食物EatFood()

吃掉食物,蛇的身体会变长,就相当于将食物的节点头插到贪吃蛇里,头插完后将新的蛇身打印出来,将原来蛇身存储的食物的数据清理一下,然后创建下一个食物节点

void EatFood(pSnakeNode pn, pSnake ps)
{
	color(1);//由于之前修改过颜色,所以要将颜色修改为最初蛇的颜色
	pn->next = ps->pSnakeHead;
	ps->pSnakeHead = pn;
	pSnakeNode cur = ps->pSnakeHead;
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", Head);//由于蛇头和蛇身不一样,所以要单独打印
    cur=cur->next;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", Body);//循环打印蛇身
cur=cur->next;
	}
	ps->_score += ps->_Food_weight;
	free(ps->pFood);//释放掉当前食物节点
	GreatFood(ps);//创建一个新的食物节点
}

4.5.4下一个位置不是食物

如果下一个节点不是食物,我们的蛇身长度肯定是不能变的,而且蛇头要指向下一个节点

void NOFood(pSnakeNode pn, pSnake ps)
{
	color(2);//修改蛇的颜色
	pn->next = ps->pSnakeHead;
	ps->pSnakeHead = pn;
pSnakeNode pcur = ps->pSnakeHead;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", Head);
pcur = pcur->next;
	while (pcur->next->next)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", Body);
		pcur = pcur->next;
	}
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");//最后一个节点打印为空格,注意是两个空格
	free(pcur->next);
	pcur->next = NULL;
}

4.5.5判断是否撞到墙

void KillByWall(pSnake ps)
{
	if (ps->pSnakeHead->x == 0 || ps->pSnakeHead->x == 98
		|| ps->pSnakeHead->y == 0 || ps->pSnakeHead->y == 26)
	{
		ps->_dir = KILL_BY_WALL;
	}
}

判断蛇头坐标是否与墙的坐标相同就可判断是否撞墙

4.5.6判断是否撞到自己

void KillBySelf(pSnake ps)
{
	pSnakeNode pcur = ps->pSnakeHead->next;//定义一个指针遍历一遍蛇身,看蛇身坐标是否和蛇头坐标相同
	while (pcur)
	{
		if (pcur->x == ps->pSnakeHead->x && pcur->y == ps->pSnakeHead->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		pcur = pcur->next;
	}
}

5.GameEnd()

void GameEnd(pSnake ps)
{
	switch (ps->_state)
	{
	case END_NORMAL:
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		printf("您撞到自己了\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到墙了\n");
		break;
	}
	//释放蛇身链表
	pSnakeNode cur = ps->pSnakeHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

最后进行善后工作,将打印怎么退出的,和释放链表

6.程序源代码

6.1代码一

test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"

void test()
{
	int ch = 0;
	do {
		Snake snake = { 0 };
	//游戏开始
	GameStart(&snake);
	游戏运行
	GameRun(&snake);
	游戏结束
	GameEnd(&snake);
	SetPos(40, 15);
	printf("是否再玩一局?(Y/N)");
	ch=getchar();
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 29);
}

int main()
{
	setlocale(LC_ALL, "");
	srand((unsigned)time(NULL));
	test();
	getchar();
	return 0;
}

Snake.c

#include"Snake.h"
#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//设置颜色
void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c);
}

//将光标移动到指定位置
void SetPos(short x, short y)
{
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}

void WelcomeToSnake()
{
	color(2);
	SetPos(40, 13);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(40, 25);
	system("pause");
	system("cls");
	SetPos(25, 13);
	wprintf(L"游戏规则:");
	SetPos(40, 10);
	wprintf(L"↑↓←→控制贪吃蛇移动");
	SetPos(40, 12);
	wprintf(L"F3加速,F4减速");
	SetPos(40, 14);
	wprintf(L"加速食物的分数更高,减速食物的分数会降低");
	SetPos(40, 16);
	wprintf(L"撞墙或撞到自己游戏均会结束");
	SetPos(40, 25);
	system("pause");
	system("cls");
}

void GreatMap()
{
	//上墙
	SetPos(0, 0);
	for (int i = 0; i < 50; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//下墙
	SetPos(0, 26);
	for (int i = 0; i < 50; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//左墙
	for (int i = 1; i < 26; i++)
	{
     SetPos(0, i);
     wprintf(L"%lc", Wall);
	}
	//右墙
	for (int i = 1; i < 26; i++)
	{
		SetPos(98, i);
		wprintf(L"%lc", Wall);
	}
}

//创建蛇
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i; i < 5; i++)
	{
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("InitSnake fail");
		return;
	}
    cur->x = Pos_X + i * 2;
	cur->y = Pos_Y ;
	cur->next = NULL;
	if (ps->pSnakeHead == NULL)
	{
		ps->pSnakeHead = cur;
	}
	else
	{
		cur->next = ps->pSnakeHead;
		ps->pSnakeHead = cur;
	}
	}
	cur = ps->pSnakeHead;
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", Head);
	cur = cur->next;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", Body);
		cur = cur->next;
	}
	ps->_dir = RIGHT;
	ps->_Food_weight = 10;
	ps->_score = 0;
	ps->_state = OK;
	ps->_Sleep_time = 300;
}

//创建食物节点
void GreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	again:
	do {
		x = rand() % 97 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);
	pSnakeNode cur = ps->pSnakeHead;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	cur = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (cur == NULL)
	{
		perror("malloc PSnakeNode fail");
		return;
	}
	cur->x = x;
	cur->y = y;
	cur->next = NULL;
	color(1);
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", Food);
	ps->pFood = cur;
}

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

int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return ((pn->x == ps->pFood->x) && (pn->y == ps->pFood->y));
}


void EatFood(pSnakeNode pn, pSnake ps)
{
	color(1);
	pn->next = ps->pSnakeHead;
	ps->pSnakeHead = pn;
	pSnakeNode cur = ps->pSnakeHead;
	SetPos(cur->x, cur->y);
	wprintf(L"%lc", Head);
	cur = cur->next;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", Body);
		cur = cur->next;
	}
	ps->_score += ps->_Food_weight;
	free(ps->pFood);
	GreatFood(ps);
}

void NOFood(pSnakeNode pn, pSnake ps)
{
	color(2);
	pn->next = ps->pSnakeHead;
	ps->pSnakeHead = pn;
pSnakeNode pcur = ps->pSnakeHead;
SetPos(pcur->x, pcur->y);
wprintf(L"%lc", Head);
pcur = pcur->next;
	while (pcur->next->next)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", Body);
		pcur = pcur->next;
	}
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
}

//检查是否撞墙
void KillByWall(pSnake ps)
{
	if (ps->pSnakeHead->x == 0 || ps->pSnakeHead->x == 98
		|| ps->pSnakeHead->y == 0 || ps->pSnakeHead->y == 25)
	{
		ps->_state= KILL_BY_WALL;
	}
}

//检查是否撞到自己
void KillBySelf(pSnake ps)
{

	pSnakeNode pcur = ps->pSnakeHead->next;
	while (pcur)
	{
		if (pcur->x == ps->pSnakeHead->x && pcur->y == ps->pSnakeHead->y)
		{
			ps->_state = KILL_BY_SELF;
			break;
		}
		pcur = pcur->next;
	}
}

void SnakeMove(pSnake ps)
{
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnext == NULL)
	{
		perror("malloc pnext fail");
		return;
	}
	switch (ps->_dir)
	{
	case UP:
		pnext->x = ps->pSnakeHead->x;
		pnext->y = ps->pSnakeHead->y - 1;
		break;
	case DOWN:
		pnext->x = ps->pSnakeHead->x;
		pnext->y = ps->pSnakeHead->y + 1;
		break;
	case RIGHT:
		pnext->x = ps->pSnakeHead->x + 2;
		pnext->y = ps->pSnakeHead->y;
		break;
	case LEFT:
		pnext->x = ps->pSnakeHead->x - 2;
		pnext->y = ps->pSnakeHead->y;
		break;
	}
	if (NextIsFood(pnext,ps))
	{
		EatFood(pnext,ps);
	}
	else
	{
		NOFood(pnext,ps);
	}
	KillBySelf(ps);
	KillByWall(ps);
}

void GameRun(pSnake ps)
{
	
	do {
color(6);
SetPos(10, 28);
printf("总分:");
color(7);
printf("%d", ps->_score);
color(6);
SetPos(60, 28);
printf("当前食物的分值为:");
color(7);
printf("%d", ps->_Food_weight);
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_RIGHT) && ps->_dir != LEFT)
{
	ps->_dir = RIGHT;
}
else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
{
	ps->_dir = LEFT;
}
else if (KEY_PRESS(VK_SPACE))
{
	//暂停
	Pause();
}
else if (KEY_PRESS(VK_ESCAPE))
{
	//退出
	ps->_state = END_NORMAL;
	break;
}
else if (KEY_PRESS(VK_F3))
{
	//F3加速
	if (ps->_Sleep_time >= 80)
	{
		ps->_Sleep_time -= 10;
		ps->_Food_weight += 2;
	}
}
else if (KEY_PRESS(VK_F4))
{
	//F4减速
	if (ps->_Food_weight >= 2)
	{
		ps->_Food_weight -= 2;
		ps->_Sleep_time+= 10;
	}
}
Sleep(ps->_Sleep_time);
SnakeMove(ps);
	} while (ps->_state == OK);
}



void GameStart(pSnake ps)
{
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CURINFO;
	GetConsoleCursorInfo(houtput, &CURINFO);
	CURINFO.bVisible = false;
	SetConsoleCursorInfo(houtput, &CURINFO);
	WelcomeToSnake();
	GreatMap();
	InitSnake(ps);
	GreatFood(ps);
}

void GameEnd(pSnake ps)
{
	switch (ps->_state)
	{
	case END_NORMAL:
		SetPos(40, 14);
		printf("您主动退出游戏\n");
		break;
	case KILL_BY_SELF:
		SetPos(40, 14);
		printf("您撞到自己了\n");
		break;
	case KILL_BY_WALL:
		SetPos(40, 14);
		printf("您撞到墙了\n");
		break;
	}
	//释放蛇身链表
	pSnakeNode cur = ps->pSnakeHead;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

Snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>

#define Wall L'卍'
#define Body L'○'
#define Head L'●'
#define Food L'★'
#define Pos_X 48
#define Pos_Y 8

enum DIRECTION
{
	UP = 1,
    DOWN,
	RIGHT,
	LEFT
};

enum STATE
{
	OK,
	KILL_BY_SELF,
	KILL_BY_WALL,
	END_NORMAL
};


typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

typedef struct Snake
{
	enum STATE _state;
	enum DIRECTION _dir;
	int _Food_weight;
	int _score;
	int _Sleep_time;
	pSnakeNode pSnakeHead;
	pSnakeNode pFood;
}Snake,*pSnake;


void GameStart(pSnake ps);
//欢迎界面和游戏规则介绍界面
void WelcomeToSnake();
// 设置颜色
void color(int c);
//将光标移动到指定位置
void SetPos(short x, short y);
//创造地图
void GreatMap();
//创建蛇
void InitSnake(pSnake ps);
//创建食物
void GreatFood(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//移动到下一步
void SnakeMove(pSnake ps);
//下一个位置是不是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
void GameEnd(pSnake ps);
void EatFood(pSnakeNode pn, pSnake ps);
void NOFood(pSnakeNode pn, pSnake ps);
void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

6.2代码二

test.c

#include"snake.h"

void test()
{
	int ch = 0;
	do {
	//创建贪吃蛇
	Snake snake = { 0 };
	//初始化游戏
	// 0.隐藏光标
	//1.打印环境界面
	//2.功能介绍
	//3.绘制地图
	//4.创建蛇
	//5.创建食物
	//6.设置蛇的相关信息
	GameStart(&snake);
	运行游戏
	GameRun(&snake);
	结束游戏
	GameEnd(&snake);
	SetPos(20, 15);
	printf("再来一局吗?(Y/N):");
	ch = getchar();
	} while (ch == 'Y' || ch == 'y');
	SetPos(0,35);
}

int main()
{
	//设置适配本地环境
	setlocale(LC_ALL, "");
	srand((unsigned)time(NULL));
	test();
	getchar();
	return 0;
}

Snake.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<locale.h>
#include<time.h>
#include<stdbool.h>
#include<stdlib.h>

#define Pos_X 20
#define Pos_Y 9
#define Wall L'◆'
#define Body L'●'
#define Food L'☆'

//蛇的方向
enum DIRECTION
{
	UP=1,
	DOWN,
	LEFT,
	RIGHT
};

//蛇的运动状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAL//正常退出
};

//蛇身节点
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;

typedef struct Snake
{
	pSnakeNode _pSnake;//指向蛇头的指针
	pSnakeNode _pFood;//指向食物的指针
	enum DIRECTION _dir;//方向
	enum GAME_STATUS _status;//状态
	int _food_weight;//食物分数
	int _score;//总分数
	int _sleep_time;//休息时间
}Snake,*pSnake;


//开始游戏
void GameStart(pSnake ps);
//游戏运行
void GameRun(pSnake ps);
//游戏善后
void GameEnd(pSnake ps);


//地图打印
void CreatMap();
//创建蛇
void InitSnake(pSnake ps);
//创建食物
void CreatFood(pSnake ps);

//蛇的移动
void SnakeMove(pSnake ps);
//下一个不是食物
void NOFood(pSnakeNode pn, pSnake ps);
//检查蛇是否撞墙
void KillByWall(pSnake ps);
//检测是否撞到自己
void KillBySelf(pSnake ps);
//吃食物
void EatFood(pSnakeNode pn, pSnake ps);

//定义光标位置
void SetPos(short x, short y);
//欢迎界面
void WelcomeToGame();
//颜色设置
void color(int c);

Snake.c

#include"snake.h"

//颜色设置
void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
}

void SetPos(short x,short y)
{
	//获取句柄
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(houtput, pos);
}
//游戏界面
void WelcomeToGame()
{ 
	color(1);
	SetPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏");
	SetPos(40, 17);
	system("pause");
	system("cls");
	SetPos(35, 12);
	wprintf(L"↑↓←→控制方向,F3加速,F4减速\n");
	SetPos(40, 14);
	wprintf(L"加速可以获得额外分数\n");
	SetPos(45, 16);
	wprintf(L"减速会扣分");
	SetPos(40, 20);
	system("pause");
	system("cls");
}

//地图打印
void CreatMap()
{
	color(2);
	//上墙
	int i = 0;
	for (i; i < 29; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//下
	SetPos(0, 28);
	for (i=0; i < 29; i++)
	{
		wprintf(L"%lc", Wall);
	}
	//左
	for (int i = 1; i <= 27; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", Wall);
	}
	//右
	for (int i = 1; i <= 27; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", Wall);
	}
	
}

void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	for (i; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():fail");
			return;
		}
		cur->next = NULL;
		cur->x = Pos_X + 2 * i;
		cur->y = Pos_Y;
		if (ps->_pSnake == NULL)
		{
			ps->_pSnake = cur;
		}
		else {
			cur->next = ps->_pSnake;
			ps->_pSnake = cur;
		}
	}
	cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", Body);
		cur = cur->next;
	}
	ps->_dir = RIGHT;
	ps->_score = 0;
	ps->_food_weight = 10;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_status = OK;
}

void CreatFood(pSnake ps)
{

	int x = 0;
	int y = 0;
	again:
	do {
    x = rand() % 53 + 2;
	y = rand() % 27 + 1;
	} while (x % 2 != 0);//x必须为2的倍数
	//x和y不能与蛇身冲突
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		if (cur->x == x && cur->y == y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//创建食物的节点
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("Creat Food fail");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	//pFood->next = NULL;
	SetPos(x, y);
	wprintf(L"%lc", Food);
	ps->_pFood = pFood;
}

void PrintfHelpInfo()
{
	SetPos(70, 10);
	wprintf(L"不能撞墙,不能咬到自己");
	SetPos(70, 12);
	wprintf(L"↑↓←→控制方向,F3加速,F4减速");
	SetPos(70, 14);
	wprintf(L"加速可以获得额外分数");
	SetPos(70, 16);
	wprintf(L"减速会扣分");
	SetPos(70, 18);
	wprintf(L"按ESC退出游戏,按空格暂停游戏");
}

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1)?1:0)
//暂停
void Pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//判断下一个节点是否有食物
int NextIsFood(pSnakeNode pn, pSnake ps)
{
	return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}

//吃食物
void EatFood(pSnakeNode pn,pSnake ps)
{
	/*ps->_pSnake->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	free(pn);
	pn = NULL;*/
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;
	pSnakeNode pcur = ps->_pSnake;
	while (pcur)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", Body);
		pcur = pcur->next;
	}
	ps->_score += ps->_food_weight;
	free(ps->_pFood);
	CreatFood(ps);
}

//下一个位置不是食物
void NOFood(pSnakeNode pn, pSnake ps)
{
	
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;
pSnakeNode pcur = ps->_pSnake;
	while (pcur->next->next)
	{
		SetPos(pcur->x, pcur->y);
		wprintf(L"%lc", Body);
		pcur = pcur->next;
	}
	SetPos(pcur->next->x, pcur->next->y);
	printf("  ");
	free(pcur->next);
	pcur->next = NULL;
}

//检测是否撞墙
void KillByWall(pSnake ps)
{
	if (ps->_pSnake->x == 0 || ps->_pSnake->x == 56
		|| ps->_pSnake->y == 0 || ps->_pSnake->y == 28)
	{
		ps->_status = KILL_BY_WALL;
	}
}

//检测是否撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode pcur = ps->_pSnake->next;
	while (pcur)
	{
		if (pcur->x == ps->_pSnake->x && pcur->y == ps->_pSnake->y)
		{
			ps->_status = KILL_BY_SELF;
			break;
		}
		pcur = pcur->next;
	}
}

//蛇的移动
void SnakeMove(pSnake ps)
{
	pSnakeNode pnext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pnext == NULL)
	{
		perror("pnext malloc fail");
		return;
	}
	switch (ps->_dir)
	{
	case UP:
		pnext->x = ps->_pSnake->x;
		pnext->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pnext->x = ps->_pSnake->x;
		pnext->y = ps->_pSnake->y + 1;
		break;
	case RIGHT:
		pnext->x = ps->_pSnake->x+2;
		pnext->y = ps->_pSnake->y;
		break;
	case LEFT:
		pnext->x = ps->_pSnake->x-2;
		pnext->y = ps->_pSnake->y;
		break;
	}
	if (NextIsFood(pnext, ps))
	{
		EatFood(pnext, ps);
	}
	else {
		NOFood(pnext, ps);
	}
	//检查是否撞墙
	KillByWall(ps);
	//检查是否撞到自己
	KillBySelf(ps);
}


void GameRun(pSnake ps)
{
	PrintfHelpInfo();//打印提示信息
	do {
		SetPos(70, 6);
		printf("总分数:%d", ps->_score);
		SetPos(70, 8);
		printf("当前食物的分数:%2d", ps->_food_weight);

		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_RIGHT) && ps->_dir != LEFT)
		{
			ps->_dir = RIGHT;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->_dir != RIGHT)
		{
			ps->_dir = LEFT;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//退出
			ps->_status = END_NORMAL;
			break;
		}
		else if (KEY_PRESS(VK_F3))
		{
			//F3加速
			if (ps->_sleep_time >= 80)
			{
				ps->_sleep_time -= 10;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(VK_F4))
		{
			//F4减速
			if (ps->_food_weight >= 2)
			{
				ps->_food_weight -= 2;
				ps->_sleep_time += 10;
			}
		}
        Sleep(ps->_sleep_time);
		SnakeMove(ps);//蛇走一步的状态
	} while (ps->_status==OK);
}

void GameEnd(pSnake ps)
{
	SetPos(20, 14);
	switch (ps->_status)
	{
	case END_NORMAL:
		
			printf("您主动退出游戏\n");
			break;
	case KILL_BY_SELF:
		printf("您撞到自己了\n");
		break;
	case KILL_BY_WALL:
		printf("您撞到墙了\n");
		break;
	}
	//释放蛇身链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}


void GameStart(pSnake ps)
{
	system("mode con cols=110 lines=35");//设置窗口大小
	system("title 贪吃蛇");//设置项目名字
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获得标准输入流的句柄
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
	//1.打印环境界面
    //2.功能介绍
	WelcomeToGame();
	//3.绘制地图
	CreatMap();
	//4.创建蛇
	InitSnake(ps);
	//5.创建食物
	CreatFood(ps);
	//6.设置蛇的相关信息
}

最后祝大家学有所成

相关推荐
爱吃西瓜的小菜鸡4 小时前
【C语言】判断回文
c语言·学习·算法
FeboReigns6 小时前
C++简明教程(文章要求学过一点C语言)(1)
c语言·开发语言·c++
FeboReigns6 小时前
C++简明教程(文章要求学过一点C语言)(2)
c语言·开发语言·c++
_小柏_7 小时前
C/C++基础知识复习(43)
c语言·开发语言·c++
yoyobravery7 小时前
c语言大一期末复习
c语言·开发语言·算法
落羽的落羽11 小时前
【落羽的落羽 C语言篇】自定义类型——结构体
c语言
Kisorge11 小时前
【C语言】代码BUG排查方式
c语言·开发语言·bug
yoyo勰12 小时前
sqlite3
c语言·sqlite
就爱学编程13 小时前
重生之我在异世界学编程之C语言:数据在内存中的存储篇(上)
c语言·数据结构
意疏13 小时前
【C 语言指针篇】指针的灵动舞步与内存的神秘疆域:于 C 编程世界中领略指针艺术的奇幻华章
c语言·开发语言·指针