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了.

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于linux下的高并发服务器开发(第二章)- 2.13 匿名管道通信案例

实现 ps aux | grep xxx 父子进程间通信 子进程&#xff1a; ps aux, 子进程结束后&#xff0c;将数据发送给父进程 父进程&#xff1a;获取到数据&#xff0c;过滤 pipe() execlp() 子进程将标准输出 stdout_fileno 重定向到管道的写端。 dup2 07 / 匿名管道…

【代码随想录 | Leetcode | 第七天】链表 | 链表相交 | 环形链表 II

前言 欢迎来到小K的Leetcode|代码随想录|专题化专栏&#xff0c;今天将为大家带来链表相交和环形链表 II的分享✨ 目录 前言面试题 02.07. 链表相交142. 环形链表 II总结 面试题 02.07. 链表相交 ✨题目链接点这里 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找…

Python应用:什么是爬虫?

文章目录 什么是爬虫虫之初&#xff0c;性本善&#xff1f;出行社交电商搜索引擎政府部门总结 面向监狱编程爬虫的君子协议什么是君子协议君子协议是怎么产生的&#xff1f;君子协议是什么内容&#xff1f;如何查看一个网站的robots协议违反君子协议的案例 参考文献 2022年初的…

用Vue如何实现低代码开发平台?

前言 在众多开发技术中&#xff0c;Vue组件化开发技术以其卓越的灵活性和高效性备受瞩目。 低代码平台相信不少人知道它的存在&#xff0c;而且现在大部分公司都在开发自己的低代码平台&#xff0c;首先我们来看看低代码平台可视化界面&#xff1a; 官网&#xff1a;https://ww…

水库大坝安全监测系统是由什么组成的?

水库大坝是防洪抗灾的重要设施&#xff0c;它们的安全性直接关系到人民群众的生命财产安全。因此&#xff0c;水库大坝的安全监测必不可少。水库大坝安全监测系统是一种集成了数据采集、传输、处理和分析的技术平台&#xff0c;能够实时、准确地监测大坝的状态&#xff0c;及时…

Unity游戏源码分享-Unity版本的经典斗地主游戏完整源码

Unity游戏源码分享-Unity版本的经典斗地主游戏完整源码 工程地址&#xff1a; https://download.csdn.net/download/Highning0007/88057828

MySQL第五章、索引事务

目录 一、索引 1.1 概念 1.2 作用 1.3 使用场景 1.4 使用 1.5 案例 二、索引背后的数据结构 2.1 B-树&#xff08;B树&#xff09; 2.2 B树&#xff08;MySQL背后数据结构&#xff09; 三、事务 3.1 为什么使用事务 3.2 事务的概念 3.3 使用 3.4并发执行事务产生…

【深度学习】张量的广播专题

一、说明 张量广播&#xff08;tensor broadcasting&#xff09;是一种将低维张量自动转化为高维张量的技术&#xff0c;使得张量之间可以进行基于元素的运算&#xff08;如加、减、乘等&#xff09;。在进行张量广播时&#xff0c;会将维度数较少的张量沿着长度为1的轴进行复制…

Vue中的侦听器:数据变化的秘密揭示

一、侦听器&#xff1a;vue中想监听数据的变化 &#x1f680;&#xff08;一&#xff09;侦听器watch 如何侦听到某个变量值改变呢&#xff1f;使用watch配置项&#x1f6a7;&#x1f6a7;&#x1f6a7;watch&#xff1a;可以侦听到data/computed属性值的改变。语法&#xff…

fileclude

背景知识 文件包含漏洞 题目 分析上述代码 file2被放入file_get_contents()函数&#xff0c;且要求返回值为hello ctf file1是要包含的文件&#xff0c;放在include函数中 用php://filter伪协议读取源代码 构造payload&#xff1a; file1php://filter/readconvert.base64-…

数字图像处理【11】OpenCV-Canny边缘提取到FindContours轮廓发现

本章主要介绍图像处理中一个比较基础的操作&#xff1a;Canny边缘发现、轮廓发现 和 绘制轮廓。概念不难&#xff0c;主要是结合OpenCV 4.5的API相关操作&#xff0c;为往下 "基于距离变换的分水岭图像分割" 做知识储备。 Canny边缘检测 在讲述轮廓之前&#xff0c;…

实现大文件传输的几种方法,并实现不同电脑间大文件传输

随着网络技术的快速发展&#xff0c;大文件的传输需求越来越多&#xff0c;如何在不同的电脑之间实现大文件的快速传输&#xff0c;是一个挑战&#xff0c;下面介绍几种常用的方法可以解决这个问题。 1、利用局域网传输&#xff1a;把两台电脑接入同一个网络环境&#xff0c;通…

Redis整合springboot笔记

redis整合springboot学习笔记 pom引入依赖 需要同时引入spring-boot-starter-data-redis和commons-pool2这2个依赖&#xff1b; spring-boot-starter-data-redis是官方封装的redis操作依赖, commons-pool2是redis需要的连接池&#xff0c;不引入这个会导致启动报错. <depe…

17 | 从后端到前端:微服务后,前端如何设计?

微服务架构通常采用前后端分离的设计方式。作为企业级的中台&#xff0c;在完成单体应用拆分和微服务建设后&#xff0c;前端项目团队会同时面对多个中台微服务项目团队&#xff0c;这时候的前端人员就犹如维修电工一样了。 面对如此多的微服务暴露出来的 API 服务&#xff0c…

适用于 Type-C接口PD应用的智能二极管保护开关

日前&#xff0c;集设计、研发、生产和全球销售一体的著名功率半导体、芯片及数字电源产品供应商Alpha and Omega Semiconductor Limited&#xff08;AOS, 纳斯达克代码:AOSL) 推出一款采用理想二极管运作进行反向电流保护的新型Type-C PD 高压电源输入保护开关。AOZ13984DI-02…

数据库应用:MySQL数据库SQL高级语句与操作

目录 一、理论 1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.MySQL中6种常见的约束 二、实验 1.克隆表与清空表 2.SQL高级语句 3.SQL函数 4.SQL高级操作 5.主键表和外键表 三、总结 一、理论 1.克隆表与清空表 克隆表&#xff1a;将数据表的数据记录…

【技巧】Maven重复依赖分析查找

【技巧】Maven重复依赖分析查找 遇到奇葩的错误可以考虑是不是依赖冲突了 比如同一段代码 再这个项目中好好的 另一个项目中不能用等 idea安装插件 maven helper 打开pom文件 输入要查找的依赖 将不用的排除掉 右键排除即可

lua脚本语言学习笔记

Lua 是一种轻量小巧的脚本语言&#xff0c;用标准C语言编写并以源代码形式开放&#xff0c; 其设计目的是为了嵌入应用程序中&#xff0c;从而为应用程序提供灵活的扩展和定制功能。 因为我们使用redis的时候一般要写lua脚本&#xff0c;这篇文章就介绍一下lua脚本语言的基础用…

前端 mock 数据的几种方式

目录 接口demo Better-mock just mock koa webpack Charles 总结 具体需求开发前&#xff0c;后端往往只提供接口文档&#xff0c;对于前端&#xff0c;最简单的方式就是把想要的数据写死在代码里进行开发&#xff0c;但这样的坏处就是和后端联调前还需要再把写死的数据…

大小端模式

文章目录 一、概念二、举例三、判大小端和交换 一、概念 大端模式&#xff08;Big-endian&#xff09;&#xff0c;是一种数据存储方式&#xff0c;其中较高的字节&#xff08;最高有效字节&#xff09;存储在较低的内存地址&#xff0c;较低的字节&#xff08;最低有效字节&am…