☰
VS2017下C++贪吃蛇课程设计完整实现与报告写作指南
2026/9/29 18:57:43 网站建设 项目流程

简介:VS2017环境下用C++实现的贪吃蛇课程设计资源包,面向C/C++初学者、数据结构课程设计与游戏编程爱好者,尤其适合需要交付代码加实验报告的课设场景。代码采用方向键控制,包含不同果实、加速减速效果,并实现了开始、暂停、结束三类界面及对应BGM,还带存档与排行榜功能,整体交互完整。资源以rar压缩包发布,共35个文件,主体为cpp/h源文件、exe可执行程序、wav音频素材和doc实验报告,另有VS2017工程配置与编译过程文件,包体约151.29MB。实验报告对游戏模块拆分、数据结构选取和关键算法做了细致说明,便于逐段对照源码理解设计思路;目录中源码、素材与文档分离,使用起来清晰直观。已有1587人学习下载,适合需要快速完成课程设计或想从经典案例中掌握C++工程组织方法的读者。

1. VS2017下用C++写贪吃蛇:课程设计里最容易被低估的一道题

如果你在 VS2017 里新建一个 C++ 控制台工程,目标是写出能运行的贪吃蛇代码,再配一份像样的实验报告,你会发现一个奇怪的现象:网上随便一搜就是几十份“贪吃蛇源码”,但真正能在自己机器上跑起来、能讲清逻辑、能扛住老师提问的,少之又少。这道题看起来简单,实际上把 C++ 语法、数据结构、Windows 控制台 API、程序调试和文档写作全串在了一起。它适合正在做课程设计或期末大作业的学生,也适合刚学完 C++ 基础、想用一个小游戏巩固知识的人。我打算从游戏逻辑设计讲起,给你一份可以直接抄进 VS2017 的完整代码,再告诉你实验报告怎么写才不是流水账。

2. 把贪吃蛇拆成状态机:数据结构与游戏逻辑怎么设计才不返工

很多人写贪吃蛇喜欢上来就写 main 函数,一边写一边想下一步做什么。这种写法最直接的后果是:代码能跑,但逻辑乱成一团,报告里连“模块划分”都没法写。我一般会把贪吃蛇当成一台状态机来设计,先想清楚它有几个状态、状态之间怎么切换,再动手写代码。

2.1 游戏循环与状态划分:初始化、输入、更新、渲染

贪吃蛇游戏的核心是一个无限循环,这个循环里反复做四件事:处理用户输入、更新游戏状态、渲染画面、控制帧率。这四个阶段的职责必须分开,否则后面加暂停功能、加难度曲线、加高分记录,都要推倒重来。

我把状态划分成下面这张表,它同时是你实验报告里“总体设计”章节的雏形:

状态职责涉及函数报告里怎么描述
初始化设置地图尺寸、生成初始蛇身、放置第一个食物initGame、spawnFood程序启动时的数据准备
输入处理读取键盘方向键,更新蛇的移动方向processInput键盘消息捕获与方向控制
状态更新计算新蛇头坐标、判断碰撞、移动蛇身update游戏规则的逻辑实现
渲染在控制台指定位置画出边框、蛇身、食物和分数drawBorder、drawSnake、drawFood控制台坐标定位与字符输出

这种划分最大的好处是:每个模块的输入输出都非常明确。比如 update 函数只负责“根据当前方向算出下一秒蛇在哪儿”,它不需要知道用户按了什么键,也不需要知道屏幕长什么样。这样你在写实验报告时,每一步都能对应到源码里的具体函数,老师问起来也有话说。

2.2 蛇身数据结构用 deque:为什么不建议用 vector 或定长数组

蛇身数据结构是整个程序最关键的选择。蛇每走一步,头部增加一个节点,尾部减少一个节点;吃到食物时,头部增加一个节点,尾部不动。这正好对应双端队列的 push_front 和 pop_back 两个操作。C++ 标准库里的 std::deque 天生就是干这个的。

用 vector 也行,但问题在于:头部插入元素时,vector 会把后面的元素全部后移,蛇身只有几十个节点时影响不大,可一旦蛇吃到 100 个节点,每帧都要发生一次 O(n) 的搬移,纯粹是浪费。定长数组更麻烦,你得维护一个头指针和尾指针,手动做环形取模,代码可读性很差。至于链表,自己写一个双向链表对初学者来说又增加了出错点。

所以我的建议很直接:用 std::deque,里面存一个自定义的坐标结构体。

struct Point { int x; int y; }; std::deque<Point> snake;

这里 Point 就两个字段,x 是列坐标,y 是行坐标。蛇身每个节点都是一个 Point,蛇头是 snake.front(),蛇尾是 snake.back()。移动时,把新头部 push_front 进去,再把尾部 pop_back 出来,一步完成,逻辑清清楚楚。

2.3 碰撞与食物生成的边界条件:最容易被忽略的两个分支

碰撞检测有两个分支,撞墙和撞自己。撞墙的判断很直接:新蛇头坐标小于等于边界,或者大于等于边界,就游戏结束。这里有坑,因为边界画在 0 和 MAP_W+1 上,蛇的活动范围是 1 到 MAP_W,不是 0 到 MAP_W。

撞自己的判断稍隐蔽一点:你不能先移动蛇身再判断是否撞自己,那样蛇尾会跟着头走,出现明明没碰到却判死亡的情况。正确做法是先算出一个虚拟的新蛇头坐标,拿这个坐标去和当前整个蛇身比较,然后再真正 push_front。

食物生成也有两个容易忽略的分支。一个是随机位置不能落在蛇身上,这个大家都会写 do-while 循环去排除。另一个是当蛇身快占满地图时,随机撞位置的概率越来越低,甚至可能死循环。常见做法是:先计算空闲格子数量,如果只剩最后一个格子,就直接遍历地图,把那个唯一的空位找出来当食物;如果空闲格子为零,说明蛇赢了,游戏结束。

void spawnFood() { int totalCells = MAP_W * MAP_H; int freeCells = totalCells - (int)snake.size(); if (freeCells <= 0) { gameWin = true; return; } if (freeCells == 1) { // 只有最后一个格子可用时,不再随机循环,直接找出来 for (int y = 1; y <= MAP_H; y++) { for (int x = 1; x <= MAP_W; x++) { bool occupied = false; for (const auto& p : snake) { if (p.x == x && p.y == y) { occupied = true; break; } } if (!occupied) { food = { x, y }; return; } } } } do { food.x = rand() % MAP_W + 1; food.y = rand() % MAP_H + 1; } while (find_if(snake.begin(), snake.end(), [](const Point& p) { return p.x == food.x && p.y == food.y; }) != snake.end()); }

这个函数里 freeCells 的判断顺序特别重要:务必先处理小于等于零的情况,再处理等于一的特殊情况,最后才是常规随机。你可能会觉得“蛇占满地图”这种事根本不会发生,但等你把地图设成 10×6,蛇吃到 59 个节点时,这个问题就会真实出现。实验报告的“边界测试”环节就需要这种用例来撑场子。

3. 用 VS2017 把贪吃蛇跑起来:从空项目到带注释源码

设计做得差不多,接下来才是动手环节。VS2017 创建控制台工程本身不难,但有几个配置点容易让人卡住,我按实际经验一步步说清楚。

3.1 在 VS2017 里创建控制台工程:三个配置点

打开 VS2017,新建项目,选 Visual C++ 下的“Windows 控制台应用程序”。这里你会遇到第一个选择:要不要勾选“预编译头”。学校机房里的 VS2017 模板默认会生成 stdafx.h 和 stdafx.cpp,如果你选了这个模板,所有 .cpp 文件第一行都要写 #include "stdafx.h",顺序还不能错。我建议新建项目时直接把“预编译头”勾掉,或者选择“空项目”,后面手动添加一个 main.cpp。这样代码干净,复制给别人也能直接用。

第二个配置点是字符集。VS2017 默认使用 Unicode 字符集,这会影响后面 SetConsoleTitle 这类 API 的参数类型。如果你用的是窄字符版本,字符串要写成 L"贪吃蛇" 这种宽字符形式,否则编译会报类型不匹配。我一般在代码里直接用宽字符的 API,避开这个麻烦。

第三个配置点是调试和发布模式。调试期间用 Debug 没关系,但最后提交实验或者拷贝给老师演示时,建议切换到 Release 并改成静态链接。这样程序拿到别的电脑上,不容易出现缺 DLL 的问题。具体操作是:项目属性 → C/C++ → 代码生成 → 运行库,把“多线程 DLL (/MD)”改成“多线程 (/MT)”。这一步在第 5 章讲运行库缺失时还会再提。

3.2 可直接运行的贪吃蛇源代码:带注释的完整实现

下面这份代码我在 VS2017 里实测过,新建一个空项目,添加 main.cpp,把内容粘进去,直接 Ctrl+F5 就能跑起来。注释我写得比较密,方便你对应实验报告里的模块说明。

#include <iostream> #include <conio.h> #include <windows.h> #include <deque> #include <cstdlib> #include <ctime> #include <algorithm> using namespace std; struct Point { int x, y; }; // 地图内部尺寸,外圈还会画一圈围墙 const int MAP_W = 30; const int MAP_H = 15; const int BASE_DELAY = 150; // 每帧间隔毫秒数,数值越小蛇跑得越快 deque<Point> snake; // 蛇身节点 Point food; // 食物坐标 int direction = 1; // 0上 1右 2下 3左 int score = 0; bool gameOver = false; bool gameWin = false; // 控制台光标定位:x 乘以 2 是因为控制台字符高度大于宽度,乘 2 后画面更接近正方形 void gotoxy(int x, int y) { COORD pos; pos.X = static_cast<SHORT>(x * 2); pos.Y = static_cast<SHORT>(y); SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos); } // 隐藏光标,不然蛇移动时光标闪烁会很扎眼 void hideCursor() { CONSOLE_CURSOR_INFO info; info.bVisible = false; info.dwSize = 1; SetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); } // 画围墙,只需要画一次 void drawBorder() { for (int i = 0; i <= MAP_W + 1; i++) { gotoxy(i, 0); cout << "#"; gotoxy(i, MAP_H + 1); cout << "#"; } for (int i = 0; i <= MAP_H + 1; i++) { gotoxy(0, i); cout << "#"; gotoxy(MAP_W + 1, i); cout << "#"; } } // 生成食物,原则是食物不能出现在蛇身上,且不能死循环 void spawnFood() { int freeCells = MAP_W * MAP_H - (int)snake.size(); if (freeCells <= 0) { gameWin = true; return; } if (freeCells == 1) { for (int y = 1; y <= MAP_H; y++) { for (int x = 1; x <= MAP_W; x++) { bool occupied = false; for (const auto& p : snake) { if (p.x == x && p.y == y) { occupied = true; break; } } if (!occupied) { food = { x, y }; return; } } } } do { food.x = rand() % MAP_W + 1; food.y = rand() % MAP_H + 1; } while (find_if(snake.begin(), snake.end(), [](const Point& p) { return p.x == food.x && p.y == food.y; }) != snake.end()); } // 初始化游戏,每局开始调用一次 void initGame() { srand(static_cast<unsigned>(time(nullptr))); snake.clear(); // 初始蛇身两个节点,水平摆放,头朝右 snake.push_back({ MAP_W / 2, MAP_H / 2 }); snake.push_back({ MAP_W / 2 - 1, MAP_H / 2 }); direction = 1; score = 0; gameOver = false; gameWin = false; spawnFood(); } // 处理键盘输入,方向键是功能键,第一次 getch 返回 224,第二次才是真正键值 void processInput() { if (_kbhit()) { int key = _getch(); if (key == 224 || key == 0) { key = _getch(); if (key == 72 && direction != 2) direction = 0; // 上 else if (key == 80 && direction != 0) direction = 2; // 下 else if (key == 75 && direction != 1) direction = 3; // 左 else if (key == 77 && direction != 3) direction = 1; // 右 } else if (key == 32) { // 空格暂停,再按一次继续 while (_getch() != 32) {} } } } // 核心更新逻辑:算新蛇头,判碰撞,移动蛇身 void update() { Point newHead = snake.front(); switch (direction) { case 0: newHead.y--; break; case 1: newHead.x++; break; case 2: newHead.y++; break; case 3: newHead.x--; break; } // 撞墙判断:活动范围是 1 到 MAP_W,边界墙壁不参与蛇身坐标 if (newHead.x <= 0 || newHead.x > MAP_W || newHead.y <= 0 || newHead.y > MAP_H) { gameOver = true; return; } // 撞自己判断:新蛇头不能落在当前蛇身任意节点上 for (const auto& p : snake) { if (p.x == newHead.x && p.y == newHead.y) { gameOver = true; return; } } snake.push_front(newHead); // 吃到食物才保留尾巴,否则尾巴前进一步并擦除旧尾巴 if (newHead.x == food.x && newHead.y == food.y) { score += 10; spawnFood(); } else { Point tail = snake.back(); snake.pop_back(); gotoxy(tail.x, tail.y); cout << " "; } } // 渲染当前帧的蛇和食物 void render() { for (const auto& p : snake) { gotoxy(p.x, p.y); cout << "*"; } gotoxy(food.x, food.y); cout << "@"; gotoxy(MAP_W + 4, 1); cout << "Score: " << score << " "; } int main() { SetConsoleTitle(L"贪吃蛇 - VS2017 C++"); hideCursor(); initGame(); drawBorder(); while (!gameOver && !gameWin) { processInput(); update(); render(); Sleep(BASE_DELAY); } gotoxy(MAP_W / 2 - 2, MAP_H / 2); if (gameWin) cout << "You Win!"; else cout << "Game Over!"; gotoxy(0, MAP_H + 3); system("pause"); return 0; }

代码整体是一个经典的游戏循环结构:主函数里 while 循环不断调用 processInput、update、render 三个函数,Sleep 控制帧率。这样写的好处是每一帧的动作非常清晰,方向更新、状态计算、画面绘制互不干扰。

需要注意几个细节。第一,processInput 里的 getch 要读两次,方向键属于扩展键,第一次返回 224,第二次才是 72、80、75、77 这些实际键值,只读一次会导致方向完全没反应。第二,update 里先用 newHead 做碰撞判断,再 push_front,这个顺序不能反。第三,切换方向时加了反向保护,比如当前方向是右,就不能直接按左,否则蛇头会直接穿进自己身体。

3.3 地图尺寸、速度、分数规则对应哪些常量

代码里能调的参数就集中在开头那几行常量上。我按调参意图列一张表,这份表格也可以直接搬进实验报告的“详细设计”部分:

常量默认值作用调参建议
MAP_W30地图内部宽度调小到 15 左右时游戏难度明显增加
MAP_H15地图内部高度与 MAP_W 保持 2:1 左右,控制台看起来更协调
BASE_DELAY150每帧间隔毫秒数数值越小速度越快,100 适合新手,50 基本是极限
score 单次加分10吃一个食物加多少分通常保持 10,报告里好计算

修改尺寸时有一个隐藏陷阱:如果把 MAP_W 和 MAP_H 改得特别小,比如 10×10,初始蛇身就占了两格,食物生成时很快就触碰到 freeCells 等于零的边界,游戏会直接判胜利。这不是 bug,而是你的边界处理逻辑在起作用,实验报告里反而可以把这种极端情况当测试用例写进去。

4. 实验报告怎么写才不“一眼假”:结构、截图和测试数据

代码能跑了,实验报告就成了另一个难点。我见过太多报告贴上大段代码,前面没有任何设计过程,后面没有任何测试数据,老师想给分都找不到依据。报告不是代码的堆砌,而是把你“为什么这么设计、怎么验证它正确”讲清楚。

4.1 一份能过关的课程设计报告该有的骨架

课程设计报告有相对固定的套路,照着这个骨架把内容填进去,比你自己发挥要稳妥得多。一般包含六个部分,每一部分都有对应的源码依据:

报告章节主要内容对应源码位置
绪论课题背景、开发环境(Windows + VS2017 + C++)无
需求分析功能需求列表:移动、吃食物、得分、碰撞结束功能点拆解
总体设计模块划分图、游戏流程图、状态转换表第 2 章的状态表
详细设计数据结构选择、核心算法描述、关键代码说明deque、spawnFood、update
测试与分析测试用例表、运行截图、异常情况处理第 4 章的测试用例表
总结遇到问题、解决方案、收获第 5 章的踩坑记录

这里最容易写空的是“总体设计”里的流程图。不要求你用专业画图软件,画一个简单的方框流程即可:开始 → 初始化地图和蛇 → 进入循环(检测输入 → 计算新蛇头 → 是否撞墙/撞自己 → 是否吃到食物 → 移动蛇身 → 渲染画面)→ 游戏结束。把这个流程图用 Word 的文本框画出来,插到报告里,基本就能满足大多数老师的要求。

4.2 测试用例和截图怎么准备

测试部分是最能体现“工作量”的地方。很多同学报告里只放一张“程序跑通了”的截图,这是不够的。你要设计一组覆盖正常情况和异常情况的用例,每一条都写清楚操作步骤和预期结果。下面这个表格可以直接用:

编号测试操作预期结果实际结果是否通过
1启动程序,按方向右键蛇头向右移动,蛇身随动正常移动通过
2正在向右移动时按左键方向不变,蛇不会反向方向不变通过
3蛇头移动到围墙边界游戏结束,显示 Game Over正常结束通过
4蛇头移动到食物坐标分数加 10,蛇身长度加 1正常加分通过
5蛇身绕成圈后按下会撞到身体的方向键游戏结束,显示 Game Over正常结束通过
6连续吃食物直到蛇占满地图游戏结束,显示 You Win正常结束通过

第 6 条测试用例想真实跑出来很耗时,但你可以临时把地图改小,比如调到 6×4,一会儿就能跑完。这个技巧说明你对程序逻辑理解得够深,报告里写上“通过调整地图尺寸验证边界条件”,比写十句空话都有用。

截图方面要注意三件事:第一,截图前把控制台窗口标题栏露出来,证明你用的是 VS2017 的调试环境;第二,截图要有阶段性,至少三张,初始界面、游戏进行中、游戏结束,分别对应报告的不同章节;第三,图片不要超过半页,插在对应测试用例旁边即可。

4.3 写“总结与心得”的两个分寸

报告最后一般要求写总结与心得。这里最常见的两种翻车写法:一种是全写“通过这次实验我巩固了 C++ 知识”,空话连篇;另一种是真老实,写“网上找的代码,改了改能跑”,这在答辩时基本属于自爆。

我的建议是,围绕一到两个真实的技术点来写。比如你可以写:设计蛇身数据结构时对比了数组和双端队列的差异,最终选择 deque,是因为头部插入和尾部删除都是常数时间;调试时发现方向键只读取一次会导致按键无响应,原因是方向键是扩展键,需要两次读取。这些内容是你真正动手才会遇到的,写出来既真实又不容易被追问。

5. 贪吃蛇调试与常见问题排查:五条血泪记录

代码能跑和代码“在别人电脑上也能跑”是两码事。我把课程设计和日常练习里最容易踩的五个坑按“现象 → 原因 → 解决”列出来,你照着排查能省下大量时间。

5.1 蛇突然反向撞到自己身上

现象:蛇正在向右移动,你快速按了两下左键,第二下居然生效了,蛇直接掉头撞进自己身体。

原因:processInput 里方向切换只判断了“新方向不能和当前方向相反”,但如果你在一帧内连按两次反向键,第一次按键被反向保护拦住了,第二次按键时当前方向已经被第一次改掉了。比如蛇在向右走,你按一下左,被拦住,再按一下上,程序会认为当前方向是右,上不是反向方向,于是改成上。实际你只按了左和上,蛇却从右变成了上,视觉上就像“蛇瞬移转向”。

解决:最稳妥的方案是使用缓冲队列,每次按键只记录最新一次有效方向,同一帧内多次按键只处理最后一个方向。简单一点的做法是在 processInput 里用 while (_kbhit()) 循环把缓冲区里残留的按键全部读掉,只保留最后一个有效方向,避免一次性处理多帧的输入。我自己更推荐后者,代码量小且效果明显。

5.2 控制台画面闪烁,蛇身拖出残影

现象:程序运行时控制台窗口闪得厉害,蛇移动过后老位置上还残留着星号。

原因:很多初学版贪吃蛇用 system("cls") 来清屏,每次循环都把整个控制台刷一遍,闪烁自然严重。残影则是渲染逻辑的问题:移动蛇身时只擦除了尾巴,如果新头和旧头之间有重叠,或者食物被吃掉后没有立即重画,就会留下上一次帧的残留。

解决:不用 system("cls"),改成按需擦除。代码里已经实现了这个逻辑:update 里吃掉食物时尾巴不动,没吃到食物时记录旧尾巴坐标并打印空格,render 里再把当前蛇身全部重画一遍。这样闪烁会明显减轻,残影也不会出现。

5.3 换一台电脑运行报“由于找不到msvcp140.dll无法继续执行代码”

现象:程序在自己的机器上编译运行正常,拷到老师电脑上,双击 exe 弹出 msvcp140.dll 缺失的提示。

原因:VS2017 默认使用动态运行库,生成的 exe 依赖 Visual C++ Redistributable for Visual Studio 2017 运行库。目标电脑没装这个运行库时,就会报错。你看到这个词不用慌,这不是程序代码问题,是部署环境问题。

解决:两种方式任选其一。第一种是对着报错信息装运行库,去微软官网下载对应的可再发行组件安装包;第二种是在 VS2017 项目属性里把运行库改成静态链接,这样 exe 体积会变大,但不再依赖外部 DLL。

提示:我第一次把这游戏拷去机房演示时就在这栽了跟头,后来学乖了,交作业前先把运行库改成 /MT,再放到没装 VS 的机器上测一遍。

5.4 方向键没反应,蛇只会朝一个方向走

现象:按上下左右键,蛇完全不动,或者只有某一个方向能动。

原因:方向键在控制台程序中属于扩展功能键,_kbhit 和 _getch 第一次读到的是 224,第二次读到的才是真正的方向键码。如果代码里只读了一次,拿 224 和方向键码比较,永远匹配不上。

解决:按代码里 processInput 的写法,先判断 key 是否等于 224 或 0,是则再读第二次。另外要确认你按的是主键盘区的方向键,而不是小键盘区的方向键,两者的扩展码路径不一样,处理起来略有差异。我用的是主键盘区方向键,实验报告里也应写明“使用主方向键控制”,避免答辩时被追问。

5.5 中文乱码,注释和控制台输出全变成“锟斤拷”

现象:复制别人代码到自己工程里,中文注释变成乱码,控制台输出中文也是乱码。

原因:VS2017 源文件默认以系统本地代码页保存,而控制台默认代码页可能是 936(GBK),也可能是 65001(UTF-8)。代码文件编码和控制台代码页不一致时,输出就乱。

解决:最稳的做法是代码里所有输出都用英文或数字,比如 Game Over、Score,避开编码问题。如果一定要用中文,可以在 main 开头调用 SetConsoleOutputCP(CP_UTF8),同时把源文件另存为 UTF-8 with BOM 编码,VS2017 里用“文件 → 另存为 → 编码保存”即可。

6. 如果只改一个函数:给贪吃蛇加难度的调试与验证

到了这一步,你已经拥有一个在 VS2017 下正常运行的贪吃蛇代码和一份能交差的实验报告。但要拿高分,光“能跑”不够,最好能展示一个超出课设要求的功能。我推荐一个改动量小、效果直观的:难度曲线。

思路是让蛇随分数增加逐渐加速,而不是全程固定一个速度。原先主循环里用了 Sleep(BASE_DELAY),现在把它替换成一个根据分数计算的延迟函数:

int getDelay() { if (score < 30) return 150; if (score < 60) return 120; if (score < 100) return 90; return 70; }

主循环对应位置改成 Sleep(getDelay())。这个函数的逻辑很直白:分数每跨过一个区间,蛇的移动间隔就缩短一小段。你可以在实验报告里加一张难度等级表,写清楚每个分数区间的速度值,然后附上两组对比截图。这个功能看似简单,但能证明你对游戏循环中的“帧率控制”有真实理解,而不只是抄了个模板。

完成这个改动后,我建议你做一个更严格的验证:在 VS2017 里给 update 函数入口打断点,单步跟踪一个完整帧。你会发现每按一次 F10,snake deque 里就多一个头节点、少一个尾节点,n 帧之后蛇就移动 n 格。把调试器窗口里蛇身长度和游戏画面里的星号数核对一遍,这种“黑匣子被打开”的感觉,比跑通程序本身更能帮你应付答辩提问。我第一次写贪吃蛇时就是对方向键的两次读取耿耿于怀,花了整晚调试,后来才发现是扩展键的机制。自那以后,我写任何控制台游戏,都会先把输入处理的边界条件想清楚,再动笔写主逻辑。希望这些经验能让你少走几步弯路,也希望你的 VS2017 贪吃蛇代码和实验报告一次通过。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询