200行代码写一个简易的C++小黑窗贪吃蛇游戏

分享一个简易的小黑窗贪吃蛇,一共就两百行代码左右(包含注释),很适合初学者巩固语法来练练手.

如果后续需要其他功能也可以再添加.

先小小展示一下:

源码在文末免费领取. 

使用工具:

  VS2019(不是用VS的也可以直接找出cpp和h文件复制到你们用的IDE,甚至是记事本都可以)

闲话(可跳过):

为什么要写贪吃蛇呢?首先是从这学期开始下定决心要走C++路线了,因此把C++又复习了一遍,基础语法又学了一遍,还写了C++primer的笔记(有专栏).

想着要来个项目练练手,但是目前就会个C++的基础语法和STL,甚至连QT都不熟练,然后我又想起了把我领进门的学长在我刚学C语言的时候就布置了一个"任务",学完C语言之后写个贪吃蛇出来 .

因此在复习完C++后我决定就写个小黑窗贪吃蛇出来,QT版本的以后也要做出来(等我再好好学学QT),然后大概是花了一个下午把贪吃蛇给写出来了,包括注释也就两百行左右,自己留着也是孤芳自赏,因此在此分享出来.

蛇:

 在写代码之前一定要构思好怎么写,绝不能想到一点写一点,否则以后加功能或是找bug的时候会很痛苦(深有体会)

贪吃蛇最主要的就是蛇,那么先给蛇写个类,类中需要的属性有

蛇的长度

蛇的头部

蛇的身子

蛇的运动方向

蛇应该要有的动作(函数):

沿着方向移动

改变方向

因此可以写出蛇类的大体框架:

class snack{friend class UI;    //将UI界面设为友元,使得界面可以接触到蛇的私有属性
private:vector<int>head = vector<int>(2);    //存放头的坐标vector<vector<int>>body;    //存放身子的坐标char direction;    //运动方向int size = 1;    //身长public:snack(int x=25, int y=10);		void crawl();	//爬行bool changeDirection(char newDirection);	//改变方向

界面:

刚才说贪吃蛇最主要的是蛇,其实最主要的是界面(至少在本项目中)

界面需要有边框,需要把蛇装起来,需要生成食物......

UI需要的属性有:

得分

食物

界面

 需要有的动作(函数):

展示画面

移动蛇

检测玩家操作

生成食物

检查蛇的移动是否合规

由上可以写出UI类的大体框架

class UI {friend void getDirection(UI& u);
private:int Width;		//宽int Height;		//高int score=0;	//得分snack role;		//蛇std::default_random_engine e;	//用于生成随机数,随机生成食物vector<int>food = vector<int>(2);	//存放食物坐标vector<vector<char>>UI_Cache;		//界面缓存,直接打印出来即可
public:UI(int width = 50, int height = 20);void show();	void moveSnack();	//移动蛇bool check(char c);	//检查输入字符void getDirection();	//获取玩家操作void createFood();	//生成食物void checkRole();	//检查蛇的移动是否合规void run();			//运行
};

生成界面

那么框架有了,我们只需要把里面的功能实现就行,那么界面该怎么生成呢?

我们直接把界面缓存打印出来,界面缓存是个二维vector,刚好是界面的长*宽,因此我们需要把二维vector的边界修改成游戏的边界,这一点可以在UI的构造函数里完成,说到构造函数,我们很多功能都可以在构造函数里完成,例如初始化蛇,初始化食物......

如下所示:

UI::UI(int width, int height) :Width(width), Height(height) {UI_Cache.resize(height, vector<char>(width, ' '));	//将画面大小重置role = snack(width/2,height/2);		//初始化蛇,传入参数使蛇在画面中间e.seed(time(0));	//给随机数引擎设置随机数种子//绘制边框for (int i = 0; i < height; i++) UI_Cache[i][0] = UI_Cache[i][width - 1] = '|';for (int j = 0; j < width; j++) UI_Cache[0][j] = UI_Cache[height - 1][j] = '-';UI_Cache[0][0] = UI_Cache[0][width - 1] = UI_Cache[height - 1][0] = UI_Cache[height - 1][width - 1] = '*';//打印开始界面for (int i = 0; i < Height / 2-1; i++) cout << endl;	//使得welcome在界面中央for (int j = 0; j < Width / 2; j++)cout << "  ";cout << "welcome" << endl;//初始化食物createFood();Sleep(1500);system("cls");
}

蛇的移动

界面有了之后,我们应该让蛇移动了,但是在游戏的一开始应该让蛇停在画面中间,直到玩家开始操作之后再开始移动,我们该怎么移动蛇呢?

蛇分为头部和身子,蛇的每次移动实际上是移动头部,而身子的大部分是不变的,我们可以找到规律,每次移动,身子的最后一节都会没有,而原本头所在的位置会变成最开始的一节身子,那么我们可以在蛇移动的时候,删去存放蛇身子的vetcor的最后一个(pop_back()),然后再把头插入到存放蛇身子的vector的第一位(insert(body.begin(),head));

void snack::crawl() {	//爬行,将头部变成第一节身子,然后将身子的末尾去掉.if (direction == ' ') return;	//开局先不动,方向默认是空字符,可以直接返回.body.insert(body.begin(), head);body.pop_back();//调增新头部的位置if (direction == 'w') head[1]--;		//向上,则是将y轴的值减一else if (direction == 's') head[1]++;	//向下,则是将y轴的值加一else if (direction == 'a') head[0]--;	//向左,则是将x轴的值减一else if (direction == 'd') head[0]++;	//向右,则是将x轴的值加一
}

说到移动,就不能忘记方向,因此我们需要有调整蛇方向的函数,要注意的是,蛇不能掉头(180°转弯),只能90°转.

bool snack::changeDirection(char newDirection) {//调整移动方向//这里需要做个小判断,比如正在向左走就不能转到右,不能直接180°转弯if (newDirection == 'w' && direction == 's') return false;if (newDirection == 's' && direction == 'w') return false;if (newDirection == 'a' && direction == 'd') return false;if (newDirection == 'd' && direction == 'a') return false;direction = newDirection;return true;
}

方向是需要玩家输入的,因此我们需要实现玩家输入的函数,本来我是想做成多线程的,这样子扫描玩家输入比较流畅,但是我查了半个下午,调试了四分之一个下午都没能搞定,因此还是就做成固定查询了.(如果有搞定了多线程扫描输入的好兄弟,可以评论告诉我)

#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0)bool UI::check(char c) {//检测某个按键是否按下if (KEY_DOWN(c)) return true;return false;
}void UI::getDirection() {if (check('Q')) {		//按q则退出cout << "game is over!" << endl;cout << "your score is " << score << endl;exit(0);}//操纵蛇else if (check('A') || check(37)) role.changeDirection('a');else if (check('S') || check(40)) role.changeDirection('s');else if (check('D') || check(39)) role.changeDirection('d');else if (check('W') || check(38)) role.changeDirection('w');
}

 当然,我们移动蛇只是改变了蛇的属性,我们还需要在UI界面实现更新蛇坐标的功能:

void UI::moveSnack() {//将蛇的头部和身子分别映射到UI的缓存中,这里可以修改蛇头部和身子的字符cout << role.head[0] << ' ' << role.head[1] << endl;UI_Cache[role.head[1]][role.head[0]] = '@';for (auto b : role.body) {UI_Cache[b[1]][b[0]] = '*';}
}

 如果直接向上面那样的话会有一个问题,就是蛇会一直变长,这是因为在UI界面的缓存中,我们仍然缓存着上一次的蛇身子的坐标,而我们这里只是在UI缓存中将蛇的身体的坐标赋值成了特定的符合,而没有删除上一次的缓存,这就导致了因为移动而被我们删除的最后一节身子仍然留在缓存中,所以我们还需要清除缓存,但是如果因此而重置缓存的话,那么会影响运行效率,因此我们仅仅将移动前的身子最后一节的坐标映射在UI缓存中的位置置位空字符即可:

UI_Cache[role.body[role.size - 1][1]][role.body[role.size - 1][0]] = ' ';

食物

接下来就剩食物了,生成食物使用随机数引擎,在界面宽高的范围内生成随机的x,y坐标,还需要检查,如果生成在了蛇的位置上则需要重新生成.

void UI::createFood() {	//生成食物std::uniform_int_distribution<unsigned> u(1,Width-2);	//设置随机数生成范围int x, y;while (1) {x = u(e), y = u(e) % (Height - 2);	if (x <= 0 || y <= 0 || x >= Width - 1 || y >= Height - 1) continue;	//如果生成的食物坐标不在边界内则重新生成.if (x == role.head[0] && y == role.head[1]) continue;	//如果生成的食物坐标和蛇头一致则重新生成.for (auto a : role.body) {if (y == a[1] && x == a[0]) continue;	//如果生成的食物坐标与蛇身一致则重新生成.}break;}food[0] = x, food[1] = y;UI_Cache[y][x] = '$';		//这里可以更改食物的字符
}

我们还需要检测蛇是否吃到了食物,以及是否吃到了自己和是否撞墙.

撞到墙或吃到自己是毫无疑问结束游戏,而吃到了食物,我们则需要将玩家的得分增加,并且增长蛇的长度,我们可以将食物的位置变成新的蛇头位置,并且将老蛇头的坐标插入到蛇身体里,这样就完成了蛇身体的增长.

不要忘记生成新的食物:

void UI::checkRole() {	//检查移动是否合法int x = role.head[0], y = role.head[1];if (x <= 0 || y <= 0 || x >= Width-1 || y >= Height-1) {	//碰到边界则失败cout << "you are lose" << endl;cout << "your score is " << score << endl;exit(0);}for (auto a : role.body) {	//碰到自己的身体则失败if (a[0] == x && a[1] == y) {cout << "you are lose" << endl;cout << "your score is " << score << endl;exit(0);}}if (x == food[0] && y == food[1]) {	//吃到食物则分数增加,蛇身体长度增加.score++; role.size++;role.body.insert(role.body.begin(),role.head);role.head[0] = food[0], role.head[1] = food[1];	//将食物位置直接变成新蛇头的位置,达到身体增长的效果.createFood();	//重新生成食物}
}

代码领取:

免费领取完整代码可以关注我的公众号:折途想要敲代码,回复关键字"贪吃蛇"即可.

也可以直接在CSDN上下载,我也已经上传到CSDN了.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/29594.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

bgp联邦

1、ip配置 [r1-LoopBack0]ip address 192.168.1.1 24 [r1-LoopBack1]ip address 10.0.0.1 24 [r1-GigabitEthernet0/0/0]ip address 12.0.0.1 24[r2-GigabitEthernet0/0/0]ip address 12.0.0.2 24 [r2-GigabitEthernet0/0/1]ip address 172.16.1.1 29 [r2-GigabitEthernet0/0/…

BTY生态系统DNS关于DeSoc的构想

2022年5月&#xff0c;以太坊创始人Vitalik Buterin与经济学家Glen Weyl和Flashbots研究员Puja Ohlhaver联合发布了《Decentralized Society: Finding Web3’s Soul》。这篇论文的核心是围绕“Web3灵魂”创造出去中心化社会的可能性。 论文中阐述&#xff0c;当下Web3 更多是表…

Docker【安装与基本使用】

【1】Docker的安装 注意&#xff1a;如果之前安装过docker其他版本&#xff0c;请删除干净。 docker-01 10.0.0.51 2G docker-02 10.0.0.52 2G docker-01 [rootdocker-01 ~]# cp -rp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime cp: overwrite ‘/etc/localtime’? …

【论文笔记】KDD2019 | KGAT: Knowledge Graph Attention Network for Recommendation

Abstract 为了更好的推荐&#xff0c;不仅要对user-item交互进行建模&#xff0c;还要将关系信息考虑进来 传统方法因子分解机将每个交互都当作一个独立的实例&#xff0c;但是忽略了item之间的关系&#xff08;eg&#xff1a;一部电影的导演也是另一部电影的演员&#xff09…

Unity打包窗口化放大、缩小、拖拽功能、无边框设置 C#

Unity打包Windows窗口实现放大、缩小、拖拽、无边框 文章目录 Unity打包Windows窗口实现放大、缩小、拖拽、无边框前言一、引入 user32.dll二、使用步骤1.引入库2.功能封装3.效果图如下&#xff0c;绑定自定义按钮 总结 前言 Unity无边框设置、窗口化放大、缩小、拖拽 提示&am…

# Linux下替换删除文件中的颜色等控制字符的方法

Linux下替换删除文件中的颜色等控制字符的方法 文章目录 Linux下替换删除文件中的颜色等控制字符的方法1 Linux下的控制字符&#xff08;显示的文字并不是他本身&#xff09;&#xff1a;2 颜色字符范例&#xff1a;3 替换4 最后 我们在shell编程显示输出时&#xff0c;会定义文…

小白带你学习Linux的rsync的基本操作(二十四)

目录 前言 一、概述 二、特性 1、快速 2、安全 三、应用场景 四、数据的同步方式 五、rsync传输模式 六、rsync应用 七、rsync命令 1、格式 2、选项 3、举例 4、配置文件 5、练习 八、rsyncinotfy实时同步 1、服务器端 2、开发客户端 前言 Rsync是一个开源的…

Linux 安装oracle_11g保姆级安装教程及安装Oracle常见问题处理

一 安装linux 1 Linux系统 安装前的准备 linux安装建议&#xff1a; 1&#xff09;脑内存1G&#xff0c;交换空间是物理内存的两倍&#xff0c;空闲空间&#xff08;/tmp目录&#xff09;至少1G 2&#xff09;选择服务时&#xff0c;建议全部选中&#xff0c;关闭防火墙&am…

python实现正态分布变换NDT算法(notebook版本)

直接看notebook&#xff0c;写的应该很清楚了 准备数据 假设两帧机器人相对位置变化如下 采集得到两帧点云 构建得分地图 优化点云位姿 红色为初值&#xff0c;蓝色为优化结果&#xff0c;可以看出NDT算法对初值要求不高。

标注工具Labelimg,正常运行显示,但是对图片点击Create RectBox画矩形框开始闪退

问题描述*&#xff1a;标注工具Labelimg&#xff0c;正常运行显示&#xff0c;但是对图片点击Create RectBox画矩形框开始闪退&#xff0c;闪退出现以下代码 File “C:\ProgramData\anaconda3\Lib\site-packages\libs\canvas.py”, line 530, in paintEvent p.drawLine(self.p…

图采样、随机游走、subgraph的实现

诸神缄默不语-个人CSDN博文目录 本博文主要关注如何用代码实现图采样、随机游走、subgraph&#xff08;为什么这些东西放在一起写&#xff0c;我感觉还蛮直觉的&#xff09;。 随机游走和subgraph我之前都写过不少博文了&#xff0c;可以参考↑ 这个主要是我前年还在干GNN时候…

Hugging Face开源库accelerate详解

官网&#xff1a;https://huggingface.co/docs/accelerate/package_reference/accelerator Accelerate使用步骤 初始化accelerate对象accelerator Accelerator()调用prepare方法对model、dataloader、optimizer、lr_schedluer进行预处理删除掉代码中关于gpu的操作&#xff0…