C++贪吃蛇游戏代码学习笔记(附完整实现解析)

C++贪吃蛇游戏代码学习笔记(附完整实现解析)

C++贪吃蛇游戏代码学习笔记(附完整实现解析)

一、游戏核心概述

1.1 游戏原理

贪吃蛇游戏是经典的即时交互游戏,核心逻辑为:通过键盘控制蛇的移动方向,蛇在固定地图内追逐食物,吃到食物后身体变长、分数增加且移动速度加快,若蛇头触碰边界或自身身体则游戏结束。

本实现基于C++控制台开发,借助Windows API控制光标位置与隐藏,通过时间戳控制蛇的移动频率,采用结构化设计封装地图、蛇、坐标等核心要素,逻辑清晰且易扩展。

1.2 核心依赖库

库名核心用途
iostream控制台输入输出(绘制地图、显示分数等)
windows.h控制控制台光标(隐藏、移动)、获取标准输出句柄
chrono时间戳计算,控制蛇的移动频率(毫秒级精度)
conio.h检测键盘输入(_kbhit())、获取按键(_getch()
ctime/cstdlib生成随机数种子(srand()),用于食物随机生成
sstream/cstring分数转换为字符串(实时更新分数显示)

二、核心数据结构设计

代码采用结构体封装游戏核心要素,实现数据与操作的解耦,符合模块化编程思想,主要包含以下4种核心结构:

2.1 坐标结构体(pos)

struct pos { int x; // 横向坐标(对应地图列数) int y; // 纵向坐标(对应地图行数) }; 

解析:作为基础数据结构,用于表示蛇头、蛇身、食物在地图上的位置,所有与“位置”相关的操作(移动、绘制、碰撞检测)均依赖此结构。

2.2 地图结构体(map)

enum blocktype { FOOD = 1, // 食物标识 EMPTY = 0 // 空区域标识 }; struct map { blocktype block[H][L]; // 地图二维数组(H行L列,宏定义H=27,L=52) bool hasfood; // 标记地图上是否存在食物(避免重复生成) }; 

关键说明:

  • 用枚举blocktype定义地图格子类型,使代码语义更清晰,避免魔法值;
  • 二维数组block存储整个地图的状态,每个元素表示对应位置是食物还是空区域;
  • hasfood作为食物生成的“开关”,仅当地图无食物时才触发生成逻辑,提升效率。

2.3 蛇结构体(snake)

const int fangxiang[4][2] = { {-1,0},//向上(y减1,x不变) {1,0},//向下(y加1,x不变) {0,-1},//向左(x减1,y不变) {0,1} };//向右(x加1,y不变) struct snake { pos aspos[H * L]; // 存储蛇身所有节的位置(最大长度为地图总格子数) int sfangxiang; // 当前移动方向(对应fangxiang数组下标:0-上,1-下,2-左,3-右) int schangdu; // 蛇的当前长度 int movelasttime; // 上一次移动的时间戳(毫秒) int movepinglu; // 移动频率(毫秒/次,值越小速度越快) }; 

关键说明:

  • 方向数组fangxiang:将方向与坐标变化绑定,通过下标快速获取移动时的坐标偏移量,简化方向控制逻辑;
  • aspos数组:按“头在前、尾在后”的顺序存储蛇身位置,aspos[0]固定为蛇头;
  • 移动控制相关属性:movelasttimemovepinglu配合,实现蛇移动频率的精确控制,避免移动过快或过慢。

三、核心功能模块解析

代码按“初始化→绘制→交互控制→逻辑判断→主循环”的流程组织,各模块职责单一,相互配合完成游戏运行。

3.1 初始化模块

负责游戏启动时的环境准备,包括地图、蛇的初始化及光标隐藏,为游戏运行奠定基础。

3.1.1 地图初始化(intimap)

void intimap(map& Map) { for (int y = 0; y < H; y++) { for (int u = 0; u < L; u++) { Map.block[y][u] = blocktype::EMPTY; // 所有格子初始化为空 } } Map.hasfood = false; // 初始无食物 } 

解析:双重循环遍历地图二维数组,将所有位置设为空区域,同时标记无食物状态,确保游戏初始环境干净。

3.1.2 蛇初始化(intisnake)

void intisnake(snake& asnake) { asnake.sfangxiang = 3; // 初始方向向右(符合玩家操作习惯) asnake.schangdu = 3; // 初始长度为3 // 初始位置:地图居中,横向排列(头在右,尾在左) asnake.aspos[0] = { L / 2, H / 2 }; asnake.aspos[1] = { L / 2 - 1, H / 2 }; asnake.aspos[2] = { L / 2 - 2, H / 2 }; asnake.movelasttime = 0; // 初始移动时间戳为0 asnake.movepinglu = 200; // 初始移动频率:200毫秒/次 } 

解析:初始化蛇的核心状态,包括方向、长度、初始位置和移动参数。初始位置设为地图居中,提升游戏体验;方向默认向右,符合多数玩家的操作直觉。

3.1.3 光标隐藏(hidecursor)

void hidecursor() { HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出句柄 CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; // 光标信息:大小1,隐藏 SetConsoleCursorInfo(hOutput, &curInfo); // 设置光标状态 } 

解析:通过Windows API隐藏控制台光标,避免光标闪烁影响游戏画面的连贯性,提升视觉体验,是控制台游戏开发的常用优化手段。

3.2 绘制模块

负责将地图、蛇、食物、分数等游戏元素渲染到控制台,是玩家与游戏交互的视觉载体,核心为“精准定位+内容输出”。

3.2.1 地图绘制(drawmap)

void drawmap(map& tmap) { system("cls"); // 清空控制台(避免上一帧画面残留) // 绘制上边框 cout << "┌"; for (int i = 0; i < L; i++) cout << "─"; cout << "┐" << endl; // 绘制中间区域(边框+空区域) for (int p = 0; p < H; p++) { cout << "│"; // 绘制左边框 for (int a = 0; a < L; a++) { if (tmap.block[p][a] == blocktype::EMPTY) cout << " "; // 空区域输出空格 } cout << "│" << endl; // 绘制右边框 } // 绘制下边框 cout << "└"; for (int i = 0; i < L; i++) cout << "─"; cout << "┘" << endl; } 

关键说明:

  • system("cls")清空控制台,确保每帧画面干净,避免残留;
  • 采用Unicode边框字符(┌、─、┐等),使地图边框更美观,区别于传统的*或#边框;
  • 中间区域仅绘制边框和空区域,蛇和食物通过后续独立绘制函数渲染,实现“分层绘制”,逻辑更清晰。

3.2.2 单个元素绘制(drawunit/drawunit2)

void drawunit(pos& p, const char unit[]) { COORD coord; // Windows API坐标结构 HANDLE houtput = GetStdHandle(STD_OUTPUT_HANDLE); // 坐标偏移:边框占1行1列,需将地图坐标转换为控制台坐标 coord.X = p.x + 1; coord.Y = p.y + 1; SetConsoleCursorPosition(houtput, coord); // 移动光标到目标位置 cout << unit; // 输出元素(蛇身用■,食物用●) } 

解析:游戏绘制的核心工具函数,通过SetConsoleCursorPosition精准移动光标到目标位置,再输出对应元素。关键在于“坐标偏移”——地图坐标(0,0)对应控制台的(1,1),因为边框占用了第一行和第一列。

3.2.3 蛇绘制(drawsnake)

void drawsnake(snake& snk) { for (int g = 0; g < snk.schangdu; ++g) { drawunit(snk.aspos[g], "■"); // 遍历蛇身所有节,绘制为■ } } 

解析:遍历蛇身位置数组aspos,调用drawunit将每一节蛇身绘制为“■”,实现蛇的可视化。由于蛇头和蛇身采用相同样式,通过位置(aspos[0])区分蛇头。

3.3 核心逻辑模块

是游戏的“大脑”,包含蛇的移动、碰撞检测、食物生成、分数计算等核心规则,决定游戏的运行状态。

3.3.1 蛇的移动(movesnake)

void movesnake(snake& snk) { // 蛇身跟随:从尾部到头部,依次复制前一节的位置(避免覆盖) for (int f = snk.schangdu - 1; f >= 1; f--) { snk.aspos[f] = snk.aspos[f - 1]; } // 蛇头移动:根据当前方向更新坐标(使用方向数组的偏移量) snk.aspos[0].y += fangxiang[snk.sfangxiang][0]; snk.aspos[0].x += fangxiang[snk.sfangxiang][1]; } 

关键逻辑(重点):

  1. 蛇身跟随规则:必须从尾部开始向前更新位置,若从头部开始会导致所有节都复制蛇头位置,蛇身“消失”。核心思想是“每一节都移动到前一节的旧位置”;
  2. 蛇头移动规则:通过方向数组获取坐标偏移量,直接更新蛇头位置,无需遍历,效率为O(1)。

时间复杂度:O(n)(n为蛇的长度),仅与蛇身长度相关,效率较高。

3.3.2 碰撞检测(checkbianyuan + 吃食物检测)

碰撞检测是游戏结束和状态更新的核心触发条件,分为“边界碰撞”“自身碰撞”和“食物碰撞”三类。

// 边界与自身碰撞检测:返回true表示安全,false表示游戏结束 bool checkbianyuan(pos& p,snake &snk) { // 1. 边界碰撞:蛇头超出地图范围 if (p.x < 0 || p.x >= L || p.y < 0 || p.y >= H) { return false; } // 2. 自身碰撞:蛇头与除自身外的蛇身重叠 for (int i = 1; i < snk.schangdu; i++) { if (snk.aspos[0].x == snk.aspos[i].x && snk.aspos[0].y == snk.aspos[i].y) { return false; } } return true; } // 食物碰撞检测:返回true表示吃到食物 bool checkeatfood(snake& snk, map& Map, pos& tail) { pos head = snk.aspos[0]; // 蛇头位置为食物时触发吃食物逻辑 if (Map.block[head.y][head.x] == blocktype::FOOD) { snk.aspos[snk.schangdu++] = tail; // 蛇身变长(尾部追加一节) Map.block[head.y][head.x] = blocktype::EMPTY; // 清除食物标记 Map.hasfood = false; // 允许生成新食物 drawunit(tail, "■"); // 绘制新增的蛇尾 return true; } return false; } 

关键说明:

  • 自身碰撞检测时,跳过蛇头(i从1开始),避免蛇头与自身比较导致误判;
  • 吃食物时,利用移动前记录的蛇尾位置(tail)追加蛇身,模拟“蛇身变长”的效果,同时清除食物标记,触发新食物生成。

3.3.3 食物生成(checkfoodgenerate)

void checkfoodgenerate(snake& snk, map& Map) { if (!Map.hasfood) { // 仅当无食物时生成 while (true) { // 生成地图范围内的随机坐标 int x = rand() % L; int y = rand() % H; bool isSnake = false; // 检查随机位置是否在蛇身上(避免食物生成在蛇体内) for (int i = 0; i < snk.schangdu; i++) { if (snk.aspos[i].x == x && snk.aspos[i].y == y) { isSnake = true; break; } } if (!isSnake) { // 位置合法(不在蛇身上) Map.block[y][x] = blocktype::FOOD; Map.hasfood = true; pos z = { x, y }; drawunit(z, "●"); // 绘制食物 return; } } } } 

关键逻辑:

  1. 随机坐标生成:通过rand() % Lrand() % H确保坐标在地图范围内;
  2. 位置合法性校验:循环检测随机位置是否在蛇身上,若在则重新生成,避免食物“嵌入”蛇身导致无法被吃;
  3. 生成开关控制hasfood确保同一时间地图上只有一个食物,符合游戏规则。

3.3.4 方向控制(controlfangxiang)

void controlfangxiang(snake& snk) { if (_kbhit()) { // 检测是否有键盘输入(无输入则不执行) switch (_getch()) { // 获取按键字符 case 'w': // 向上:不能从向下反向(避免蛇直接回头撞自身) if (snk.sfangxiang != 1) snk.sfangxiang = 0; break; case 's': // 向下:不能从向上反向 if (snk.sfangxiang != 0) snk.sfangxiang = 1; break; case 'a': // 向左:不能从向右反向 if (snk.sfangxiang != 3) snk.sfangxiang = 2; break; case 'd': // 向右:不能从向左反向 if (snk.sfangxiang != 2) snk.sfangxiang = 3; break; } } } 

核心优化:

添加“反向限制”——蛇不能直接向相反方向移动(如当前向下时不能直接向上),避免玩家误操作导致蛇头立即碰撞自身,提升游戏的容错性和体验。

3.4 游戏主循环

主循环是游戏的“心脏”,不断循环执行“输入检测→状态更新→画面渲染”流程,直到游戏结束条件触发。

int main() { map Map; snake snk; int score = 0; // 分数(全局累加) int i = 4; srand((unsigned int)time(NULL)); // 初始化随机种子(仅一次,避免食物位置重复) hidecursor(); // 隐藏光标 intimap(Map); // 初始化地图 drawmap(Map); // 绘制初始地图 intisnake(snk); // 初始化蛇 drawsnake(snk); // 绘制初始蛇 // 分数和游戏结束提示的固定位置 pos scorePos = { L/2-5, 0 }; // 分数显示在顶部居中 pos gameOverPos = { L/2-4, H/2 };// 游戏结束提示在屏幕居中 // 游戏主循环 while (true) { // 1. 控制蛇移动(按频率移动,失败则游戏结束) if (!checksnakemove(snk, Map, score,i)) break; // 2. 实时更新分数显示 stringstream ss; ss << "得分: " << score; char scoreChar[100]; strcpy_s(scoreChar, ss.str().c_str()); drawunit(scorePos, scoreChar); // 3. 检测方向输入 controlfangxiang(snk); // 4. 检测并生成食物 checkfoodgenerate(snk, Map); } // 游戏结束:显示提示 drawunit(gameOverPos, "游戏结束"); while (true) {} // 保持窗口(避免程序立即退出) return 0; } 

主循环核心流程:

  1. 初始化阶段:完成地图、蛇、随机种子等初始化,绘制初始画面;
  2. 循环阶段: 通过checksnakemove控制蛇的移动频率,移动失败则退出循环(游戏结束);
  3. 将分数转换为字符串并实时更新显示;
  4. 检测方向键输入,更新蛇的移动方向;
  5. 检测食物状态,无食物则生成新食物。
  6. 结束阶段:显示“游戏结束”提示,通过无限循环保持控制台窗口,避免程序立即关闭。

四、关键优化与易错点总结

4.1 核心优化点

  • 随机种子优化srand()放在main函数开头仅初始化一次,避免每次生成食物时重复初始化,导致食物位置集中或重复;
  • 移动频率控制:通过chrono库的时间戳计算移动间隔,实现毫秒级精确控制,比Sleep()更灵活(不阻塞输入检测);
  • 方向反向限制:避免蛇直接反向移动撞自身,提升容错性;
  • 分层绘制:地图、蛇、食物、分数分别绘制,逻辑清晰,便于维护和扩展。

4.2 常见易错点

  1. 坐标偏移错误:绘制时未考虑边框的1行1列偏移,导致蛇或食物“超出”地图边框;
  2. 蛇身移动顺序错误:从头部开始更新蛇身位置,导致蛇身覆盖,表现为“蛇身消失”;
  3. 随机种子重复初始化:在checkfoodgenerate中调用srand(),导致同一秒内生成的食物位置相同;
  4. 碰撞检测时机错误:移动前检测蛇头位置,未考虑移动后的新位置,导致碰撞判断延迟;
  5. 分数更新不及时:未实时刷新分数显示,导致玩家无法及时获取得分反馈。

五、扩展与改进方向

  1. 增加游戏难度等级:设置不同初始速度和加速幅度,满足不同玩家需求;
  2. 添加特殊食物效果:如“减速食物”“加分食物”“无敌时间”等,提升游戏趣味性;
  3. 记录最高分:将最高分存储到文件,游戏结束时对比并更新,增加游戏粘性;
  4. 优化画面效果:为蛇头和蛇身设置不同颜色(通过Windows API控制控制台文字颜色),提升视觉区分度;
  5. 添加暂停功能:支持空格键暂停/继续游戏,提升操作灵活性。

Read more

在openi启智社区的dcu bw1000使用llama.cpp推理 stelterlab/Qwen3-Coder-30B-A3B-Instruct-AWQ(失败)

openi启智社区的dcu新推出 bw1000计算卡,不耗费积分,可以可劲用! 但是提供的镜像只有一个,感觉用起来很麻烦.... 用llmfit看看模型情况 llmfit info stelterlab/Qwen3-Coder-30B-A3B-Instruct-AWQ === stelterlab/Qwen3-Coder-30B-A3B-Instruct-AWQ === Provider: stelterlab Parameters: 4.6B Quantization: Q4_K_M Best Quant: Q8_0 Context Length: 262144 tokens Use Case: Code generation and completion Category: Coding Released: 2025-07-31 Runtime: llama.cpp (est. ~17.2 tok/s) Score Breakdown:

By Ne0inhk
GitHub Copilot 在 VS Code 上的终极中文指南:从安装到高阶玩法

GitHub Copilot 在 VS Code 上的终极中文指南:从安装到高阶玩法

GitHub Copilot 在 VS Code 上的终极中文指南:从安装到高阶玩法 前言 GitHub Copilot 作为 AI 编程助手,正在彻底改变开发者的编码体验。本文将针对中文开发者,深度解析如何在 VS Code 中高效使用 Copilot,涵盖基础设置、中文优化、核心功能详解,并提供多个实战场景配置模板。 一、安装与配置全流程 1. 完整安装步骤 1. 扩展安装 * 打开 VS Code → 点击左侧活动栏的 Extensions 图标(或按 Ctrl+Shift+X) * 搜索框输入 GitHub Copilot → 点击安装按钮 2. 账号授权 * 安装完成后右下角弹出通知 → 点击 Sign in

By Ne0inhk

新手必看:Whisper 模型版本号解析与首次下载安装全流程

Whisper 模型版本号解析与首次下载安装全流程指南 作为新手,了解OpenAI的Whisper模型(一款高效的开源语音识别模型)的版本号和安装流程至关重要。Whisper模型支持多种语言和任务,如语音转文本(ASR),其版本号反映了模型的大小、性能和更新内容。本指南将逐步解析版本号,并提供完整的首次下载安装流程,确保你轻松上手。所有步骤基于官方文档和社区最佳实践,力求真实可靠。 第一部分:Whisper 模型版本号解析 Whisper模型的版本号由OpenAI发布,格式通常为whisper-<size>-v<version>,其中<size>表示模型大小(影响精度和速度),<version>表示迭代版本(优化功能和性能)。以下是关键版本解析: * 版本号结构: * 大小标识:如tiny、base、small、medium、large。模型越大,精度越高,但计算资源需求也越大。

By Ne0inhk
GitHub 热榜项目 - 日榜(2026-01-30)

GitHub 热榜项目 - 日榜(2026-01-30)

GitHub 热榜项目 - 日榜(2026-01-30) 生成于:2026-01-30 统计摘要 共发现热门项目: 14 个 榜单类型:日榜 本期热点趋势总结 本期GitHub热榜显示AI智能体开发依然是绝对焦点,跨平台个人助手、多智能体协作平台和专业化工具链项目表现抢眼。具体趋势体现在三个方面:首先,可定制化个人AI助手(如moltbot、kimi-cli)持续走热,强调全平台兼容和代码驱动。其次,智能体能力边界拓展成为关键,围绕记忆增强(memU)、系统提示词逆向(system_prompts_leaks)和标准化协议(MCP)的创新工具涌现,助力开发者突破大模型固有局限。最后,开源社区正积极构建从开发框架(pi-mono)到应用范例(awesome-llm-apps)的完整生态,推动AI智能体技术走向成熟落地,为实际业务场景提供可复现的解决方案。 1. moltbot/moltbot * 🏷️ 项目名称:moltbot/moltbot

By Ne0inhk