16 贪吃蛇

目录

  1. 游戏背景
  2. 游戏效果展示
  3. 基本功能
  4. 技术要点
  5. WIN32 API介绍
  6. 设计与分析
  7. 实现
  8. 参考代码

1. 游戏背景

贪吃蛇是久负盛名的游戏,是一款经典游戏

2. 效果展示

16 贪吃蛇_第1张图片

3. 基本功能

使用c语言在windows环境的控制台模拟实现小游戏贪吃蛇
基本的功能:

  • 地图绘制
  • 吃食物
  • 上下左右移动
  • 撞墙、撞自己死亡
  • 计算得分
  • 加速、减速
  • 暂停游戏

4. 要点

c语言函数、枚举、结构体、动态内存管理、预处理指令、链表。Win32API等

5. Win32API介绍

5.1 Win32API

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

5.2 控制台程序

平常的黑框程序就是控制台程序

可以使用cmd命令来控制控制台窗口的长宽: 设置控制台窗口的大小,30行,100列

mode con cols=100 lines=30

也可以设置控制台窗口的名字

title 贪吃蛇

这些能在控制台窗口执行的命令,也可以调用c语言函数system执行

#include 
int main()
{
 //设置控制台窗⼝的⻓宽:设置控制台窗⼝的⼤⼩,30⾏,100列
 system("mode con cols=100 lines=30");
 //设置cmd窗⼝名称
 system("title 贪吃蛇"); 
 return 0;
}

5.3 控制台屏幕上的坐标COORD

COORD是windows api定义的一个结构体,表示一个字符在控制台屏幕上的坐标

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

给坐标赋值:
COORD pos = { 10, 15 };

5.4 GetStdHandle

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

HANDLE GetStdHandle (DWORD nStdHandle) ;

HANDLE hQutput = NULL;

//获取标准输出的句柄(用来标识不同设备的数值)
hQutput = GetStdHandle(STD_OUTPUT_HANDLE);

5.5 GetConsoleCursorInfo

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

BOOL WINAPI GetConsoleCursorInfo(
HANDLE hConsoleOutput,
PCONSOLE_CURSOR_INFO lpConsoleCursorInfo
)

实例:

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

CONSOLE_CURSOR_INFO CursorInfo;
//获取控制台光标信息
GetConsoleCursorInfo(hOutput, &CursorInfo);

CONSOLE_CURSOR_INFO

这个结构体,包含控制台光标的信息

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

  • dwSize,由光标填充的字符单元格的百分比。此值介于1到100之间。光标外观会变化,范围从完全填充单元格到单元底部的水平线条
  • bVisble,游标的可见性,可见设置为true

CursorInfo.bVisible = false; //隐藏控制台光标

5.7 SetConsoleCursorPosition

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

BOOL WINAPI SetConsoleCursorPosition(
HANDLE hConsoleOutput,
COORD pos
);

COORD pos = { 10, 5 };
HANDLE hOutput = NULL;
//获取标准输出的句柄(用来标识不同设备的数值)
hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
//设置标准输出上光标的位置pos
SetConsoleCursorPosition(hOutput, pos);

封装一个设置光标位置的函数

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

5.8 GetAsyncKeyState

获取按键情况,GetAsyncKeyState的函数原型如下:

SHORT GetAsyncKeyState(
int vKey;
);

将键盘上每个键的虚拟键值传递给函数,函数通过返回值来分辨按键的状态

GetAsyncKeyState的返回值是short类型,在上一次调用GetAsyncKeyState函数后,如果返回的16位的short数据中,最高位是1,说明按键的状态是按下,如果最高位是0,说明按键的状态是抬起;如果最低位被置1说明,该按键被按过,否则是0

如果判断一个键是否被按过,可以检测这个函数的返回值的最低位是否为1

GetAsyncKeyState(VK) & 0x1 == 1
//宏定义
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? ! : 0)

6. 设计与分析

6.1 地图

如下图
16 贪吃蛇_第2张图片16 贪吃蛇_第3张图片16 贪吃蛇_第4张图片

想在控制台指定位置输出信息,必须知道该位置的坐标

如下图所示,横向的是x轴,从左右次依次增长,纵向是y轴,从上到下依次增长

16 贪吃蛇_第5张图片
在游戏地图上,打印墙体使用宽字符:□,蛇用宽字符:● 食物用宽字符:★
普通的字符是占一个字节的,这类宽字符是占用2个字节

c语言字符默认采用ASCII编码,ASCII字符集采用的是单字节编码,且只使用了单字节中的低7位,最高位是没有使用的,可表示为0xxxxxxxx;可以看到,ASCII字符集共包含了128个字符,在英语国家中,128个字符基本够用,但是在其他国家语言中,就无法表示。一些欧洲国家用最高位编入新的符号,比如,法语é的编码是130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。但是,又出现了新的问题。不同的国家有不同的字母,哪怕都是用256个符号的编码方式,同一个码表示的字符确不一样,不管怎么样,0-127表示的符号是一样的,不一样的只是128-255这一段

至于亚洲国家的文字,需要的符号就更多了,汉字就多达10万左右,一个字节肯定不够,就必须使用多个字节表达一个符号。简体中文常见的编码方式是GB2312,使用后两个字节表示一个汉字,理论上最多可以表示256×256=65536个符号

后来为了使c语言国际化,不断加入了国家,用宽字节类型wchar_t和宽字节的输入和输出函数,加入和头文件,提供了设置特定的地区(通常是国家或者特定语言的地理区域)调整程序行为的函数

6.1.1 本地化

提供的函数用于控制c标准库中对于不同的地区会产生不一样行为的部分

在标准中,依赖地区的部分有以下几项:

  • 数字量的格式
  • 货币量的格式
  • 字符集
  • 日期和时间的表现形式

6.1.2 类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分不希望修改,支持堆不同的类项修改,下面的宏指定类项

  • LC_COLLATE
  • LC_CTYPE
  • LC_NUMERIC
  • LC_TIME
  • LC_ALL -针对所有类项修改

6.1.3 setlocale函数

char* setlocale (int category, cosnt char* locale) ;

setlocale 函数用来修改当前地区,可以针对一个类项,也可以针对所有类项

setlocale 的第一个参数可以是前面说明的类型中的一个,那么每次只会影响一个类型,如果第一个参数是LC_ALL,就会影响所有的类项

c标准给第二个参数定义了2种可能取值: “C"和” ",任意程序执行开始,都会隐藏式执行调用

setlocale(LC_ALL, “C” ) ;

当地区设置为C,库函数按正常方式执行,小数点是一个点

想改变地区,只能显示调用函数,用“ ”作为第2个参数,调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。比如:切换到我们的本地模式后就支持宽字符的输出

setlocale (LC_ALL, “”);

6.1.4 宽字符的打印

#include 
#include 

int main()
{
	setlocale(LC_ALL, "");
	wchar_t ch1 = L'●';
	wchar_t ch2 = L'★';

	printf("%c%c", 'a', 'b');
	printf("%c%c\n", 'a', 'b');
	wprintf(L"%c",ch1);
	wprintf(L"%c",ch2);
}

16 贪吃蛇_第6张图片
从结果看出,一个普通字符占一个位置,汉字字符占两个位置,所以打印宽字符地图,需要计算好坐标

16 贪吃蛇_第7张图片

从左上角的00点开始,和控制台边框空出一段间隔,x从2开始打印,y从1开始打印,宽字符一个字符占两列,地图19行44列,从上面的起点算,x从2-45,y从1-19,四周最边的一面为墙

16 贪吃蛇_第8张图片

6.2 蛇身和食物

初始化状态,假设蛇的长度是3,蛇身的每个节点●,在固定的坐标处,y在第一列,横坐标放在地图的中间。需要注意,每个节点的x坐标必须是2的倍数,否则就会和墙体对不齐

食物是在墙体内随机生成一个坐标,x坐标也必须是2的倍数对齐,同时不能和蛇身重合,打印★

6.3 数据结构设计

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

首先得有一个坐标的结构体

struct Point
{
	int x;
	int y;
};

蛇节点的结构体

//蛇身结点
typedef struct _SnakeNode
{
	struct Point spoint;  //坐标
	struct _SnakeNode* next;
}SnakeNode, * pNode;

有了蛇身的结构,还需要管理整条蛇的属性信息,比如速度、蛇头等

//蛇属性
typedef struct _Snake
{
	pNode phead;   //蛇头
	int score;     //分数
	int weight;    //权重
	int speed;     //移动间隔
	enum Dorection dre;  //方向
	enum STATUS sta; //状态
}* pSnake;

蛇的方向和状态用枚举出所有可能得结果

//方向
enum Dorection
{
	UP,
	DOWN,
	LEFT,
	RIGHT,
};
//状态
enum STATUS
{
	OK = 1,
	PAUSE,
	KILL_WALL,
	KILL_SELF,
	ESC,
};

6.4 游戏流程设计

16 贪吃蛇_第9张图片

7. 实现

7.1 主逻辑

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include "Sanke.h"

int main()
{
	int ch = 0;
	do
	{
		//创建蛇
		struct _Snake snake;
		//创建食物
		struct Point food;
		//初始化
		GameInit(&snake, &food);
		//游戏循环
		GameRun(&snake, &food);
		//游戏结束
		GameEnd(&snake);

		MoveCursor(16, 11);
		printf("再来一局吗?(Y/N)");
		while (_kbhit())
		{
			getch();
		}
		ch = getchar();
		//读取换行
		getchar();
	} while (ch == 'Y' || ch == 'y');

	MoveCursor(0, 21);
	return 0;
}

7.2 初始化信息

void GameInit(pSnake snake, struct Point* food)
{
	//控制台设置
	system("mode con cols=82 lines=23");
	system("title 贪吃蛇");
	srand(time(0));
	HideCursor();
	菜单
	Menu();

7.2.1 打印菜单界面

开始之前,做一些功能提醒

void Menu()
{
	//欢迎
	MoveCursor(30, 10);
	puts("欢迎来到贪吃蛇游戏");
	MoveCursor(31, 18);
	system("pause");
	system("cls");
	//功能介绍
	MoveCursor(15, 8);
	puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");
	MoveCursor(18, 10);
	printf("加速能得到更高的分数");
	MoveCursor(31, 18);
	system("pause");
	system("cls");
}

7.2.2 创建地图

将墙打印出来,宽字节打印要用wprintf,字符串前面用L

关键是算好坐标

#define WALL L’□’
//地图大小
#define ROW 19
#define COL 44

void DrawMap()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);
	//四周
	for (int i = 0; i < COL; i += 2)
	{
		MoveCursor(2 + i, 1);
		wprintf(L"%lc", WALL);
		MoveCursor(2 + i, 1 + ROW);
		wprintf(L"%lc", WALL);
	}

	for (int i = 0; i < ROW; i++)
	{
		MoveCursor(2, 2 + i);
		wprintf(L"%lc", WALL);
		MoveCursor(COL, 2 + i);
		wprintf(L"%lc", WALL);
	}
	printf("\r\n");
}

16 贪吃蛇_第10张图片

7.2.3 初始化蛇身

蛇的初始长度宏定义,每结对应一个节点,每个节点都有自己的坐标,将所有节点用蛇头节点管理,创建完后打印。设置蛇的速度,方向,分数等

蛇打印的宽字符

#define SNAKE L’●’

//初始化蛇
snake->phead = NULL;
for (int i = 0; i < LENGTH; i++)
{
	pNode newnode = (pNode)malloc(sizeof(SnakeNode));
	newnode->next = NULL;
	newnode->spoint.x = SNAKE_X + i * 2;
	newnode->spoint.y = SNAKE_Y;
	//头插法
	if (snake->phead == NULL)
	{
		snake->phead = newnode;
	}
	else
	{
		newnode->next = snake->phead;
		snake->phead = newnode;
	}
	
}
snake->score = 0;
snake->weight = 10;
snake->speed = 500;
snake->dre = RIGHT;
snake->sta = OK;

16 贪吃蛇_第11张图片

7.2.4 创建第一个食物

随机生成食物的坐标

  • x坐标必须是2的倍数
  • 食物的坐标不能和蛇身每个节点的坐标重复

创建食物对象,打印食物

食物打印的宽字符

#define FOOD L’★’

首先食物的坐标不能生成在墙上,所以x坐标从4-42,就是先生成0-38的随机数,再加4,y坐标就是2-18,先生成0-16的随机数再加2。再判断是不是在蛇身上,下面是食物坐标的生成

void FoodPoint(struct Point* food , pSnake snake)
{
	pNode cur = snake->phead;
	int flag = 0;
	do
	{
		food->x = rand() % (COL - 5) + 4;
		food->y = rand() % (ROW - 2) + 2;
		flag = 1;
		while (cur != NULL)
		{
			if (food->x == cur->spoint.x && food->y == cur->spoint.y)
			{
				flag = 0;
				break;
			}
			cur = cur->next;
		}
	} while (food->x % 2 != 0 && flag == 1);
		
}

16 贪吃蛇_第12张图片

7.3 游戏运行

右侧打印帮助信息
根据游戏状态检查是否继续,如果状态OK则继续,否则游戏结束

如果继续就要检测按键情况,确定蛇下一步的方向和坐标,是否加速减速暂停退出等,就可以移动蛇了

void GameRun(pSnake snake, struct Point* food)
{

	//打印帮助信息
	PrintHelp();
	DrawMap();
	DrawSnake(snake);
	DrawFood(food);
	//循环
	do
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

		//打印分数
		MoveCursor(52, 6);
		printf("总分:%3d\n", snake->score);
		MoveCursor(51, 8);
		printf("食物的分值:%4d\n", snake->weight);

		//检测按键
		//上 下 左 右 空格 F3
		if (KEY_PRESS(0x57) && snake->dre != DOWN)
		{
			snake->dre = UP;
		}
		else if (KEY_PRESS(0x53) && snake->dre != UP)
		{
			snake->dre = DOWN;
		}
		else if (KEY_PRESS(0x41) && snake->dre != RIGHT)
		{
			snake->dre = LEFT;
		}
		else if (KEY_PRESS(0x44) && snake->dre != LEFT)
		{
			snake->dre = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			snake->sta = ESC;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			snake->sta = PAUSE;
			//暂停
			GamePause(snake);
		}
		else if (KEY_PRESS(VK_F3))
		{
			if (snake->speed >= 80)
			{
				snake->speed -= 100;
				snake->weight += 2;
			}
	
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (snake->weight >= 2)
			{
				snake->speed += 100;
				snake->weight -= 2;
			}

		}

		//移动

		MoveSnake(snake, food);
		Sleep(snake->speed);
	} while (snake->sta == OK);
}

7.3.1 KEY_PRESS

检测按键,定义为宏

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

7.3.2 PrintHelpInfo

void PrintHelp()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

	MoveCursor(49, 12);
	printf("1.不能穿墙,不能咬到自己");
	MoveCursor(49, 14);
	printf("2.用 w.s.a.d 来控制蛇的移动");
	MoveCursor(49, 16);
	printf("3.F3是加速,F4是减速");

}

16 贪吃蛇_第13张图片

7.3.3 移动蛇

先创建下一个节点,根据移动的方向和蛇头的坐标,设置新蛇身的坐标

确定之后就要分下一个位置是不是食物,是食物就吃掉,刷新分数,重新生成食物不是就移动

将新节点连接蛇结构
移动后判断是否撞墙或撞自身,影响游戏状态结束游戏

void GameRun(pSnake snake, struct Point* food)
{

	//打印帮助信息
	PrintHelp();
	DrawMap();
	DrawSnake(snake);
	DrawFood(food);
	//循环
	do
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

		//打印分数
		MoveCursor(52, 6);
		printf("总分:%3d\n", snake->score);
		MoveCursor(51, 8);
		printf("食物的分值:%4d\n", snake->weight);

		//检测按键
		//上 下 左 右 空格 F3
		if (KEY_PRESS(0x57) && snake->dre != DOWN)
		{
			snake->dre = UP;
		}
		else if (KEY_PRESS(0x53) && snake->dre != UP)
		{
			snake->dre = DOWN;
		}
		else if (KEY_PRESS(0x41) && snake->dre != RIGHT)
		{
			snake->dre = LEFT;
		}
		else if (KEY_PRESS(0x44) && snake->dre != LEFT)
		{
			snake->dre = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			snake->sta = ESC;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			snake->sta = PAUSE;
			//暂停
			GamePause(snake);
		}
		else if (KEY_PRESS(VK_F3))
		{
			if (snake->speed >= 80)
			{
				snake->speed -= 100;
				snake->weight += 2;
			}
	
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (snake->weight >= 2)
			{
				snake->speed += 100;
				snake->weight -= 2;
			}

		}

		//移动

		MoveSnake(snake, food);
		Sleep(snake->speed);
	} while (snake->sta == OK);
}

void MoveSnake(pSnake snake, struct Point* food)
{
	pNode newnode = (pNode)malloc(sizeof(SnakeNode));
	newnode->next = NULL;

	switch (snake->dre)
	{
	case UP:
		newnode->spoint.x = snake->phead->spoint.x;
		newnode->spoint.y = snake->phead->spoint.y - 1;
		break;
	case DOWN:
		newnode->spoint.x = snake->phead->spoint.x;
		newnode->spoint.y = snake->phead->spoint.y + 1;
		break;
	case LEFT:
		newnode->spoint.x = snake->phead->spoint.x - 2;
		newnode->spoint.y = snake->phead->spoint.y;
		break;
	case RIGHT:
		newnode->spoint.x = snake->phead->spoint.x + 2;
		newnode->spoint.y = snake->phead->spoint.y;
		break;
	}

	//下一个坐标是不是食物
	if (newnode->spoint.x == food->x && newnode->spoint.y == food->y)
	{
		EatFood(snake, food);
	}
	else
	{
		//释放尾结点
		pNode cur = snake->phead;
		while(cur->next->next != NULL)
		{
			cur = cur->next;
		}
		//尾结点处打印空
		MoveCursor(cur->next->spoint.x, cur->next->spoint.y);
		printf("  ");
		free(cur->next);
		cur->next = NULL;
	}

	newnode->next = snake->phead;
	snake->phead = newnode;
	//检测撞
	KillWall(snake);
	KillSelf(snake);
	DrawSnake(snake);

}

吃掉食物更新分数,重新生成食物

void EatFood(pSnake snake, struct Point* food)
{
	FoodPoint(food, snake);
	snake->score += snake->weight;
	DrawFood(food);
}

7.3.4 撞墙

void KillWall(pSnake snake)
{
	int x = snake->phead->spoint.x;
	int y = snake->phead->spoint.y;
	if (x == 2 || x == 44 || y == 1 || y == 19)
	{
		snake->sta = KILL_WALL;
		return;
	}
}

16 贪吃蛇_第14张图片

7.3.5 撞自己

void KillSelf(pSnake snake)
{
	pNode cur = snake->phead->next;
	while (cur != NULL)
	{
		if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y)
		{
			snake->sta = KILL_SELF;
			return;
		}
		cur = cur->next;
	}
}

16 贪吃蛇_第15张图片

7.4 游戏结束

当游戏状态不再是OK或PAUSE的时候,游戏结束,告知结束的原因,并释放蛇身节点

void GameEnd(pSnake snake)
{
	MoveCursor(18, 9);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

	switch (snake->sta)
	{
	case ESC:
		printf("退出游戏");
		break;
	case KILL_WALL:
		printf("撞墙死亡");
		break;
	case KILL_SELF:
		printf("撞自己死亡");
		break;
	}

	//释放资源
	pNode cur = snake->phead;
	while (cur)
	{
		pNode del = cur;
		cur = cur->next;
		free(del);
	}
}

8. 参考代码

源.c

#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include 
#include "Sanke.h"

int main()
{
	int ch = 0;
	do
	{
		//创建蛇
		struct _Snake snake;
		//创建食物
		struct Point food;
		//初始化
		GameInit(&snake, &food);
		//游戏循环
		GameRun(&snake, &food);
		//游戏结束
		GameEnd(&snake);

		MoveCursor(16, 11);
		printf("再来一局吗?(Y/N)");
		while (_kbhit())
		{
			getch();
		}
		ch = getchar();
		//读取换行
		getchar();
	} while (ch == 'Y' || ch == 'y');

	MoveCursor(0, 21);
	return 0;
}

Sanke.h

#pragma once

//地图大小
#define ROW 19
#define COL 44

//各种物体形状
#define WALL L'□'
#define SNAKE L'●'
#define FOOD L'★'

//蛇的初始长度
#define LENGTH 3
//蛇初始位置
#define SNAKE_X COL / 2 + 2
#define SNAKE_Y 4

//按键
#define KEY_PRESS(VK)  ( (GetAsyncKeyState(VK) & 0x1) ? 1 : 0 )
//方向
enum Dorection
{
	UP,
	DOWN,
	LEFT,
	RIGHT,
};
//状态
enum STATUS
{
	OK = 1,
	PAUSE,
	KILL_WALL,
	KILL_SELF,
	ESC,
};
struct Point
{
	int x;
	int y;
};
//蛇身结点
typedef struct _SnakeNode
{
	struct Point spoint;  //坐标
	struct _SnakeNode* next;
}SnakeNode, * pNode;

//蛇属性
typedef struct _Snake
{
	pNode phead;   //蛇头
	int score;     //分数
	int weight;    //权重
	int speed;     //移动间隔
	enum Dorection dre;  //方向
	enum STATUS sta; //状态
}* pSnake;

//隐藏光标
void HideCursor();
//移动光标
void MoveCursor(int x, int y);
//菜单界面
void Menu();
//食物坐标随机检测
void FoodPoint(struct Point* food, pSnake snake);
//游戏初始化
void GameInit(pSnake snake, struct Point* food);
//绘制地图
void DrawMap();
//绘制蛇
void DrawSnake(pSnake snake);
//绘制食物
void DrawFood();
//游戏暂停
void GamePause(pSnake snake);
//游戏循环
void GameRun(pSnake snake, struct Point* food);
//移动
void MoveSnake(pSnake snake, struct Point* food);
//吃食物
void EatFood(pSnake snake, struct Point* food);
//打印帮助信息
void PrintHelp();
//检测撞墙
void KillWall(pSnake snake);
//检测撞自己
void KillSelf(pSnake snake);
//游戏结束释放资源
void GameEnd(pSnake snake);

Sanke.c

#include "Sanke.h"
#include 
#include 
#include 
#include 
#include 

void HideCursor()
{
	CONSOLE_CURSOR_INFO info;
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	GetConsoleCursorInfo(handle, &info);
	info.bVisible = false;
	SetConsoleCursorInfo(handle, &info);
}

void MoveCursor(int x, int y)
{
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord = { x , y };
	SetConsoleCursorPosition(handle, coord);
}

void Menu()
{
	//欢迎
	MoveCursor(30, 10);
	puts("欢迎来到贪吃蛇游戏");
	MoveCursor(31, 18);
	system("pause");
	system("cls");
	//功能介绍
	MoveCursor(15, 8);
	puts("用 w . s . a . d 来控制蛇的移动,F3是加速,F4是减速");
	MoveCursor(18, 10);
	printf("加速能得到更高的分数");
	MoveCursor(31, 18);
	system("pause");
	system("cls");
}

void FoodPoint(struct Point* food , pSnake snake)
{
	pNode cur = snake->phead;
	int flag = 0;
	do
	{
		food->x = rand() % (COL - 5) + 4;
		food->y = rand() % (ROW - 2) + 2;
		flag = 1;
		while (cur != NULL)
		{
			if (food->x == cur->spoint.x && food->y == cur->spoint.y)
			{
				flag = 0;
				break;
			}
			cur = cur->next;
		}
	} while (food->x % 2 != 0 && flag == 1);
		
}

void GameInit(pSnake snake, struct Point* food)
{
	//控制台设置
	system("mode con cols=82 lines=23");
	system("title 贪吃蛇");
	srand(time(0));
	HideCursor();
	菜单
	Menu();
	//初始化蛇
	snake->phead = NULL;
	for (int i = 0; i < LENGTH; i++)
	{
		pNode newnode = (pNode)malloc(sizeof(SnakeNode));
		newnode->next = NULL;
		newnode->spoint.x = SNAKE_X + i * 2;
		newnode->spoint.y = SNAKE_Y;
		//头插法
		if (snake->phead == NULL)
		{
			snake->phead = newnode;
		}
		else
		{
			newnode->next = snake->phead;
			snake->phead = newnode;
		}
		
	}
	snake->score = 0;
	snake->weight = 10;
	snake->speed = 500;
	snake->dre = RIGHT;
	snake->sta = OK;

	//初始化食物
	FoodPoint(food, snake);
	//设置地区
	setlocale(LC_ALL, "");
}

void DrawMap()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x6);
	//四周
	for (int i = 0; i < COL; i += 2)
	{
		MoveCursor(2 + i, 1);
		wprintf(L"%lc", WALL);
		MoveCursor(2 + i, 1 + ROW);
		wprintf(L"%lc", WALL);
	}

	for (int i = 0; i < ROW; i++)
	{
		MoveCursor(2, 2 + i);
		wprintf(L"%lc", WALL);
		MoveCursor(COL, 2 + i);
		wprintf(L"%lc", WALL);
	}
	printf("\r\n");
}

void DrawSnake(pSnake snake)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN);
	pNode cur = snake->phead;
	while (cur != NULL)
	{
		MoveCursor(cur->spoint.x, cur->spoint.y);
		wprintf(L"%lc", SNAKE);  
		cur = cur->next;
	}
}

void DrawFood(struct Point* food)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED);

	MoveCursor(food->x, food->y);
	wprintf(L"%lc", FOOD);
}

void GamePause(pSnake snake)
{
	while (snake->sta == PAUSE)
	{
		if (KEY_PRESS(VK_SPACE))
		{
			snake->sta = OK;
		}
		Sleep(100);
	}
}

void GameRun(pSnake snake, struct Point* food)
{

	//打印帮助信息
	PrintHelp();
	DrawMap();
	DrawSnake(snake);
	DrawFood(food);
	//循环
	do
	{
		SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

		//打印分数
		MoveCursor(52, 6);
		printf("总分:%3d\n", snake->score);
		MoveCursor(51, 8);
		printf("食物的分值:%4d\n", snake->weight);

		//检测按键
		//上 下 左 右 空格 F3
		if (KEY_PRESS(0x57) && snake->dre != DOWN)
		{
			snake->dre = UP;
		}
		else if (KEY_PRESS(0x53) && snake->dre != UP)
		{
			snake->dre = DOWN;
		}
		else if (KEY_PRESS(0x41) && snake->dre != RIGHT)
		{
			snake->dre = LEFT;
		}
		else if (KEY_PRESS(0x44) && snake->dre != LEFT)
		{
			snake->dre = RIGHT;
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			snake->sta = ESC;
		}
		else if (KEY_PRESS(VK_SPACE))
		{
			snake->sta = PAUSE;
			//暂停
			GamePause(snake);
		}
		else if (KEY_PRESS(VK_F3))
		{
			if (snake->speed >= 80)
			{
				snake->speed -= 100;
				snake->weight += 2;
			}
	
		}
		else if (KEY_PRESS(VK_F4))
		{
			if (snake->weight >= 2)
			{
				snake->speed += 100;
				snake->weight -= 2;
			}

		}

		//移动

		MoveSnake(snake, food);
		Sleep(snake->speed);
	} while (snake->sta == OK);
}

void MoveSnake(pSnake snake, struct Point* food)
{
	pNode newnode = (pNode)malloc(sizeof(SnakeNode));
	newnode->next = NULL;

	switch (snake->dre)
	{
	case UP:
		newnode->spoint.x = snake->phead->spoint.x;
		newnode->spoint.y = snake->phead->spoint.y - 1;
		break;
	case DOWN:
		newnode->spoint.x = snake->phead->spoint.x;
		newnode->spoint.y = snake->phead->spoint.y + 1;
		break;
	case LEFT:
		newnode->spoint.x = snake->phead->spoint.x - 2;
		newnode->spoint.y = snake->phead->spoint.y;
		break;
	case RIGHT:
		newnode->spoint.x = snake->phead->spoint.x + 2;
		newnode->spoint.y = snake->phead->spoint.y;
		break;
	}

	//下一个坐标是不是食物
	if (newnode->spoint.x == food->x && newnode->spoint.y == food->y)
	{
		EatFood(snake, food);
	}
	else
	{
		//释放尾结点
		pNode cur = snake->phead;
		while(cur->next->next != NULL)
		{
			cur = cur->next;
		}
		//尾结点处打印空
		MoveCursor(cur->next->spoint.x, cur->next->spoint.y);
		printf("  ");
		free(cur->next);
		cur->next = NULL;
	}

	newnode->next = snake->phead;
	snake->phead = newnode;
	//检测撞
	KillWall(snake);
	KillSelf(snake);
	DrawSnake(snake);

}

void EatFood(pSnake snake, struct Point* food)
{
	FoodPoint(food, snake);
	snake->score += snake->weight;
	DrawFood(food);
}

void PrintHelp()
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

	MoveCursor(49, 12);
	printf("1.不能穿墙,不能咬到自己");
	MoveCursor(49, 14);
	printf("2.用 w.s.a.d 来控制蛇的移动");
	MoveCursor(49, 16);
	printf("3.F3是加速,F4是减速");

}

void KillWall(pSnake snake)
{
	int x = snake->phead->spoint.x;
	int y = snake->phead->spoint.y;
	if (x == 2 || x == 44 || y == 1 || y == 19)
	{
		snake->sta = KILL_WALL;
		return;
	}
}

void KillSelf(pSnake snake)
{
	pNode cur = snake->phead->next;
	while (cur != NULL)
	{
		if (cur->spoint.x == snake->phead->spoint.x && cur->spoint.y == snake->phead->spoint.y)
		{
			snake->sta = KILL_SELF;
			return;
		}
		cur = cur->next;
	}
}

void GameEnd(pSnake snake)
{
	MoveCursor(18, 9);
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x07);

	switch (snake->sta)
	{
	case ESC:
		printf("退出游戏");
		break;
	case KILL_WALL:
		printf("撞墙死亡");
		break;
	case KILL_SELF:
		printf("撞自己死亡");
		break;
	}

	//释放资源
	pNode cur = snake->phead;
	while (cur)
	{
		pNode del = cur;
		cur = cur->next;
		free(del);
	}
}


参考:汉字字符集编码查询;中⽂字符集编码:GB2312、BIG5、GBK、GB18030、Unicode

你可能感兴趣的:(02,数据结构,初阶,c语言,算法,数据结构,贪吃蛇,游戏)