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

C++ 适合新手的贪吃蛇小项目

这是我的第一个C++项目,虽然很简短 ,但写完还是蛮有成就感的😀


1.设计思路

  • 如何设计蛇身?可以看作一个双端队列,队头是蛇头,队尾为蛇,通过出入队来控制蛇身。

  • 如何移动蛇身?可以简化为只有头和尾动,中间的部分并不需要移动。控制光标移动,打印新蛇头,删除旧蛇尾即可。

  • 如何检测碰撞?因为程序很小,可以设置一个数组,记录每个坐标是否已有实体,每次移动检测蛇头的坐标。

2.可能遇到的问题

  • 打印格式错乱:该项目在终端中运行,windows终端默认大小为80*40,而地图为120*40,可能需要重新设置终端的显示大小和缓冲区大小。输入时注意是否超过缓冲区大小,如果超过会导致一些意外的错误。比如,终端大小80*40,缓冲区120*40,你每行输入100个字符,可能开始几行显示正常,但后面可能会出现如直接跳过输入的错误。

  • 坐标错乱:坐标是从(0,0)开始,如果程序开始之前终端有其他输入,会导致坐标错乱。最好先使用cls,清空终端。

  • 无法自动的移动问题:_getch()是阻塞式输入,会一直等待用户输入,期间暂停程序的运行。使用_kbhit(),它会检测是否有待输入内容,如果无就返回,不会阻塞程序运行。

3.代码展示

#pragma once #include <iostream> #include <deque> #include <windows.h> #include <ctime> #include <vector> #include <conio.h> #include <algorithm> using namespace std; struct Snake { char image; // 用于表示蛇身体的图标 short x, y; // 坐标 }; class snakeGame { private: enum MapSize { height = 40, // x width = 120 // y }; // 地图尺寸,行高40,列宽120 HANDLE hout; // 控制台输出句柄,用于光标操作 COORD pos; // 光标位置的结构题 char dir; // 蛇头移动方向 double speed = 300; // 移动速度 deque<Snake> snake; // 双端队列,队头为蛇头,队尾为蛇尾 short food_x, food_y; // 食物的坐标 int score = 0; // 得分 // 不要用bool,其在c++中位压缩,1bit一个bool,无法取元素地址且访问慢 vector<char> maptag; // 时间换空间,标记地图坐标,0表示安全,1表示墙或蛇身 public: snakeGame(); // 构造函数 void printMap(); // 创建地图 void displayInfo(); // 显示得分,速度,蛇长 void gotoxy(short x, short y); // 将控制台光标移动到(x,y)处 void HideCursor(); // 隐藏光标,让画面更干净 void initSnake(); // 初始化蛇身 void printSnakeHead(); // 打印蛇头, int WrongLocation(); // 判断食物生产位置是否和蛇身冲突 void createFood(); // 生产食物 void clearSnaketail(Snake &tail); // 擦除蛇尾,用空格覆盖, void judgeCrash(const Snake &); // 检测蛇头是否撞墙或与蛇身重叠 void foodEaten(); // 当蛇吃到食物,加分,加速,生产新食物 void userInput(); // 处理用户输入 };
#include "snake.h" void snakeGame::gotoxy(short x, short y) { // STD_OUTPUT_HANDLE 申请操作标准输出设备的权限 // GetStdHandle 是系统给的句柄,后面队屏幕的操作需要使用这个通行证 hout = GetStdHandle(STD_OUTPUT_HANDLE); pos = {x, y}; // SetConsoleCursorPosition是Windows系统提供的底层移动函数 // hout告诉系统哪个设备,pos告诉系统将光标放在哪个坐标 SetConsoleCursorPosition(hout, pos); } void snakeGame::displayInfo() { // 使用固定的两行显示信息 gotoxy(0, height + 3); cout << "速度: " << speed << "ms "; cout << "得分: " << score << " "; cout << "蛇长: " << snake.size() << " "; } void snakeGame::printMap() { // height = 40,width = 120 // 注意终端尺寸,不要超出了,不然会显示错误 maptag.assign(height * width, 0); short i; for (i = 0; i < width; ++i) { cout << "#"; maptag[static_cast<size_t>(i)] = 1; // 标记格子被占用 } cout << i; for (i = 1; i < height - 1; ++i) { gotoxy(0, i); // 光标移到(0,i) cout << "#"; maptag[static_cast<size_t>(i * width)] = 1; gotoxy(width - 1, i); cout << "#" << i; maptag[static_cast<size_t>((i + 1) * width - 1)] = 1; } gotoxy(0, height - 1); int temp = (height - 1) * width; for (i = 0; i < width; ++i) { cout << "#"; maptag[static_cast<size_t>(temp + i)] = 1; } cout << i; cout << "\n贪吃蛇: 1.按方向键开始游戏 2. * 代表食物 3.空格暂停游戏\n" << " 4.按\'v\'加快速度 5. 按\'b\'减慢速度\n"; } void snakeGame::HideCursor() { HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); // 获取屏幕控制许可 CONSOLE_CURSOR_INFO CursorInfo; // 创建一个光标信息体变量,存放光标 GetConsoleCursorInfo(handle, &CursorInfo); // 用句柄去系统那将当前光标取出来,存放到CursorInfo里 CursorInfo.bVisible = false; // 光标不可见 SetConsoleCursorInfo(handle, &CursorInfo); // 把修改后的光标信息写回系统,让设置生效 } void snakeGame::initSnake() { // 初始化蛇身并打印 snake.push_front({'@', width / 2, height / 2}); maptag[height / 2 * width + width / 2] = 1; gotoxy(width / 2, height / 2); cout << '@'; for (short i = 0; i < 2; ++i) { short x = width / 2; short y = static_cast<short>(height / 2 + i + 1); snake.push_back({'+', x, y}); maptag[static_cast<size_t>(y * width + x)] = 1; gotoxy(x, y); cout << '+'; } } void snakeGame::printSnakeHead() { // 蛇移动,只用处理蛇头和蛇尾其余部分无需处理 auto iter = snake.begin(); gotoxy(iter->x, iter->y); cout << iter->image; // 新蛇头 ++iter; gotoxy(iter->x, iter->y); cout << (iter->image = '+'); // 原来的蛇头变蛇身,重新打印 } int snakeGame::WrongLocation() { if (maptag[static_cast<size_t>(food_x + food_y * width)] == 1) return 1; return 0; } void snakeGame::createFood() { do { food_x = static_cast<short>(rand() % (width - 2) + 2); food_y = static_cast<short>(rand() % (height - 2) + 2); } while (WrongLocation()); gotoxy(food_x, food_y); cout << "*"; } void snakeGame::clearSnaketail(Snake &tail) { // 用空格覆盖蛇尾,而非清屏,避免了闪烁 gotoxy(tail.x, tail.y); cout << ' '; maptag[static_cast<size_t>(tail.y * width + tail.x)] = 0; } void snakeGame::judgeCrash(const Snake &s) { // 检查是否碰撞 if (maptag[static_cast<size_t>(s.y * width + s.x)] == 1) { gotoxy(width / 2 - 10, height / 2); cout << "游戏结束!你的分数是:" << score << "分(回车继续)" << endl; dir = static_cast<char>(_getch()); runtime_error quit("游戏结束,正常退出"); throw quit; } } void snakeGame::foodEaten() { speed = max(speed*0.8, 100.0); ++score; createFood(); } void snakeGame::userInput() { char ch; switch (ch = static_cast<char>(_getch())) // 输入控制,带反向检测 { case 'W': case 'w': if (dir != 's') dir = 'w'; break; case 'S': case 's': if (dir != 'w') dir = 's'; break; case 'A': case 'a': if (dir != 'd') dir = 'a'; break; case 'D': case 'd': if (dir != 'a') dir = 'd'; break; case 'V': case 'v': speed = max(speed * 0.9, 100.0); displayInfo(); break; case 'B': case 'b': speed *= 1.5; displayInfo(); break; case ' ': gotoxy(width / 2, height); cout << "游戏已暂停,任意键继续"; _getch(); gotoxy(width / 2, height); cout << " "; break; default: break; } } snakeGame::snakeGame() { system("mode con cols=128 lines=64"); // 设置终端大小 system("cls"); // 清空屏幕 HideCursor(); // 隐藏光标 srand(static_cast<unsigned int>(time(NULL))); // 随机种子 maptag.assign(width * height, 0); // 初始化maptag Snake head, tail; // 蛇头和蛇尾 printMap(); // 打印地图 initSnake(); // 初始化蛇身 head = snake.front(); // 蛇头 tail = snake.back(); // 蛇尾 dir = 'w'; // 默认初始向上移动 createFood(); // 创建食物 while (true) { // _getch()是阻塞式输入,程序等待用户输入,在此期间程序会暂停执行 // _kbhit()检查缓冲区是否有等待输入的流,如果没有就马上返回,不会阻塞程序执行。 if (_kbhit()) { userInput(); // 处理用户输入 } if (dir == 's') // 更改蛇头坐标 { // 如果新头不合法游戏就结束,所以直接修改原蛇头图标没有影响(只是修改,无打印) snake.front().image = '+'; ++head.y; } else if (dir == 'w') { snake.front().image = '+'; --head.y; } else if (dir == 'a') { snake.front().image = '+'; --head.x; } else if (dir == 'd') { snake.front().image = '+'; ++head.x; } // 判断是否撞墙或吃到自己 try { judgeCrash(head); // 这里的得用新的head,因为snake.front()坐标处maptag肯定是1 } catch (runtime_error &quitSignal) { throw quitSignal; } // 更新蛇头 snake.push_front(head); // 新蛇头入队 maptag[static_cast<size_t>(head.y * width + head.x)] = 1; printSnakeHead(); // 依据是否吃到食物,更新蛇尾 if (head.x == food_x && head.y == food_y) { foodEaten(); displayInfo(); } else { clearSnaketail(tail); // 没吃到食物就删除蛇尾 snake.pop_back(); // 尾元素出队 tail = snake.back(); // 新蛇尾 } // 添加延迟,不然蛇移动过快 Sleep(static_cast<DWORD>(speed)); } }
#include <iostream> #include "snake.h" int main() { try { snakeGame game; } catch (runtime_error &gameEnd) { // 结束 system("cls"); cout << gameEnd.what(); getch(); } return 0; }
http://www.cnnetsun.cn/news/3794451.html

相关文章:

  • 胶原蛋白护肤品怎么选、怎么用、和谁搭效果好? 一张表全说清楚
  • Java 集合进阶(一)
  • 终极指南:如何彻底禁用Windows Defender提升系统性能15-35%
  • UART指纹模块C语言驱动开发:从协议解析到实战优化
  • 基于YOLOv5的水果检测系统:从数据标注到GUI应用的全流程实践
  • 解决onnxsim模块缺失:从环境诊断到模型优化部署全攻略
  • 易学破哈希,关于哈希函数,有多么lose
  • Godot引擎VRM虚拟化身插件实战:从导入到高级控制全流程
  • 从零实现Attention-LSTM:PyTorch实战与情感分析应用
  • 贝叶斯分类器实战指南:从原理到应用,掌握朴素贝叶斯、高斯与伯努利模型
  • 基于Spark的气象大数据处理:架构设计、性能优化与实战应用
  • Java通用Word解析方案:兼容多格式、生产级实践指南
  • UE5程序化生成技术
  • 网盘直链下载助手:无需安装客户端,浏览器直接下载网盘文件的终极解决方案
  • 5分钟掌握地理数据编辑:让空间数据处理变得简单高效的终极指南
  • FOMO:超轻量目标检测模型,专为嵌入式与IoT设备设计
  • 什么图传设备能实现地对空10公里以上的稳定传输?云慧信达hd520A传输距离可达16km
  • Flume对接Kafka:构建高可靠实时数据管道的完整指南
  • BLE双模串口模块实战:从硬件选型到嵌入式与主机端开发全解析
  • Hadoop核心架构与集群搭建实战:从基础原理到环境部署
  • 10.5英寸HDMI AMOLED显示模组:从接口桥接到系统集成的技术解析
  • 第10天:指针 — 操作指南 ★★★ 全12天最重要的一天
  • 处理提示“wsl: 检测到 localhost 代理配置,但未镜像到 WSL。NAT 模式下的 WSL 不支持 localhost 代理。”【笔记】
  • WebPShop:Photoshop用户的终极WebP格式支持插件解决方案
  • 5分钟搭建3D打印机Web监控仪表盘:基于Flask的轻量级实践
  • GLM-5模型如何赋能智能体工程:从核心原理到实战应用
  • 有限元法核心原理与应用:从数学基础到工程实践
  • AI时代职场MBTI:五类角色重塑人机协作与职业发展
  • 桁架、管桁架、网架区别
  • 小模型如何实现精准文本长度控制?3B模型击败GPT-4的技术解析