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

c语言贪吃蛇游戏开发

C 语言实战:Win32 API 打造经典贪吃蛇游戏(附完整代码解析)

大家好!今天我们来拆解一个经典编程练手项目 ——C 语言 + Win32 API 实现贪吃蛇。作为入门级游戏开发案例,贪吃蛇不仅能巩固 C 语言语法,还能让你掌握「数据结构(链表)」「控制台交互(Win32 API)」「游戏逻辑设计」等核心技能。本文会从项目结构、核心函数到游戏逻辑,一步步带你看懂每一行代码,即使是编程新手也能轻松跟上。

一、前置知识与环境准备

在开始前,先确认你具备这些基础,以及准备好开发环境:

1. 前置知识

  • C 语言基础:结构体、枚举、动态内存管理(malloc/free)、宏定义

  • 数据结构:链表的基本操作(头插法、遍历、节点删除)

  • 简单 Win32 API 概念:控制台窗口控制、光标操作、按键检测

2. 开发环境

推荐使用 Visual Studio 2022/2019(支持 Win32 API,无需额外配置),创建「控制台应用」项目即可。

3. 项目结构

我们将代码拆分为 3 个文件,职责清晰,便于维护:

文件名作用
snake.h头文件:声明枚举(方向 / 游戏状态)、结构体(蛇节点 / 蛇管理)、函数原型
snake.cpp源文件:实现所有游戏逻辑函数(地图创建、蛇移动、碰撞检测等)
test.cpp入口文件:主函数(初始化环境、调用游戏启动 / 运行 / 结束函数)

二、核心头文件解析(snake.h)

头文件是项目的「骨架」,定义了游戏的核心数据结构和接口。我们逐段解析关键内容:

1. 引入依赖与宏定义

#pragma once
#include <windows.h>   // Win32 API头文件(控制台控制、按键检测等)
#include <time.h>      // 随机数种子(食物随机生成)
#include <stdio.h>     // 输入输出(printf/wprintf)// 按键检测宏:判断某个键是否被按下(基于GetAsyncKeyState)
#define KEY_PRESS(VK) ((GetAsyncKeyState(VK) & 0x1) ? 1 : 0)// 宽字符定义(地图、蛇身、食物的显示符号)
#define WALL L'□'   // 墙体符号(宽字符,占2个字节)
#define BODY L'●'   // 蛇身符号
#define FOOD L'★'   // 食物符号// 蛇的初始位置(X必须是2的倍数,避免宽字符显示错位)
#define POS_X 24  // 初始X坐标
#define POS_Y 5   // 初始Y坐标
  • 宽字符说明:普通字符(如'a')占 1 字节,中文 / 特殊符号(如)需用宽字符wchar_t,前缀加L(如L'□'),否则会显示乱码。

  • KEY_PRESS 宏GetAsyncKeyState(VK)获取按键状态,返回值最低位为 1 表示按键被按下,通过&0x1提取该位,简化按键判断逻辑。

2. 枚举定义(清晰表示状态)

用枚举替代魔法数字,让代码更易读:

// 蛇的移动方向
enum DIRECTION {UP = 1,    // 上DOWN,      // 下(默认比前一个值+1,即2)LEFT,      // 左(3)RIGHT      // 右(4,初始方向)
};// 游戏状态
enum GAME_STATUS {OK,             // 正常运行KILL_BY_WALL,   // 撞墙死亡KILL_BY_SELF,   // 撞自身死亡END_NOMAL       // 主动退出(ESC键)
};

3. 结构体定义(数据模型)

贪吃蛇的核心是「蛇身」和「食物」,用链表管理蛇身(动态变长),用结构体封装蛇的整体状态:

// 蛇身节点(链表的每个节点,存储单个蛇节的坐标)
typedef struct SnakeNode {int x;          // 节点X坐标int y;          // 节点Y坐标struct SnakeNode* next;  // 指向下一个节点的指针(链表核心)
} SnakeNode, *pSnakeNode;// 蛇的整体管理结构体(封装蛇的所有状态)
typedef struct Snake {pSnakeNode _pSnake;    // 指向蛇头的指针(管理整条蛇)pSnakeNode _pFood;     // 指向食物的指针enum DIRECTION _Dir;   // 当前移动方向(默认RIGHT)enum GAME_STATUS _Status;  // 当前游戏状态(默认OK)int _Socre;            // 当前得分int _Add;              // 每个食物的加分(默认10,加速时增加)int _SleepTime;        // 每步休眠时间(控制速度,默认200ms)
} Snake, *pSnake;
  • 链表选择原因:蛇身长度会动态变化(吃食物变长),链表的「头插法」能高效添加新节点,删除尾节点也方便,比数组更灵活。

4. 函数声明(接口约定)

// 游戏流程函数
void GameStart(pSnake ps);  // 游戏初始化(窗口、地图、蛇、食物)
void GameRun(pSnake ps);    // 游戏主循环(按键检测、蛇移动)
void GameEnd(pSnake ps);    // 游戏结束(释放内存、提示原因)// 辅助函数
void SetPos(short x, short y);    // 设置光标位置(控制台定位)
void WelcomeToGame();             // 欢迎界面
void PrintHelpInfo();             // 打印操作提示(右侧帮助栏)
void CreateMap();                 // 创建游戏地图(墙体)
void InitSnake(pSnake ps);        // 初始化蛇身(5个节点)
void CreateFood(pSnake ps);       // 随机生成食物(避免与蛇身重叠)
void pause();                     // 暂停游戏(空格触发)
int NextIsFood(pSnakeNode psn, pSnake ps);  // 判断下一个节点是否是食物
void EatFood(pSnakeNode psn, pSnake ps);    // 吃食物逻辑(蛇身变长)
void NoFood(pSnakeNode psn, pSnake ps);     // 不吃食物逻辑(删尾巴)
int KillByWall(pSnake ps);        // 撞墙检测
int KillBySelf(pSnake ps);        // 撞自身检测
void SnakeMove(pSnake ps);        // 蛇移动核心逻辑

三、核心源文件解析(snake.cpp)

这部分是游戏的「肌肉」,实现了所有交互逻辑。我们挑关键函数拆解:

1. 辅助函数:控制台定位与界面搭建

(1)SetPos:设置光标位置

要在控制台指定位置显示内容(如蛇身、食物),必须先移动光标,否则会出现乱跳:

void SetPos(short x, short y) {COORD pos = {x, y};  // COORD是Win32定义的坐标结构体(X横向,Y纵向)HANDLE hOutput = NULL;// 获取标准输出设备句柄(控制台窗口)hOutput = GetStdHandle(STD_OUTPUT_HANDLE);// 设置光标位置到指定坐标SetConsoleCursorPosition(hOutput, pos);
}
  • 控制台坐标规则:左上角为(0,0),X 轴从左到右递增,Y 轴从上到下递增(和数学坐标系不同)。

  • 宽字符坐标注意:由于WALL/BODY是宽字符(占 2 个 X 坐标),蛇的 X 坐标必须是 2 的倍数(如 24、26),否则会显示错位。

(2)WelcomeToGame:欢迎界面

游戏启动前的引导界面,提升用户体验:

void WelcomeToGame() {SetPos(40, 15);  // 光标定位到中间位置printf("欢迎来到贪吃蛇小游戏");SetPos(40, 25);  // 定位到下方system("pause"); // 按任意键继续system("cls");   // 清屏(准备显示操作提示)// 显示操作说明SetPos(25, 12);printf("用 ↑ ↓ ← → 分别控制蛇的移动,F3为加速,F4为减速\n");SetPos(25, 13);printf("加速将能得到更高的分数!\n");SetPos(40, 25);system("pause");system("cls");   // 清屏(准备显示地图)
}
(3)CreateMap:创建游戏地图

地图由「上下左右四堵墙」组成,用宽字符WALL绘制:

void CreateMap() {int i = 0;// 1. 上墙:Y=0,X从0到56(步长2,宽字符)SetPos(0, 0);for (i = 0; i < 58; i += 2) {wprintf(L"%c", WALL);  // 宽字符打印用wprintf}// 2. 下墙:Y=26,X从0到56SetPos(0, 26);for (i = 0; i < 58; i += 2) {wprintf(L"%c", WALL);}// 3. 左墙:X=0,Y从1到25for (i = 1; i < 26; i++) {SetPos(0, i);wprintf(L"%c", WALL);}// 4. 右墙:X=56,Y从1到25for (i = 1; i < 26; i++) {SetPos(56, i);wprintf(L"%c", WALL);}
}
  • 地图大小:横向X=0~56(共 29 个宽字符位置),纵向Y=0~26(共 27 行),中间区域为游戏可移动范围。

2. 核心逻辑:蛇与食物的初始化

(1)InitSnake:初始化蛇身(5 个节点)

用「头插法」创建 5 个链表节点,初始方向为右,X 坐标依次递增 2(宽字符对齐):

void InitSnake(pSnake ps) {pSnakeNode cur = NULL;int i = 0;// 1. 头插法创建5个蛇节点for (i = 0; i < 5; i++) {// 动态分配节点内存cur = (pSnakeNode)malloc(sizeof(SnakeNode));if (cur == NULL) {perror("InitSnake()::malloc()");  // 内存分配失败提示return;}// 设置节点坐标:X=24+2*i(右移),Y=5(固定行)cur->x = POS_X + i * 2;cur->y = POS_Y;cur->next = NULL;// 头插法:新节点作为新蛇头if (ps->_pSnake == NULL) {ps->_pSnake = cur;  // 第一个节点直接作为蛇头} else {cur->next = ps->_pSnake;  // 新节点指向旧蛇头ps->_pSnake = cur;        // 更新蛇头为新节点}}// 2. 打印蛇身(遍历链表)cur = ps->_pSnake;while (cur) {SetPos(cur->x, cur->y);wprintf(L"%c", BODY);cur = cur->next;}// 3. 初始化蛇的状态ps->_SleepTime = 200;  // 初始速度(200ms一步)ps->_Socre = 0;        // 初始得分0ps->_Status = OK;      // 游戏状态正常ps->_Dir = RIGHT;      // 初始方向右ps->_Add = 10;         // 每个食物加10分
}
  • 头插法优势:蛇移动时,新节点(蛇头)需要添加在最前面,头插法时间复杂度为 O (1),效率高。
(2)CreateFood:随机生成食物

食物需满足两个条件:① X 是 2 的倍数(宽字符对齐);② 不与蛇身重叠:

void CreateFood(pSnake ps) {int x = 0, y = 0;
again:  // 标签:若食物与蛇身重叠,重新生成// 1. 随机生成坐标:X∈[2,54](步长2),Y∈[1,25]do {x = rand() % 53 + 2;  // X范围:2~54(53=56-2-1,避免超出右墙)y = rand() % 25 + 1;  // Y范围:1~25(避免超出上下墙)} while (x % 2 != 0);  // 确保X是2的倍数// 2. 检查食物是否与蛇身重叠pSnakeNode cur = ps->_pSnake;while (cur) {if (cur->x == x && cur->y == y) {goto again;  // 重叠则重新生成}cur = cur->next;}// 3. 创建食物节点并打印pSnakeNode pFood = (pSnakeNode)malloc(sizeof(SnakeNode));if (pFood == NULL) {perror("CreateFood::malloc()");return;}pFood->x = x;pFood->y = y;SetPos(x, y);wprintf(L"%c", FOOD);ps->_pFood = pFood;  // 关联食物指针
}
  • 随机数种子:在test.cpp中用srand((unsigned int)time(NULL))初始化,确保每次运行食物位置不同。

3. 游戏主循环与移动逻辑

(1)GameRun:游戏主循环

游戏的「心脏」,持续检测按键、更新蛇的状态,直到游戏结束:

void GameRun(pSnake ps) {PrintHelpInfo();  // 打印右侧操作提示// 主循环:只要游戏状态为OK,就持续运行do {// 1. 显示当前得分和加分SetPos(64, 10);printf("得分:%d ", ps->_Socre);printf("每个食物得分:%d分", ps->_Add);// 2. 按键检测与处理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_NOMAL;  // ESC主动退出break;} else if (KEY_PRESS(VK_F3) && ps->_SleepTime >= 50) {// F3加速:减少休眠时间,增加加分ps->_SleepTime -= 30;ps->_Add += 2;} else if (KEY_PRESS(VK_F4) && ps->_SleepTime < 350) {// F4减速:增加休眠时间,减少加分ps->_SleepTime += 30;ps->_Add -= 2;if (ps->_SleepTime == 350) {ps->_Add = 1;  // 最低加分1}}// 3. 控制蛇的移动速度(休眠时间越短,速度越快)Sleep(ps->_SleepTime);// 4. 蛇移动(核心逻辑)SnakeMove(ps);} while (ps->_Status == OK);
}
  • 方向限制:蛇不能直接反向移动(如向右时不能直接向左),避免「瞬间自杀」。

  • 速度控制Sleep(ps->_SleepTime)控制每步间隔,_SleepTime越小,蛇移动越快。

(2)SnakeMove:蛇移动核心逻辑

蛇移动的本质是「添加新蛇头,删除旧蛇尾」(吃食物时不删尾):

void SnakeMove(pSnake ps) {// 1. 创建新蛇头(下一个位置的节点)pSnakeNode pNextNode = (pSnakeNode)malloc(sizeof(SnakeNode));if (pNextNode == NULL) {perror("SnakeMove()::malloc()");return;}// 2. 根据当前方向计算新蛇头坐标switch (ps->_Dir) {case UP:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y - 1;  // Y减1(上移)break;case DOWN:pNextNode->x = ps->_pSnake->x;pNextNode->y = ps->_pSnake->y + 1;  // Y加1(下移)break;case LEFT:pNextNode->x = ps->_pSnake->x - 2;  // X减2(左移,宽字符)pNextNode->y = ps->_pSnake->y;break;case RIGHT:pNextNode->x = ps->_pSnake->x + 2;  // X加2(右移)pNextNode->y = ps->_pSnake->y;break;}// 3. 判断新蛇头是否是食物if (NextIsFood(pNextNode, ps)) {EatFood(pNextNode, ps);  // 吃食物(蛇身变长)} else {NoFood(pNextNode, ps);   // 不吃食物(删尾巴)// 4. 碰撞检测(不吃食物时才检测,避免吃食物后误判)KillByWall(ps);KillBySelf(ps);}
}

4. 碰撞检测与游戏结束

(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;
}
  • 墙体边界:X=0(左墙)、X=56(右墙)、Y=0(上墙)、Y=26(下墙)。
(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;
}
(3)GameEnd:游戏结束处理

释放链表内存(避免内存泄漏),并提示游戏结束原因:

void GameEnd(pSnake ps) {pSnakeNode cur = ps->_pSnake;// 1. 提示结束原因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;}// 2. 释放蛇身节点内存(遍历链表)while (cur) {pSnakeNode del = cur;  // 暂存当前节点cur = cur->next;       // 移动到下一个节点free(del);             // 释放当前节点}// 3. 释放食物节点内存if (ps->_pFood != NULL) {free(ps->_pFood);ps->_pFood = NULL;}
}

四、入口文件解析(test.cpp)

主函数负责初始化环境,调用游戏的「启动 - 运行 - 结束」流程:

#include "Snake.h"
#include <locale.h>  // 宽字符本地化支持// 游戏测试函数
void test() {int ch = 0;// 初始化随机数种子(确保每次运行食物位置不同)srand((unsigned int)time(NULL));do {Snake snake = {0};  // 初始化蛇结构体(所有成员为0)GameStart(&snake);  // 游戏启动(窗口、地图、蛇、食物)GameRun(&snake);    // 游戏运行(主循环)GameEnd(&snake);    // 游戏结束(释放内存、提示原因)// 询问是否再来一局SetPos(20, 15);printf("再来一局吗?(Y/N):");ch = getchar();getchar();  // 清理输入缓冲区的换行符(避免下次循环误判)system("cls");  // 清屏(准备新游戏)} while (ch == 'Y' || ch == 'y');  // 输入Y/y则重新开始SetPos(0, 27);  // 光标定位到窗口底部
}int main() {// 设置本地化环境(支持中文宽字符显示,避免乱码)setlocale(LC_ALL, "");test();  // 调用游戏测试函数return 0;
}
  • setlocale(LC_ALL, “”):关键!设置当前系统的本地化环境,确保宽字符(中文、□、●等)正常显示,否则会出现乱码。

五、运行效果与优化方向

1. 运行效果

  1. 启动后显示欢迎界面,按任意键进入操作提示;

  2. 进入游戏界面:左侧为地图(□为墙,●为蛇,★为食物),右侧为操作提示;

  3. 用方向键控制蛇移动,F3 加速(加分增加),F4 减速(加分减少),空格暂停,ESC 退出;

  4. 撞墙 / 撞自身后提示结束原因,询问是否再来一局。

2. 优化方向(进阶练习)

  • 增加难度梯度:随着得分增加,自动加速;

  • 加入排行榜:用文件存储最高得分,每次运行时读取并更新;

  • 自定义皮肤:允许用户选择蛇身、食物、墙体的符号;

  • 音效支持:用 Win32 API 添加吃食物、撞墙的音效;

  • 图形界面:用 EasyX 或 SDL 替代控制台,实现更精美的界面。

六、总结

通过这个贪吃蛇项目,我们不仅巩固了 C 语言基础,还掌握了:

  1. 数据结构:链表在动态数据(蛇身)管理中的实际应用;

  2. Win32 API:控制台窗口控制、光标定位、按键检测等底层交互;

  3. 游戏逻辑:主循环设计、状态管理、碰撞检测等核心思想。

建议大家亲手敲一遍代码,尝试修改参数(如地图大小、初始速度、加分规则),甚至实现上面的优化方向,这样才能真正掌握!

http://www.dtcms.com/a/454073.html

相关文章:

  • 360网站seo优化怎么做简单网站
  • 小说网站静态模板设计师设计费一般多少
  • 网站主机要怎么做跨境电商交3980元培训费可信吗
  • Coze源码分析-资源库-编辑知识库-后端源码-流程/技术/总结
  • 哺乳动物双等位基因表达的 “守护者”--解析 MSL2对基因剂量平衡与疾病机制的新启示--文献精读164
  • 阿里云网站建设需要多少钱开发软件公司都有哪些
  • CTFHub SQL注入通关笔记4:布尔盲注(手注法+脚本法)
  • 虎书Fundamentals Of Computer Graphics翻译及笔记(1)——前言
  • 建网站公司室内设计效果图网站推荐
  • 网站建设收徒弟设计韩国电影完整版下载
  • 应聘网站优化的简历怎么做全新升级网站
  • 我的第一个网站
  • windows下将redis注册为windows服务
  • IT架构中的前后中台:SuperCell与阿里中台战略启示
  • 外贸工厂的网站建设长沙做网站智投未来
  • 国外商品网站企业微网站建站
  • 第六章 面向对象编程(基础部分)
  • 接口联调常见错误排查:从400 Bad Request到502 Gateway
  • 南通网站制作价格微金所网站谁做的
  • 域名哪个网站续费单项否决 网站建设
  • three.js与WebGL
  • 网站推广计划怎么写标书制作员是干什么的
  • Doris专题9- 行列混存和冷热数据分层
  • 山西省建筑工程网怎么做外贸网站优化
  • mysql数据类型——认识并掌握数据类型
  • 做搜狗手机网站山东大源建设集团网站
  • 网站收录怎么设置wordpress 科技联盟
  • asp网站开发教程pdf南阳最新通知今天
  • 三极管元器件的识别、检测与主要参数
  • 个人网站备案如何取名称正规网站建设商家