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

news2024/12/22 6:27:53

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://www.coloradmin.cn/o/964729.html

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

相关文章

CSS中你不得不知道的盒子模型

目录 1、CSS的盒子模型1.1 css盒子模型有哪些&#xff1a;1.2 css盒子模型的区别1.3 通过css如何转换css盒子模型 1、CSS的盒子模型 1.1 css盒子模型有哪些&#xff1a; 标准盒子模型、怪异盒子模型&#xff08;IE盒子模型&#xff09; 1.2 css盒子模型的区别 标准盒子模型&a…

人工智能安全吗?OpenAI正在让大模型和人类“对齐”-确保ChatGPT比人类聪明的同时还遵循人类意图...

“ 人工智能的发展给人类带来福祉的同时&#xff0c;也存在巨大的风险。为了防止人工智能走向不受控制的方向&#xff0c;对齐技术应运而生。通过人工智能安全技术的研究与探索&#xff0c;我们期望在人工智能能力成熟前建立起有效的对齐机制&#xff0c;让人工智能能够真正为人…

【狂神】Spring5(Aop的实现方式)

今天没有偷懒&#xff0c;只是忘了Mybatis&#xff0c;所以去补课了~ ┏━━━━━━━━━━━━━━━┓ NICE PIGGY PIG.. ┗━━━━━━━△━━━━━━━┛ ヽ(&#xff65;ω&#xff65;)&#xff89; | / UU 1.Aop实现方式一 1.1、什…

Redis-Cluster集群操作--添加节点、删除节点

一、环境部署 部署好Redis-Cluster集群&#xff0c;参考上个本人的博客&#xff1a;Redis-Cluster集群的部署&#xff08;详细步骤&#xff09;_是胡也是福的博客-CSDN博客 新准备一台机器&#xff0c;修改主机名&#xff0c;关闭防火墙和selinux&#xff0c;参考&#xff1a…

MySQL 使用规范 —— 如何建好字段和索引

一、案例背景 二、库表规范 1. 建表相关规范 2. 字段相关规范 3. 索引相关规范 4. 使用相关规范 三、建表语句 三、语句操作 1. 插入操作 2. 查询操作 四、其他配置 1. 监控活动和性能&#xff1a; 2. 连接数查询和配置 本文的宗旨在于通过简单干净实践的方式教会读…

【Python】应用:Python数据分析基础

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍Python数据分析基础。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次…

解决:在宝塔站点上添加域名(8080,888等端口)显示“端口范围不合法“

在宝塔上给站点添加域名访问时&#xff0c;有时候需要部署站点的端口为8080或者888端口。但是添加之后显示&#xff1a; 解决方法 点击宝塔上的文件 切换到根目录搜索 public.py 包含子目录 选择这个&#xff1a; 修改其中的checkport函数&#xff1a; 最后&#xff0c;重启面…

【CSS】CSS 布局——浮动

浮动的主要用途是在一个容器中创建多列布局&#xff0c;以及在文本周围环绕图片等内容。然而&#xff0c;由于现代的 CSS 布局技术的发展&#xff0c;浮动已经不再是主要的布局方法&#xff0c;但了解它仍然是有用的。 下面我将详细解释 CSS 浮动的用法、特性和影响。 浮动的…

泛型-使用总结

泛型&#xff1a; 1、介绍&#xff1a; ! 2、泛型类&#xff1a; 3、接口&#xff1a; 4、泛型方法&#xff1a; 泛型&#xff1a;不支持多态 !

常见路由跳转的几种方式

常见的路由跳转有以下四种&#xff1a; 1. <router-link to"跳转路径"> /* 不带参数 */ <router-link :to"{name:home}"> <router-link :to"{path:/home}"> // 更建议用name // router-link链接中&#xff0c;带/ 表示从根…

sql:SQL优化知识点记录(八)

&#xff08;1&#xff09;索引面试题分析 所谓索引&#xff1a;就是排好序的快速查找数据结构&#xff0c;排序家查找是索引的两个用途 select * 在where使用到了索引&#xff0c;当select * 有模糊查询%在左边索引会失效 当select * where后面索引的顺序发生变化&#xff0…

51单片机项目(7)——基于51单片机的温湿度测量仿真

本次做的设计&#xff0c;是利用DHT11传感器&#xff0c;测量环境的温度以及湿度&#xff0c;同时具备温度报警的功能&#xff1a;利用两个按键&#xff0c;设置温度阈值的加和减&#xff0c;当所测温度大于温度阈值的时候&#xff0c;蜂鸣器就会响起&#xff0c;进行报警提示。…

浅谈MES系统中的物料管理

导 读 ( 文/ 2269 ) 物料管理是对企业在生产中使用的各种物料的采购、保管和发放环节进行计划与控制等管理活动的总称。物料管理是企业生产执行的基础&#xff0c;它接收来自生产执行层的物料请求&#xff0c;通过一系列物料管理活动的执行&#xff0c;对生产执行层进行及…

长城网络靶场,第一题笔记

黑客使用了哪款扫描工具对论坛进行了扫描&#xff1f;&#xff08;小写简称&#xff09; 第一关&#xff0c;第三小题的答案是awvs 思路是先统计查询 然后过滤ip检查流量 过滤语句&#xff1a;tcp and ip.addr ip 114.240179.133没有 第二个101.36.79.67 之后找到了一个…

【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型(更新中)

【MATLAB第71期】基于MATLAB的Abcboost自适应决策树多输入单输出回归预测及多分类预测模型&#xff08;更新中&#xff09; 一、效果展示&#xff08;多分类预测&#xff09; 二、效果展示&#xff08;回归预测&#xff09; 三、代码获取 CSDN后台私信回复“71期”即可获取下…

Linux中Tomcat发布war包后无法正常访问非静态资源

事故现象 在CentOS8中安装完WEB环境&#xff0c;首次部署WEB项目DEMO案例&#xff0c;发现可以静态的网页内容&#xff0c; 但是无法向后台发送异步请求&#xff0c;全部出现404问题&#xff0c;导致数据库数据无法渲染到界面上。 原因分析 CentOS请求中提示用来获取资源的连…

设计模式-7--代理模式(Proxy Pattern)

一、什么是代理模式&#xff08;Proxy Pattern&#xff09; 代理模式&#xff08;Proxy Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许一个对象&#xff08;代理&#xff09;充当另一个对象&#xff08;真实对象&#xff09;的接口&#xff0c;以控制对该对象的…

Shell编程之cut

cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出&#xff0c;默认分割符是水平制表符。 如果不指定 File 参数&#xff0c; cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一。 基础语法&#xff1a; cut [参数选项] 文件名 参数&a…

Java抛出异常

当某个方法抛出了异常时&#xff0c;如果当前方法没有捕获异常&#xff0c;异常就会被抛到上层调用方法&#xff0c;直到遇到某个try ... catch被捕获为止 调用printStackTrace()可以打印异常的传播栈&#xff0c;对于调试非常有用&#xff1b;捕获异常并再次抛出新的异常时&am…

【Vue CLI】

node.js安装 https://nodejs.org/download/release/v15.14.0/ 管理员运行cmd node -v 安装npm npm install -g cnpm --registryhttps://registry.npm.taobao.org 查看是否安装成功 npm -v 注册淘宝镜像加速器 npm config set registry https://registry.npm.taobao.org/ 查看…