dc420c3b7a
2. 顺便完善了底层通信类的封装,基于websocket,基准测试通过,但存在一点很小的线程bug,似乎是来自于esp32 idf底层的问题,待解决
390 lines
14 KiB
C++
390 lines
14 KiB
C++
//
|
|
// Created by misaki on 2025/9/8.
|
|
//
|
|
/**
|
|
* 需要实现的最终效果
|
|
1. 可以选择多种宠物,如雪豹,卡皮巴拉,守宫,每种宠物有独特音频和动作,通过联网更新切换。
|
|
2. 每种宠物有可以编辑的身份设定,类似小智AI的身份
|
|
3. 宠物三个阶段,幼年,青年,成年,阶段切换载入新模型即可。
|
|
4. 语音互动可以实现喂食,开心,生气,沮丧几种动作
|
|
5. 宠物具有生命值hp和亲密度,当发生喂食等动作会对生命值和亲密度产生影响,亲密度达到一定程度宠物可以进化到新的阶段。
|
|
6. 宠物的各种信息是可以保存和读取的。
|
|
|
|
分析一下具体设计思路:
|
|
1. 首先抽象出所有的宠物的共同点为一个基类,包括:名称,生命值,亲密度,身份设定
|
|
对于阶段,动作,不同的宠物之间存在不同的实现差异,可以为其抽象出不同的接口,组合到抽象出来的宠物基类当中
|
|
这样当创建新的宠物时,只需要实现对应的接口即可(在此使用策略模式),而在创建不同的宠物的过程中,又使用到了抽象工厂模式
|
|
对于音频,由于阶段变化或是产生动作,都会触发响应的音频,因此音频需要作为一个观察者,当宠物产生阶段变化或是产生动作,就会触发相应的音频播放
|
|
2. 接着要考虑到与宠物相关的一些事件,例如外部通知网络更新,语音输入互动,喂食这些事件,需要及时做出响应,在此使用依赖注入来解决这一类事件处理问题
|
|
向外部类当中组合一个依赖注入类,将宠物类当中的回调函数注入到其中,当发生某些事件时,就会触发相应的回调函数,并执行相应的操作
|
|
这样在增加新的事件时,就不需要频繁修改外部事件类,只需要新增新的依赖注入项(如果C++17支持反射的话还有更好的实现方法)
|
|
3. 再者是对于宠物的信息保存与读取,在此处增加一个DAO层,专门用于数据持久化,信息以json的方式存储与读取
|
|
既然存在了宠物的数据信息,那么就需要在DAO层之上增加一个可以从json数据中得到新的宠物对象的方法或者是类,以此组合出所需的宠物对象
|
|
4. 还需要考虑到一些极端事件的情况,例如设备断电,用户强制关机,网络中断等,最好的处理方式就是及时做好数据持久化和数据备份
|
|
设置bak备份防止在写文件的时候断电导致写文件错误
|
|
*/
|
|
#pragma once
|
|
#include <string>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
#include <list>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
// 前向声明主题
|
|
class PetSubject;
|
|
|
|
// 某个宠物不同阶段的抽象接口 宠物的不同阶段对应于不同的模型文件路径
|
|
// 如果需要添加新的阶段,要在PetStageType当中增加新的阶段,并将新的类继承自PetStage类
|
|
// 由于使用的是C++17,并不支持反射,只能通过宏定义统一管理,以此实现松耦合和开闭原则
|
|
#define PET_STAGE_TYPES \
|
|
X(PET_STAGE_YOUNG, "幼年") \
|
|
X(PET_STAGE_ADULT, "青年") \
|
|
X(PET_STAGE_OLD, "成年")
|
|
enum class PetStageType {
|
|
#define X(name, desc) name,
|
|
PET_STAGE_TYPES
|
|
#undef X
|
|
};
|
|
// 某个宠物所有阶段的接口集合,该接口集合会被组合到抽象出来的宠物基类当中,该接口还需要抽象出宠物不同阶段可能具备的行为
|
|
class PetStageStrategy {
|
|
public:
|
|
virtual ~PetStageStrategy() = default;
|
|
// 定义阶段顺序的静态常量
|
|
static const std::vector<PetStageType>& getStageOrder() {
|
|
static const std::vector<PetStageType> stageOrder = []() {
|
|
std::vector<PetStageType> order;
|
|
#define X(name, desc) order.push_back(PetStageType::name);
|
|
PET_STAGE_TYPES
|
|
#undef X
|
|
return order;
|
|
}();
|
|
return stageOrder;
|
|
}
|
|
/**
|
|
* 添加阶段及其模型路径
|
|
* @param stage 阶段类型
|
|
* @param model_path 模型路径
|
|
*/
|
|
virtual void addStage(PetStageType stage, const std::string& model_path) {
|
|
stage_model_map[stage] = model_path;
|
|
// 如果这是第一个阶段,设置为当前阶段
|
|
if (stage_model_map.size() == 1) {
|
|
current_stage = stage;
|
|
}
|
|
}
|
|
/**
|
|
* 添加阶段音频映射
|
|
* @param stage 阶段类型
|
|
* @param audio_path 音频文件路径
|
|
*/
|
|
virtual void addStageAudio(PetStageType stage, const std::string& audio_path) {
|
|
stage_audio_map[stage] = audio_path;
|
|
}
|
|
/**
|
|
* 获取阶段模型映射表
|
|
* @return 阶段到模型路径的映射表
|
|
*/
|
|
virtual const std::unordered_map<PetStageType, std::string>& getStageModelMap() const {
|
|
return stage_model_map;
|
|
}
|
|
/**
|
|
* 获取阶段音频映射表
|
|
* @return 阶段到音频路径的映射表
|
|
*/
|
|
virtual const std::unordered_map<PetStageType, std::string>& getStageAudioMap() const {
|
|
return stage_audio_map;
|
|
}
|
|
/**
|
|
* 获取特定阶段的模型路径
|
|
* @param stage 阶段类型
|
|
* @return 模型路径,如果不存在则返回空字符串
|
|
*/
|
|
virtual std::string getStageModelPath(PetStageType stage) const {
|
|
auto it = stage_model_map.find(stage);
|
|
if (it != stage_model_map.end()) {
|
|
return it->second;
|
|
}
|
|
return "";
|
|
}
|
|
/**
|
|
* 获取特定阶段的音频路径
|
|
* @param stage 阶段类型
|
|
* @return 音频路径,如果不存在则返回空字符串
|
|
*/
|
|
virtual std::string getStageAudioPath(PetStageType stage) const {
|
|
auto it = stage_audio_map.find(stage);
|
|
if (it != stage_audio_map.end()) {
|
|
return it->second;
|
|
}
|
|
return "";
|
|
}
|
|
/**
|
|
* 切换到指定阶段
|
|
* @param newStage 要切换到的阶段类型
|
|
* @return 是否成功切换
|
|
*/
|
|
virtual bool switchToStage(PetStageType newStage) {
|
|
if (stage_model_map.find(newStage) != stage_model_map.end()) {
|
|
current_stage = newStage;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* 进化到下一阶段(基于当前阶段)
|
|
* @return 是否成功进化
|
|
*/
|
|
virtual bool evolveToNextStage() {
|
|
const auto& stageOrder = getStageOrder();
|
|
auto it = std::find(stageOrder.begin(), stageOrder.end(), current_stage);
|
|
|
|
if (it == stageOrder.end() || it == stageOrder.end() - 1) {
|
|
return false;
|
|
}
|
|
// 查找下一个可用阶段
|
|
for (auto iter = it + 1; iter != stageOrder.end(); ++iter) {
|
|
if (stage_model_map.find(*iter) != stage_model_map.end()) {
|
|
current_stage = *iter;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* 获取当前阶段
|
|
* @return 当前阶段类型
|
|
*/
|
|
virtual PetStageType getCurrentStageType() const {
|
|
return current_stage;
|
|
}
|
|
/**
|
|
* 获取当前阶段的模型路径
|
|
* @return 当前阶段模型路径
|
|
*/
|
|
virtual std::string getCurrentStageModelPath() const {
|
|
return getStageModelPath(current_stage);
|
|
}
|
|
/**
|
|
* 获取所有可用阶段
|
|
* @return 所有阶段的列表
|
|
*/
|
|
virtual std::list<PetStageType> getAllStages() const {
|
|
std::list<PetStageType> stages;
|
|
for (const auto& stage : stage_model_map) {
|
|
stages.push_back(stage.first);
|
|
}
|
|
return stages;
|
|
}
|
|
/**
|
|
* 移除阶段
|
|
* @param stage 要移除的阶段类型
|
|
*/
|
|
virtual void removeStage(PetStageType stage) {
|
|
if (current_stage == stage && stage_model_map.size() > 1) {
|
|
// 找到另一个阶段作为当前阶段
|
|
for (const auto& s : stage_model_map) {
|
|
if (s.first != stage) {
|
|
current_stage = s.first;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
stage_model_map.erase(stage);
|
|
stage_audio_map.erase(stage);
|
|
}
|
|
public:
|
|
///<! 阶段到模型路径的映射表
|
|
std::unordered_map<PetStageType, std::string> stage_model_map;
|
|
///<! 阶段到音频路径的映射表
|
|
std::unordered_map<PetStageType, std::string> stage_audio_map;
|
|
///<! 当前阶段
|
|
PetStageType current_stage;
|
|
};
|
|
|
|
// 宠物动作接口
|
|
// 一般而言,对于所有的动物,其动作行为基本相同,不同的地方在与发生动作时,所触发的模型不同
|
|
// 因此,宠物的动作设计,和阶段设计基本类似
|
|
#define PET_ACTION_TYPES \
|
|
X(PET_ACTION_SLEEP, "睡觉") \
|
|
X(PET_ACTION_EAT, "喂食") \
|
|
X(PET_ACTION_HAPPY, "开心") \
|
|
X(PET_ACTION_ANGRY, "生气") \
|
|
X(PET_ACTION_SAD, "沮丧") \
|
|
X(PET_ACTION_EVOLVE, "进化") \
|
|
X(PET_ACTION_TOUCH, "被触摸")
|
|
enum class PetActionType {
|
|
#define X(name, desc) name,
|
|
PET_ACTION_TYPES
|
|
#undef X
|
|
};
|
|
// 宠物动作策略类
|
|
class PetActionStrategy {
|
|
public:
|
|
virtual ~PetActionStrategy() = default;
|
|
// 定义动作顺序的静态常量
|
|
static const std::vector<PetActionType>& getActionOrder() {
|
|
static const std::vector<PetActionType> actionOrder = []() {
|
|
std::vector<PetActionType> order;
|
|
#define X(name, desc) order.push_back(PetActionType::name);
|
|
PET_ACTION_TYPES
|
|
#undef X
|
|
return order;
|
|
}();
|
|
return actionOrder;
|
|
}
|
|
/**
|
|
* 添加动作及其模型路径
|
|
* @param action 动作类型
|
|
* @param model_path 模型路径
|
|
*/
|
|
virtual void addAction(PetActionType action, const std::string& model_path) {
|
|
action_model_map[action] = model_path;
|
|
}
|
|
/**
|
|
* 添加动作音频映射
|
|
* @param action 动作类型
|
|
* @param audio_path 音频文件路径
|
|
*/
|
|
virtual void addActionAudio(PetActionType action, const std::string& audio_path) {
|
|
action_audio_map[action] = audio_path;
|
|
}
|
|
/**
|
|
* 获取动作模型映射表
|
|
* @return 动作到模型路径的映射表
|
|
*/
|
|
virtual const std::unordered_map<PetActionType, std::string>& getActionModelMap() const {
|
|
return action_model_map;
|
|
}
|
|
/**
|
|
* 获取动作音频映射表
|
|
* @return 动作到音频路径的映射表
|
|
*/
|
|
virtual const std::unordered_map<PetActionType, std::string>& getActionAudioMap() const {
|
|
return action_audio_map;
|
|
}
|
|
/**
|
|
* 获取特定动作的模型路径
|
|
* @param action 动作类型
|
|
* @return 模型路径,如果不存在则返回空字符串
|
|
*/
|
|
virtual std::string getActionModelPath(PetActionType action) const {
|
|
auto it = action_model_map.find(action);
|
|
if (it != action_model_map.end()) {
|
|
return it->second;
|
|
}
|
|
return "";
|
|
}
|
|
/**
|
|
* 获取特定动作的音频路径
|
|
* @param action 动作类型
|
|
* @return 音频路径,如果不存在则返回空字符串
|
|
*/
|
|
virtual std::string getActionAudioPath(PetActionType action) const {
|
|
auto it = action_audio_map.find(action);
|
|
if (it != action_audio_map.end()) {
|
|
return it->second;
|
|
}
|
|
return "";
|
|
}
|
|
/**
|
|
* 执行指定动作
|
|
* @param action 要执行的动作类型
|
|
* @return 是否成功执行
|
|
*/
|
|
virtual bool performAction(PetActionType action) {
|
|
if (action_model_map.find(action) != action_model_map.end()) {
|
|
current_action = action;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* 获取当前动作
|
|
* @return 当前动作类型
|
|
*/
|
|
virtual PetActionType getCurrentActionType() const {
|
|
return current_action;
|
|
}
|
|
/**
|
|
* 获取当前动作的模型路径
|
|
* @return 当前动作模型路径
|
|
*/
|
|
virtual std::string getCurrentActionModelPath() const {
|
|
return getActionModelPath(current_action);
|
|
}
|
|
/**
|
|
* 获取所有可用动作
|
|
* @return 所有动作的列表
|
|
*/
|
|
virtual std::list<PetActionType> getAllActions() const {
|
|
std::list<PetActionType> actions;
|
|
for (const auto& action : action_model_map) {
|
|
actions.push_back(action.first);
|
|
}
|
|
return actions;
|
|
}
|
|
/**
|
|
* 获取动作的描述信息
|
|
* @param action 动作类型
|
|
* @return 动作描述
|
|
*/
|
|
virtual std::string getActionDescription(PetActionType action) const {
|
|
switch (action) {
|
|
#define X(name, desc) case PetActionType::name: return desc;
|
|
PET_ACTION_TYPES
|
|
#undef X
|
|
default: return "未知动作";
|
|
}
|
|
}
|
|
/**
|
|
* 获取当前动作的描述信息
|
|
* @return 当前动作描述
|
|
*/
|
|
virtual std::string getCurrentActionDescription() const {
|
|
return getActionDescription(current_action);
|
|
}
|
|
/**
|
|
* 移除动作
|
|
* @param action 要移除的动作类型
|
|
*/
|
|
virtual void removeAction(PetActionType action) {
|
|
action_model_map.erase(action);
|
|
action_audio_map.erase(action);
|
|
if (current_action == action) {
|
|
current_action = PetActionType::PET_ACTION_SLEEP; // 默认动作
|
|
}
|
|
}
|
|
public:
|
|
///<! 动作到模型路径的映射表
|
|
std::unordered_map<PetActionType, std::string> action_model_map;
|
|
///<! 动作到音频路径的映射表
|
|
std::unordered_map<PetActionType, std::string> action_audio_map;
|
|
///<! 当前动作
|
|
PetActionType current_action = PetActionType::PET_ACTION_SLEEP; // 默认动作
|
|
};
|
|
|
|
// 观察者接口
|
|
class PetObserver {
|
|
public:
|
|
virtual ~PetObserver() = default;
|
|
// 宠物动作
|
|
virtual void onPetAction(PetActionType action) = 0;
|
|
// 宠物阶段
|
|
virtual void onPetStageChange(PetStageType oldStage, PetStageType newStage) = 0;
|
|
};
|
|
|
|
// 主题接口
|
|
class PetSubject {
|
|
public:
|
|
virtual ~PetSubject() = default;
|
|
// 添加观察者
|
|
virtual void addObserver(std::shared_ptr<PetObserver> observer) = 0;
|
|
// 移除观察者
|
|
virtual void removeObserver(std::shared_ptr<PetObserver> observer) = 0;
|
|
// 通知动作
|
|
virtual void notifyAction(PetActionType action) = 0;
|
|
// 通知阶段
|
|
virtual void notifyStageChange(PetStageType oldStage, PetStageType newStage) = 0;
|
|
};
|