C语言实战:贪吃蛇(万字详解)

news2025/1/17 20:24:12

💡目录

效果图

界面设计思路

1. 基本布局

2. 视觉元素

游戏机制设计

基本规则

游戏代码

 前期准备

游戏代码详解

数据结构设计

宏定义

数据结构定义

函数原型(详见后文)

主函数代码

核心代码

Review


效果图

界面设计思路


1. 基本布局

  • 游戏区域:屏幕中央占据大部分面积,用于显示游戏地图,地图通常被设计成一个矩形网格,每个单元格代表蛇可以移动的一个位置
  • 得分显示:位于屏幕的顶部或角落,实时更新并显示玩家当前的得分,鼓励玩家追求更高的分数
  • 控制提示:初学者友好设计,简短说明如何控制蛇的移动(如使用箭头键),通常在游戏开始前或暂停时显示

2. 视觉元素

  • :使用不同的字符或颜色块来表示蛇的身体和头部,头部可能有特殊标记以区分,蛇移动时,通过改变字符或颜色的位置来模拟动画效果
  • 食物:采用醒目的颜色或符号,使其在地图上易于识别,吸引玩家去追逐
  • 边界:地图边缘用特殊的符号或颜色加重,提醒玩家避免碰撞

游戏机制设计


基本规则

  • 移动:玩家通过键盘控制蛇的前进方向,蛇会在每一帧自动向前移动一格,无法倒退或立即转向180度
  • 生长:当蛇头触碰到食物时,蛇的长度增加,同时分数增加,并在地图上的随机空白处生成新的食物
  • 死亡条件:蛇碰到自己的身体或地图边界时,游戏结束。此外,可以设定时间限制或生命值系统增加挑战性
  • 速度控制:玩家可通过按键调整蛇的移动速度


 

游戏代码


snake.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1
#define _KEY_STATE(x) (GetAsyncKeyState(x)&1?1:0)
#define _SNAKE_LENTH_ 5
#define GAME_WIDTH 26
#define GAME_HEIGHT 26


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


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

typedef enum STATE//蛇的状态
{
	RUN,PAUSE,KILL_BY_SELF,KILL_BY_WALL
}STATE;

typedef struct Psnake //用于记录蛇的各个节点
{
	int x;
	int y;
	struct Psnake* next;
}Psnake;
Psnake* PsnakeNode;

typedef struct Food 
{
	int x;
	int y;
	int score;
}Food;

typedef struct object 
{
	Psnake* psnakenode;
	Food food;
	int score;
	int speed;
	DIRECTION dir;
	STATE state;
}object;
object SNAKE;

void set_pos(short x, short y);//设置光标坐标

void cursor(bool state, int x);//设置光标可见性和大小

void map();//打印地图

void cover();//打印封面

void GameStart();//游戏初始化

void printInstruction(int y, const wchar_t* message);//打印提示信息

void PrintFood(Food* pos);//打印食物

void CreateFood(Psnake* snake);//创建食物

bool FoodJudge(Psnake* next);//判断下一节点是否为食物

Psnake* buynode(int x, int y);//申请节点

Psnake* InitSnake(Psnake** head);//初始化蛇

void PrintSnake(Psnake* head);//打印蛇

void IsDie();//判断满足结束条件

void SnakeMove(Psnake** head);//移动蛇

void ClearScreen();//清屏

void GameOver(int score);//游戏结束

void GameRun(Psnake** head);//运行游戏

void GameStart();//游戏前准备

snake.c

#include "snake.h"

//设置坐标
void set_pos(short x, short y)
{
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(hConsole, coord);
}//设置坐标

//设置光标信息
void cursor(bool state, int x)
{
	HANDLE op = GetStdHandle(STD_OUTPUT_HANDLE);

	CONSOLE_CURSOR_INFO con;

	con.dwSize = x;
	con.bVisible = state;
	
	SetConsoleCursorInfo(op, &con);
}

//打印提示信息
void printInstruction(int y, const wchar_t* message) {
	set_pos(60, y);
	wprintf(message);
}

//打印地图
void map() {
	// Draw top border
	set_pos(0, 0);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}

	// Draw left and right borders
	for (int i = 1; i < 29; i++) {
		set_pos(0, i);
		wprintf(L"□");
		set_pos(28 * 2, i); //右边的坐标(28*2 ,i)
		wprintf(L"□");
	}

	// Draw bottom border
	set_pos(0, 29);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}
	const wchar_t* instructions[] = {
		L"操作指南: 使用方向键←↑→↓",
		L"速度控制: F2 加速, F3 减速",
		L"暂停游戏: 按 P",
		L"退出游戏: 按 ESC"
	};

	for (int i = 0; i < sizeof(instructions) / sizeof(instructions[0]); ++i) {
		printInstruction(10 + i, instructions[i]);
	}
}

//打印封面
void cover()
{
	system("mode con cols=120 lines=50");
	system("title 贪吃蛇");
	
	set_pos(40, 10);
	wprintf(L"                                                                                                         \n");
	wprintf(L"         .-----------------.  .-----------------. .----------------.  .----------------.  .---------------- .\n");
	wprintf(L"	| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |\n");
	wprintf(L"	| |    _______   | || | ____  _____  | || |      __      | || |     ______   | || |  ___  ____   | |\n");
	wprintf(L"	| |   /  ___  |  | || ||_   \\ |_  _| | || |     /  \\     | || |   .' ___  |  | || | |_  ||_  _|  | |\n");
	wprintf(L"	| |  |  (__ \\_|  | || |  |   \\ | |   | || |    / /\\ \\    | || |  / .'   \\_|  | || |   | |_/ /    | |\n");
	wprintf(L"	| |   '.___`-.   | || |  | |\\ \\| |   | || |   / ____ \\   | || | | |          | || |   |  __'.    | |\n");
	wprintf(L"	| |  |`\\____) |  | || | _| |_\\ | |_  | || |  / /    \\ \\_ | || |  \\ `.___.'\\  | || |  _| |  \\ \\_  | |\n");
	wprintf(L"	| |  |_______.'  | || ||_____|\\____| | || ||____|  |____|| || |   `._____.'  | || | |____ || __|_| |\n");
	wprintf(L"	| |              | || |              | || |              | || |              | || |              | |\n");
	wprintf(L"	| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |\n");
	wprintf(L"	'----------------'  '------'---------'  '----------------'  '------'---------'    '--'-------------'\n");
	
	set_pos(45, 29);

	system("pause");
	system("cls");

	map();

}

//打印食物坐标
void PrintFood(Food* pos)
{
	set_pos((*pos).x, (*pos).y);
	wprintf(L"◆");
}

//创造食物
void CreateFood(Psnake* snake)
{
	Food* pos = (Food*)malloc(sizeof(Food));

	if (!pos)
	{
		perror("MALLOC FAILED:");
		return;
	}

	Psnake* cur = snake;
	//生成随机数
	srand((unsigned int)time(NULL));

again:
	(*pos).x = (rand() % GAME_WIDTH + 2) * 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).y = rand() % GAME_HEIGHT + 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).score = 1;
	while (cur)
	{
		if ((*pos).x == cur->x && (*pos).y == cur->y)
			goto again;
		cur = cur->next;
	}

	SNAKE.food = *pos;
	PrintFood(pos);
}

//判断下一个节点是否为食物
bool FoodJudge(Psnake* next)
{
	return ((next->x == SNAKE.food.x) && (next->y == SNAKE.food.y)) ? true : false;
}

//申请一个节点
Psnake* buynode(int x,int y)
{
	Psnake* newnode = (Psnake*)calloc(1, sizeof(Psnake));
	if (newnode == NULL)
	{
		perror("calloc:");
		return NULL;
	}
	newnode->next = NULL;
	newnode->x = x;
	newnode->y = y;

	return newnode;
}

//初始化蛇的信息
Psnake* InitSnake(Psnake**head)
{
	if (!(head&&*head)) {
		fprintf(stderr, "Memory allocation failed\n");
		return NULL;
	}
	(*head) = (Psnake*)calloc(1, sizeof(Psnake));
	

	// 初始化成员
	(*head)->x = 6;
	(*head)->y = 10;
	(*head)->next = NULL;

	Psnake* cur = *head;
	if (!cur)
	{
		perror("head is nullptr");
		return NULL;
	}
	int x = _SNAKE_LENTH_;
	while (x--)
	{
		Psnake* newnode= buynode((cur->x)+2, cur->y);
		newnode->next = cur;
		cur = newnode;
	}
	SNAKE.dir = RIGHT;
	SNAKE.psnakenode = cur;
	SNAKE.state = RUN;
	SNAKE.speed = 150;

	return cur;
}

//打印蛇
void PrintSnake(Psnake* head)
{
	Psnake* cur = SNAKE.psnakenode;
	while (cur)
	{
		set_pos(cur->x, cur->y);
		wprintf(L"●");
		cur = cur->next;
	}
}

void IsDie() {
	Psnake* cur = SNAKE.psnakenode->next; // 从第二个节点开始
	Psnake* prev = SNAKE.psnakenode; // 保存蛇头节点用于比较

	while (cur->next) {
		cur = cur->next;
		if (cur->x == prev->x && cur->y == prev->y) { // 检查当前节点是否与之前的节点重合
			SNAKE.state = KILL_BY_SELF;
			return;
		}
		
	}

	if (SNAKE.psnakenode->x == 56||SNAKE.psnakenode->x==0 ||SNAKE.psnakenode->y==0||SNAKE.psnakenode->y == 28) {
		SNAKE.state = KILL_BY_WALL;
		return;
	}
}

//蛇的移动
void SnakeMove(Psnake** head)
{
	// 首先检查蛇头的下一个位置
	Psnake* next = (Psnake*)calloc(1, sizeof(Psnake));
	if (!next) {
		perror("Failed to allocate memory for next snake node");
		exit(EXIT_FAILURE);
	}

	// 根据当前方向设置蛇头的下一个位置
	switch (SNAKE.dir)
	{
	case UP:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y - 1;
		break;
	case DOWN:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y + 1;
		break;
	case LEFT:
		next->x = SNAKE.psnakenode->x - 2;
		next->y = SNAKE.psnakenode->y;
		break;
	case RIGHT:
		next->x = SNAKE.psnakenode->x + 2;
		next->y = SNAKE.psnakenode->y;
		break;
	default:
		free(next);
		return; // 如果方向无效,不移动蛇
	}

	// 检查下一个位置是否有食物
	if (FoodJudge(next))
	{
		SNAKE.score += SNAKE.food.score; // 增加得分
		next->next = (*head); // 将新节点放在头部
		*head = next; // 更新头指针
		CreateFood(*head); // 生成新的食物
	}
	else
	{
		// 没有食物,移动蛇的尾部到头部
		Psnake* tail = SNAKE.psnakenode;
		Psnake* prev = NULL;
		if (!(tail || prev))
		{
			perror("POINTER IS NULL:");
			return;
		}
		// 找到尾节点和其前一个节点
		while (tail->next) {
			prev = tail;
			tail = tail->next;
		}

		// 将尾部节点移动到头部
		prev->next = NULL; // 将前一个节点的next设置为NULL,它现在是尾部
		set_pos(tail->x, tail->y);
		wprintf(L"  ");

		tail->next = SNAKE.psnakenode; // 将原尾部节点放在头部
		SNAKE.psnakenode = tail; // 更新头指针

		// 更新头部位置
		SNAKE.psnakenode->x = next->x;
		SNAKE.psnakenode->y = next->y;

		// 释放之前分配的next节点,因为我们只是移动了尾部节点
		free(next);
	}
	IsDie();
	PrintSnake(SNAKE.psnakenode);
}

//=========================================
//运行游戏

void ClearScreen() {  // 定义清屏函数,适用于Windows和Linux/macOS
#ifdef _WIN32
	system("cls");
#else
	system("clear");
#endif
}

void GameOver(int score) {
	// 游戏结束后的善后处理
	ClearScreen();  // 清除屏幕

	wprintf(L"Game Over!\n");  // 打印游戏结束信息
	wprintf(L"总分: %d\n", score);  // 显示玩家得分

	// 询问玩家是否想再玩一次
	wprintf(L"再来一次?(y/n): ");

	system("pause >> nul");
	if (_KEY_STATE(0x59)){
		GameStart();
		fflush(stdin);
	}
	else {
		wprintf(L"感谢游玩\n");
		exit(0);  // 结束程序
	}
}

void GameRun(Psnake**head)
{
	do
	{
		set_pos(60, 6);
		wprintf(L"当前分数: %d", SNAKE.score);

		if ((_KEY_STATE(VK_UP) || _KEY_STATE(0x57)) && SNAKE.dir != DOWN)
		{
			SNAKE.dir = UP;
		}
		else if ((_KEY_STATE(VK_DOWN) || _KEY_STATE(0x53)) && SNAKE.dir != UP)
		{
			SNAKE.dir = DOWN;
		}
		else if ((_KEY_STATE(VK_LEFT) || _KEY_STATE(0x41)) && SNAKE.dir != RIGHT)
		{
			SNAKE.dir = LEFT;
		}
		else if ((_KEY_STATE(VK_RIGHT) || _KEY_STATE(0x44)) && SNAKE.dir != LEFT)
		{
			SNAKE.dir = RIGHT;
		}
		else if (_KEY_STATE(0x71)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) -= 30;
		}		
		else if (_KEY_STATE(0x72)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) += 30;
		}
		else if (_KEY_STATE(0x50))
		{
			SNAKE.speed = 2147483647;
		}

		SnakeMove(head);
		Sleep(SNAKE.speed);
	} while (SNAKE.state == RUN);
	GameOver(SNAKE.score);
}

//开始游戏及游戏前准备
void GameStart()
{
	cover();

	SNAKE.psnakenode = (Psnake*)malloc(sizeof(Psnake));
	InitSnake(&SNAKE.psnakenode);
	PrintSnake(SNAKE.psnakenode);

	CreateFood(SNAKE.psnakenode);


	set_pos(40, 36);
	GameRun(&SNAKE.psnakenode);
}

main.c

#include"snake.h"

int main()
{
//	环境配置
//=================================================
//	可打印宽字符
	_setmode(_fileno(stdout), _O_U16TEXT);
	//切换到本地
	setlocale(LC_ALL, "");

	system("mode con cols=10 lines=20");

	set_pos(0, 20);
	cursor(false, 10);
//=================================================
//	正式开始
	GameStart();

	set_pos(0, 40);
	return 0;
}

 前期准备


visual studio 2022中,默认窗口为cmd终端,无法实现预期效果,我们需要以下设置。若为其他开发环境请自查资料。

游戏代码详解


数据结构设计

snake.h

#pragma once

#define _CRT_SECURE_NO_WARNINGS 1
#define _KEY_STATE(x) (GetAsyncKeyState(x)&1?1:0)
#define _SNAKE_LENTH_ 5
#define GAME_WIDTH 26
#define GAME_HEIGHT 26


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


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

typedef enum STATE//蛇的状态
{
	RUN,PAUSE,KILL_BY_SELF,KILL_BY_WALL
}STATE;

typedef struct Psnake //用于记录蛇的各个节点
{
	int x;
	int y;
	struct Psnake* next;
}Psnake;
Psnake* PsnakeNode;

typedef struct Food 
{
	int x;
	int y;
	int score;
}Food;

typedef struct object 
{
	Psnake* psnakenode;
	Food food;
	int score;
	int speed;
	DIRECTION dir;
	STATE state;
}object;
object SNAKE;

void set_pos(short x, short y);//设置光标坐标

void cursor(bool state, int x);//设置光标可见性和大小

void map();//打印地图

void cover();//打印封面

void GameStart();//游戏初始化

void printInstruction(int y, const wchar_t* message);//打印提示信息

void PrintFood(Food* pos);//打印食物

void CreateFood(Psnake* snake);//创建食物

bool FoodJudge(Psnake* next);//判断下一节点是否为食物

Psnake* buynode(int x, int y);//申请节点

Psnake* InitSnake(Psnake** head);//初始化蛇

void PrintSnake(Psnake* head);//打印蛇

void IsDie();//判断满足结束条件

void SnakeMove(Psnake** head);//移动蛇

void ClearScreen();//清屏

void GameOver(int score);//游戏结束

void GameRun(Psnake** head);//运行游戏

void GameStart();//游戏前准备

宏定义

  • #pragma once: 用于防止头文件被重复包含。
  • _CRT_SECURE_NO_WARNINGS 1: 禁用Visual Studio中的某些不安全函数警告,比如使用scanf等。
  • _KEY_STATE(x) (GetAsyncKeyState(x)&1?1:0): 自定义宏,用来检查给定虚拟键是否被按下。GetAsyncKeyState是Windows API函数( 配合虚拟键码使用 ),用于检查按键状态。
  • _SNAKE_LENTH_ 5: 定义初始蛇的长度为5个单位。
  • GAME_WIDTH 26  GAME_HEIGHT 26: 定义游戏地图的宽度和高度,均为26个单位。

数据结构定义

  • enum DIRECTION: 定义蛇的移动方向,包括上、下、左、右。
  • enum STATE: 定义蛇的状态,包括运行中、暂停、自杀死亡、撞墙死亡。
  • struct Psnake: 蛇的节点结构体,包含位置坐标(x, y)和指向下一个节点的指针。
  • struct Food: 食物结构体,包含位置坐标(x, y)和分数。
  • struct object: 游戏对象结构体,整合蛇、食物、得分、速度、方向和状态等游戏所需的所有信息。

将蛇、食物抽象成一个结构体,用宽字符来打印,enum STATEenum DIRECTION 来表示蛇的状态。将以上数据放在object结构体中进行管理。

函数原型(详见后文)

  • set_pos(short x, short y): 设置控制台光标位置。
  • cursor(bool state, int x): 设置控制台光标的可见性和大小。
  • map(): 打印游戏地图。
  • cover(): 打印游戏封面。
  • GameStart(): 游戏初始化入口,可能包含了封面展示、地图绘制、蛇的初始化等。
  • printInstruction(int y, const wchar_t* message): 在指定行打印宽字符格式的提示信息。
  • PrintFood(Food* pos): 在指定位置打印食物。
  • CreateFood(Psnake* snake): 随机生成食物,并确保不与蛇身重叠。
  • FoodJudge(Psnake* next): 判断蛇的下一个位置是否为食物。
  • buynode(int x, int y): 申请一个新的蛇节点内存。
  • InitSnake(Psnake** head): 初始化蛇的链表结构。
  • PrintSnake(Psnake* head): 打印蛇的当前位置。
  • IsDie(): 判断游戏是否结束,即蛇是否撞墙或自相残杀。
  • SnakeMove(Psnake** head): 处理蛇的移动逻辑,包括方向改变和吃到食物的处理。
  • ClearScreen(): 清除屏幕内容。
  • GameOver(int score): 游戏结束时的操作,显示分数并可能询问是否重新开始。
  • GameRun(Psnake** head): 游戏主循环,处理用户输入和游戏逻辑。

整个框架围绕着贪吃蛇的基本逻辑展开,包括了游戏的初始化、地图绘制、蛇的移动与增长、食物的生成与检测、游戏状态管理以及用户交互。


主函数代码

main.c

#include"snake.h"

int main()
{
//	环境配置
//=================================================
//	可打印宽字符
	_setmode(_fileno(stdout), _O_U16TEXT);
	//切换到本地
	setlocale(LC_ALL, "");

	system("mode con cols=10 lines=20");

	set_pos(0, 20);
	cursor(false, 10);
//=================================================
//	正式开始
	GameStart();

	set_pos(0, 40);
	return 0;
}

_setmode(_fileno(stdout) , _O_U16TEXT);    将输出模式设置为 _O_U16TEXT 模式,以便后续打印宽字符。

  • _setmode 函数来自 <io.h> 头文件。
  • _fileno 函数来自 <stdio.h> 头文件。
  • _O_U16TEXT 宏定义通常在 <fcntl.h> 或 <io.h> 中找到

setlocale(LC_ARR,"");    切换到本地模式

  • LC_ALL是一个宏,代表所有类别,包括日期和时间格式、数字表示、货币符号、语言等。使用 LC_ALL 意味着你想改变所有方面的区域设置。
  • "" 作为第二个参数,是一个特殊的值,它告诉函数使用用户默认的或者环境变量(如 LANG 或 LC_ALL)指定的区域设置。

system("mode con cols=10 lines=20");     将窗口设置为20行高,10列宽,在vs控制台中,字符高度一致,但宽度不一致,一般来说宽字符是窄字符的两倍,为了达到预期效果,需按照以上比例设置。

set_pos(0,20);      设置打印字符的位置,这是我自己封装的函数,后续会讲到。

cursor(false,10);    设置光标状态,自己封装的函数,后续会讲到。

GameStart();    正式进入游戏。跳转到snake.c文件中。


核心代码

snake.c

#include "snake.h"

//设置坐标
void set_pos(short x, short y)
{
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(hConsole, coord);
}//设置坐标

//设置光标信息
void cursor(bool state, int x)
{
	HANDLE op = GetStdHandle(STD_OUTPUT_HANDLE);

	CONSOLE_CURSOR_INFO con;

	con.dwSize = x;
	con.bVisible = state;
	
	SetConsoleCursorInfo(op, &con);
}

//打印提示信息
void printInstruction(int y, const wchar_t* message) {
	set_pos(60, y);
	wprintf(message);
}

//打印地图
void map() {
	// Draw top border
	set_pos(0, 0);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}

	// Draw left and right borders
	for (int i = 1; i < 29; i++) {
		set_pos(0, i);
		wprintf(L"□");
		set_pos(28 * 2, i); //右边的坐标(28*2 ,i)
		wprintf(L"□");
	}

	// Draw bottom border
	set_pos(0, 29);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}
	const wchar_t* instructions[] = {
		L"操作指南: 使用方向键←↑→↓",
		L"速度控制: F2 加速, F3 减速",
		L"暂停游戏: 按 P",
		L"退出游戏: 按 ESC"
	};

	for (int i = 0; i < sizeof(instructions) / sizeof(instructions[0]); ++i) {
		printInstruction(10 + i, instructions[i]);
	}
}

//打印封面
void cover()
{
	system("mode con cols=120 lines=50");
	system("title 贪吃蛇");
	
	set_pos(40, 10);
	wprintf(L"                                                                                                         \n");
	wprintf(L"         .-----------------.  .-----------------. .----------------.  .----------------.  .---------------- .\n");
	wprintf(L"	| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |\n");
	wprintf(L"	| |    _______   | || | ____  _____  | || |      __      | || |     ______   | || |  ___  ____   | |\n");
	wprintf(L"	| |   /  ___  |  | || ||_   \\ |_  _| | || |     /  \\     | || |   .' ___  |  | || | |_  ||_  _|  | |\n");
	wprintf(L"	| |  |  (__ \\_|  | || |  |   \\ | |   | || |    / /\\ \\    | || |  / .'   \\_|  | || |   | |_/ /    | |\n");
	wprintf(L"	| |   '.___`-.   | || |  | |\\ \\| |   | || |   / ____ \\   | || | | |          | || |   |  __'.    | |\n");
	wprintf(L"	| |  |`\\____) |  | || | _| |_\\ | |_  | || |  / /    \\ \\_ | || |  \\ `.___.'\\  | || |  _| |  \\ \\_  | |\n");
	wprintf(L"	| |  |_______.'  | || ||_____|\\____| | || ||____|  |____|| || |   `._____.'  | || | |____ || __|_| |\n");
	wprintf(L"	| |              | || |              | || |              | || |              | || |              | |\n");
	wprintf(L"	| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |\n");
	wprintf(L"	'----------------'  '------'---------'  '----------------'  '------'---------'    '--'-------------'\n");
	
	set_pos(45, 29);

	system("pause");
	system("cls");

	map();

}

//打印食物坐标
void PrintFood(Food* pos)
{
	set_pos((*pos).x, (*pos).y);
	wprintf(L"◆");
}

//创造食物
void CreateFood(Psnake* snake)
{
	Food* pos = (Food*)malloc(sizeof(Food));

	if (!pos)
	{
		perror("MALLOC FAILED:");
		return;
	}

	Psnake* cur = snake;
	//生成随机数
	srand((unsigned int)time(NULL));

again:
	(*pos).x = (rand() % GAME_WIDTH + 2) * 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).y = rand() % GAME_HEIGHT + 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).score = 1;
	while (cur)
	{
		if ((*pos).x == cur->x && (*pos).y == cur->y)
			goto again;
		cur = cur->next;
	}

	SNAKE.food = *pos;
	PrintFood(pos);
}

//判断下一个节点是否为食物
bool FoodJudge(Psnake* next)
{
	return ((next->x == SNAKE.food.x) && (next->y == SNAKE.food.y)) ? true : false;
}

//申请一个节点
Psnake* buynode(int x,int y)
{
	Psnake* newnode = (Psnake*)calloc(1, sizeof(Psnake));
	if (newnode == NULL)
	{
		perror("calloc:");
		return NULL;
	}
	newnode->next = NULL;
	newnode->x = x;
	newnode->y = y;

	return newnode;
}

//初始化蛇的信息
Psnake* InitSnake(Psnake**head)
{
	if (!(head&&*head)) {
		fprintf(stderr, "Memory allocation failed\n");
		return NULL;
	}
	(*head) = (Psnake*)calloc(1, sizeof(Psnake));
	

	// 初始化成员
	(*head)->x = 6;
	(*head)->y = 10;
	(*head)->next = NULL;

	Psnake* cur = *head;
	if (!cur)
	{
		perror("head is nullptr");
		return NULL;
	}
	int x = _SNAKE_LENTH_;
	while (x--)
	{
		Psnake* newnode= buynode((cur->x)+2, cur->y);
		newnode->next = cur;
		cur = newnode;
	}
	SNAKE.dir = RIGHT;
	SNAKE.psnakenode = cur;
	SNAKE.state = RUN;
	SNAKE.speed = 150;

	return cur;
}

//打印蛇
void PrintSnake(Psnake* head)
{
	Psnake* cur = SNAKE.psnakenode;
	while (cur)
	{
		set_pos(cur->x, cur->y);
		wprintf(L"●");
		cur = cur->next;
	}
}

void IsDie() {
	Psnake* cur = SNAKE.psnakenode->next; // 从第二个节点开始
	Psnake* prev = SNAKE.psnakenode; // 保存蛇头节点用于比较

	while (cur->next) {
		cur = cur->next;
		if (cur->x == prev->x && cur->y == prev->y) { // 检查当前节点是否与之前的节点重合
			SNAKE.state = KILL_BY_SELF;
			return;
		}
		
	}

	if (SNAKE.psnakenode->x == 56||SNAKE.psnakenode->x==0 ||SNAKE.psnakenode->y==0||SNAKE.psnakenode->y == 28) {
		SNAKE.state = KILL_BY_WALL;
		return;
	}
}

//蛇的移动
void SnakeMove(Psnake** head)
{
	// 首先检查蛇头的下一个位置
	Psnake* next = (Psnake*)calloc(1, sizeof(Psnake));
	if (!next) {
		perror("Failed to allocate memory for next snake node");
		exit(EXIT_FAILURE);
	}

	// 根据当前方向设置蛇头的下一个位置
	switch (SNAKE.dir)
	{
	case UP:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y - 1;
		break;
	case DOWN:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y + 1;
		break;
	case LEFT:
		next->x = SNAKE.psnakenode->x - 2;
		next->y = SNAKE.psnakenode->y;
		break;
	case RIGHT:
		next->x = SNAKE.psnakenode->x + 2;
		next->y = SNAKE.psnakenode->y;
		break;
	default:
		free(next);
		return; // 如果方向无效,不移动蛇
	}

	// 检查下一个位置是否有食物
	if (FoodJudge(next))
	{
		SNAKE.score += SNAKE.food.score; // 增加得分
		next->next = (*head); // 将新节点放在头部
		*head = next; // 更新头指针
		CreateFood(*head); // 生成新的食物
	}
	else
	{
		// 没有食物,移动蛇的尾部到头部
		Psnake* tail = SNAKE.psnakenode;
		Psnake* prev = NULL;
		if (!(tail || prev))
		{
			perror("POINTER IS NULL:");
			return;
		}
		// 找到尾节点和其前一个节点
		while (tail->next) {
			prev = tail;
			tail = tail->next;
		}

		// 将尾部节点移动到头部
		prev->next = NULL; // 将前一个节点的next设置为NULL,它现在是尾部
		set_pos(tail->x, tail->y);
		wprintf(L"  ");

		tail->next = SNAKE.psnakenode; // 将原尾部节点放在头部
		SNAKE.psnakenode = tail; // 更新头指针

		// 更新头部位置
		SNAKE.psnakenode->x = next->x;
		SNAKE.psnakenode->y = next->y;

		// 释放之前分配的next节点,因为我们只是移动了尾部节点
		free(next);
	}
	IsDie();
	PrintSnake(SNAKE.psnakenode);
}

//=========================================
//运行游戏

void ClearScreen() {  // 定义清屏函数,适用于Windows和Linux/macOS
#ifdef _WIN32
	system("cls");
#else
	system("clear");
#endif
}

void GameOver(int score) {
	// 游戏结束后的善后处理
	ClearScreen();  // 清除屏幕

	wprintf(L"Game Over!\n");  // 打印游戏结束信息
	wprintf(L"总分: %d\n", score);  // 显示玩家得分

	// 询问玩家是否想再玩一次
	wprintf(L"再来一次?(y/n): ");

	system("pause >> nul");
	if (_KEY_STATE(0x59)){
		GameStart();
		fflush(stdin);
	}
	else {
		wprintf(L"感谢游玩\n");
		exit(0);  // 结束程序
	}
}

void GameRun(Psnake**head)
{
	do
	{
		set_pos(60, 6);
		wprintf(L"当前分数: %d", SNAKE.score);

		if ((_KEY_STATE(VK_UP) || _KEY_STATE(0x57)) && SNAKE.dir != DOWN)
		{
			SNAKE.dir = UP;
		}
		else if ((_KEY_STATE(VK_DOWN) || _KEY_STATE(0x53)) && SNAKE.dir != UP)
		{
			SNAKE.dir = DOWN;
		}
		else if ((_KEY_STATE(VK_LEFT) || _KEY_STATE(0x41)) && SNAKE.dir != RIGHT)
		{
			SNAKE.dir = LEFT;
		}
		else if ((_KEY_STATE(VK_RIGHT) || _KEY_STATE(0x44)) && SNAKE.dir != LEFT)
		{
			SNAKE.dir = RIGHT;
		}
		else if (_KEY_STATE(0x71)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) -= 30;
		}		
		else if (_KEY_STATE(0x72)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) += 30;
		}
		else if (_KEY_STATE(0x50))
		{
			SNAKE.speed = 2147483647;
		}

		SnakeMove(head);
		Sleep(SNAKE.speed);
	} while (SNAKE.state == RUN);
	GameOver(SNAKE.score);
}

//开始游戏及游戏前准备
void GameStart()
{
	cover();

	SNAKE.psnakenode = (Psnake*)malloc(sizeof(Psnake));
	InitSnake(&SNAKE.psnakenode);
	PrintSnake(SNAKE.psnakenode);

	CreateFood(SNAKE.psnakenode);


	set_pos(40, 36);
	GameRun(&SNAKE.psnakenode);
}

1.    set_pos( short  x , short y ) 

void set_pos(short x, short y)
{
	HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	COORD coord;
	coord.X = x;
	coord.Y = y;
	SetConsoleCursorPosition(hConsole, coord);
}//设置坐标
  • 作用: 设置光标在控制台上的位置,以便在指定坐标输出字符。
  • 思路: 使用GetStdHandle获得控制台光标句柄 ---> 修改值 ---> SetConsoleCursorPosition实现对光标的控制。           

  注:SetConsoleCursorPosition的使用需一个COORD结构体,将xy改成目标值。

  • 知识点: Windows API使用、句柄概念、坐标系统。

2.    cursor ( bool state , short x ) 

//设置光标信息
void cursor(bool state, int x)
{
	HANDLE op = GetStdHandle(STD_OUTPUT_HANDLE);

	CONSOLE_CURSOR_INFO con;

	con.dwSize = x;
	con.bVisible = state;
	
	SetConsoleCursorInfo(op, &con);
}
  • 作用: 控制光标的显示状态(可见或隐藏)及大小。
  • 思路: 使用 GetStdHandle 获得光标句柄 ---> 进行值的修改 ---> 使用SetConsoleCursorInfo进行设置 。

注:SetConsoleCursorInfo需要定义 CONSOLE_CURSOR_INFO类型的结构体

  • 知识点: 控制台编程、光标属性设置。

3. printInstruction( int y , const wchar_t* message )

//打印提示信息
void printInstruction(int y, const wchar_t* message) {
	set_pos(60, y);
	wprintf(message);
}
  • 作用: 在指定的行上打印游戏的指令或提示信息。
  • 思路: 调用set_pos定位光标,然后使用宽字符输出信息。
  • 知识点: 宽字符输出、动态打印信息布局。

4.  map( )

//打印地图
void map() {
	// Draw top border
	set_pos(0, 0);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}

	// Draw left and right borders
	for (int i = 1; i < 29; i++) {
		set_pos(0, i);
		wprintf(L"□");
		set_pos(28 * 2, i); //右边的坐标(28*2 ,i)
		wprintf(L"□");
	}

	// Draw bottom border
	set_pos(0, 29);
	for (int i = 0; i < 29; i++) {
		wprintf(L"□");
	}
	const wchar_t* instructions[] = {
		L"操作指南: 使用方向键←↑→↓",
		L"速度控制: F2 加速, F3 减速",
		L"暂停游戏: 按 P",
		L"退出游戏: 按 ESC"
	};

	for (int i = 0; i < sizeof(instructions) / sizeof(instructions[0]); ++i) {
		printInstruction(10 + i, instructions[i]);
	}
}
  • 作用: 绘制游戏的地图边界和游戏说明。
  • 思路: 分别绘制上、下、左、右边界,并在固定位置打印游戏规则。

注:宽字符打印需要wprintf,wprintf( L "  " ) ;

  • 知识点: 循环结构、字符串输出、坐标计算。

5.  cover ( )

//打印封面
void cover()
{
	system("mode con cols=120 lines=50");
	system("title 贪吃蛇");
	
	set_pos(40, 10);
	wprintf(L"                                                                                                         \n");
	wprintf(L"         .-----------------.  .-----------------. .----------------.  .----------------.  .---------------- .\n");
	wprintf(L"	| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |\n");
	wprintf(L"	| |    _______   | || | ____  _____  | || |      __      | || |     ______   | || |  ___  ____   | |\n");
	wprintf(L"	| |   /  ___  |  | || ||_   \\ |_  _| | || |     /  \\     | || |   .' ___  |  | || | |_  ||_  _|  | |\n");
	wprintf(L"	| |  |  (__ \\_|  | || |  |   \\ | |   | || |    / /\\ \\    | || |  / .'   \\_|  | || |   | |_/ /    | |\n");
	wprintf(L"	| |   '.___`-.   | || |  | |\\ \\| |   | || |   / ____ \\   | || | | |          | || |   |  __'.    | |\n");
	wprintf(L"	| |  |`\\____) |  | || | _| |_\\ | |_  | || |  / /    \\ \\_ | || |  \\ `.___.'\\  | || |  _| |  \\ \\_  | |\n");
	wprintf(L"	| |  |_______.'  | || ||_____|\\____| | || ||____|  |____|| || |   `._____.'  | || | |____ || __|_| |\n");
	wprintf(L"	| |              | || |              | || |              | || |              | || |              | |\n");
	wprintf(L"	| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |\n");
	wprintf(L"	'----------------'  '------'---------'  '----------------'  '------'---------'    '--'-------------'\n");
	
	set_pos(45, 29);

	system("pause");
	system("cls");

	map();

}
  • 作用: 打印游戏封面。
  • 思路: 使用特殊字符和格式化输出来设计一个吸引人的启动画面,随后清除屏幕并进入游戏。
  • 知识点: 字符串操作、系统调用(改变窗口尺寸、标题)、多行输出。

6. void PrintFood(Food* pos)

//打印食物坐标
void PrintFood(Food* pos)
{
	set_pos((*pos).x, (*pos).y);
	wprintf(L"◆");
}
  • 作用: 在指定位置打印食物图标。
  • 思路: 移动光标至食物坐标,输出食物的符号。(Food 在 snake.h 会详解)
  • 知识点: 结构体使用、函数传参。

7. CreateFood ( Psnake* snake )

//创造食物
void CreateFood(Psnake* snake)
{
	Food* pos = (Food*)malloc(sizeof(Food));

	if (!pos)
	{
		perror("MALLOC FAILED:");
		return;
	}

	Psnake* cur = snake;
	//生成随机数
	srand((unsigned int)time(NULL));

again:
	(*pos).x = (rand() % GAME_WIDTH + 2) * 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).y = rand() % GAME_HEIGHT + 2; // rand() % 27 会得到0到26之间的数,加2后变为2到28
	(*pos).score = 1;
	while (cur)
	{
		if ((*pos).x == cur->x && (*pos).y == cur->y)
			goto again;
		cur = cur->next;
	}

	SNAKE.food = *pos;
	PrintFood(pos);
}
  • 作用: 随机生成食物并确保不与蛇身重叠。
  • 思路: 使用 rand 随机生成食物 x、y 坐标,食物遍历贪吃蛇每个节点的坐标,然后更新食物位置信息。
  • 知识点: 随机数生成、链表遍历、内存管理(malloc)。

8. FoodJudge(Psnake* next)

//判断下一个节点是否为食物
bool FoodJudge(Psnake* next)
{
	return ((next->x == SNAKE.food.x) && (next->y == SNAKE.food.y)) ? true : false;
}

  • 作用: 判断下一个节点位置是否为食物所在。
  • 思路: 比较节点坐标与食物坐标。
  • 知识点: 简单逻辑判断、结构体成员访问。

9.buynode(int x, int y)

//申请一个节点
Psnake* buynode(int x,int y)
{
	Psnake* newnode = (Psnake*)calloc(1, sizeof(Psnake));
	if (newnode == NULL)
	{
		perror("calloc:");
		return NULL;
	}
	newnode->next = NULL;
	newnode->x = x;
	newnode->y = y;

	return newnode;
}
  • 作用: 创建一个新的蛇节点。
  • 思路: 申请内存,初始化节点数据,链接到链表。

蛇按照方向进行移动,需要对蛇头的前,左右进行申请节点

  • 知识点: 动态内存分配、链表操作。

10.InitSnake(Psnake**head)

//初始化蛇的信息
Psnake* InitSnake(Psnake**head)
{
	if (!(head&&*head)) {
		fprintf(stderr, "Memory allocation failed\n");
		return NULL;
	}
	(*head) = (Psnake*)calloc(1, sizeof(Psnake));
	

	// 初始化成员
	(*head)->x = 6;
	(*head)->y = 10;
	(*head)->next = NULL;

	Psnake* cur = *head;
	if (!cur)
	{
		perror("head is nullptr");
		return NULL;
	}
	int x = _SNAKE_LENTH_;
	while (x--)
	{
		Psnake* newnode= buynode((cur->x)+2, cur->y);
		newnode->next = cur;
		cur = newnode;
	}
	SNAKE.dir = RIGHT;
	SNAKE.psnakenode = cur;
	SNAKE.state = RUN;
	SNAKE.speed = 150;

	return cur;
}
  • 作用: 初始化蛇的结构,创建初始蛇身。
  • 思路: 设置蛇头位置、按照长度申请节点,并设置蛇其他信息
  • 知识点: 指针引用、循环构造链表、内存管理。

11.PrintSnake(Psnake* head)

//打印蛇
void PrintSnake(Psnake* head)
{
	Psnake* cur = SNAKE.psnakenode;
	while (cur)
	{
		set_pos(cur->x, cur->y);
		wprintf(L"●");
		cur = cur->next;
	}
}
  • 作用: 打印蛇身在屏幕上。
  • 思路: 遍历链表,逐个输出蛇身位置的字符。
  • 知识点: 链表遍历、光标移动打印。

12. IsDie()

void IsDie() {
	Psnake* cur = SNAKE.psnakenode->next; // 从第二个节点开始
	Psnake* prev = SNAKE.psnakenode; // 保存蛇头节点用于比较

	while (cur->next) {
		cur = cur->next;
		if (cur->x == prev->x && cur->y == prev->y) { // 检查当前节点是否与之前的节点重合
			SNAKE.state = KILL_BY_SELF;
			return;
		}
		
	}

	if (SNAKE.psnakenode->x == 56||SNAKE.psnakenode->x==0 ||SNAKE.psnakenode->y==0||SNAKE.psnakenode->y == 28) {
		SNAKE.state = KILL_BY_WALL;
		return;
	}
}
  • 作用: 检查游戏是否结束(撞墙或自食)。
  • 思路: 检测是否撞到自身:将蛇头坐标与每个蛇身节点坐标相比较;撞墙:蛇头与游戏边界重叠。之后将蛇的状态修改。
  • 知识点: 边界条件检测、循环逻辑。

13. SnakeMove(Psnake** head)

//蛇的移动
void SnakeMove(Psnake** head)
{
	// 首先检查蛇头的下一个位置
	Psnake* next = (Psnake*)calloc(1, sizeof(Psnake));
	if (!next) {
		perror("Failed to allocate memory for next snake node");
		exit(EXIT_FAILURE);
	}

	// 根据当前方向设置蛇头的下一个位置
	switch (SNAKE.dir)
	{
	case UP:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y - 1;
		break;
	case DOWN:
		next->x = SNAKE.psnakenode->x;
		next->y = SNAKE.psnakenode->y + 1;
		break;
	case LEFT:
		next->x = SNAKE.psnakenode->x - 2;
		next->y = SNAKE.psnakenode->y;
		break;
	case RIGHT:
		next->x = SNAKE.psnakenode->x + 2;
		next->y = SNAKE.psnakenode->y;
		break;
	default:
		free(next);
		return; // 如果方向无效,不移动蛇
	}

	// 检查下一个位置是否有食物
	if (FoodJudge(next))
	{
		SNAKE.score += SNAKE.food.score; // 增加得分
		next->next = (*head); // 将新节点放在头部
		*head = next; // 更新头指针
		CreateFood(*head); // 生成新的食物
	}
	else
	{
		// 没有食物,移动蛇的尾部到头部
		Psnake* tail = SNAKE.psnakenode;
		Psnake* prev = NULL;
		if (!(tail || prev))
		{
			perror("POINTER IS NULL:");
			return;
		}
		// 找到尾节点和其前一个节点
		while (tail->next) {
			prev = tail;
			tail = tail->next;
		}

		// 将尾部节点移动到头部
		prev->next = NULL; // 将前一个节点的next设置为NULL,它现在是尾部
		set_pos(tail->x, tail->y);
		wprintf(L"  ");

		tail->next = SNAKE.psnakenode; // 将原尾部节点放在头部
		SNAKE.psnakenode = tail; // 更新头指针

		// 更新头部位置
		SNAKE.psnakenode->x = next->x;
		SNAKE.psnakenode->y = next->y;

		// 释放之前分配的next节点,因为我们只是移动了尾部节点
		free(next);
	}
	IsDie();
	PrintSnake(SNAKE.psnakenode);
}
  • 作用: 处理蛇的移动逻辑,包括食物检测和身体跟随。
  • 思路:申请三个节点(蛇头前左右) 用switch对蛇的方向进行判断(先让蛇头走一步),判断该方向下一节点是否为食物,将蛇身剩下的节点逐个移动一个节点。

移动分两部分,蛇头移动和蛇身移动,先让蛇头走一步,蛇身每个节点进行移动

  • 知识点: 方向判断、链表头节点更新、内存释放。

14.ClearScreen( )


void ClearScreen() {  // 定义清屏函数,适用于Windows和Linux/macOS
#ifdef _WIN32
	system("cls");
#else
	system("clear");
#endif
}
  • 作用: 清除屏幕内容。
  • 思路: 根据操作系统调用不同的清屏命令。

#ifdef#endif 要成对存在

  • 知识点: 条件编译、系统调用。

15.GameOver( int score )

void GameOver(int score) {
	// 游戏结束后的善后处理
	ClearScreen();  // 清除屏幕

	wprintf(L"Game Over!\n");  // 打印游戏结束信息
	wprintf(L"总分: %d\n", score);  // 显示玩家得分

	// 询问玩家是否想再玩一次
	wprintf(L"再来一次?(y/n): ");

	system("pause >> nul");
	if (_KEY_STATE(0x59)){
		GameStart();
		fflush(stdin);
	}
	else {
		wprintf(L"感谢游玩\n");
		exit(0);  // 结束程序
	}
}
  • 作用: 游戏结束时显示分数,并询问是否重新开始。
  • 思路: 显示最终得分,根据用户输入决定是否重启游戏。

system("pause>>nul"); 与 system("pause"); 效果类似,后者会打印按任意键继续字样,前者不会

  • 知识点: 用户输入读取、流程控制。

16.  GameRun(Psnake**head)

void GameRun(Psnake**head)
{
	do
	{
		set_pos(60, 6);
		wprintf(L"当前分数: %d", SNAKE.score);

		if ((_KEY_STATE(VK_UP) || _KEY_STATE(0x57)) && SNAKE.dir != DOWN)
		{
			SNAKE.dir = UP;
		}
		else if ((_KEY_STATE(VK_DOWN) || _KEY_STATE(0x53)) && SNAKE.dir != UP)
		{
			SNAKE.dir = DOWN;
		}
		else if ((_KEY_STATE(VK_LEFT) || _KEY_STATE(0x41)) && SNAKE.dir != RIGHT)
		{
			SNAKE.dir = LEFT;
		}
		else if ((_KEY_STATE(VK_RIGHT) || _KEY_STATE(0x44)) && SNAKE.dir != LEFT)
		{
			SNAKE.dir = RIGHT;
		}
		else if (_KEY_STATE(0x71)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) -= 30;
		}		
		else if (_KEY_STATE(0x72)&&SNAKE.speed>=0&&SNAKE.speed<=500)
		{
			(SNAKE.speed) += 30;
		}
		else if (_KEY_STATE(0x50))
		{
			SNAKE.speed = 2147483647;
		}

		SnakeMove(head);
		Sleep(SNAKE.speed);
	} while (SNAKE.state == RUN);
	GameOver(SNAKE.score);
}
  • 作用: 游戏主循环,处理用户输入和游戏逻辑。
  • 思路: 此处do while效果要比while效果要好,通过检测检测按键状态来确定蛇的移动方向。

虚拟键码(Virtual Key Codes)是在计算机键盘输入中使用的一套编码标准,用于标识键盘上的每一个按键,无论键盘的实际物理布局如何。

  • 知识点: 主循环控制、事件响应、时间延迟。

17.  GameStart ()

//开始游戏及游戏前准备
void GameStart()
{
	cover();

	SNAKE.psnakenode = (Psnake*)malloc(sizeof(Psnake));
	InitSnake(&SNAKE.psnakenode);
	PrintSnake(SNAKE.psnakenode);

	CreateFood(SNAKE.psnakenode);


	set_pos(40, 36);
	GameRun(&SNAKE.psnakenode);
}
  • 作用: 游戏启动入口,包含初始化、封面展示和游戏开始。
  • 思路: 先展示封面,初始化游戏环境,然后进入游戏主循环。

将蛇“创建”出来,作为游戏运行的主体

  • 知识点: 函数调用顺序、程序入口点。

Review


整理一下思路:

  1. 需求分析

    • 明确游戏目标:控制蛇吃食物变长,避免碰撞自身或边界。
    • 确定基本功能:初始化游戏、绘制游戏界面、处理用户输入、蛇的移动与增长、食物的随机生成、碰撞检测、得分计算、游戏结束与重开机制。
  2. 技术选型与环境搭建

    • 准备vs2022
  3. 核心模块设计

    • 游戏界面:设计简洁直观的游戏界面,包括游戏区域、分数显示、游戏说明等。
    • 蛇的实现
      • 数据结构:链表或数组来存储蛇身的各个部分。
      • 移动逻辑:根据用户输入改变蛇头方向,自动延伸蛇身。
    • 食物管理:随机位置生成食物,检测蛇头碰撞并重新生成。
    • 碰撞检测:检查蛇头是否触碰边界或自身身体。
    • 得分系统:每次吃到食物增加分数,记录最高分。
  4. 用户交互

    • 接收用户输入(键盘/触摸),转换为游戏内操作指令。
    • 提供开始新游戏、退出游戏等选项。
  5. 性能与优化

    • 优化游戏循环,确保流畅运行。
    • 考虑内存管理,避免内存泄漏。

        在这个数字编织的宇宙🌌中,你已悄然绘制了一条属于自己的璀璨轨迹。

        每一次思考与实践,都在为你的技能之树浇灌成长的甘露🌱。愿你带着这份宝贵的经历,继续遨游于代码的星辰大海,发现更多的奇遇与美好。

      加油,程序员之旅的探险者们,前路广阔,星辰大海,我们共勉!🚀✨

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1790445.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMD-PSO-LSTM单维时序预测模型(单输入单输出)-附代码

VMD-PSO-LSTM单维时序预测模型&#xff08;单输入单输出&#xff09; 1&#xff09;首先对原始单维数据进行VMD分解&#xff0c;分解为K个模态分量和1个残差分量 2&#xff09;将各个模态分量输入模型&#xff0c;建立模型进行预测 3&#xff09;将各个预测结果相加得到最终…

Redis-02

redis安装包位置 /opt/redis-7.2.5 redis默认安装路径&#xff1a; 配置文件路径&#xff1a;/usr/local/bin/redisconfig gcc安装位置 /opt/rhwindows在D:\bs\code\Redis\Redis-x64-3.2.100目录下 启动&#xff1a;进入redis安装目录--->cmd打开--->输入"redis-s…

【面试笔记】C++ 软件开发工程师,智驾研发方向(非算法)

文章目录 1. 前言2. 基础问题2.1 什么是C++中的类?如何定义和实例化一个类?2.2 请解释C++中的继承和多态性。2.3 什么是虚函数?为什么在基类中使用虚函数?2.4 解释封装、继承和多态的概念,并提供相应的代码示例。2.5 如何处理内存泄漏问题?提供一些常见的内存管理技术。2…

【Java】数据加密

目录 数据加密介绍使用场景密码学历史古代密码学凯撒密码例子特点 维吉尼亚密码原理例子特点 现代密码学介绍 现代密码学的加密算法分类哈希算法优点缺点代码示例【封装写法】 对称加密算法对称加密算法的加密过程解密过程对称加密算法的优点&#xff1a;对称加密算法的缺点&am…

【C语言】结构体(及位段)

你好&#xff01;感谢支持孔乙己的新作&#xff0c;本文就结构体与大家分析我的思路。 希望能大佬们多多纠正及支持 &#xff01;&#xff01;&#xff01; 个人主页&#xff1a;爱摸鱼的孔乙己-CSDN博客 欢迎 互粉哦&#x1f648;&#x1f648;&#xff01; 目录 1. 声明结构…

LeetCode-43. 字符串相乘【数学 字符串 模拟】

LeetCode-43. 字符串相乘【数学 字符串 模拟】 题目描述&#xff1a;解题思路一&#xff1a;模拟乘法&#xff0c;两个数中每一位数相乘的时候乘上他们各自的进制数&#xff0c;之后求和。循环时&#xff0c;分别记录各自的进制数背诵版&#xff1a;解题思路三&#xff1a;0 题…

hcia datacom学习(12):vlan间路由

不同vlan相当于不同网段&#xff0c;如果vlan间没有三层技术&#xff0c;那么它们就无法互相通信。 vlan间路由可以有3种方式&#xff1a; 1.直接使用路由器转发 *路由器本身不需要额外设置&#xff0c;只需配置端口ip作为网关即可。 *路由器不能处理带有vlan标签的数据帧&a…

Linux下SpringBoot项目部署(centos系统)

一、首先找到自己的sql文件&#xff0c;没有就从数据库挪进来 二、在Maven下打包一下&#xff08;点击package&#xff09;&#xff0c;看到BUILD SUCCESS就是打包好了 三、将上面两个文件分别挪到 linux 中对应的文件&#xff0c;没有就创建一个&#xff08;我的是spring_blog…

centos安装部署Mysql8详细教程

文章目录 一、下载安装1.下载2.安装 二、常见问题1.You must reset your password using ALTER USER statement before executing this statement2.IP is not allowed to connect to this mysql 结尾 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、…

【初识Objective-C】

Objective-C学习 什么是OCOC的特性OC跑的第一个程序helloworld OC的一些基础知识标识符OC关键字数据类型字符型c字符串为什么NSString类型定义时前面要加和普通的c对象有什么区别 一些基础知识if语句switch语句三种循坏语句for循环&#xff1a;用于固定次数的循环while循环&…

转让北京防水防腐保温工程施工二级资质流程和要求

防水资质全称叫作防水防腐保温专业承包二级资质&#xff0c;办理的条件和要求相对于其他专业的资质门槛比较低&#xff0c;如果条件允许建议企业直接新办&#xff0c;因为转让还是有一定风险存在&#xff0c;防水二级资质转让的费用取决于多个因素&#xff0c;如地区、市场需求…

HarmonyOS(30) @LocalStorageLink使用指南

这里写目录标题 LocalStorageLink。使用示例参考资料 LocalStorageLink。 LocalStorage是页面级的UI状态存储&#xff0c;通过Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。而LocalStorageLink则是LocalStorage 相关装饰器之一。LocalStorageLink装饰的变量…

低代码开发平台一般都有哪些功能和模块?

在当今快速变化的数字化时代&#xff0c;企业对于高效、灵活且经济的软件开发解决方案的需求愈发迫切。低代码开发平台应运而生&#xff0c;成为众多企业实现数字化转型的首选工具。本文将详细探讨低代码开发平台一般具备的主要功能和模块&#xff0c;以及它们如何助力企业提升…

Java数据结构-哈希表

目录 1. 概念2. 哈希冲突2.1 冲突的避免2.1.1 设计合理的哈希函数2.1.2 降低负载因子 2.2 冲突的解决-闭散列2.3 冲突的解决-开散列 3. 哈希桶的实现 1. 概念 哈希表&#xff08;Hash table&#xff0c;也叫散列表&#xff09;&#xff0c;是根据关键码值(Key)而直接进行访问的…

反激变压器的设计要点

反激电源的设计最关键的就是在于开关电源的变压器&#xff0c;我们对于反激电源变压器的设计计算的最终目的是为了得到一下几点&#xff1a; 1 原边和副边的电流波形 2 原边和副边的电压波形或幅值 3 磁通密度状况 &#xff08;我们选择的磁芯是不是饱和了&#xff0c;是不是…

Matplotlib | 绘制柱状图

简介 安装 Matplotlib 开始绘制 简单柱状图 改变颜色 改变纹理 改变边框样式 改变透明度 改变柱子宽度 改变图表标题 ​编辑 并列柱状图 横向柱状图 堆叠柱状图 更多函数 简介 柱状图&#xff08;Bar chart&#xff09;&#xff0c;是一种以长方形的长度为变量的…

基于JSP的人才公寓管理系统

你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有需求可以文末加我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSP技术 工具&#xff1a;浏览器&#xff08;如360浏览器、谷歌浏览器、QQ浏览器等&#xff09;、MySQL数据库 系统展示 …

实时数据传输:Django 与 MQTT 的完美结合

文章目录 准备工作创建 Django 项目与应用设置 MQTT 服务器编写 Django 视图编写前端模板发布 MQTT 消息运行 Django 项目 在当今互联网应用中&#xff0c;实时数据传输已经成为许多项目的核心需求。无论是社交媒体平台、在线游戏、金融交易还是物联网设备&#xff0c;都需要及…

OneCommander使用与安装手册

OneCommander使用与安装手册 一、引言 OneCommander是一款专为Windows 10和Windows 11用户设计的现代化文件管理器&#xff0c;它提供了直观、高效的文件浏览和管理体验。本手册将指导您完成OneCommander的安装过程&#xff0c;并介绍其主要功能和操作方法。 二、安装前准备…

8个免费下载音乐的网站,建议收藏!

1、My Free MP3 tools.liumingye.cn/music/ 一个好用且免费的在线音乐播放和下载网站&#xff0c;几乎收录了所有国内外大火的歌手和歌曲&#xff0c;可以通过歌手列表找单曲&#xff0c;也可以直接搜索歌手或歌曲名&#xff0c;下面还有一些热门搜索&#xff0c;可以直接播放…