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

news/2024/7/5 3:30:15

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

主要考察

模块划分 / 文本文件读取

UI与业务分离 / 模块划分

控制台交互 / 数据抽象

需求

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

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

输入文件格式

data.txt

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

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

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

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

数组的元素为 0 表示这里什么也没有,当然也没有蛇的身体。

数组的元素为 1 表示蛇的头。

程序启动一开始运行的时候,蛇没有身体,只有头

程序输出样式

运行效果

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

实现思路

文件加载

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

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

内存模型

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

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

游戏盘面二维数组

我们需要一个

蛇的身体队列

界面显示

游戏控制

正式实现代码Snake.cpp只有180行不到。

枚举类型 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;
}

输入文件data.txt 的内容

7 7
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 # 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0

参考答案

贪吃蛇(控制台版)(答案)

其他参考:

Qt版本的一个贪吃蛇实现 Qt Snake C++


http://lihuaxi.xjx100.cn/news/1509767.html

相关文章

Rest和Http什么关系?

分析&回答 REST 定义了一组体系架构原则&#xff0c;您可以根据这些&#xff0c;包括使用不同语言编写的客户端如何通过 HTTP 处理和传输资源状态。 REST只是一种风格&#xff0c;不是一种标准REST是以资源为中心的 用不同的 HTTP 请求方法来处理对资源的 CRUD&#xff0…

认识JVM的内存模型

从上一节了解到整个JVM大的内存区域&#xff0c;分为线程共享的heap&#xff08;堆&#xff09;&#xff0c;MethodArea&#xff08;方法区&#xff09;&#xff0c;和线程独享的 The pc Register&#xff08;程序计数器&#xff09;、Java Virtual Machine Stacks&#xff08;…

酷克数据与华为合作更进一步 携手推出云数仓联合解决方案

在一起&#xff0c;共迎新机遇&#xff01;8月25-26日&#xff0c;2023华为数据存储用户精英论坛在西宁召开。酷克数据作为国内云原生数据仓库的代表企业&#xff0c;也是华为重要的生态合作伙伴&#xff0c;受邀参与本次论坛&#xff0c;并展示了云数仓领域最新前沿技术以及联…

Java | File类

目录&#xff1a; File类File类中常用的方法&#xff1a;boolean exists( ) &#xff1a;判断此 文件/目录 是否存在boolean createNewFile( ) &#xff1a;创建一个文件boolean mkdir( ) &#xff1a;创建 “单层” 目录/文件夹boolean mkdirs( ) &#xff1a;创建 “多层” 目…

win10 连接公共WIFI不跳转

win10 连接公共WIFI不跳转 尝试了很多方法&#xff1a; cmd 输入 ipconfig/flushdns忘记WIFI取消代理 都没成功&#xff0c;最后是在浏览器输入 192.168.1.1 成功跳转 问了一下chat&#xff0c;原因是 在某些公共WiFi设置中&#xff0c;captive portal&#xff08;即验证页…

数据分析基础-数据可视化07-用数据分析讲故事

如何构建⼀个引⼈⼊胜的故事&#xff1f; ⾸先&#xff1a;要想象什么&#xff1f; 可视化什么⽐如何可视化更重要 统计分析&#xff1a;GIGO&#xff08;垃圾输⼊&#xff0c;垃圾输出&#xff09; 在可视化分析环境中&#xff1a; 吉⾼ → 您⽆法从可视化的不适当数据中获…

Three.js之模型对象、材质

参考资料 三维向量Vector3与模型位置…克隆.clone()和复制.copy() 知识点 注&#xff1a;基于Three.jsv0.155.0 三维向量Vector3与模型位置欧拉Euler与角度属性.rotation模型材质颜色(Color对象)模型材质父类Material&#xff1a;透明、面属性模型材质和几何体属性克隆.clo…

FFmpeg报错:Connection to tcp://XXX?timeout=XXX failed: Connection timed out

一、现象 通过FFmpeg&#xff08;FFmpeg的版本是5.0.3&#xff09;拉摄像机的rtsp流获取音视频数据&#xff0c;执行命令&#xff1a; ./ffmpeg -timeout 3000000 -i "rtsp://172.16.17.156/stream/video5" 报错&#xff1a;Connection to tcp://XXX?timeoutXXX …