当前位置: 首页 > article >正文

贪吃蛇实现

1.资料来源

https://learn.microsoft.com/zh-cn/windows/console/getstdhandle

2.前言

简介

贪吃蛇是久负盛名的游戏,和俄罗斯方块、扫雷等游戏位列于经典游戏的行列。
《贪食蛇》中玩家控制一条不断移动的蛇,在屏幕上吃掉出现的食物。每吃掉一个食物,蛇的身体就会变长。游戏的目标是尽可能长时间地生存下去,同时避免蛇头撞到自己的身体或屏幕边缘。游戏最初是像素风格,后来发展出了3D版本和多人对战模式。玩家需要灵活操作,利用策略在有限的空间内避免碰撞,挑战高分。

实现基本功能

  1. 贪吃蛇地图绘制
  2. 蛇吃食物的功能(上下左右方向键控制蛇的动作)
  3. 蛇撞墙死亡
  4. 蛇撞自身死亡
  5. 计算得分
  6. 蛇身加速、减速
  7. 暂停游戏

技术要点

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

WIN32API

Win32 API是Windows操作系统的核心编程接口,用于与操作系统内核直接交互。

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

控制台程序(Console)

平常运行起来的黑框其实就是控制台程序
win + R输入cmd打开控制台窗口
可以使用cmd命令来设置控制台窗口的长宽:设置控制台窗口的大小,30行,100列

mode con cols=100 lines=30

也可以通过命令命令设置控制台窗口的名字

title 贪吃蛇

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

#include <stdlib.h>
//system函数可以执行系统命令
int main()
{
	//设置控制台相关属性
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");
	//暂停
	//getchar();  
	system("pause");
	return 0;
}

3.相关win32API函数

控制台屏幕上的坐标 COORSD

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

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

示例

#include <windows.h>
int main()
{
	COORD pos1 = { 0, 0 };
	COORD pos2 = { 10, 20 };

	system("pause");
	return 0;
}

GetStdHandle 获取句柄

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

HANDLE WINAPI GetStdHandle(
  _In_ DWORD nStdHandle
);

在这里插入图片描述

HANDLE GetStdHandle(DWORD nStdHandle);
typedef void* HANDLE

CONSOLE_CURSOR_INFO 结构

包含有关控制台游标的信息。

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

在这里插入图片描述


GetConsoleCursorInfo 检索游标信息

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

BOOL WINAPI GetConsoleCursorInfo(
  _In_  HANDLE               hConsoleOutput,  //句柄
  _Out_ PCONSOLE_CURSOR_INFO lpConsoleCursorInfo  //指针
);

PCONSOLE_CURSOR_INFO 是指向 CONSOLE_CURSOR_INFO 结构的指针,该结构接收有关
主机游标(光标)的信息。

SetConsoleCursorInfo 设置游标信息

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

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

示例1

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);

	//定义一个光标信息的结构体
	CONSOLE_CURSOR_INFO cursor_info = { 0 };

	//获取和houtput句柄相关的控制台上的光标信息,存储在cursor_info中
	GetConsoleCursorInfo(houtput, &cursor_info);
	printf("%d\n", cursor_info.dwSize);
	system("pause");   //暂停

	//修改光标的占比
	cursor_info.dwSize = 50;

	//设置和houtput句柄相关的控制台上的光标信息
	SetConsoleCursorInfo(houtput, &cursor_info);
	system("pause");
	return 0;
}

SetConsoleCursorPosition 设置控制台光标位置

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

BOOL WINAPI SetConsoleCursorPosition(
  _In_ HANDLE hConsoleOutput,
  _In_ COORD  dwCursorPosition
);

在这里插入图片描述
示例:

void set_pos(short x, short y)
{
	//获得标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位光标的位置
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}
int main()
{
	//直接定位光标位置
	set_pos(10, 20);
	system("pause");
	return 0;
}

GetAsyncKeyState 获取按键情况

确定调用函数时键是向上还是向下,以及上次调用 GetAsyncKeyState 后是否按下了该键。

SHORT GetAsyncKeyState(
  [in] int vKey
);

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

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

参考:虚拟键代码 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/virtual-key-codes


分装宏函数

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
//结果是1表示按过,结果是0表示没按过

示例:

#define KEY_PRESS(vk) ((GetAsyncKeyState(vk)&1) ? 1 : 0)
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");
		else if (KEY_PRESS(0x38))
			printf("8\n");
		else if (KEY_PRESS(0x39))
			printf("9\n");
	}
	return 0;
}

4.贪吃蛇游戏设计与分析

4.1地图

在游戏地图上,打印宽字符。普通的字符是占一个字节的,这类宽字符是占2个字节。
汉字本质上也是宽字符。

这里再简单的讲一下C语言的国际化特性相关的知识,过去C语言并不适合非英语国家(地区)使用。C语言最初假定地址都是单字节的,但是这些假定并不是在世界的任何地方都适用。
后来为了使C语言适应国际化,C语言的标准中不断加入了国际化的支持。比如:加入了宽字符的类型 wchar_t 和宽字符的输入输出函数,加入了 <locale.h> 头文件,其中提供了允许程序员针对特定地区(通常是国家或者说某种特定语言的地理区域)调整程序行为的函数。

4.1.1<locale.h>本地化

<locale.h>提供的函数用于控制C标准库中对于不同的地区会产生不一样行为的部分。
在标准库中,依赖地区的部分有以下几项:

  1. 数字量的格式
  2. 货币量的格式
  3. 字符集
  4. 日期和时间的表示形式

4.1.2类项

通过修改地区,程序可以改变它的行为来适应世界的不同区域。但地区的改变可能会影响库的许多部分,其中一部分可能是我们不希望修改的。所以C语言支持针对不同的类项进行修改。下面的一个宏,指定一个类项:
在这里插入图片描述


参考资料:https://learn.microsoft.com/zh-cn/cpp/text/locales-and-code-pages

https://learn.microsoft.com/zh-cn/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-170

4.1.3 setlocale函数

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

wchar_t *_wsetlocale(
   int category,
   const wchar_t *locale
);

setlocale函数用于修改当前地区,可以针对一个类项修改,也可以针对所有类项。
setlocale的第一个参数可以是前面说明的类项中的一个,那么每次只会影响一个类项,如果第一个参数是LC_ALL,就会影响所有的类项。
C标准给第二个参数仅定义了2种可能取值: “C”(正常模式)和 “”(本地模式)。

在任意程序执行开始,都会隐藏式执行调用:

setlocale(LC_ALL, "C");

调用setlocale函数就可以切换到本地模式,这种模式下程序会适应本地环境。
比如:切换到我们的本地模式后就支持宽字符(汉字)的输出等。

setlocale(LC_ALL, "");

setlocale的返回值是一个字符串指针,表示已经设置好的格式,如果调用失败,则返回空指针NULL。
setlocale()可以用来查询当前的地区,这时第二个参数设为NULL就可以了。


示例:

#include <locale.h>
int main()
{
	char* ret = setlocale(LC_ALL, NULL);
	printf("%s\n", ret);

	ret = setlocale(LC_ALL, "");
	printf("%s\n", ret);
	return 0;
}

参考资料:https://legacy.cplusplus.com/reference/clocale/setlocale/?kw=setlocale

/* setlocale example */
#include <stdio.h>      /* printf */
#include <time.h>       /* time_t, struct tm, time, localtime, strftime */
#include <locale.h>     /* struct lconv, setlocale, localeconv */

int main ()
{
  time_t rawtime;
  struct tm * timeinfo;
  char buffer [80];

  struct lconv * lc;

  time ( &rawtime );
  timeinfo = localtime ( &rawtime );

  int twice=0;

  do {
    printf ("Locale is: %s\n", setlocale(LC_ALL,NULL) );

    strftime (buffer,80,"%c",timeinfo);
    printf ("Date is: %s\n",buffer);

    lc = localeconv ();
    printf ("Currency symbol is: %s\n-\n",lc->currency_symbol);

    setlocale (LC_ALL,"");
  } while (!twice++);

  return 0;
}

4.1.3 宽字符的打印

宽字符的字面量必须叫上前缀L,否则C语言会把字面量当作窄字符类型处理。前缀L在单引号前面,表示宽字符。宽字符的打印使用wprintf,对应wprintf()的占位符为 %lc;在双引号前面,表示宽字符串,对应wprintf()的占位符为 %ls

int main()
{
	//设置本地化
	setlocale(LC_ALL, "");
	//宽字符字面量前加 L
	wchar_t ch1 = L'你';
	wchar_t ch2 = L'好';
	wchar_t ch3 = L'世';
	wchar_t ch4 = L'界';
	printf("%c%c\n", 'a', 'b');
	//格式串前面也要加 L
	wprintf(L"%lc\n", ch1);
	wprintf(L"%lc\n", ch2);
	wprintf(L"%lc\n", ch3);
	wprintf(L"%lc\n", ch4);
	return 0;
}

代码

snake.h

#pragma once

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

#define POS_X 24
#define POS_Y 5
#define WALL L'□'
#define BOOY 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 SetPos(short x, short y);
//游戏初始化
void GameStart(pSnake ps);
//欢迎界面的打印
void WelcomeToGame();
//绘制地图
void CreateMap();
//初始化蛇身
void InitSnake(pSnake ps);
//创建食物
void CreateFood(pSnake ps);
//运行游戏
void GameRun(pSnake ps);
//蛇的移动-走一步
void SnakeMove(pSnake ps);
//判断下一个坐标是否是食物
int NextIsFood(pSnakeNode pn, pSnake ps);
//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps);
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps);
//检查是否撞墙
void KillByWall(pSnake ps);
//检查是否撞到自己
void KillBySelf(pSnake ps);
//结束运行-善后工作
void GameEnd(pSnake ps);

snake.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "snake.h"

//设置光标位置
void SetPos(short x, short y)
{
	//获取标准输出设备的句柄
	HANDLE houtput = NULL;
	houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	//定位光标的位置
	COORD pos = { x, y };
	SetConsoleCursorPosition(houtput, pos);
}

//欢迎界面的打印
void WelcomeToGame()
{
	SetPos(38, 14);
	wprintf(L"欢迎来到贪吃蛇小游戏\n");
	SetPos(40, 20);
	system("pause");
	system("cls");
	SetPos(30, 14);
	wprintf(L"用↑.↓.→.←来控制蛇的移动,按z加速,按x减速\n");
	SetPos(30, 15);
	wprintf(L"加速能够获得更高的分数\n");
	SetPos(40, 20);
	system("pause");
	system("cls");
}

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

//初始化蛇身
void InitSnake(pSnake ps)
{
	int i = 0;
	pSnakeNode cur = NULL;
	//创建蛇身节点
	for (i = 0; i < 5; i++)
	{
		cur = (pSnakeNode)malloc(sizeof(SnakeNode));
		if (cur == NULL)
		{
			perror("InitSnake():malloc()");
			exit(1);
		}
		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", BOOY);
		cur = cur->next;
	}
	//设置贪吃蛇属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;
	ps->_food_weight = 10;
	ps->_sleep_time = 200;//单位是毫秒
	ps->_status = OK;
}

//创建食物
void CreateFood(pSnake ps)
{
	int x = 0;
	int y = 0;

	//生成x是2的倍数
	// x: 2-54
	// y: 1-25
	//x = 2 * (rand() % 27) + 2;
again:
	do
	{
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//x和y的坐标不能和设的身体坐标冲突
	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;
	pFood->next = NULL;

	SetPos(x, y);
	wprintf(L"%lc", FOOD); 
	ps->_pFood = pFood;
}

//1.游戏初始化
void GameStart(pSnake ps)
{
	//0.先设置窗口信息,再光标隐藏
	system("mode con cols=100 lines=30");//设置窗口大小
	system("title 贪吃蛇");        
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);//获取句柄
	CONSOLE_CURSOR_INFO CursorInfo;//光标信息
	GetConsoleCursorInfo(houtput, &CursorInfo);//获取控制台光标信息
	CursorInfo.bVisible = false;//隐藏控制台光标
	SetConsoleCursorInfo(houtput, &CursorInfo);//设置控制台光标状态
	//1.打印环境界面和功能介绍
	WelcomeToGame();
	//2.绘制地图
	CreateMap();
	//3.初始化蛇身
	InitSnake(ps);
	//4.创建食物
	CreateFood(ps);
}


//打印帮助信息
void printHelpInfo()
{
	SetPos(64, 14);
	wprintf(L"%ls", L"不能穿墙,不能咬到自己");
	SetPos(64, 15);
	wprintf(L"%ls", L"用↑.↓.→.←来控制蛇的移动");
	SetPos(64, 16);
	wprintf(L"%ls", L"按z加速,按x减速");
	SetPos(64, 17);
	wprintf(L"%ls", 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)
{
	//是食物返回1,不是食物返回0
	//return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
	if (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y)
		return 1;
	else
		return 0;
}
//下一个位置是食物,吃掉食物
void EatFood(pSnakeNode pn, pSnake ps)
{
	//在CreateFood()里创建了pFood和SnakeMove()里创建了pNextNode两个食物节点,需要连接一个,释放一个
	//头插法
	ps->_pFood->next = ps->_pSnake;
	ps->_pSnake = ps->_pFood;
	//释放下一个位置的节点
	free(pn);
	pn = NULL;
	//打印
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BOOY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;
	//重新创建食物
	CreateFood(ps);
}
//下一个位置不是食物
void NoFood(pSnakeNode pn, pSnake ps)
{
	//头插法
	pn->next = ps->_pSnake;
	ps->_pSnake = pn;

	pSnakeNode cur = ps->_pSnake;
	while (cur->next->next != NULL)
	{
		SetPos(cur->x, cur->y);
		wprintf(L"%lc", BOOY);
		cur = cur->next;
	}
	//最后一个节点要打印空白字符(两个空格)覆盖原来的蛇身节点BOOY
	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;
	}
}
//检查是否撞到自己
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;
			break;
		}
		cur = cur->next;
	}
}
//蛇的移动-走一步
void SnakeMove(pSnake ps)
{
	//创建一个节点,表示蛇即将到的下一个节点
	pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));
	if (pNextNode == NULL)
	{
		perror("SnakeMove()::malloc()");
		return;
	}
	switch (ps->_dir)
	{
	case UP:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y - 1;
		break;
	case DOWN:
		pNextNode->x = ps->_pSnake->x;
		pNextNode->y = ps->_pSnake->y + 1;
		break;
	case LEFT:
		pNextNode->x = ps->_pSnake->x - 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	case RIGHT:
		pNextNode->x = ps->_pSnake->x + 2;
		pNextNode->y = ps->_pSnake->y;
		break;
	}
	//检查下一个坐标处是否是食物
	if (NextIsFood(pNextNode, ps))
	{
		EatFood(pNextNode, ps);
	}
	else
	{
		NoFood(pNextNode, ps);
	}
	//检查是否撞墙
	KillByWall(ps);
	//检查是否撞到自己
	KillBySelf(ps);
}

//2.运行游戏
void GameRun(pSnake ps)
{
	//打印帮助信息
	printHelpInfo();
	do
	{
		//打印总分数和食物的分值
		SetPos(64, 10);
		printf("总分数:%d\n", ps->_score);
		SetPos(64, 11);
		printf("当前食物的分数:%2d\n", 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_LEFT) && ps->_dir != RIGHT)
			ps->_dir = LEFT;
		else if (KEY_PRESS(VK_RIGHT) && ps->_dir != LEFT)
			ps->_dir = RIGHT;
		else if (KEY_PRESS(VK_SPACE))
		{
			//暂停
			Pause();
		}
		else if (KEY_PRESS(VK_ESCAPE))
		{
			//正常退出游戏
			ps->_status = END_NORMAL;
		}
		else if (KEY_PRESS(0x5A))
		{
			//加速
			if (ps->_sleep_time > 80)//分四档
			{
				ps->_sleep_time -= 30;
				ps->_food_weight += 2;
			}
		}
		else if (KEY_PRESS(0x58))
		{
			//减速
			if (ps->_food_weight > 2)
			{
				ps->_sleep_time += 30;
				ps->_food_weight -= 2;
			}
		}

		//蛇的移动-走一步
		SnakeMove(ps);
		Sleep(ps->_sleep_time);
	} while (ps->_status == OK);
}



//3.结束运行-善后工作
void GameEnd(pSnake ps)
{
	SetPos(24, 12);
	switch (ps->_status)
	{
	case END_NORMAL:
		printf("正常退出游戏\n");
		break;
	case KILL_BY_WALL:
		printf("撞到墙上,游戏结束\n");
		break;
	case KILL_BY_SELF:
		printf("撞到自己,游戏结束\n");
		break;
	}
	//释放蛇身链表
	pSnakeNode cur = ps->_pSnake;
	while (cur)
	{
		pSnakeNode del = cur;
		cur = cur->next;
		free(del);
	}
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "snake.h"

//游戏的测试逻辑
void test()
{
	int ch = 0;
	do
	{
		system("cls");//清屏
		//创建贪吃蛇
		Snake snake = { 0 };
		GameStart(&snake);
		//运行游戏
		GameRun(&snake);
		//结束运行-善后工作
		GameEnd(&snake);
		SetPos(20, 15);
		printf("再来一局吗?(Y/N)");
		ch = getchar();
		while (getchar() != '\n');//清理\n
	} while (ch == 'Y' || ch == 'y');
	SetPos(0, 27);
}

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

http://www.kler.cn/a/529541.html

相关文章:

  • 标准IO与文件IO 进程与线程
  • JavaScript面向对象编程:Prototype与Class的对比详解
  • 完全卸载mysql server步骤
  • 深入解析“legit”的地道用法——从俚语到正式表达:Sam Altman用来形容DeepSeek: legit invigorating(真的令人振奋)
  • hexo部署到github page时,hexo d后page里面绑定的个人域名消失的问题
  • 省级-新质生产力数据(2010-2022年)-社科数据
  • SpringBoot的配置(配置文件、加载顺序、配置原理)
  • UE5 蓝图学习计划 - Day 11:材质与特效
  • 大模型训练(5):Zero Redundancy Optimizer(ZeRO零冗余优化器)
  • 操作系统和中间件的信息收集
  • 踏入编程世界的第一个博客
  • 在 Ubuntu 中使用 Conda 创建和管理虚拟环境
  • 使用朴素贝叶斯对散点数据进行分类
  • 5分钟在本地PC上使用VLLM快速启动DeepSeek-R1-Distill-Qwen-32B
  • Github 2025-02-02 php开源项目日报 Top10
  • Windows程序设计11:文件的查找与遍历
  • PyTorch数据建模
  • 【Leetcode 热题 100】5. 最长回文子串
  • 91,【7】 攻防世界 web fileclude
  • 【深度解析】DeepSeek-R1的五大隐藏提示词
  • LeetCode 15.三数之和
  • 保姆级教程:利用Ollama与Open-WebUI本地部署 DeedSeek-R1大模型
  • C++11—右值引用
  • AI技术在SEO关键词优化中的应用策略与前景展望
  • 深度解析:网站快速收录与网站安全性的关系
  • 物业管理收费系统如何提升收费效率与业主满意度的全新实践