​7.3 项目3 贪吃蛇(控制台版) (A)​

C++自学精简实践教程 目录(必读)

主要考察

模块划分 / 文本文件读取

UI与业务分离 / 模块划分

控制台交互 / 数据抽象

需求

用户输入字母表示方向,实现贪吃蛇游戏

规则:碰到边缘和碰到蛇自己都算游戏结束

输入文件 data.txt

data.txt 内容如下:

6 7
0 0 0 0 0 0 0
0 0 2 0 2 0 0
0 0 0 0 0 0 0
0 0 0 0 0 2 0
0 0 0 0 0 1 0
0 0 0 0 0 0 0

第一行,包括两个整数,表示游戏棋盘大小。分别表示行数列数

例如,上图中表示游戏大小为6行,7列。

后面的内容是一个行数乘以列数的二维数组。

数组的元素为 0 表示这里什么也没有

数组的元素为 1 表示蛇的头。程序开始的时候,蛇没有身体,只有头

数组的元素为 2 表示食物。程序开始的时候,可以有多个食物

例如,上图中表示,蛇一开始位于棋盘的 第 5 行,第 6 列。同时有 3 个食物。

程序输出样式

运行效果

如下图所示,蛇的身体需要显示为 #蛇的头需要显示为 @食物需要显示为 $; 

实现思路

文件加载

文件加载只需要按照文件格式的规定,读取对应的信息保存在内存模型变量中即可。

所以,主要的问题在于应该如何设计内存模型(Model) 。

内存模型

内存模型设计的合理,符合对事物本周的抽象,程序代码就简单,易于理解。

反之,代码就会晦涩难懂。

游戏盘面二维数组

我们需要一个二维数组来存储游戏盘面。这个二维数组可以用 vector<vector<char>> m_playBoard 来表示。

蛇的身体队列

蛇会越来越长,身体的每个部分我们不希望只是单独的放在游戏盘面上。因为这样意味着每次更新蛇的位置的时候,找蛇的身体的每个位置都非常的麻烦。

我们把蛇的身体单独存放一份,放到一个队列里 queue<pair<int, int>> m_snakeBody 。队列是有方向的,这样就可以轻易的知道蛇的头在哪里。

蛇的移动

但是 queue 没有办法遍历元素,这样我们想让蛇往前走一步就好像变的不可能了。

真的是这样吗? 蛇需要每个元素都需要往前移动一步,才能完成蛇走一步吗? 

如上图所示,蛇的身体全部都用  1 表示,实际上我们移动蛇的时候,只需要将尾巴上的 1 搬到 蛇的头的下一个将要移动到的地方就完成了蛇的整体移动。

如上图所示,蛇头向下方移动了一个位置,我们把原来尾巴搬到了蛇头新位置,就完成了蛇整体的移动。

这样做的好处就是,蛇的移动每次只需要搬蛇身体的尾巴一个元素。移动蛇的身体总是固定的常数时间。计算量最大限度的降低了。程序也变的简单了。

游戏盘面和蛇身体的同步

由于蛇的移动变成了游戏盘面上把蛇的尾巴上的 1 搬运到蛇的头部的下一个将要移动到的位置,所以蛇的身体队列 m_snakeBody 里只需要存储蛇的身体的每一个元素在游戏盘面上的位置即可。

这就是为何 m_snakeBody 的每一个元素都是一个 pair<int, int> 原因了。

游戏控制

用户输入

玩家在键盘上输入一个表示方向的字母,这样程序就知道蛇应该往哪里移动了。

使用 GoAhead 来实现往前走一步。

游戏结束 

当蛇往前走碰到了墙壁(超出了游戏盘面)的时候,游戏结束。

当蛇往前走碰到了自己的身体的一部分,游戏结束。

枚举类型 enum class

enum class 通常用来提供字面值常量。也就是一些固定值。比如,表示方向的东、南、西、北。表示空间的上、下、左、右、前、后。

在本游戏中,我们用来表示盘面上物品的类型:什么也没有,蛇的身体,食物。

启动代码

#include <list>
#include <utility>
#include <fstream>
#include <sstream>
#include <iostream>
#include <random>//随机数
#include <chrono>//日期时间
using namespace std;

class Snake
{
	// 游戏的任意位置 只有三种情况:什么也没有;蛇的身体;食物
	enum class MatrixValueEnum
	{
		NOTHING = '0', SNAKE_BODY = '#', FOOD = '2'
	};
public:
	// 从文件中加载界面数据,存放到内部容器中,再根据容器内容绘制界面
	bool LoadPlayDataFromFile(const std::string& file);
	// 开始游戏
	void Play(void);
private:
	// 用户输入一个字符(e/s/f/d),决定将蛇的头部往哪个方向移动
	bool GoAhead(char userInputDirection);// 核心函数
	// 移动蛇的头的坐标(x,y) = (x,y) + (i,j)
	bool GoAhead(int i, int j);
	//撞到墙壁或者蛇自己的身体就结束游戏
	bool IsGameOver(int, int) const;
	// 获取蛇的头的坐标
	std::pair<int, int> GetCurrentPosition(void) const;
	// 计算蛇的头移动一次后的新坐标
	std::pair<int, int> GetNextPosition(int, int) const;
	// 打印贪吃蛇游戏
	void PrintMatrix(void) const;
	// 判断 (i,j) 处是否是一个食物
	bool ExistFood(int i, int j) const;
	// 在界面上生成一个新的食物给蛇吃
	void CreateFood(void);
private:
	std::vector<std::vector<char>> m_playMatrix;// 整个游戏的数据(二维数组)
	std::list<std::pair<int, int>> m_snakeBody;// 蛇的身体数据
};

bool Snake::LoadPlayDataFromFile(const std::string& file)
{
	std::ifstream fin(file);
	if (!fin)
	{
		std::cout << "can not open file " << file << endl;
		return false;
	}
	std::string line;
	std::getline(fin, line);
	std::istringstream iss(line);// 字符串流 https://zhuanlan.zhihu.com/p/441027904
	int row = 0, column = 0;
	//读取行数和列数
	//(1) your code


	for (size_t i = 0; i < row; i++)
	{
		std::vector<char> lineData;
		std::getline(fin, line);
		std::istringstream issLineData(line);
		for (size_t j = 0; j < column; j++)
		{
			char data;
			//读取一个元素
			// (2) your code


			//将组成蛇的头#存放到蛇m_snakeBody容器中
                        //在文件里,一开始蛇的身体只有一个头,需要把这个数据存起来
			//(3) your code  判断两个char相等即可
			// 参考:https://zhuanlan.zhihu.com/p/357348144

		}
		//将第一行数据存放到二维数组中,作为第一维的一个元素(子数组)
		//(4) your code


	}
	if (m_snakeBody.size() != 1)
	{
		cout << "snake body is empty! init game failed." << endl;
		return false;
	}
	return true;
}

bool Snake::IsGameOver(int x, int y) const
{
	//判断游戏是否已经结束了
	// x y 是蛇的头打算要去的目的地,这个目的地会导致gomeover
	// 比如超出了游戏界面(下标越界)
	// 比如撞到了蛇的身体
	//(5) your code

	return true;
}
std::pair<int, int> Snake::GetCurrentPosition(void) const
{
	//返回蛇 的头的坐标,是m_snakeBody的第一个元素的值
	//(6) your code  下面的代码需要自己修改,不可以直接使用
	std::pair<int, int> front;

	return front;
}
std::pair<int, int> Snake::GetNextPosition(int i, int j) const
{
	//根据蛇的头的位置,以及一个移动的向量 (i,j) 得到蛇头部打算要去的新目的地的坐标
	auto old = GetCurrentPosition();
	//(7) your code 下面的代码需要自己修改,不可以直接使用
	int x = 0;
	int y = 0;
	return std::make_pair(x, y);
}
bool Snake::ExistFood(int i, int j) const
{
	//返回 坐标(i,j)处是否是有蛇的食物可以吃
	//(8) your code 下面的代码需要自己修改,不可以直接使用
	return false;
}
void Snake::CreateFood(void)
{
	// 生成一个新的食物给蛇来吃
	// 随机生成一个新的位置,但是这个位置可能已经是蛇的身体了
	// 所以,需要用一个循环不断的重复在一个新生成的随机位置放置食物
	// 直到放置成功为止
	do
	{
		unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
		std::mt19937 g(seed);  // mt19937 is a standard mersenne_twister_engine
		//生成新的随机的坐标
		//随机数的用法:https://blog.csdn.net/calmreason/article/details/72655060
		//(9) your code 下面的代码需要自己修改,不可以直接使用
		int x = 0;
		int y = 0;
		// 在新坐标处放置一个食物,记得检查可以放才能放
		// 一旦放好,记得退出循环,让程序继续执行
		//(10) your code



	} while (true);
}
bool Snake::GoAhead(char userInputDirection)
{
	switch (userInputDirection)
	{
	case 'w':
	case 'W':
		return GoAhead(-1, 0);//up
	case 'a':
	case 'A':
		return GoAhead(0, -1);//left
	case 'd':
	case 'D':
		return GoAhead(0, +1);//right
	case 's':
	case 'S':
		return GoAhead(+1, 0);//down
	default:
		return true;
	}
}
bool Snake::GoAhead(int i, int j)
{
	auto nextPosition = GetNextPosition(i, j);//垂直方向x不变,竖直方向y减少1
	// 首先判断游戏是否已经结束
	if (IsGameOver(nextPosition.first, nextPosition.second))
	{
		return false;
	}
	// 判断nextPosition 处是否有食物
	// 如果有食物,就吃掉这个食物
	// 并生成一个新的食物
	if (ExistFood(nextPosition.first, nextPosition.second))
	{
		// (11) your code


		//直接吃掉,尾巴不用移动
		m_playMatrix[nextPosition.first][nextPosition.second] = static_cast<char>(MatrixValueEnum::SNAKE_BODY);
		CreateFood();//随机生成一个食物
	}
	// 如果 nextPosition 处没有食物,就移动蛇的身体
	else
	{
		// (12) your code



		//尾巴移动 
		auto tail = m_snakeBody.back();
		m_playMatrix[tail.first][tail.second] = static_cast<char>(MatrixValueEnum::NOTHING);
		m_snakeBody.pop_back();

	}
}


void Snake::Play(void)
{
	CreateFood();//随机生成一个食物
	while (true)
	{
		/*清屏,这不是C++的一部分,是系统调用。
		  这个语句执行的快慢与代码无关,与控制台用户自己设置的缓冲区大小有关。
		*/
		system("cls");
		PrintMatrix();

		std::cout << "direction: W(up) A(left) S(down) D(right)\n";
		std::cout << "$: food\n";
		std::cout << "@: snake head\n";
		std::cout << "#: snake tail\n";

		char direction;
		std::cin >> direction;
		//往前走一步,如果判断无法往前走到用户指定的位置,就退出程序
		// (13) your code
		if (!GoAhead(direction))
		{
			std::cout << "Game Over!" << std::endl;
			break;
		}
	}
}
void Snake::PrintMatrix(void) const
{
	auto headPosition = m_snakeBody.front();
	for (size_t i = 0; i < m_playMatrix.size(); i++)
	{
		for (size_t j = 0; j < m_playMatrix[i].size(); j++)
		{
			if (i == headPosition.first && j == headPosition.second)
			{
				std::cout << "@" << " ";
			}
			else if (m_playMatrix[i][j] == static_cast<char>(MatrixValueEnum::FOOD))
			{
				std::cout << "$" << " ";
			}
			else if (m_playMatrix[i][j] == static_cast<char>(MatrixValueEnum::NOTHING))
			{
				std::cout << "_" << " ";
			}
			else
			{
				std::cout << m_playMatrix[i][j] << " ";
			}
		}
		std::cout << std::endl;
	}
}

int main(int argc, char** argv)
{
	Snake snake;
	if (snake.LoadPlayDataFromFile("data.txt"))
	{
		snake.Play();
	}

	return 0;
}

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

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

相关文章

CSS学习笔记02

CSS笔记02 美化网页元素 为什么要美化网页 目的&#xff1a; 有效的传递页面信息美化网页、页面漂亮、才能吸引用户突显页面的主题提高用户的体验 span标签 span标签是短语内容的通用行内容器&#xff0c;它本身并没有任何特殊语义。 通常我们使用span标签来把我们想要重…

02-请解释一下Java的内存模型和happens-before规则?【Java面试题总结】

请解释一下Java的内存模型和happens-before规则&#xff1f; 概念&#xff1a;Java内存模型&#xff0c;简称JMM&#xff0c;是一种定义了多线程程序中内存访问行为的规范。它定义了线程如何与主内存和工作内存进行交互&#xff0c;以及如何保证多线程程序的正确性和可见性。J…

基于grpc从零开始搭建一个准生产分布式应用(6) - 02 - MapStruct数据转换

一、基础转换 1.1、基础类型 基本类型、包装类、BigDecimal转String默认使用DecimalFormat格式化&#xff0c;Mapping#numberFormat可以指定格式&#xff0c;Date转String默认使用SimpleDateFormat格式化&#xff0c;如默认格式不符要求&#xff0c;可以用&#xff0c;Mapping…

PMP项目管理主要学习内容是什么?

PMP项目管理是指根据美国项目管理学会(Project Management Institute&#xff0c;简称PMI)制定的项目管理知识体系和方法论进行项目管理的一种认证。PMP主要关注项目的规划、执行和控制等方面的知识和技能。 下面是PMP项目管理《PMBOK指南》第六版的主要学习内容&#xff1a; …

FFmpeg4.3.1+h264在windows下编译与VS2017项目集成

前言 在Android音视频开发中&#xff0c;网上知识点过于零碎&#xff0c;自学起来难度非常大&#xff0c;不过音视频大牛Jhuster提出了《Android 音视频从入门到提高 - 任务列表》&#xff0c;结合我自己的工作学习经历&#xff0c;我准备写一个音视频系列blog。本文是音视频系…

【Grasshopper基础15】“右键菜单似乎不太对劲”

距离上一篇文章已经过去了挺久的&#xff0c;很长时间没有写GH基础部分的内容了&#xff0c;原因其一是本职工作太忙了&#xff0c;进度也有些落后&#xff0c;白天工作累成马&#xff0c;回家只想躺着&#xff1b;其二则是感觉GH基础系列基本上也介绍得差不多了&#xff0c;电…

【微服务部署】08-监控与告警

文章目录 1. PrometheusOperator1.1 优势1.2 配置脚本1.3 部署脚本 2. Granfana实现监控看板2.1 Granfana核心特性2.2 部署文件 3. prometheus-net收集自定义指标3.1 组件包3.2 使用场景 目前Kubernetes中最流行的监控解决方案是使用Prometheus和AlertManager 1. PrometheusOpe…

尚硅谷SpringMVC (5-8)

五、域对象共享数据 1、使用ServletAPI向request域对象共享数据 首页&#xff1a; Controller public class TestController {RequestMapping("/")public String index(){return "index";} } <!DOCTYPE html> <html lang"en" xmln…

INDEMIND:“大+小”多机协同,实现机器人商用场景全覆盖

随着商用清洁机器人进入越来越多的场景中&#xff0c;单一的中型机器人并不能有效覆盖所有区域&#xff0c;更加细分化的产品组合正在成为新的趋势。 产品形态的“新趋势” 在商用场景中&#xff0c;目前的商用清洁机器人几乎均是中大型的产品形态&#xff0c;较大的体型意味…

性能测试(测试系列10)

目录 前言&#xff1a; 1.什么是性能测试 1.1生活中遇到的软件问题 1.2性能测试的定义 1.3性能测试和功能测试有什么区别 1.4性能的好坏的区分 1.5影响一个软件性能的因素 2.为什么要进行性能测试 3.性能测试常见的术语以及衡量指标 3.1并发 3.2用户数 3.3响应时间 …

Vulnhub: Ragnar Lothbrok: 1靶机

kali&#xff1a;192.168.111.111 靶机&#xff1a;192.168.111.226 信息收集 端口扫描 nmap -A -sC -v -sV -T5 -p- --scripthttp-enum 192.168.111.226 作者提示修改hosts文件 目录爆破 gobuster dir -u http://armbjorn -w /usr/share/wordlists/dirbuster/directory-l…

HFSS 3维曲线导入

HFSS 3维曲线导入 简介环境参考代码使用结果 简介 如图一所示&#xff0c;CST中可以通过导入和到出由任意点组成的曲线&#xff0c;但是HFSS中貌似不能导入&#xff08;如图二所示&#xff09;&#xff0c;如果我们要将matlab的产生的曲线的点的数据导入特变麻烦&#xff0c;特…

英码深元“三位一体”AI场景化解决方案,助力多地化工园区快速实现智慧化转型!

我国是世界公认的化工大国&#xff0c;同时也是崛起中的化工强国。近年来多起重大爆炸事故暴露出我国化工园区安全问题突出&#xff0c;特别是在安全风险管控数字化转型、智能化升级方面存在明显短板和不足&#xff0c;尤其突出的痛点&#xff1a;化工园区的日常管理方式较为粗…

【DRONECAN】(三)WSL2 及 ubuntu20.04 CAN 驱动安装

【DRONECAN】&#xff08;三&#xff09;WSL2 及 ubuntu20.04 CAN 驱动安装 前言 这一篇文章主要介绍一下 WSL2 及 ubuntu20.04 CAN 驱动的安装&#xff0c;首先说一下介绍本文的目的。 大家肯定都接触过 ubuntu 系统&#xff0c;但是我们常用的操作系统都是 Windows&#x…

python unitest自动化框架

以下举一个最简单的unitest实例&#xff0c;包含备注&#xff0c;自己拉取代码运行一次就知道原理了 import unittest import osclass TestSample(unittest.TestCase):classmethoddef setUpClass(cls) -> None:print(整个测试类只执行一次)def setUp(self) -> None:prin…

睿趣科技:抖音开网店卖玩具怎么样

近年来&#xff0c;随着社交媒体平台的飞速发展&#xff0c;抖音作为一款短视频分享应用也迅速崭露头角。而在这个充满创业机遇的时代背景下&#xff0c;许多人开始探索在抖音平台上开设网店&#xff0c;尤其是卖玩具类商品&#xff0c;那么抖音开网店卖玩具究竟怎么样呢? 首先…

QT的介绍和优点,以及使用QT初步完成一个登录界面

QT介绍 QT主要用于图形化界面的开发&#xff0c;QT是基于C编写的一套界面相关的类库&#xff0c;进程线程库&#xff0c;网络编程的库&#xff0c;数据库操作的库&#xff0c;文件操作的库…QT是一个跨平台的GUI图形化界面开发工具 QT的优点 跨平台&#xff0c;具有较为完备…

leetcode做题笔记107. 二叉树的层序遍历 II

给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历&#xff09; 思路一&#xff1a;递归调换顺序 int** levelOrderBottom(struct TreeNode* root, int* returnSize, i…

技术干货 —— 手把手教你通过缓存提升 API 性能

许多开发者都希望能够彻底搞清楚 API 的工作方式&#xff0c;以及如何利用缓存 API 请求来提升业务&#xff0c;但是当这个需求进入实现阶段时&#xff0c;许多人就会发现手头并没有合适的工具和恰当的方法&#xff0c;所以我们今天就为大家做一个全面的讲解&#xff1a; ① 几…

数据结构(Java实现)-字符串常量池与通配符

字符串常量池 在Java程序中&#xff0c;类似于&#xff1a;1&#xff0c; 2&#xff0c; 3&#xff0c;3.14&#xff0c;“hello”等字面类型的常量经常频繁使用&#xff0c;为了使程序的运行速度更快、更节省内存&#xff0c;Java为8种基本数据类型和String类都提供了常量池。…