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

news2024/10/7 2:26:46

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

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

先小小展示一下:

源码在文末免费领取. 

使用工具:

  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.coloradmin.cn/o/772298.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

three.js学习记录(基础)

前言&#xff1a; 页面渲染3D特效&#xff0c;可以从各种图表库&#xff08;例如ECharts&#xff09;中寻找各种适用的模版&#xff0c;也可以寻找第三方插件。。。 一直以来都对three.js充满向往&#xff0c;终于偷闲找了个借口学了起来 参考资料 Three.js – JavaScript 3D…

MySQL数据库(五)

目录 一、数据库的约束 1.1 约束类型 1.1.1 null约束 1.1.2unique约束 1.1.3default默认值约束 1.1.4primary key主键约束 1.1.5foreign key外键约束 二、内容重点总结 一、数据库的约束 1.1 约束类型 not null - 指示某列不能存储 null值。unique - 保证某列的每行必须有唯一…

【C++】入门基础知识

命名空间 由来 在c语言中&#xff0c;任何函数&#xff0c;变量的名字都不能一样&#xff0c;如果是一个人的代码库里还好&#xff0c;大不了自己再改改&#xff0c;但是当和其他人&#xff0c;公司的库合起来&#xff0c;那就很麻烦了&#xff0c;到底是谁的名字改呢&#x…

求Sn=a + aa + aaa + aaaa + aaaaa的前5项之和,其中a是一个数字

求Sna aa aaa aaaa aaaaa的前5项之和&#xff0c;其中a是一个数字。 例如&#xff1a;2 22 222 2222 22222 解析&#xff1a; a aa aaa aaaa aaaaa a a*10a (a*10a)*10 a ((a*10a)*10 a) a ((a*10a)*10 a) a 直接上代码吧&#xff1a; #include&l…

java工作随笔

String s JSONObject.toJSONString(fixedAsset);logger.error("-----------------8------------------" s);CusFixedAssettDTO CusFixedAssettDTO3 JSONObject.parseObject(s, CusFixedAssettDTO.class);父类转子类 相同对象合并 import java.util.Date; Data T…

手把手教你搭建SpringCloud项目(九)集成OpenFeign服务接口调用

一、OpenFeign简介 什么是Feign&#xff1f; Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行封装&#xff0c;使其支持…

要想做好日语翻译,你需要这样做?

我们知道&#xff0c;中日两国一衣带水比邻而居&#xff0c;彼此之间的文化贸易往来非常频繁&#xff0c;对日语翻译的需求也日益增加。那么&#xff0c;想要做好日语翻译&#xff0c;我们该怎么做&#xff1f; 首先&#xff0c;要具备扎实的日语语言能力、较高的母语水平以及灵…

零基础学习,轻松打造物业服务小程序

现如今&#xff0c;物业服务已经成为了人们生活中不可或缺的一部分。为了更好地满足人们对物业服务的需求&#xff0c;许多企业和个人开始开发物业服务小程序&#xff0c;以便提供更加便捷和高效的服务。然而&#xff0c;对于大多数人来说&#xff0c;搭建一个小程序可能需要一…

从小白到大神之路之学习运维第64天--------Zabbix监控mysql、ftp服务以及自定义配置

第三阶段基础 时 间&#xff1a;2023年7月19日 参加人&#xff1a;全班人员 内 容&#xff1a; Zabbix监控mysql、ftp服务以及自定义 目录 一、Zabbix监控mysql数据库 二、Zabbix监控ftp服务 三、Zabbix自定义监控项 整体zabbix搭建完成&#xff0c;server端huyang1监…

golang 日志库zap和日志切割实践

介绍 在许多Go语言项目中&#xff0c;我们需要一个好的日志记录器能够提供下面这些功能&#xff1a; 能够将事件记录到文件中&#xff0c;而不是应用程序控制台。 日志切割-能够根据文件大小、时间或间隔等来切割日志文件。 支持不同的日志级别。例如INFO&#xff0c;DEBUG&a…

uni.app开发小程序如何获取当前经纬度、位置信息以及如何重新发起授权定位

uni.app开发小程序如何获取当前经纬度、位置信息以及如何重新发起授权定位 前提 先去微信小程序后台申请 wx.getLocation接口1.引入下载的高德小程序SDK2.data中定义所需变量3.onLoad中获取实例 并调用获取经纬度 位置方法4.定义获取定位经纬度 位置信息方法5.用户拒绝授权后,可…

【MySQL】查询进阶

查询进阶 数据库约束约束类型NULL , DEFAULT , UNIQUE 约束主键约束外键约束 聚合查询聚合函数group by子句HAVING 联合查询内连接外连接自连接子查询单行子查询多行子查询 数据库约束 约束类型 NOT NULL #表示某行不能储存空值 UNIQUE #保证每一行必须有唯一的值 DEFAULT #规…

DT灯光基础(辉光 雾 阴影 渲染选项)

点光源 不能宣染&#xff0c;换个版本。不能正常预览 聚光灯 t 手柄 挡光版 平行光阴影 光线追踪阴影 没有看见阴影 灯光使用贴图 环境光 不进行渲染物体 不渲染阴影 接收阴影 不反射 可以看到反射 没有反射了 灯光链接 取消灯照 灯光雾 辉光 变化不明显

【C++】C++入门必备知识详细讲解

C入门必备知识 一、命名空间1. namespace2. namespace 的使用场景 二、了解 C 中的输入和输出三、缺省参数四、函数重载1. 函数重载的概念2. C支持函数重载的原理 五、引用1. 引用的概念2. 引用特性3. 常引用4. 引用的使用场景&#xff08;1&#xff09;做参数&#xff08;传引…

No.2(3)——双指针算法实现平方数组排序

双指针算法指的是&#xff0c;从数组的两侧开辟指针变量进行查找&#xff0c;这类问题往往通过暴力&#xff08;双循环&#xff09;可以解出&#xff0c;而采用双指针相当于用空间换取时间&#xff0c;省略双层循环中重复的部分。 对于一个含有负数的有序数组&#xff0c;要求保…

Kubernetes Volume及其类型(NFS、SAN) - PV - PVC - PV与PVC与Pod的关系

目录 volume 卷 官方文档&#xff1a;卷 | Kubernetes 一、emptyDir&#xff08;临时卷&#xff09; 二、hostPath卷 type字段参数 hostPath 实验&#xff1a; 三、第3方提供的存储卷&#xff08;百度云、阿里云、亚马逊云、谷歌云等&#xff09; 四、local卷 五、NF…

Geany配置python虚拟环境(Anaconda)

Geany配置python运行环境步骤&#xff1a; 以我的conda为例 我的conda中python虚拟环境base环境python位于路径 D:\Anaconda3 打开Geany -> 生成 -> 设置生成命令 在弹出的窗口内输入以下命令 # Compile选项后修改 "D:\Anaconda3\python.exe" -m py_compi…

准备WebUI自动化测试面试?这30个问题你必须掌握(一)

本文共有8600字&#xff0c;包含了前十五个问题&#xff0c;如需要后十五个问题&#xff0c;可查看文末链接~ 1. 什么是WebUI自动化测试&#xff1f; WebUI自动化测试是指使用自动化测试工具和技术来模拟用户在Web用户界面&#xff08;UI&#xff09;上执行操作&#xff0c;并…

条件概率我知道,但什么是条件期望?--草稿

目录 1 目标问题&#xff1a; 什么是条件期望 2 条件期望&#xff0c;全期望公式 1 目标问题&#xff1a; 什么是条件期望 这次先不说目标 先引用一个小学数学题 1班平均分是93,2班平均分是95,两个班的平均分怎么算 错误算法&#xff0c;(9395)/294 除非两个班的学生数量一…

园区电能监测系统

园区电能监测系统是一种能够对园区内电能使用情况进行实时监测和管理的系统&#xff0c;可以帮助企业更好地控制能源消耗&#xff0c;提高能源利用效率&#xff0c;从而降低能源成本&#xff0c;对于推进节能减排和可持续发展具有重要意义。 园区电能监测系统通常由多个子系统组…