【C语言】经典贪吃蛇游戏:从设计到代码全解析


效果演示:

贪吃蛇演示

一、核心目标与技术储备

1. 核心目标

本次项目的核心目标是使用C语言在Windows控制台中模拟实现贪吃蛇游戏,具体需要完成以下功能:

  • 绘制贪吃蛇游戏地图
  • 通过方向键(上、下、左、右)控制蛇的移动
  • 实现蛇吃食物增长身体的功能
  • 判定蛇撞墙死亡和蛇撞自身死亡的逻辑
  • 计算并显示游戏得分
  • 支持蛇身加速、减速功能
  • 实现游戏暂停功能

2. 核心技术要点

  • C语言基础:函数、枚举、结构体、动态内存管理、预处理指令
  • 数据结构:链表(用于管理蛇身节点)
  • 系统接口:Win32 API(用于控制台操作)

二、Win32 API核心知识

Windows系统提供了丰富的应用程序编程接口(API),我们需要利用这些接口实现控制台的各种操作。

1. 控制台程序基础

我们日常看到的"黑框程序"就是控制台程序,可通过命令或API函数控制其窗口属性:

  • 设置窗口大小:mode con cols=100 lines=30(100列,30行)
  • 设置窗口标题:title 贪吃蛇
  • 在C语言中通过system函数执行上述命令:
#include <stdio.h>
int main() {
    // 设置控制台窗口大小
    system("mode con cols=100 lines=30");
    // 设置窗口名称
    system("title 贪吃蛇");
    return 0;
}

2. 控制台坐标系统

控制台使用COORD结构体表示坐标,原点(0,0)位于窗口左上角:

// COORD结构体定义
typedef struct _COORD {
    SHORT X;  // 横向坐标(列)
    SHORT Y;  // 纵向坐标(行)
} COORD, *PCOORD;

// 坐标赋值示例
COORD pos = { 10, 15 };  // X=10,Y=15的位置

3. 控制台句柄操作

  • GetStdHandle:获取标准设备(输入、输出、错误)的句柄
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);  // 获取标准输出句柄
  • 光标控制:通过GetConsoleCursorInfoSetConsoleCursorInfo控制光标可见性
// 隐藏光标示例
HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(hOutput, &CursorInfo);  // 获取光标信息
CursorInfo.bVisible = false;  // 隐藏光标
SetConsoleCursorInfo(hOutput, &CursorInfo);  // 设置光标状态
  • 设置光标位置:使用SetConsoleCursorPosition函数
// 封装设置光标位置的函数
void SetPos(short x, short y) {
    COORD pos = { x, y };
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleCursorPosition(hOutput, pos);
}

4. 键盘输入检测

GetAsyncKeyState 的返回值是short类型,在上⼀次调用GetAsyncKeyState 函数后返回的16位的short数据中:

  • 最高位是1,说明按键的状态是按下,
  • 最高是0,说明按键的状态是抬起;
  • 最低位被置为1则说明,该按键被按过,否则为0。

如果我们要判断⼀个键是否被按过,可以检测GetAsyncKeyState返回值的最低值是否为1.

虚拟键码:

常数Value说明
VK_LEFT0x25向左键
VK_UP0x26向上键
VK_RIGHT0x27向右键
VK_DOWN0x28向下键

通过宏定义简化检测逻辑

// 按键检测宏定义
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)

// 检测数字键示例
if (KEY_PRESS(0x30)) printf("0被按下\n");  // 0x30是数字0的虚拟键码
if (KEY_PRESS(VK_UP)) printf("上方向键被按下\n");

00000001按位与得到的结果就是最后一位的数值

三、 数据结构设计

1. 蛇身节点结构

使用链表存储蛇身,每个节点记录坐标信息:

// 蛇身节点结构体
typedef struct SnakeNode {
    int x;  // X坐标
    int y;  // Y坐标
    struct SnakeNode* next;  // 指向 next 节点
} SN, *pSN;

2. 游戏核心结构

封装游戏所需的所有状态信息:

// 方向枚举
enum DIRECTION {
    UP = 1,
    DOWN,
    LEFT,
    RIGHT
};

// 游戏状态枚举
enum GAME_STATUS {
    OK,             // 正常运行
    KILL_BY_WALL,   // 撞墙
    KILL_BY_SELF,   // 咬到自己
    END_NOMAL       // 正常结束
};

// 游戏主结构体
typedef struct Snake {
    pSN _pSnake;      // 蛇身链表头指针
    pSN _pFood;       // 食物节点指针
    enum DIRECTION _Dir;     // 当前方向
    enum GAME_STATUS _Status;// 游戏状态
    int _Socre;              // 当前得分
    int _foodWeight;         // 每个食物的分数
    int _SleepTime;          // 移动间隔时间(控制速度)
} Snake, *pSnake;

四、游戏初始化(initSnake)

1.设置窗口

system("mode con cols=100 lines=30");
system("title 贪吃蛇");

2. 隐藏光标

HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_CURSOR_INFO CursorInfo;
GetConsoleCursorInfo(houtput, &CursorInfo);
CursorInfo.bVisible = false;
SetConsoleCursorInfo(houtput, &CursorInfo);

3. 欢迎界面(welcomeToGame)

在这里插入图片描述

void welcomeToGame()
{
	setPos(40, 14);
	wprintf(L"欢迎来到贪吃蛇大作战");

	setPos(41, 20);
	system("pause");

	//清理屏幕
	system("cls");

	//功能介绍
	setPos(35, 10);
	wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
	setPos(41, 13);
	wprintf(L"按F3加速,F4减速\n");
	setPos(38, 16);
	wprintf(L"加速能够得到更高的分数\n");

	setPos(42, 25);
	system("pause");
	system("cls");
}

4. 游戏地图(createMap)

4.1 地图坐标与宽字符

游戏地图使用宽字符绘制,需注意:

  • 宽字符的字面量必须加上前缀“L”,否则 C 语言会把字面量当作窄字符类型处理。
  • 前缀“L”在单引号前面,表示宽字符,对应 wprintf() 的占位符为 %lc
  • 前缀“L”在双引号前面,表示宽字符串,对应wprintf() 的占位符为 %ls
  • 普通字符占1个字节,宽字符占2个字节

需通过setlocale函数设置本地模式以支持宽字符输出:

#include <locale.h>
int main() {
    setlocale(LC_ALL, "");  // 切换到本地模式,支持中文和宽字符
    return 0;
}
  • 游戏使用的宽字符定义:
#define WALL L'□'  // 墙体
#define BODY L'●'  // 蛇身
#define FOOD L'★'  // 食物

4.2 地图结构

设计一个27行(Y=0到Y=26)、58列(X=0到X=56)的地图:

  • 上边界:(0,0)到(56,0)
  • 下边界:(0,26)到(56,26)
  • 左边界:(0,1)到(0,25)
  • 右边界:(56,1)到(56,25)
    在这里插入图片描述
void createMap()
{
	//上
	for (int i = 0;i < 29;i++)
	{
		wprintf(L"%lc", L'□');
	}
	//下
	setPos(0, 26);
	for (int i = 0;i < 29;i++)
	{
		wprintf(L"%lc", L'□');
	}
	//左 右
	for (int i = 1;i <= 25;i++)
	{
		setPos(0, i);
		wprintf(L"%lc", L'□');
		setPos(56, i);
		wprintf(L"%lc", L'□');
	}
}

5. 初始化蛇身

  • 蛇身初始状态:长度为5节,从坐标(24,5)开始,每节X坐标递增2(适应宽字符)
void initSnake(pSnake ps)
{
	//创建
	pSN cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (pSN)malloc(sizeof(SN));
		if (cur == NULL)
		{
			perror("malloc fail!");
			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"%c", BODY);
		cur = cur->next;
	}

	//设置属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;
	ps->_pFood = 10;
	ps->_sleep_time = 200;
	ps->_status = OK;
}

6.创建食物

食物生成规则

  • 随机生成在墙体内
  • X坐标必须是2的倍数(与蛇身对齐)
  • 不能与蛇身坐标重合
void creatFood(pSnake ps)
{
	int x, y;

again:
	//x:2-54 y:1-25
	do {
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//不能与蛇身重复,便利蛇身
	pSN cur = ps->_psnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//创建食物的节点
	pSN pFood = (pSN)malloc(sizeof(SN));
	if (pFood == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	//打印
	setPos(x, y);
	wprintf(L"%c", FOOD);

	ps->_pFood = pFood;
}

五、游戏运行逻辑

1. 打印帮助信息(printHelpInfo)

void printHelpInfo()
{
	setPos(66, 17);
	wprintf(L"不能穿墙,不能咬到自己");
	setPos(64, 19);
	wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
	setPos(70, 21);
	wprintf(L"按F3加速,按F4减速\n");
	setPos(61, 23);
	wprintf(L"按ESC退出游戏,按空格键暂停或继续游戏\n");
	setPos(71, 26);
	wprintf(L"@Daisy 制作\n");
}

在这里插入图片描述

2. 蛇移动逻辑(SnakeMove)

蛇的移动是游戏的核心机制,需要根据当前方向计算下一个位置,并处理吃食物、撞墙、撞自身等情况。

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;  // 左移2单位(宽字符)
            pNextNode->y = ps->_pSnake->y;
            break;
        case RIGHT:
            pNextNode->x = ps->_pSnake->x + 2;  // 右移2单位(宽字符)
            pNextNode->y = ps->_pSnake->y;
            break;
    }
    
    // 判断下一个位置是否是食物
    if (NextIsFood(pNextNode, ps)) {
        EatFood(pNextNode, ps);  // 吃食物逻辑
    } else {
        NoFood(pNextNode, ps);   // 不吃食物逻辑
    }
    
    // 检测是否撞墙或撞自身
    KillByWall(ps);
    KillBySelf(ps);
}

2.1 判断是否吃到食物(NextIsFood)

通过比较下一个节点与食物的坐标,判断蛇是否吃到食物:

int NextIsFood(pSnakeNode psn, pSnake ps) {
    return (psn->x == ps->_pFood->x) && (psn->y == ps->_pFood->y);
}

2.2 吃食物处理(EatFood)

吃到食物后,蛇身增长,分数增加,并生成新食物:

void EatFood(pSnakeNode psn, pSnake ps) {
    // 头插法将新节点加入蛇身
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    
    // 重新绘制蛇身
    pSnakeNode cur = ps->_pSnake;
    while (cur) {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    
    // 增加分数
    ps->_Socre += ps->_foodWeight;
    
    // 释放当前食物节点并创建新食物
    free(ps->_pFood);
    CreateFood(ps);
}

2.3 未吃食物处理(NoFood)

未吃到食物时,蛇移动后尾部需要删除,保持长度不变:

void NoFood(pSnakeNode psn, pSnake ps) {
    // 头插法添加新节点(蛇头)
    psn->next = ps->_pSnake;
    ps->_pSnake = psn;
    
    // 绘制蛇身(除最后一节)
    pSnakeNode cur = ps->_pSnake;
    while (cur->next->next) {
        SetPos(cur->x, cur->y);
        wprintf(L"%c", BODY);
        cur = cur->next;
    }
    
    // 清除尾部节点并释放内存
    SetPos(cur->next->x, cur->next->y);
    printf(" ");  // 用空格覆盖原尾部
    free(cur->next);
    cur->next = NULL;  // 避免野指针
}

3. 碰撞检测

3.1 撞墙检测(KillByWall)

判断蛇头是否与墙体坐标重合:

int 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 1;
    }
    return 0;
}

3.2 撞自身检测(KillBySelf)

判断蛇头是否与自身身体其他节点坐标重合:

int KillBySelf(pSnake ps) {
    pSnakeNode cur = ps->_pSnake->next;  // 从第二节开始检查
    while (cur) {
        if ((ps->_pSnake->x == cur->x) && (ps->_pSnake->y == cur->y)) {
            ps->_Status = KILL_BY_SELF;  // 设置状态为撞自身
            return 1;
        }
        cur = cur->next;
    }
    return 0;
}

六、游戏结束处理(GameEnd)

当游戏状态变为非OK时,显示结束原因并释放内存资源:

void GameEnd(pSnake ps) {
    pSnakeNode cur = ps->_pSnake;
    SetPos(24, 12);  // 定位显示结束信息
    
    // 根据游戏状态显示不同信息
    switch (ps->_Status) {
        case END_NOMAL:
            printf("您主动退出游戏\n");
            break;
        case KILL_BY_SELF:
            printf("您撞上自己了,游戏结束!\n");
            break;
        case KILL_BY_WALL:
            printf("您撞墙了,游戏结束!\n");
            break;
    }
    
    // 释放蛇身所有节点内存
    while (cur) {
        pSnakeNode del = cur;
        cur = cur->next;
        free(del);
    }
}

七、完整代码结构

1. test.c(程序入口)

包含main函数和测试逻辑,负责启动游戏:

#define _CRT_SECURE_NO_WARNINGS 1;
#include"snake.h"

//游戏测试逻辑
void test()
{
	int ch;
	do
	{
		//创建
		Snake snake = { 0 };


		//初始化
		gameStart(&snake);


		//运行
		gameRun(&snake);

		//结束
		gameEnd(&snake);
		setPos(20, 15);
		printf("再来一局吗?(Y/N)");
		setPos(27, 17);

		ch = getchar();
		getchar();//清理\n
	} while (ch == 'y' || ch == 'Y');
		setPos(0, 27);

}

int main()
{
	setlocale(LC_ALL, "");
	srand((unsigned int)time(NULL));
	test();
	return 0;
}

2. Snake.h(头文件)

声明结构体、枚举和所有函数:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<Windows.h>
#include<stdbool.h>
#include<locale.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 DIRECTION
{
	UP = 1,
	DOWN,
	LEFT,
	RIGHT
};

//游戏状态
enum GAME_STATUS
{
	OK,//正常
	KILL_BY_WALL,//撞墙
	KILL_BY_SELF,//撞到自己
	END_NORMAI//正常退出
};

//蛇身
struct SnakeNode
{
	//坐标
	int x;
	int y;
	struct SnakeNode* next;
};
typedef struct SnakeNode SN;
typedef struct SnakeNode* pSN;

//蛇
typedef struct snake
{
	pSN _psnake;//指向蛇头的指针
	pSN _pFood;//指向食物的指针
	enum DIRECTION _dir;//蛇头方向
	enum GAME_STATUS _status;//游戏状态
	int _food_weight;//一个食物的分数
	int _score;//分数
	int _sleep_time;//休息时间--->速度
}Snake,* pSnake;





//函数声明

//初始化
void gameStart(pSnake ps);

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

//欢迎界面
void welcomeToGame();

//创建地图
void createMap();

//初始化蛇身
void initSnake(pSnake ps);

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


//游戏运行逻辑
void gameRun(pSnake ps);

//蛇运动--一步
void snakeMove(pSnake ps);

//判断下一个节点是否为食物
int NextIsFood(pSN pn, pSnake ps);

//吃掉食物
void eatFood(pSN pn, pSnake ps);

//不是食物
void notFood(pSN pn, pSnake ps);

//检测是否撞墙
void killByWall(pSnake ps);

//检测蛇是否撞到自己
void killBySelf(pSnake ps);


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

3. 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(40, 14);
	wprintf(L"欢迎来到贪吃蛇大作战");

	setPos(41, 20);
	system("pause");

	//清理屏幕
	system("cls");

	//功能介绍
	setPos(35, 10);
	wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
	setPos(41, 13);
	wprintf(L"按F3加速,按F4减速\n");
	setPos(38, 16);
	wprintf(L"加速能够得到更高的分数\n");

	setPos(42, 25);
	system("pause");
	system("cls");
}

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

//初始化蛇身
void initSnake(pSnake ps)
{
	//创建
	pSN cur = NULL;
	for (int i = 0; i < 5; i++)
	{
		cur = (pSN)malloc(sizeof(SN));
		if (cur == NULL)
		{
			perror("malloc fail!");
			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"%c", BODY);
		cur = cur->next;
	}

	//设置属性
	ps->_dir = RIGHT;//默认向右
	ps->_score = 0;
	ps->_pFood = 10;
	ps->_sleep_time = 200;
	ps->_status = OK;
	ps->_food_weight = 10;
}

//创建食物
void createFood(pSnake ps)
{
	int x, y;

again:
	//x:2-54 y:1-25
	do {
		x = rand() % 53 + 2;
		y = rand() % 25 + 1;
	} while (x % 2 != 0);

	//不能与蛇身重复,便利蛇身
	pSN cur = ps->_psnake;
	while (cur)
	{
		if (x == cur->x && y == cur->y)
		{
			goto again;
		}
		cur = cur->next;
	}

	//创建食物的节点
	pSN pFood = (pSN)malloc(sizeof(SN));
	if (pFood == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	pFood->x = x;
	pFood->y = y;
	pFood->next = NULL;

	//打印
	setPos(x, y);
	wprintf(L"%c", FOOD);

	ps->_pFood = pFood;
}

//初始化
void gameStart(pSnake ps)
{
	//1.窗口大小
	system("mode con cols=100 lines=30");
	system("title 贪吃蛇");

	//2.隐藏光标
	HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE);
	CONSOLE_CURSOR_INFO CursorInfo;
	GetConsoleCursorInfo(houtput, &CursorInfo);
	CursorInfo.bVisible = false;
	SetConsoleCursorInfo(houtput, &CursorInfo);

	//3.欢迎界面
	welcomeToGame();
	
	//4.绘制地图
	createMap();

	//5.创建蛇
	initSnake(ps);

	//6.创建食物
	createFood(ps);
}






//帮助信息
void printHelpInfo()
{
	setPos(66, 15);
	wprintf(L"不能穿墙,不能咬到自己");
	setPos(64, 17);
	wprintf(L"用 → ← ↑ ↓ 来控制蛇的移动\n");
	setPos(70, 19);
	wprintf(L"按F3加速,按F4减速\n");
	setPos(61, 21);
	wprintf(L"按ESC退出游戏,按空格键暂停或继续游戏\n");
	setPos(71, 26);
	wprintf(L"@Daisy 制作\n");
}

//暂停
void pause()
{
	while (1)
	{
		Sleep(200);
		if (KEY_PRESS(VK_SPACE))
			break;
	}
}

//判断下一个坐标是否为食物
int NextIsFood(pSN pn, pSnake ps)
{
	return (ps->_pFood->x == pn->x && ps->_pFood->y == pn->y);
}

//吃掉食物
void eatFood(pSN pn,pSnake ps)
{
	//头插增加蛇身长
	ps->_pFood->next = ps->_psnake;
	ps->_psnake = ps->_pFood;

	//释放下一位置节点
	free(pn);
	pn = NULL;
	pSN cur = ps->_psnake;

	//打印
	while (cur)
	{
		setPos(cur->x, cur->y);
		wprintf(L"%lc", BODY);
		cur = cur->next;
	}
	ps->_score += ps->_food_weight;

	//创建新食物
	createFood(ps);
}

//不是食物
void notFood(pSN pn, pSnake ps)
{
	//头插蛇头
	pn->next = ps->_psnake;
	ps->_psnake = pn;

	//蛇身
	pSN cur = ps->_psnake;
	while (cur->next->next)
	{
		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;
}

//检测蛇是否撞到自己
void killBySelf(pSnake ps)
{
	pSN 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)
{
	//创建一个节点,表示蛇的下一个节点
	pSN pNextNode = (pSN)malloc(sizeof(SN));
	if (pNextNode == NULL)
	{
		perror("snakeMove()::malloc");
	}
	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
	{
		notFood(pNextNode, ps);
	}

	//检测蛇是否撞墙
	killByWall(ps);

	//检测蛇是否撞到自己
	killBySelf(ps);
}

//游戏运行
void gameRun(pSnake ps)
{
	//打印帮助信息
	printHelpInfo();
	do
	{
		//打印总分数和食物分值
		setPos(64, 6);
		printf("得分:%d", ps->_score);
		setPos(64, 8);
		printf("食物分值:%d", 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_NORMAI;
		//加速
		else if (KEY_PRESS(VK_F3))
		{
			if (ps->_sleep_time >= 80)
			{
				ps->_sleep_time -= 30;
				ps->_score += 2;
			}
		}
		//减速
		else if (KEY_PRESS(VK_F4))
		{
			if (ps->_sleep_time <= 320)
			{
				ps->_sleep_time += 30;
				ps->_score -= 2;
			}
		}

		snakeMove(ps);//蛇走一步的过程
		Sleep(ps->_sleep_time);

	} while (ps->_status == OK);
}

//结束游戏
void gameEnd(pSnake ps)
{
	setPos(21, 12);
	switch (ps->_status)
	{
		case END_NORMAI:
		{
		printf("退出游戏");
			break;
		}
		case KILL_BY_WALL:
		{
			printf("撞墙,游戏结束\n");
			break;
		}
		case KILL_BY_SELF:
		{
			printf("撞到自己,游戏结束\n");
			break;
		}
	}

	//释放链表
	pSN cur = ps->_psnake;
	while (cur)
	{
		pSN del = cur;
		cur = cur->next;
		free(del);
	}
}

八、控制台设置与常见问题

1. Win11系统控制台适配

部分Win11用户可能遇到控制台显示异常,可按以下步骤调整:

  1. 打开命令提示符,点击右上角设置图标
  2. 进入"默认终端应用程序"设置
  3. 将"终端启动时"的默认配置文件设置为"命令提示符"
  4. 调整启动大小为合适的行列数
  5. 保存设置后重新打开控制台

2. 宽字符显示问题

若游戏中墙体、蛇身或食物显示为乱码,需检查:

  1. 是否正确调用setlocale(LC_ALL, "")切换到本地模式
  2. 宽字符打印是否使用wprintf函数并添加L前缀
  3. 控制台字体是否支持中文和特殊字符

3. 编译环境

推荐使用支持Win32 API的编译环境,如:

  • Microsoft Visual Studio
  • Dev-C++(需配置链接器支持Windows库)
  • MinGW(需包含Windows头文件)

通过以上步骤,我们完成了贪吃蛇游戏的设计与实现。这个项目涵盖了C语言结构体、枚举、链表等核心知识点,以及Win32 API控制台操作技巧,是一个非常适合巩固编程基础的实践案例。

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值