【贪吃蛇:C语言实现】


在这里插入图片描述

前言

彻底学习完C语言之后,为巩固所学,我们来实现一个贪吃蛇小游戏。
该游戏涉及到的C语言的知识有:函数、枚举、结构体、动态内存管理、预处理指令。
另外,还包含数据结构(链表)以及Win32API相关知识。

先看一下游戏效果吧
在这里插入图片描述

目前该游戏还有很多可以改进的地方,由于蛇的移动使用的是Sleep函数,看起来有点卡顿,而且按键会有一点延迟。
代码仓库链接

1.了解Win32API相关知识

1.1什么是Win32API

Win32 API也就是Microsoft Windows32位平台的应用程序编程接口。调用各种接口,可以帮应⽤程序达到开启视窗、描绘图形、使⽤周边设备等目的,由于这些函数服务的对象是应用程序(Application), 所以便称之为 Application Programming Interface,简称 API 函数。

1.2设置控制台的大小、名称

设置大小和名称的命令为:

  • mode con cols=列 lines=行
  • tiele 名称

如果想在C语言中设置窗口,需要使用system函数。

在这里插入图片描述

1.3控制台上的光标

COORD 是Windows API中定义的⼀个结构体,表示⼀个字符在控制台屏幕上的坐标,坐标系(0,0)的原点位于缓冲区的左侧顶部的单元格。使用需要window.h的头文件
在这里插入图片描述

COORD类型的声明:

typedef struct _COORD {
	SHORT X;//横坐标
	SHORT Y;//纵坐标
} COOR, *PCOORD;

1.4 GetStdHandle(获得控制台信息)

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

HANDLE WINAPI GetStdHandle( _In_ DWORD nStdHandle);

该函数需要一个参数,参数如下:
在这里插入图片描述

1.5 SetConsoleCursorPosition(设置光标位置)

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

BOOL WINAPI SetConsoleCursorPosition(
 	HANDLE hConsoleOutput,     /要设置哪个控制台
 	COORD pos		           /将光标设置的位置
);

通过上述三个功能,实现设置光标位置

在这里插入图片描述

1.6 GetConsoleCursorInfo(获得光标信息)

GetConsoleCursorInfo:检索有关指定控制台屏幕缓冲区的光标大小可见性的信息。然后将获得的信息放在一个结构体中

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

_ Out_ PCONSOLE_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。

在这里插入图片描述

1.7 SetConsoleCursorInfo(设置光标信息)

SetConsoleCursorInfo:设置指定控制台屏幕缓冲区的光标的大小和可见性。
其参数和get方法相同

BOOL WINAPI SetConsoleCursorInfo(
 	HANDLE hConsoleOutput,
 	const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo
);

设置前:

在这里插入图片描述
设置后:

在这里插入图片描述
隐藏光标:

在这里插入图片描述

1.8 GetAsyncKeyState(获取按键信息)

GetAsyncKeyState:获取按键情况,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 )
#include<Windows.h>
int main()
{
	while (1)
	{
		if (KEY_PRESS(0x30))
			printf("0\n");
		else if (KEY_PRESS(0x31))
			printf("1\n");
		else if (KEY_PRESS(0x32))
			printf("2\n");
		else if (KEY_PRESS(0x33))
			printf("3\n");
		else if (KEY_PRESS(0x34))
			printf("4\n");
		else if (KEY_PRESS(0x35))
			printf("5\n");
		else if (KEY_PRESS(0x36))
			printf("6\n");
		else if (KEY_PRESS(0x37))
			printf("7\n");
	}
	return 0;
}

2.游戏设计与分析

2.1地图设计

由于我打印墙体和蛇想使用 □,但是它是宽字符,该如何打印呢?
在这里插入图片描述
C语言规定:宽字符的类型wchar_t 和宽字符的输⼊和输出函数。
头文件为:<locale.h>

在这里插入图片描述

宽字符与普通字符对比:

在这里插入图片描述

由于墙体使用的是宽字符,所以在打印时需要注意!

在这里插入图片描述
在这里插入图片描述

以下函数实现打印墙体的功能:

#define WALL L'□'
void DrawMap()
{
	SetPos(0, 0);
	//上
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//左、右
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);  
}

再打印地图之前,我们需要设置窗口的大小,并且给玩家一些引导(Win32API的使用已在上面讲过)

//窗口相关设置
void ScreenPrepare()
{
	//设置窗体大小
	system("mode con cols=100 lines=40");
	system("title 贪吃蛇小游戏");
	//隐藏光标
	HideCurSor();
	//打印欢迎语
	Welcome();
	//打印地图
	DrawMap();
	//显示提示信息
	HelpInfo();
}

具体函数的实现我们会在后面给出,先看一下效果吧~

在这里插入图片描述

2.2数据结构的设计

2.2.1蛇身的设计

在游戏运行的过程中,蛇每次吃⼀个⻝物,蛇的⾝体就会变长⼀节,如果我们使用链表存储蛇的信息,那么蛇的每⼀节其实就是链表的每个节点。每个节点只要记录好蛇身节点在地图上的坐标就行,所以蛇节点结构如下:

//蛇节点的定义
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针

2.2.2蛇的方向

蛇的方向可以一一列举,使用枚举

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

2.2.3游戏的状态

游戏状态,可以⼀⼀列举,使用枚举

//游戏的状态
enum GameState
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END//结束
};

2.2.4管理整条蛇

我们再封装⼀个Snake的结构体来维护整条贪吃蛇。

//整条蛇
typedef struct Snake
{
	pSnakeNode pSnake;  //指向整条蛇的指针
	pSnakeNode PFood;   //指向食物的指针

	enum Direction Dire;  //蛇的方向
	enum GameState State; //蛇的状态

	int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快
	int Score;//游戏得分
	int FoodScore;//一个食物的分数
}Snake,* pSnake;

2.3.游戏流程

在这里插入图片描述

3. 功能的具体实现

游戏开始前

3.1 初始化蛇

游戏开始,蛇的长度默认是3。

  • 创建节点,头插法依次连接
  • 节点的纵坐标相同,横坐标依次增加2,产生串的效果
  • 蛇的方向默认为右
  • 打印蛇,并且为蛇头设置颜色

此处颜色设置我们使用Win32API提供的函数:

BOOL WINAPI SetConsoleTextAttribute(
  _In_ HANDLE hConsoleOutput,//当前设备句柄
  _In_ WORD   wAttributes//颜色
);

在这里插入图片描述

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

//打印蛇
void PrintSnake(pSnake ps)
{
	pSnakeNode cur = ps->pSnake;
	int flag = 1;
	while (cur)
	{
		if (flag == 1)
		{
			//为蛇头设置颜色
			SetColor(12);
		}
		SetPos(cur->x, cur->y);//设置每个节点的位置
		wprintf(L"%lc", BODY);
		cur = cur->next;
		flag = 0;
		SetColor(10);
	}
}

//初始化蛇
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	//蛇的长度开始为3
	for (i = 0; i < 3; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc");
			return;
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;  //节点横坐标依次增加2
		cur->y = POS_Y;

		//头插法将节点相连
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;
		}
	}
	//打印蛇
	PrintSnake(ps);
	//初始化蛇的其它信息
	ps->Dire = RIGHT;
	ps->FoodScore = 10;
	ps->SleepTime = 200;
	ps->State = OK;
	ps->Score = 0;
}

3.2 创建食物

食物要随机生成,有以下几个注意事项:

  • 食物应该在墙体内
  • 食物不可与蛇身重叠
  • 由于我们蛇的打印使用的是宽字符,所以食物的坐标应该在2的倍数处
//创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//食物坐标应该在墙体内
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//食物坐标不与蛇重叠
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//根据坐标创建食物
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreatFood():malloc()");
		return;
	}
	//打印食物
	pFood->x = x;
	pFood->y = y;
	SetColor(14);
	SetPos(pFood->x, pFood->y);
	wprintf(L"%lc", FOOD);
	SetColor(10);
	ps->pFood = pFood;
}

在这里插入图片描述

游戏运行

3.3 检测按键

玩家按上下左右键控制蛇的移动,在检测时需要注意:

  • 玩家按的键若与蛇的方向相反,则不做响应;玩家按键符合,则修改蛇的方向
  • 玩家按F1或F2修改蛇的移动速度;蛇的速度应大于0,食物的分数最低为1分
  • 按键的检测使用Win32API提供的功能:GetAsyncKeyState
//玩游戏
void GameRun(pSnake ps)
{
	do
	{
		//显示分数
		SetColor(12);
		SetPos(65, 12);
		printf("目前得分:%-5d",ps->Score);
		SetColor(10);
		SetPos(65, 13);
		printf("每个食物:%2d分",ps->FoodScore);
		//检测按键
		if (KEY_PRESS(VK_UP) && ps->Dire != DOWN)
		{
			//按上键,且蛇的方向不能向下
			ps->Dire = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP)
		{
			//按下键,且蛇的方向不能向上
			ps->Dire = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT)
		{
			//按左键,且蛇的方向不能向右
			ps->Dire = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT)
		{
			//按右键,且蛇的方向不能向左
			ps->Dire = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//按ESC,退出游戏
			ps->State = END;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//按空格,暂停
			pause();
		}
		else if (KEY_PRESS(VK_F1))
		{
			//按F1加速,即睡眠时间变短
			
			//休眠时间不能是负数,最快就是休眠30ms
			if (ps->SleepTime >= 50)
			{
				ps->SleepTime -= 20;
				//速度变快,食物的分数变高
				ps->FoodScore += 2;
			}
		}
		else if (KEY_PRESS(VK_F2))
		{
			//F2减速,睡眠时间变长

			//食物的分数不能减到负数,最多减为1分
			if (ps->FoodScore >= 3)
			{
				ps->SleepTime += 20;
				ps->FoodScore -= 2;
			}
		}
		//按照蛇的睡眠时间,真正实现休眠
		Sleep(ps->SleepTime);

		//休眠后,蛇要移动
		SnakeMove(ps);

	} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}

3.4 蛇移动

蛇的移动就是根据蛇的方向,产生节点,判断节点是不是食物。

  • 所产生的节点的位置根据蛇的方向而定
  • 节点是食物,吃掉食物,蛇身变长
  • 节点不是食物,也吃掉食物,但长度不变
//移动蛇
void SnakeMove(pSnake ps)
{
	//先产生节点
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL)
	{
		perror("SnakeMove():malloc()");
		return;
	}
	//根据蛇的方向,设定节点的位置
	switch (ps->Dire)
	{
	case UP:
		//蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1
		pNext->x = ps->pSnake->x;
		pNext->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		//蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1
		pNext->x = ps->pSnake->x;
		pNext->y = ps->pSnake->y + 1;
		break;
	case LEFT:
		//蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2
		pNext->x = ps->pSnake->x - 2;
		pNext->y = ps->pSnake->y;
		break;
	case RIGHT:
		//蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2
		pNext->x = ps->pSnake->x + 2;
		pNext->y = ps->pSnake->y;
		break;
	}

	//判断下一个节点是否是食物
	if (JudgeNext(ps,pNext))
	{
		//是食物,吃掉,长度增加
		EatFood(ps, pNext);
	}
	else
	{
		//不是食物,吃掉,长度不增加
		NoFood(ps, pNext);
	}

	//未完
	//撞墙
	
	//撞自己
}

3.5 判断节点是否是食物

将所产生的节点与食物的节点对比即可

//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{
	return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}

3.6 吃食物

吃掉食物很简单,将所产生的节点与蛇想连;连接后得分增加,并释放所产生的节点,再次产生食物。

//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{
	//吃掉食物,头插法将节点插入
	pNext->next = ps;
	ps = pNext;
	//打印蛇
	PrintSnake(ps_->snake);
	//加分
	PrintSnake(ps->pSnake);
	//释放食物节点
	free(ps->pFood);
	//再次创建食物
	CreatFood(ps);
}

3.7 不吃食物

不吃食物需要将产生的节点与蛇相连,然后删除蛇尾(将蛇尾打印尾空格,并释放蛇尾节点)

//不吃食物
void NoFood(pSnake ps, pSnakeNode pNext)
{
	//头插法连接
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//删除蛇尾
	pSnakeNode cur = ps->pSnake;
	//找到蛇尾的前一个节点
	while (cur->next->next)
	{
		cur = cur->next;
	}
	pSnakeNode del = cur->next;
	//将蛇尾打印尾空格
	SetPos(del->x, del->y);
	printf("  ");
	//释放蛇尾节点
	free(del);
	cur->next = NULL;
	//打印蛇
	PrintSnake(ps);
}

3.8 撞墙

若蛇头坐标与墙体坐标重合,则说明撞墙了。

//撞墙
void KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 ||
		ps->pSnake->x == 56 ||
		ps->pSnake->y == 0 ||
		ps->pSnake->y == 26)
	{
		ps->State = KillByWall;
	}
}

3.9 撞到自己

若蛇头坐标与蛇身节点的坐标重合,则说明撞到自己了。

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->State = KILL_BY_SELF;
		}
		cur = cur->next;
	}
}

3.10 游戏结束

给出游戏结束的原因,释放蛇身节点。

//游戏结束
void GameEnd(pSnake ps)
{
	SetPos(20, 13);
	SetColor(12);

	switch (ps->State)
	{
	case END:
		printf("您结束了游戏");
		break;
	case KILL_BY_SELF:
		printf("很遗憾!您撞到了自己");
		break;
	case KILL_BY_WALL:
		printf("很遗憾!您撞墙了");
		break;
	}
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
	ps = NULL;
	SetColor(10);
}

3.11 main函数

让玩家选择

int main()
{
	srand((unsigned int)time(NULL));
	setlocale(LC_ALL, "");//适应本地中文环境

	int ch = 0;
	do
	{
		Snake snake = { 0 };//创建贪吃蛇
		//游戏开始前的初始化
		GameStart(&snake);

		玩游戏
		GameRun(&snake);

		游戏结束,善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("还要再来一局吗?(Y/N)");
		ch = getchar();
		getchar();//吸收换行
	} while (ch == 'Y' || ch == 'y');

	SetPos(0, 30);
	return 0;
}

4.参考代码

4.1 main

#define _CRT_SECURE_NO_WARNINGS 1
#include"Snake.h"

int main()
{
	srand((unsigned int)time(NULL));
	setlocale(LC_ALL, "");//适应本地中文环境

	int ch = 0;
	do
	{
		Snake snake = { 0 };//创建贪吃蛇
		//游戏开始前的初始化
		GameStart(&snake);

		玩游戏
		GameRun(&snake);

		游戏结束,善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("还要再来一局吗?(Y/N)");
		ch = getchar();
		getchar();//吸收换行
	} while (ch == 'Y' || ch == 'y');

	SetPos(0, 30);
	return 0;
}

4.2 Snake.h

#define _CRT_SECURE_NO_WARNINGS 1

#include<locale.h>
#include<stdio.h>
#include<Windows.h>
#include<stdlib.h>
#include<stdbool.h>
#include<time.h>

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

#define WALL L'□'//墙体
#define BODY L'●'//蛇身
#define FOOD L'★'//食物

//蛇初始位置
#define POS_X 24
#define POS_Y 12

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

//游戏的状态
enum GameState
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END//结束
};

//蛇节点的定义
typedef struct SnakeNode
{
	int x;
	int y;
	struct SnakeNode* next;//指向下一个节点
}SnakeNode,*pSnakeNode;//同时定义一个指向该节点的指针

//整条蛇
typedef struct Snake
{
	pSnakeNode pSnake;  //指向整条蛇的指针
	pSnakeNode pFood;   //指向食物的指针

	enum Direction Dire;  //蛇的方向
	enum GameState State; //蛇的状态

	int SleepTime; //蛇的休眠时间;休眠越短,蛇移动的越快
	int Score;//游戏得分
	int FoodScore;//一个食物的分数
}Snake,* pSnake;

//设置光标位置
void SetPos(int x, int y);

//设置颜色
void SetColor(int HeadColor);

//游戏准备
void GameStart(pSnake psnake);

//初始化蛇
void InitSnake(pSnake psnake);

//创建食物
void CreatFood(pSnake ps);

//打印蛇
void PrintSnake(pSnake ps);

//玩游戏
void GameRun(pSnake ps);

//移动蛇
void SnakeMove(pSnake ps);

//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext);

//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext);

//不是食物
void NoFood(pSnake ps, pSnakeNode pNext);

//撞墙
void KillByWall(pSnake ps);

//撞到自己
void KillBySelf(pSnake ps);

//游戏结束
void GameEnd(pSnake ps);

4.3 Snake.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"Snake.h"

//隐藏光标
void HideCurSor()
{
	CONSOLE_CURSOR_INFO cursor = { 0 };
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleCursorInfo(handle, &cursor);
	cursor.bVisible = false;
	SetConsoleCursorInfo(handle, &cursor);
}

//设置光标位置
void SetPos(int x, int y)
{
	COORD pos = { x,y }; //要设置的位置
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//哪个设备
	SetConsoleCursorPosition(handle, pos);//设置
}

void Welcome()
{
	//显示欢迎语一
	SetPos(38, 15);
	printf("欢迎来到贪吃蛇小游戏!");
	SetPos(40, 24);
	system("pause");
	system("cls");//清屏
	//提示语
	SetPos(25, 15);
	printf("按 ↑、↓、←、→ 控制蛇蛇的移动,F1为加速,F2为减速,");
	SetPos(25, 16);
	printf("加速将能得到更高的分数");
	SetPos(40, 24);
	system("pause");
}

//打印地图
void DrawMap()
{
	system("cls");
	SetColor(6);
	SetPos(0, 0);
	//上
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);
	//左、右
	for (int i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
	//下
	SetPos(0, 26);
	for (int i = 0; i <= 56; i += 2)
		wprintf(L"%lc", WALL);  
	SetColor(10);
}

//显示提示信息
void HelpInfo()
{
	SetPos(65, 18);
	printf("不能穿墙,不能碰到自己,");
	SetPos(65, 19);
	printf("按 ↑、↓、←、→ 控制蛇蛇的移动,");
	SetPos(65, 21);
	printf("F1为加速,F2为减速,");
	SetPos(65, 22);
	printf("ESC:退出游戏 SPACE:暂停");
}

//窗口相关设置
void ScreenPrepare()
{
	//设置窗体大小
	system("mode con cols=100 lines=40");
	system("title 贪吃蛇小游戏");
	//隐藏光标
	HideCurSor();
	//打印欢迎语
	Welcome();
	//打印地图
	DrawMap();
	//显示提示信息
	HelpInfo();
}

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

//打印蛇
void PrintSnake(pSnake ps)
{
	pSnakeNode cur = ps->pSnake;
	int flag = 1;
	while (cur)
	{
		if (flag == 1)
		{
			//为蛇头设置颜色
			SetColor(12);
		}
		SetPos(cur->x, cur->y);//设置每个节点的位置
		wprintf(L"%lc", BODY);
		cur = cur->next;
		flag = 0;
		SetColor(10);
	}
}

//初始化蛇
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	//蛇的长度开始为3
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc");
			return;
		}
		cur->next = NULL;
		cur->x = POS_X + i * 2;  //节点横坐标依次增加2
		cur->y = POS_Y;

		//头插法将节点相连
		if (ps->pSnake == NULL)
		{
			ps->pSnake = cur;
		}
		else
		{
			cur->next = ps->pSnake;
			ps->pSnake = cur;
		}
	}
	//打印蛇
	PrintSnake(ps);
	//初始化蛇的其它信息
	ps->Dire = RIGHT;
	ps->FoodScore = 10;
	ps->SleepTime = 200;
	ps->State = OK;
	ps->Score = 0;
}

//创建食物
void CreatFood(pSnake ps)
{
	int x = 0;
	int y = 0;
	//食物坐标应该在墙体内
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//食物坐标不与蛇重叠
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}
	//根据坐标创建食物
	pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pFood == NULL)
	{
		perror("CreatFood():malloc()");
		return;
	}
	//打印食物
	pFood->x = x;
	pFood->y = y;
	SetColor(14);
	SetPos(pFood->x, pFood->y);
	wprintf(L"%lc", FOOD);
	SetColor(10);
	ps->pFood = pFood;
}

//游戏准备
void GameStart(pSnake psnake)
{
	//设置好窗口、地图
	ScreenPrepare();
	//初始化蛇
	InitSnake(psnake);
	//创建食物
	CreatFood(psnake);
}

//暂停
void pause()
{
	while (1)
	{
		//一直休眠,直到再次按空格键
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
		{
			break;
		}
	}
}

//玩游戏
void GameRun(pSnake ps)
{
	do
	{
		//显示分数
		SetColor(12);
		SetPos(65, 12);
		printf("目前得分:%-5d",ps->Score);
		SetColor(10);
		SetPos(65, 13);
		printf("每个食物:%2d分",ps->FoodScore);
		//检测按键
		if (KEY_PRESS(VK_UP) && ps->Dire != DOWN)
		{
			//按上键,且蛇的方向不能向下
			ps->Dire = UP;
		}
		else if (KEY_PRESS(VK_DOWN) && ps->Dire != UP)
		{
			//按下键,且蛇的方向不能向上
			ps->Dire = DOWN;
		}
		else if (KEY_PRESS(VK_LEFT) && ps->Dire != RIGHT)
		{
			//按左键,且蛇的方向不能向右
			ps->Dire = LEFT;
		}
		else if (KEY_PRESS(VK_RIGHT) && ps->Dire != LEFT)
		{
			//按右键,且蛇的方向不能向左
			ps->Dire = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//按ESC,退出游戏
			ps->State = END;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			//按空格,暂停
			pause();
		}
		else if (KEY_PRESS(VK_F1))
		{
			//按F1加速,即睡眠时间变短
			
			//休眠时间不能是负数,最快就是休眠30ms
			if (ps->SleepTime >= 50)
			{
				ps->SleepTime -= 20;
				//速度变快,食物的分数变高
				ps->FoodScore += 2;
			}
		}
		else if (KEY_PRESS(VK_F2))
		{
			//F2减速,睡眠时间变长

			//食物的分数不能减到负数,最多减为1分
			if (ps->FoodScore >= 3)
			{
				ps->SleepTime += 20;
				ps->FoodScore -= 2;
			}
		}
		//按照蛇的睡眠时间,真正实现休眠
		Sleep(ps->SleepTime);

		//休眠后,蛇要移动
		SnakeMove(ps);

	} while (ps->State == OK);//只有蛇的状态为OK,才能继续检测按键
}

//移动蛇
void SnakeMove(pSnake ps)
{
	//先产生节点
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL)
	{
		perror("SnakeMove():malloc()");
		return;
	}
	//根据蛇的方向,设定节点的位置
	switch (ps->Dire)
	{
	case UP:
		//蛇的方向向上,节点应该在当前蛇头的上方;横坐标不变,纵坐标减1
		pNext->x = ps->pSnake->x;
		pNext->y = ps->pSnake->y - 1;
		break;
	case DOWN:
		//蛇的方向向下,节点应该在当前蛇头的下方;横坐标不变,纵坐标加1
		pNext->x = ps->pSnake->x;
		pNext->y = ps->pSnake->y + 1;
		break;
	case LEFT:
		//蛇的方向向左,节点应该在当前蛇头的左边;纵坐标不变,横坐标减2
		pNext->x = ps->pSnake->x - 2;
		pNext->y = ps->pSnake->y;
		break;
	case RIGHT:
		//蛇的方向向右,节点应该在当前蛇头的右方;纵坐标不变,横坐标加2
		pNext->x = ps->pSnake->x + 2;
		pNext->y = ps->pSnake->y;
		break;
	}

	//判断下一个节点是否是食物
	if (JudgeNext(ps, pNext))
	{
		//是食物,吃掉,长度增加
		EatFood(ps, pNext);
	}
	else
	{
		//不是食物,吃掉,长度不增加
		NoFood(ps, pNext);
	}

	//撞墙
	KillByWall(ps);

	//撞自己
	KillBySelf(ps);
}

//判断节点是否是食物
int JudgeNext(pSnake ps, pSnakeNode pNext)
{
	return ((ps->pFood->x == pNext->x) && (ps->pFood->y == pNext->y));
}

//是食物,吃掉,长度增加
void EatFood(pSnake ps, pSnakeNode pNext)
{
	//吃掉食物,头插法将节点插入
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//打印蛇
	PrintSnake(ps);
	//加分
	ps->Score +=  ps->FoodScore;
	//释放食物节点
	free(ps->pFood);
	//再次创建食物
	CreatFood(ps);
}

//不是食物
void NoFood(pSnake ps, pSnakeNode pNext)
{
	//头插法连接
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//删除蛇尾
	pSnakeNode cur = ps->pSnake;
	//找到蛇尾的前一个节点
	while (cur->next->next)
	{
		cur = cur->next;
	}
	pSnakeNode del = cur->next;
	//将蛇尾打印为空格
	SetPos(del->x, del->y);
	printf("  ");
	//释放蛇尾节点
	free(del);
	cur->next = NULL;
	//打印蛇
	PrintSnake(ps);
}

//撞墙
void KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 ||
		ps->pSnake->x == 56 ||
		ps->pSnake->y == 0 ||
		ps->pSnake->y == 26)
	{
		ps->State = KILL_BY_WALL;
	}
}

//撞到自己
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->State = KILL_BY_SELF;
		}
		cur = cur->next;
	}
}

//游戏结束
void GameEnd(pSnake ps)
{
	SetPos(20, 13);
	SetColor(12);

	switch (ps->State)
	{
	case END:
		printf("您结束了游戏");
		break;
	case KILL_BY_SELF:
		printf("很遗憾!您撞到了自己");
		break;
	case KILL_BY_WALL:
		printf("很遗憾!您撞墙了");
		break;
	}
	pSnakeNode cur = ps->pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
	ps = NULL;
	SetColor(10);

}
  • 91
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 35
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值