C语言实现贪吃蛇小游戏详细讲解

今天我们用C语言来编写一个简易版的1贪吃蛇小游戏,在代码层面是由Snake.h  Snake.c  game.c 三个文件构成的项目,使用的集成开发环境是VS2022,下面我将较为详细的介绍相关的背景知识与整体实现的代码。

完全运行后效果演示如下:

 

 

 

Win32 API 介绍

这次的贪吃蛇游戏需要用到部分Win32 API的知识来完成许多游戏界面的布设。

1:Win32 API

我们平常所说的windows,既是一个可以协调应用程序的执行、分配内存、管理资源的多作业系统,也是一个服务中心,调用这个服务中心的各种服务(实际上就是函数),可以帮助应用程序达到开启视窗、描绘图形、使用周边设备的目的。这样的函数被称为API函数。

Win32 API指的就是Microsoft Windows 32位平台的应用程序编程接口

2:控制台程序(console)

平常我们运行程序时显示出来的黑框就是控制台,为了实现贪吃蛇游戏,我们需要对控制台窗口进行设置。

首先我们右击控制台窗口上方,选择属性,确保你的默认终端应用程序是Windows控制台主机。

在属性——颜色界面,可以根据个人喜好选择背景与字体颜色,如图为灰色背景和蓝色字体。

很可能你的VS默认是“让Windows决定”,这时我们在相同位置右击—— 设置——启动——将默认终端应用程序修改为Windows控制台主机。

平时我们可以直接在电脑的搜索栏或者 Windows+R键 用cmd指令调用控制台进行设置。如:mode con cols=100 lines=30 是用来设置控制台窗口的长度(100列)和宽度(30行)的。

游戏过程中我们希望标题栏可以显示“贪吃蛇字样”,可以使用指令:title 贪吃蛇

那么在C语言代码中想要实现以上功能,需要使用库函数 system(); 同时需要包含头文件<stdlib.h> 列如:

system("mode con cols=50 lines=20");
system("title 贪吃蛇");
system("pause");
//标题只会在程序运行过程中显示,结束之后不再显示
//可以用pause指令暂停以查看

3:控制台屏幕上的坐标 COORD

想要在屏幕上的不同位置打印字样,必然用到坐标,控制台屏幕上的坐标是以左上角为(0,0)点,水平向右为X轴正方向,竖直向下是Y轴正方向。位置坐标的数据类型是COORD

//COORD类型的声明
typedef struct _COORD{
SHORT X;
SHORT Y;
}COORD,*PCOORD;

现在我可以定义一个坐标(需要头文件<windows.h>)。

COORD pos ={40,10};

4:GetStdHandle

这是一个Windows API函数,用于从一个特定的标准设备(包括标准输入、标准输出、标准错误)中取得一个句柄(用来标识不同设备的数值),使用这个句柄可以操作设备。我们往往通过传不同的控制台对应的句柄给函数来操作

5:GetConsoleCursorInfo

这个函数用于检索有关指定控制台屏幕缓冲区的光标大小和可见性的信息。应用实例:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
CONSOLE_CURSOR_INFO CursorInfo;//初始化光标信息
GetConsoleCursorInfo(handle, &CursorInfo);//获取光标信息

以下是光标信息的内部结构体:

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

其中dwSize由光标填充的字符单元格的百分比,介于1到100;bVisible游标可见性,可见为TRUE。

6:SetConsoleCursorInfo

这个函数用于设置指定控制台屏幕缓冲区的光标大小和可见性。我们可以接续上述代码:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
CONSOLE_CURSOR_INFO CursorInfo;//初始化光标信息
GetConsoleCursorInfo(handle, &CursorInfo);//获取光标信息
CursorInfo.dwSize=100;
CursorInfo.bVisible=false;//隐藏光标
SetConsoleCursorInfo(handle,&CursorInfo);//设置光标信息

7:SetConsoleCursorPosition

这个函数用于设置指定控制台屏幕缓冲区中的光标位置。下面是使用格式:

HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
COORD pos={20,5};
SetConsoleCursorPosition(handle,pos);
printf("111");
//这样打印的位置就会在窗口的(20,5)

为了使用的方便,我们可以将这一整个设置位置的操作封装为函数:

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

8:GetAsynckeyState

这个函数可以获取按键情况,将键盘上每个键的虚拟键值传递给函数,函数返回值(short类型)可以用来分辨按键的状态要判断一个键是否被按过,可检测GetAsynckeyState返回值最低值是否为1,使用时我们将它封装为一个宏:

#define KEY_PRESS(vk) (GetAsynckeyState(vk)&0x1?1:0)

在vk的位置输入指定的虚拟键值(详情可自查),如此按键被按过则为1,否则为0,恰好对应条件为真或假。

游戏设计

在了解了这些函数之后,我们实际上已经可以将诸多程序的实现做得更加完善了,下面系统介绍

一下贪吃蛇游戏的编写。

1:地图设计

①:宽字符

我们平常所打印的英文字母等实际只会占一个字节(一格光标)的位置,打印地图时使用到的是宽字符 ■ ★ ● ,一次占两个字节(两格光标),包括汉字也是如此。

②:<locale.h>本地化

控制C标准库中对于不同地区会产生不一样行为的部分,这里有数字量、货币量的格式,字符集,日期和时间的表示形式

③:setlocale函数

C语言支持针对不同类项的修改,如LC_COLLATE、LC_ALL等。setlocale函数可以修改当前地区,参数一是类项,参数二只有 "c"(正常)和 ""(本地)。列如:

setlocale(LC_ALL,"c");
setlocale(LC_ALL,"");

函数返回一个字符串指针,若调用失败,返回空指针NULL。如果将参数二设为NULL,可用来查询当前地区。

④:宽字符的打印

宽字符数据类型为 wchar_t,宽字符字面量前须加前缀L,打印时使用wprintf

L在 ' ' 前表示宽字符,对应的占位符为%lc,在 " " 前表示宽字符串,对应的占位符为%ls。举例:

setlocale(LC_ALL,"");
wchar_t ch1=L'中',ch2=L'国';
wprintf(L"%lc",ch1);
wprintf(L"%lc",ch2);

2:数据结构设计

我们同过单链表的形式来维护蛇,在头文件中,定义蛇的结构体:

//蛇的每个节点
typedef struct SnakeNode {
	int x;//坐标
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
//重命名,并定义指向每个节点的指针

然后是对整条蛇的结构体:

typedef struct Snake
{
	pSnakeNode pSnake;//维护整条蛇的指针,指向蛇头
	pSnakeNode pFood;//指向食物的指针
	int score;//分数
	int FoodWeight;//食物的分数
	int SleepTime;//休眠时间
	enum GAME_STATUS status;//游戏状态
	enum DIRECTION dir;//蛇当前走的方向
 }Snake,*pSnake;

游戏状态可以分为正常运行、退出,因为撞墙或装到自己而死亡,可以用枚举来表示:

enum GAME_STATUS
{
	OK = 1,//正常运行
	ESC,//正常退出
	KILL_BY_WALL,
	KILL_BY_SELF
};

蛇的运行与速度和方向有关,速度即是每走一步的休眠时间,方向再用一个枚举来表示:

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

3:游戏开始

在主函数中将依次运行各个部分的函数,首先是GameStart函数:

void GameStart(pSnake ps)
{
	//设置控制台窗口
	system("mode concols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(handle, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &CursorInfo);

	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

接下来是打印开头的信息,将光标定位到屏幕的中间位置,每次结束,暂停程序,清理屏幕

void WelcomeToGame()
{
	SetPos(35, 10);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(39, 11);
	printf("作者:zhuoer");
	SetPos(38, 20);
	system("pause");
	system("cls");
	SetPos(22, 10);
	printf("用↑.↓.←.→来控制方向,左shift是加速,右shift是减速");
	SetPos(36, 11);
	printf("加速能获得更高的分数");
	SetPos(38, 20);
	system("pause"); 
	system("cls");
}

然后是打印墙体,为了方便,将墙体的宽字符封装为一个宏:

#define WALL L'■'
void CreateMap()
{
	int i = 0;

	SetPos(0, 0);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(0,26);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

初始化蛇体,我们假设蛇的基础长度是5,首先建立蛇头,蛇头的基础位置再可以封装为宏,接下来依次建立一个个蛇体的节点,并头插到链表中。

#define BODY L'●'
#define POS_X 24
#define POS_Y 5
void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc()");
			return;
		}
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;
		cur->next = NULL;
		//头插
		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->FoodWeight = 10;
	ps->pFood = NULL;
	ps->score = 0;
	ps->SleepTime = 200;//单位毫秒
	ps->status = OK;
}

在地图上随机地生成食物,并确保他们的位置不会重复,也不会生成在蛇的身体里。

#define FOOD L'★'
void CreateFood(pSnake ps)
{
	srand((unsigned int)time(NULL));
	int x = 0, 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("CreateFood()::malloc()");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	ps->pFood = pFood;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	
}

4:游戏运行

接下来是游戏运行的部分,完成GameRun函数,我们先在地图界面的旁边打印分数和帮助信息,分数应该按照循环多次打印,并希望食物的分数随速度变化而变化。

void PrintHelpInfo()
{
	SetPos(62, 14);
	printf("注意:");
	SetPos(62, 15);
	printf("不能穿墙,不能咬到自己");
	SetPos(62, 16);
	printf("用↑.↓.←.→来控制蛇前进的方向");
	SetPos(62, 17);
	printf("左shift是加速,右shift是减速");
	SetPos(62, 18);
	printf("按ESC键退出,按空格键暂停");
}
SetPos(62, 10);
printf("总分:%5d", ps->score);
SetPos(62, 11);
printf("每个食物的分数:%02d", 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_ESCAPE))//退出
{
	ps->status = ESC;
	break;
}
else if (KEY_PRESS(VK_SPACE))//暂停
{
	Pause();
}
//加减速,注意食物权重跟随改变,并设置上下限
else if (KEY_PRESS(VK_LSHIFT))
{
	if (ps->SleepTime >= 80)
	{
		ps->SleepTime -= 30;
		ps->FoodWeight += 2;
	}
}
else if (KEY_PRESS(VK_RSHIFT))
{
	if (ps->FoodWeight >2)
	{
		ps->SleepTime += 30;
		ps->FoodWeight -= 2;
	}
}

完善暂停的函数,实际上就是一直循环休眠,再次空格会继续游戏:

void Pause()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PRESS(VK_SPACE))
		{
			SetPos(15, 12);
			break;
		}
	}
}

GameRun函数的最后一个部分,在休眠一个SleepTime的时间过后,进行移动,再将整个部分用循环框起来,条件就是游戏状态处于“OK”(运行中)。下面完成蛇的移动的函数,每次移动,都需要判断蛇头的下一个位置是否是食物,先封装为一个函数:

int NextIsFood(pSnake ps, pSnakeNode pNext)
//pNext指向下一个位置
{
	if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
	{
		return 1;
	}
	else 
		return 0;
}

然后是SnakeMove函数,根据当前方向的不同可以得出下一个位置的坐标,同时每一步移动都要区分下一步是吃了食物,还是没有吃,并且有没有撞到自己或者墙:

void SnakeMove(pSnake ps)
{
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	pNext->next = NULL;
	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;
    //注意x坐标一次移动两格
	case LEFT:pNext->x = ps->pSnake->x-2;
		pNext->y = ps->pSnake->y;
		break;
	case RIGHT:pNext->x = ps->pSnake->x+2;
		pNext->y = ps->pSnake->y;
		break;
	}
	if (NextIsFood(ps, pNext))
	{
		EatFood(ps, pNext);
	}
	else
	{
		NotEatFood(ps, pNext);
	}
	KillByWall(ps);
	KillBySelf(ps);
}

无可避免的,我们要依次完成4个函数。每吃一个食物,蛇应该增长一格,如果没有吃,应该维持原长度,为了实现蛇的移动,我们同样增长一格,并删除当前的尾节点(这里不进行改变链表结构的多余操作,只是在末尾打印空白符)。

void EatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//吃食物,头插一个节点
	pSnakeNode cur = ps->pSnake;//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->score += ps->FoodWeight;//增加分数
	free(ps->pFood);//释放当前的食物
	CreateFood(ps);//创建新的食物
}
void NotEatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	pSnakeNode cur = ps->pSnake;//打印蛇
	while (cur->next->next!=NULL)
	{
		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;
}

被撞死的函数,相较而言简单,只需进行位置的判断,并对应的改变游戏状态(这体现出将游戏状态封装为枚举的好处),代码在后续的完整代码中再体现。

5:游戏结束

完成游戏结束的函数GameEnd,在这一步,我们只需要判断当前的“游戏状态”,打印对应的提示信息即可。

SetPos(15, 12);
switch(ps->status)
{
case ESC:
	printf("已退出游戏...");
	SetPos(38, 27);
	exit(0);
case KILL_BY_SELF:
	printf("很遗憾,你咬到自己了");
	break;
case KILL_BY_WALL:
	printf("很遗憾,你撞到墙了");
	break;
}

为了游戏代码的完善性,不能忘记将蛇的链表资源和食物、指针等释放,当然这主要是使代码更像样,实际不会太多地影响我们这个简易的程序。

pSnakeNode cur = ps->pSnake;
pSnakeNode del = NULL;
while (cur)
{
	del = cur;
	cur = cur->next;
	free(del);
}
ps->pSnake = NULL;
free(ps->pFood);
ps->pSnake = NULL;

整个游戏还需要一些重新开始的循环系统,这些的实现都比较灵活,可自行选择,我们可以通过键位、输入数字等方法实现交互,使得整个游戏的体验更为流畅和完备。初次之外,代码还有头文件中的声明和主函数的编写部分(当然这些较为简单),我们直接在完整代码中呈现我的方案。

完整代码

Snake.h

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

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

#define POS_X 24
#define POS_Y 5

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

enum GAME_STATUS
{
	OK = 1,//正常运行
	ESC,//正常退出
	KILL_BY_WALL,
	KILL_BY_SELF
};

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

//蛇的每个节点
typedef struct SnakeNode {
	int x;//坐标
	int y;
	struct SnakeNode* next;
}SnakeNode,*pSnakeNode;
//重命名,并定义指向每个节点的指针

typedef struct Snake
{
	pSnakeNode pSnake;//维护整条蛇的指针,指向蛇头
	pSnakeNode pFood;//指向食物的指针
	int score;//分数
	int FoodWeight;//食物的分数
	int SleepTime;//休眠时间
	enum GAME_STATUS status;//游戏状态
	enum DIRECTION dir;//蛇当前走的方向
 }Snake,*pSnake;

void SetPos(int x, int y);

void GameStart(pSnake ps);
void WelcomeToGame();
void CreateMap();
void InitSnake(pSnake ps);
void CreateFood(pSnake ps);

void GameRun(pSnake ps);
void PrintHelpInfo();
void Pause();
void SnakeMove(pSnake ps);
int NextIsFood(pSnake ps, pSnakeNode pNext);
void EatFood(pSnake ps, pSnakeNode pNext);
void NotEatFood(pSnake ps, pSnakeNode pNext);
void KillByWall(pSnake ps);
void KillBySelf(pSnake ps);

void GameEnd(pSnake ps);

Snake.c

#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"
void SetPos(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD pos = { x,y };
	SetConsoleCursorPosition(handle, pos);
}

void WelcomeToGame()
{
	SetPos(35, 10);
	printf("欢迎来到贪吃蛇小游戏");
	SetPos(39, 11);
	printf("作者:zhuoer");
	SetPos(38, 20);
	system("pause");
	system("cls");
	SetPos(22, 10);
	printf("用↑.↓.←.→来控制方向,左shift是加速,右shift是减速");
	SetPos(36, 11);
	printf("加速能获得更高的分数");
	SetPos(38, 20);
	system("pause"); 
	system("cls");
}

void CreateMap()
{
	int i = 0;

	SetPos(0, 0);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	SetPos(0,26);
	for (i = 0; i <= 56; i += 2)
	{
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i <= 25; i++)
	{
		SetPos(0, i);
		wprintf(L"%lc", WALL);
	}
	for (i = 1; i <= 25; i++)
	{
		SetPos(56, i);
		wprintf(L"%lc", WALL);
	}
}

void InitSnake(pSnake ps)
{
	pSnakeNode cur = NULL;
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc()");
			return;
		}
		cur->x = POS_X + 2 * i;
		cur->y = POS_Y;
		cur->next = NULL;
		//头插
		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->FoodWeight = 10;
	ps->pFood = NULL;
	ps->score = 0;
	ps->SleepTime = 200;//单位毫秒
	ps->status = OK;
}

void CreateFood(pSnake ps)
{
	srand((unsigned int)time(NULL));
	int x = 0, 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("CreateFood()::malloc()");
		return;
	}
	pFood->x = x;
	pFood->y = y;
	ps->pFood = pFood;
	SetPos(x, y);
	wprintf(L"%lc", FOOD);
	
}

void GameStart(pSnake ps)
{
	//设置控制台窗口
	system("mode concols=100 lines=30");
	system("title 贪吃蛇");
	//隐藏光标
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(handle, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(handle, &CursorInfo);

	WelcomeToGame();
	CreateMap();
	InitSnake(ps);
	CreateFood(ps);
}

void PrintHelpInfo()
{
	SetPos(62, 14);
	printf("注意:");
	SetPos(62, 15);
	printf("不能穿墙,不能咬到自己");
	SetPos(62, 16);
	printf("用↑.↓.←.→来控制蛇前进的方向");
	SetPos(62, 17);
	printf("左shift是加速,右shift是减速");
	SetPos(62, 18);
	printf("按ESC键退出,按空格键暂停");
}

void Pause()
{
	while (1)
	{
		Sleep(100);
		if (KEY_PRESS(VK_SPACE))
		{
			SetPos(15, 12);
			break;
		}
	}
}

int NextIsFood(pSnake ps, pSnakeNode pNext)
//pNext指向下一个位置
{
	if (ps->pFood->x == pNext->x && ps->pFood->y == pNext->y)
	{
		return 1;
	}
	else 
		return 0;
}

void EatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	//吃食物,头插一个节点
	pSnakeNode cur = ps->pSnake;//打印蛇
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->score += ps->FoodWeight;//增加分数
	free(ps->pFood);//释放当前的食物
	CreateFood(ps);//创建新的食物
}
void NotEatFood(pSnake ps, pSnakeNode pNext)
{
	pNext->next = ps->pSnake;
	ps->pSnake = pNext;
	pSnakeNode cur = ps->pSnake;//打印蛇
	while (cur->next->next!=NULL)
	{
		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 KillByWall(pSnake ps)
{
	if (ps->pSnake->x == 0 || ps->pSnake->x == 56 ||
		ps->pSnake->y == 0 || ps->pSnake->y == 26)
	{
		ps->status = KILL_BY_WALL;
		return;
	}
}
void KillBySelf(pSnake ps)
{
	pSnakeNode cur = ps->pSnake->next;
	//从第二个节点开始,检测蛇身是否为蛇头的下一个节点
	while (cur)
	{
		if (cur->x == ps->pSnake->x && cur->y == ps->pSnake->y)
		{
			ps->status = KILL_BY_SELF;
			return;
		}
		cur = cur->next;
	}
}
void SnakeMove(pSnake ps)
{
	pSnakeNode pNext = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNext == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	pNext->next = NULL;
	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;
    //注意x坐标一次移动两格
	case LEFT:pNext->x = ps->pSnake->x-2;
		pNext->y = ps->pSnake->y;
		break;
	case RIGHT:pNext->x = ps->pSnake->x+2;
		pNext->y = ps->pSnake->y;
		break;
	}
	if (NextIsFood(ps, pNext))
	{
		EatFood(ps, pNext);
	}
	else
	{
		NotEatFood(ps, pNext);
	}
	KillByWall(ps);
	KillBySelf(ps);
}

void GameRun(pSnake ps)
{
	PrintHelpInfo();
	
	do {
		SetPos(62, 10);
		printf("总分:%5d", ps->score);
		SetPos(62, 11);
		printf("每个食物的分数:%02d", 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_ESCAPE))
		{
			ps->status = ESC;
			break;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			Pause();
		}
		else if (KEY_PRESS(VK_LSHIFT))
		{
			if (ps->SleepTime >= 80)
			{
				ps->SleepTime -= 30;
				ps->FoodWeight += 2;
			}
		}
		else if (KEY_PRESS(VK_RSHIFT))
		{
			if (ps->FoodWeight >2)
			{
				ps->SleepTime += 30;
				ps->FoodWeight -= 2;
			}
		}
		Sleep(ps->SleepTime);
		SnakeMove(ps);
	} while (ps->status == OK);
}

void GameEnd(pSnake ps)
{
	SetPos(15, 12);
	switch(ps->status)
	{
	case ESC:
		printf("已退出游戏...");
		SetPos(38, 27);
		exit(0);
	case KILL_BY_SELF:
		printf("很遗憾,你咬到自己了");
		break;
	case KILL_BY_WALL:
		printf("很遗憾,你撞到墙了");
		break;
	}
	//释放链表资源
	pSnakeNode cur = ps->pSnake;
	pSnakeNode del = NULL;
	while (cur)
	{
		del = cur;
		cur = cur->next;
		free(del);
	}
	ps->pSnake = NULL;
	free(ps->pFood);
	ps->pSnake = NULL;
}

game.c

#define _CRT_SECURE_NO_WARNINGS
#include"Snake.h"

void game()
{
	int x = 0;
start:
	{
		Snake snake = { 0 };
		GameStart(&snake);
		GameRun(&snake);
		GameEnd(&snake);
		SetPos(15, 14);
		printf("输入1重新开始游戏");
		SetPos(15, 15);
		printf("输入0退出游戏");
		SetPos(15, 16);
		printf("输入你的选择:");
		scanf("%d", &x);
		switch (x)
		{
		case 1:
			goto start; 
			break;
		case 0:
			SetPos(15, 17);
			printf("已退出");
			SetPos(38, 27);
			return;
		default:
			SetPos(15, 17);
			printf("输入错误,自动退出");
			SetPos(38, 27);
			return;
		}
	}
}
int main()
{
	setlocale(LC_ALL, "");
	game();
	return 0;
}

  • 21
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值