贪吃蛇小游戏(C++)

news2024/11/17 6:02:26

首先我们需要下载EasyX(具体的方法在EasyX专栏中有提到)

easyX下载和绘制简单基本图形_小梁今天敲代码了吗的博客-CSDN博客

贪吃蛇这个游戏我们一定都玩过,玩家使用方向键操控一条“蛇”,蛇会朝着一个方向不断移动,玩家可以通过上下左右键改变其运动方向。同时屏幕上随机会出现各种“食物”,玩家要控制蛇去吃掉这些食物,每吃掉一个,蛇的身体就会增长一节。

想要实现这个游戏,我们主要得考虑蛇和食物

蛇:

蛇的移动方向如何实现

蛇的身体长度如何实现增长

食物:

食物如何随机生成

下面我将代码部分拆解做一个简单的说明,后附源码

首先是播放背景音乐:

将音乐的mp3格式与代码放在一个文件夹中,用以下函数调用:

#include<mmsystem.h>//头文件
#pragma comment(lib,"winmm.lib")//库文件
	//播放音乐 mci media control interface(多媒体设备接口)
	//Send 发送 String字符串
//音乐需要放在程序同一文件目录中
mciSendString("open 音乐.mp3",0,0,0);
mciSendString("play 音乐.mp3", 0, 0, 0);

 创建“精灵类”,它是“蛇类”和“食物类”的父类

这里难理解的地方是:碰撞检测(我们如何去判定蛇能吃到食物?这里采用的方法是当蛇头的左上方x,y坐标完全与食物的左上方x,y坐标相等时,我们认为蛇吃到了食物)

这个函数用于设置当前设备填充颜色。

void setfillcolor(COLORREF color);

 

这个函数用于画有边框的填充矩形。

void fillrectangle(
	int left,
	int top,
	int right,
	int bottom
);

 

//精灵类
class Sprite
{
public:
	Sprite():Sprite(0,0) {};
	Sprite(int x, int y) :m_x(x), m_y(y),m_color(RED) {};
	//绘制精灵
	virtual void draw()
	{
		//设置填充颜色
		setfillcolor(m_color);
		//绘制矩形
		fillrectangle(m_x, m_y, m_x + 10, m_y + 10);
	}
	//移动
	void moveBy(int dx, int dy)
	{
		m_x += dx;
		m_y += dy;
	}
	//碰撞检测
	bool collision(const Sprite& other)
	{
		return m_x == other.m_x && m_y == other.m_y;
	}
protected:
	int m_x;
	int m_y;
	COLORREF m_color;//颜色
};

蛇类这里移动时,将上一个格子坐标赋值给下一个格子,实现移动

push_back()函数的用法

函数将一个新的元素加到vector的最后面,位置为当前最后一个元素的下一个元素

//蛇类
class Snake : public Sprite
{
public:
	Snake():Snake(0,0) {}
	Snake(int x,int y):Sprite(x,y),dir(VK_RIGHT)
	{
		//初始化三节蛇
		nodes.push_back(Sprite(20, 0));
		nodes.push_back(Sprite(10, 0));
		nodes.push_back(Sprite(0, 0));
	}
	void draw() override
	{
		for (int i = 0; i < nodes.size(); i++)
		{
			nodes[i].draw();
		}
	}
	//蛇的身体移动
	void bodyMove()
	{
		//身体跟着蛇头移动
		for (size_t i = nodes.size()-1; i >0; i--)
		{
			nodes[i] = nodes[i - 1];
		}
		//移动蛇头
		switch (dir)
		{
		case VK_UP:
			nodes[0].moveBy(0,-10);
			break;
		case VK_DOWN:
			nodes[0].moveBy(0,10);
			break;
		case VK_LEFT:
			nodes[0].moveBy(-10, 0);
			break;
		case VK_RIGHT:
			nodes[0].moveBy(10, 0);
			break;
		}
		
	}
	bool collision(const Sprite& other)
	{
		return nodes[0].collision(other);
	}
	//蛇增加一节
	void incrment()
	{
		nodes.push_back(Sprite());
	}
private:
	//蛇只有一节吗?
	std::vector<Sprite> nodes;//蛇的所有节点

public:
	int dir;//蛇的方向
};

 食物:

为了使程序在每次执行时都能生成一个新序列的随机值,我们通常通过为随机数生成器提供一粒新的随机种子。函数srand()可以为随机数生成器播散种子。随机生成食物时,我们需要它的坐标为10的整数倍,因为蛇的身体我们设定的占10个像素,所以只能吃到坐标为10的整数倍的食物

//食物
class Food :public Sprite
{
public:
	Food() :Sprite(0, 0) 
	{
		changePos();
	}
	void draw()override
	{
		setfillcolor(m_color)
			;
		solidellipse(m_x, m_y, m_x + 10, m_y + 10);
	}
	//改变食物的坐标
	void changePos()
	{
		//随机生成坐标
		m_x = rand() % 64 * 10;
		m_y = rand() % 48 * 10;
	}
};

 游戏场景:

这里引用的函数是什么意思基本已经在代码中进行了标注

这里的switch代码是在蛇的移动过程中,让它不能掉头

switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}

 ExMessage这个结构体用于保存鼠标消息

WM_KEYDOWNEX_KEY按键按下消息

BeginBatchDraw()这个函数用于开始批量绘图。执行后,任何绘图操作都将暂时不输出到绘图窗口上,直到执行 FlushBatchDraw 或 EndBatchDraw 才将之前的绘图输出。

void BeginBatchDraw();

 

cleardevice()这个函数使用当前背景色清空绘图设备。

void cleardevice();
/*游戏场景*/
class GameScene
{
public:
	GameScene() 
	{

	};
	void run()
	{
		BeginBatchDraw();//双缓冲绘图 防止屏幕闪烁
		cleardevice();//清屏
		snake.draw();
		food.draw();
		EndBatchDraw();
		//移动蛇,改变蛇的坐标
		snake.bodyMove();
		snakeEatFood();


		//获取消息
		ExMessage msg = { 0 };
		while (peekmessage(&msg, EX_KEY))
		{
			onMsg(msg);
		}

	}
	//改变蛇的移动方向 获取键盘按键 _getch() 
	//响应消息:鼠标消息 键盘消息
	void onMsg(const ExMessage& msg)
	{
		//如果有键盘消息(有没有按键按下)
		if (msg.message == WM_KEYDOWN)
		{
			//判断具体的是哪个按键按下 virtual key code 虚拟键码

			switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}
			
			//std::cout << msg.vkcode << std::endl;
		}

	}
	//判断蛇能否吃到食物
	void snakeEatFood()
	{
		if (snake.collision(food))//如果蛇和食物产生了碰撞
		{
			//蛇的节数增加
			snake.incrment();
			//食物重新产生在别的地方
			food.changePos();
		}
	}
private:
	Snake snake;
	Food food;

};

 最后源码如下:

#include<iostream>//标准输入输出头文件
#include<graphics.h>
#include<mmsystem.h>
#pragma comment(lib,"winmm.lib")
#include<easyx.h>
#include<vector> //顺序表
#include<ctime>
//精灵类
class Sprite
{
public:
	Sprite():Sprite(0,0) {};
	Sprite(int x, int y) :m_x(x), m_y(y),m_color(RED) {};
	//绘制精灵
	virtual void draw()
	{
		//设置填充颜色
		setfillcolor(m_color);
		//绘制矩形
		fillrectangle(m_x, m_y, m_x + 10, m_y + 10);
	}
	//移动
	void moveBy(int dx, int dy)
	{
		m_x += dx;
		m_y += dy;
	}
	//碰撞检测
	bool collision(const Sprite& other)
	{
		return m_x == other.m_x && m_y == other.m_y;
	}
protected:
	int m_x;
	int m_y;
	COLORREF m_color;//颜色
};

//蛇类
class Snake : public Sprite
{
public:
	Snake():Snake(0,0) {}
	Snake(int x,int y):Sprite(x,y),dir(VK_RIGHT)
	{
		//初始化三节蛇
		nodes.push_back(Sprite(20, 0));
		nodes.push_back(Sprite(10, 0));
		nodes.push_back(Sprite(0, 0));
	}
	void draw() override
	{
		for (int i = 0; i < nodes.size(); i++)
		{
			nodes[i].draw();
		}
	}
	//蛇的身体移动
	void bodyMove()
	{
		//身体跟着蛇头移动
		for (size_t i = nodes.size()-1; i >0; i--)
		{
			nodes[i] = nodes[i - 1];
		}
		//移动蛇头
		switch (dir)
		{
		case VK_UP:
			nodes[0].moveBy(0,-10);
			break;
		case VK_DOWN:
			nodes[0].moveBy(0,10);
			break;
		case VK_LEFT:
			nodes[0].moveBy(-10, 0);
			break;
		case VK_RIGHT:
			nodes[0].moveBy(10, 0);
			break;
		}
		
	}
	bool collision(const Sprite& other)
	{
		return nodes[0].collision(other);
	}
	//蛇增加一节
	void incrment()
	{
		nodes.push_back(Sprite());
	}
private:
	//蛇只有一节吗?
	std::vector<Sprite> nodes;//蛇的所有节点

public:
	int dir;//蛇的方向
};
//食物
class Food :public Sprite
{
public:
	Food() :Sprite(0, 0) 
	{
		changePos();
	}
	void draw()override
	{
		setfillcolor(m_color)
			;
		solidellipse(m_x, m_y, m_x + 10, m_y + 10);
	}
	//改变食物的坐标
	void changePos()
	{
		//随机生成坐标
		m_x = rand() % 64 * 10;
		m_y = rand() % 48 * 10;
	}
};
/*游戏场景*/
class GameScene
{
public:
	GameScene() 
	{

	};
	void run()
	{
		BeginBatchDraw();//双缓冲绘图 防止屏幕闪烁
		cleardevice();//清屏
		snake.draw();
		food.draw();
		EndBatchDraw();
		//移动蛇,改变蛇的坐标
		snake.bodyMove();
		snakeEatFood();


		//获取消息
		ExMessage msg = { 0 };
		while (peekmessage(&msg, EX_KEY))
		{
			onMsg(msg);
		}

	}
	//改变蛇的移动方向 获取键盘按键 _getch() 
	//响应消息:鼠标消息 键盘消息
	void onMsg(const ExMessage& msg)
	{
		//如果有键盘消息(有没有按键按下)
		if (msg.message == WM_KEYDOWN)
		{
			//判断具体的是哪个按键按下 virtual key code 虚拟键码

			switch (msg.vkcode)
			{
			case VK_UP:
				if (snake.dir != VK_DOWN)
					snake.dir = msg.vkcode;
				break;
			case VK_DOWN:
				if (snake.dir != VK_UP)
					snake.dir = msg.vkcode;
				break;
			case VK_LEFT:
				if (snake.dir != VK_RIGHT)
					snake.dir = msg.vkcode;
				break;
			case VK_RIGHT:
				if (snake.dir != VK_LEFT)
					snake.dir = msg.vkcode;
				break;
			}
			
			//std::cout << msg.vkcode << std::endl;
		}

	}
	//判断蛇能否吃到食物
	void snakeEatFood()
	{
		if (snake.collision(food))//如果蛇和食物产生了碰撞
		{
			//蛇的节数增加
			snake.incrment();
			//食物重新产生在别的地方
			food.changePos();
		}
	}
private:
	Snake snake;
	Food food;

};
int main()
{
	//初始化窗口界面
	initgraph(640,480,EW_SHOWCONSOLE);
	GameScene scene;
	scene.run();
	//设置随机数种子
	srand(time(nullptr));
	//播放音乐 mci media control interface(多媒体设备接口)
	//Send 发送 String字符串
	mciSendString("open 音乐.mp3",0,0,0);
	mciSendString("play 音乐.mp3", 0, 0, 0);

	Snake snake;
	snake.draw();
	while (true)
	{
		scene.run();
		Sleep(100);
	}
	getchar();//防止程序闪退
	return 0;
}

至此我们就实现了简单的贪吃蛇游戏,它可以上下左右移动,并在吃到食物后增长一格身体

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

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

相关文章

主成分分析

一、案例与数据 某研究者对企业员工进行调查&#xff0c;并且制定了一份问卷&#xff0c;研究者想要将问卷中的多个量表题进行浓缩以便后续分析&#xff0c;比如休假制度、资金制度、工资水平或者晋升制度等等&#xff0c;其中部分数据如下&#xff1a; 二、分析问题 其实想要…

时序预测 | MATLAB实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆网络时间序列预测

时序预测 | MATLAB实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆网络时间序列预测 目录 时序预测 | MATLAB实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现WOA-BiLSTM鲸鱼算法优化双向长短期记忆网络时间…

前端已死还是“娱乐至死”?做个清醒的前端

目录 一、前言 二、为什么会出现“前端已死”的观点&#xff1f; 1、技术变化太快&#xff1a; 2、前端工程化带来的挑战&#xff1a; 3、新技术的崛起&#xff1a; 三、前端该如何提升自己 1、学习新技术&#xff1a; 2、掌握前端工程化&#xff1a; 3、参与社区和开…

远程网关桥接模式实现同一局域网组网管理(Superlink)

远程网关桥接模式配置介绍及示例 功能简述 智联物联远程网关桥接模式&#xff0c;是指电脑侧Superlink远程工具及前端ZP网关通过4G/WAN/WiFi客户端等方式分别接入互联网及后端服务器平台远程服务后&#xff0c;再借助虚拟网卡桥接技术&#xff0c;先由服务器平台下发桥接网络参…

室内定位之5G定位

一、前言 随着5G通信基础设施逐步完善&#xff0c;5G网络的优势一方面在于其具有的更大的带宽和子载波间隔&#xff0c;使得5G室内定位精度提升(3.5GHz频段下定位精度在5米左右&#xff0c;毫米波定位精度可以达到3米或更高)&#xff0c;相对于其他定位技术&#xff0c;已建成…

解决docker启动mysql无法输入中文以及中文不显示或乱码问题

前言 我在使用MySQL时&#xff0c;遇到了两个问题。一是在插入中文数据时&#xff0c;无法输入中文。二是在select的时候&#xff0c;查出来的中文数据是空的&#xff08;因为插入时为空&#xff09;&#xff0c;然后我就使用Navicat连接数据库添加了中文数据&#xff0c;再到…

【MySQL】联合查询子查询以及合并查询的使用

目录 上篇在这里喔~ GROUP BY分组子句与联合查询的使用详解 联合查询步骤 1.自连接 1.查询每位同学的计算机原理和Java的成绩 2.显示所有计算机原理成绩比java成绩高的成绩信息 2.子查询 1.单行子查询 1.查询’许仙‘的同班同学 2.多行子查询 1.查询语文或英语课程的…

综合管廊智慧运维管理平台应用研究

摘要&#xff1a;为提升综合管廊运维管理水平&#xff0c;实现管理的数字化转型&#xff0c;采用综合监测系统、BIMGIS 可视化系统、智能机器人巡检、结构安全监测等技术&#xff0c;搭建实时监控、应急管理、数据分析等多功能为一体的智慧管廊运维管理平 台&#xff0c;为综合…

Python爬虫基础之二

Python爬虫基础包括HTTP协议、HTML、CSS和JavaScript语言基础、requests库的使用、Beautiful Soup库的使用、xpath和正则表达式的使用等。此外&#xff0c;还应该了解反爬虫机制和爬虫的一些常见问题及解决方法。 上一篇文章讲解了有关条件判断语句、循环语句等相关知识&#…

stm32串口中断流程

NVIC简介 内嵌向量中断控制器:Nested Vectored Interrupt Controller (NVIC) 硬件发生中断后,相关电信号会触发内核跳转中断向量表查找中断函数, 中断向量名查于汇编文件startup_stm32xxx.s文件 从本质上说这里就是数字电路的组合形成的一系列行为&#xff0c;当相关中断的寄存…

一键汇总——高效电脑检索方案

文件检索和分类对于电脑内部的文件检索非常重要&#xff0c;因为它们可以帮助用户快速找到需要的文件。在电脑内部&#xff0c;文件通常被分类存储在不同的分区中&#xff0c;这有助于管理员更好地组织和查找文件。对于分区资源整合&#xff0c;以下是一些建议&#xff1a; 了…

大好河山集团董事长黄国林受邀出席2023中国好公司高峰论坛暨产学研合作峰会

大好河山集团董事长黄国林受邀出席2023中国好公司高峰论坛暨产学研合作峰会 本网消息 2023年4月19日&#xff0c;由中国智慧工程研究会、华夏商邦俱乐部、中国流通研究院共同主办&#xff0c;中国品牌发展网联合主办的“2023中国好公司高峰论坛暨产学研合作峰会”在福州福清举…

应急照明系统在民用建筑的设计应用与产品选型

【摘要】应急照明分为备用照明、安全照明及疏散照明。文章介绍了应急照明系统的设计、灯具选择、灯具布置、配电等要求。并结合实例进行疏散照明的计算&#xff0c;以指导应急照明系统的设计与应用。 【关键词】照度&#xff1b;光通量&#xff1b;消防应急灯具&#xff1b;A型…

【Linux】解决切换用户出现bash-4.2$问题创建普通用户并设置密码、授权

【问题描述】 linux中创建了一个wxh用户&#xff0c;然后使用su命令切换用户后&#xff0c;终端提示符显示成“bash-4.2$”而不是[rootlocalhost wxh]#&#xff0c;导致ll等命令无法执行。 [rootlocalhost xhh]# su wxh bash-4.2$ ll bash: ll: 未找到命令 【原因】 没有在hom…

13种权重的计算方法

权重计算方法有很多种&#xff0c;不同的方法有不同的特点和适用情况。AHP层次分析法和熵值法在权重计算中属于比较常用的方法。除此之外&#xff0c;还有一些与权重计算相关的方法&#xff0c;今天一文总结了13种与权重计算相关的方法&#xff0c;大家可以对比选择使用。 一、…

【001-Java基础练习】-适合初学者的练习

用于巩固java基础知识&#xff0c;初学者多练多敲&#xff0c;熟悉代码&#xff0c;熟悉语法就ok。 练习1、从控制台获取Java、ps、HTML三门课程的成绩&#xff0c;计算总分和平均分&#xff08;平均分保留2位小数&#xff0c;要求四舍五入&#xff09;&#xff0c;输出总分和…

OpenAI最新官方ChatGPT聊天插件接口《插件部署上生产》全网最详细中英文实用指南和教程,助你零基础快速轻松掌握全新技术(五)(附源码)

Plugins in production 插件部署上生产 前言Rate limits 速率限制Updating your plugin 更新您的插件Plugin termsDomain verification and security 域验证和安全性Defining the plugins root domain 定义插件的根域Manifest validation 清单验证Resolving the API spec 解析A…

【小程序】input输入双向数据绑定

小程序中&#xff0c;input标签中的数据为单向绑定&#xff1a; <inputtype"number"bindinput"inputRealmoney"value"{{ amount }}"placeholder"请输入金额" />如上代码&#xff0c;我们绑定了输入框的数据amount&#xff0c;并…

前端后端实现防盗链

防盗链&#xff08;Referer Header&#xff09;是指在网页中嵌入的外部资源&#xff08;如图片、音视频等&#xff09;被非法使用或盗链的现象。为了避免这种情况的发生&#xff0c;我们可以通过前端和后端技术来防止盗链。 前端防盗链 前端防盗链可以通过在页面中添加 JavaSc…

在f1tenth仿真中如何实现更快速的跑圈-曲线分析篇

本文使用蓝桥云课&#xff0c;即开即用&#xff0c;如果配置第三方课程资源&#xff0c;通常也在10分钟内完成。 效果如下&#xff1a; 全部参考资料如下&#xff1a; 蓝桥ROS之f1tenth案例学习与调试&#xff08;失败&#xff09; 蓝桥ROS之f1tenth案例学习与调试&#xff…