C++ 实现一个简单的状态机和行为树结合示例

文章目录

    • 0. 概述
    • 1. 设计方案
      • 1.1 状态机图 (FSM)
      • 1.2 行为树图 (BT)
      • 1.3 整体架构图
      • 1.4 其它
    • 2. 代码实现
      • 2.1 代码结构
      • 2.2 CMakeLists.txt
      • 2.3 FSM 实现 src/fsm.hpp
      • 2.4 行为树实现 src/behavior_tree.hpp
      • 2.5 主程序 src/main.cpp
      • 2.6 代码解释
    • 3. 结果

0. 概述

用纯 C++ 实现一个简单的状态机和行为树示例,不依赖外部库。

结合有限状态机和行为树,设计一个简单的门控制逻辑。该逻辑不仅展示了 FSM 和 BT 的结合应用,还引入随机性和条件判断。

进一步阅读: 使用 TinyFSM 和 BehaviorTree.CPP 构建状态机与行为树示例
本文示例代码: https://gitee.com/liudegui/simple-fsm-bt-example

1. 设计方案

设计方案的图示表示,包括状态机图、行为树图以及整体架构图。

1.1 状态机图 (FSM)

状态机图展示了门的不同状态以及状态之间的转换。

unlock
lock
open
lock
close
open
close
halfOpen
jam
unjam
close
LOCKED
UNLOCKED
OPENED
CLOSED
HALF_OPEN
JAMMED

状态机(FSM) 将用于管理门的不同状态,并定义相应的状态转换逻辑。定义以下状态和事件:

  • 状态

    • LOCKED(锁定)
    • UNLOCKED(解锁)
    • CLOSED(关闭)
    • OPENED(打开)
    • HALF_OPEN(半开)
    • JAMMED(卡住)
  • 事件

    • lock:将门锁定
    • unlock:将门解锁
    • open:将门打开
    • close:将门关闭
    • halfOpen:将门半开
    • jam:将门卡住
    • unjam:将门解卡

每个状态都有相应的处理方法,确保在状态转换时执行相应的操作

1.2 行为树图 (BT)

行为树图展示了行为树的结构和各个行为节点。

BehaviorTree
Sequence
UnlockAction
OpenAction
HalfOpenAction
JamAction
UnjamAction
LockAction

行为树(BT) 将用于定义门的行为序列,包含多个行为节点,每个节点对应一个具体的操作。包含以下行为节点:

  • 行为节点
    • UnlockAction:尝试解锁门
    • OpenAction:尝试打开门
    • HalfOpenAction:尝试将门半开
    • JamAction:尝试将门卡住
    • UnjamAction:尝试将门解卡
    • LockAction:尝试锁定门

每个行为节点在执行之前会检查当前门的状态,以确保操作的逻辑性。行为树中的每个步骤都有一定的随机性(50% 的概率)以模拟现实中行为的不确定性。

1.3 整体架构图

整体架构图展示了 FSM 和 BT 如何结合在一起工作。

calls
executes
executes
executes
executes
executes
executes
interacts
interacts
interacts
interacts
interacts
interacts
Main Program
BehaviorTree
UnlockAction
OpenAction
HalfOpenAction
JamAction
UnjamAction
LockAction
DoorFSM
LOCKED
UNLOCKED
CLOSED
OPENED
HALF_OPEN
JAMMED

1.4 其它

  • 整体架构图

    • 主程序调用行为树,行为树顺序执行各个行为节点。

    • 每个行为节点根据当前状态与状态机(DoorFSM)进行交互,执行相应的状态转换。

    • 状态机管理门的状态,处理各种事件,并在状态之间进行转换。

  • 输出信息
    为了让系统的行为更具可读性,我们在每个行为节点中添加详细的输出信息,说明当前正在尝试执行的操作。例如:

    • Step 0: Attempting to unlock the door...
    • Step 1: Attempting to open the door...
    • Skipping step 1 due to random choice.

这些图示展示了 FSM 和 BT 的结合应用,能够更直观地理解整个系统的设计和工作流程。

2. 代码实现

2.1 代码结构

.
├── CMakeLists.txt
├── src
│   ├── main.cpp
│   ├── fsm.hpp
│   └── behavior_tree.hpp

2.2 CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(SimpleFSM_BT_DoorExample)

set(CMAKE_CXX_STANDARD 17)

add_executable(SimpleFSM_BT_DoorExample
    src/main.cpp
)

2.3 FSM 实现 src/fsm.hpp

#ifndef FSM_HPP
#define FSM_HPP

#include <iostream>

class DoorFSM {
 public:
  enum State { LOCKED, UNLOCKED, CLOSED, OPENED, HALF_OPEN, JAMMED };

  DoorFSM() : state(LOCKED) {
  }

  void lock() {
    if (state != LOCKED) {
      state = LOCKED;
      std::cout << "Door is now locked." << std::endl;
    }
  }

  void unlock() {
    if (state == LOCKED) {
      state = UNLOCKED;
      std::cout << "Door is now unlocked." << std::endl;
    }
  }

  void open() {
    if (state == CLOSED || state == UNLOCKED || state == HALF_OPEN) {
      state = OPENED;
      std::cout << "Door is now opened." << std::endl;
    }
  }

  void close() {
    if (state == OPENED || state == HALF_OPEN) {
      state = CLOSED;
      std::cout << "Door is now closed." << std::endl;
    }
  }

  void halfOpen() {
    if (state == CLOSED || state == OPENED) {
      state = HALF_OPEN;
      std::cout << "Door is now half open." << std::endl;
    }
  }

  void jam() {
    if (state != JAMMED) {
      state = JAMMED;
      std::cout << "Door is now jammed." << std::endl;
    }
  }

  void unjam() {
    if (state == JAMMED) {
      state = CLOSED;
      std::cout << "Door is now unjammed and closed." << std::endl;
    }
  }

  State getState() const {
    return state;
  }

 private:
  State state;
};

#endif  // FSM_HPP

2.4 行为树实现 src/behavior_tree.hpp

#ifndef BEHAVIOR_TREE_HPP
#define BEHAVIOR_TREE_HPP

#include <cstdlib>
#include <ctime>
#include <functional>
#include <vector>

#include "fsm.hpp"

class BehaviorTree {
 public:
  using Action = std::function<void()>;

  explicit BehaviorTree(DoorFSM& fsm) : fsm(fsm), currentStep(0) {
    actions.push_back([this, &fsm]() {
      std::cout << "Step 0: Attempting to unlock the door..." << std::endl;
      if (fsm.getState() == DoorFSM::LOCKED) {
        fsm.unlock();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 1: Attempting to open the door..." << std::endl;
      if (fsm.getState() == DoorFSM::UNLOCKED || fsm.getState() == DoorFSM::CLOSED) {
        fsm.open();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 2: Attempting to half open the door..." << std::endl;
      if (fsm.getState() == DoorFSM::OPENED) {
        fsm.halfOpen();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 3: Attempting to jam the door..." << std::endl;
      if (fsm.getState() == DoorFSM::HALF_OPEN) {
        fsm.jam();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 4: Attempting to unjam the door..." << std::endl;
      if (fsm.getState() == DoorFSM::JAMMED) {
        fsm.unjam();
      }
    });
    actions.push_back([this, &fsm]() {
      std::cout << "Step 5: Attempting to lock the door..." << std::endl;
      if (fsm.getState() == DoorFSM::CLOSED) {
        fsm.lock();
      }
    });

    std::srand(std::time(nullptr));  // 初始化随机数生成器
  }

  void run() {
    while (currentStep < actions.size()) {
      if (std::rand() % 2 == 0) {  // 50%的概率执行当前步骤
        actions[currentStep]();
      } else {
        std::cout << "Skipping step " << currentStep << " due to random choice." << std::endl;
      }
      currentStep++;
    }
    currentStep = 0;
  }

 private:
  DoorFSM& fsm;
  std::vector<Action> actions;
  int currentStep;
};

#endif  // BEHAVIOR_TREE_HPP

2.5 主程序 src/main.cpp

#include "behavior_tree.hpp"
#include "fsm.hpp"
#include <thread>

int main() {
  DoorFSM fsm;
  BehaviorTree bt(fsm);

  while (true) {
    bt.run();  // 执行带有随机性的行为树
    std::this_thread::sleep_for(std::chrono::seconds(1));
  }

  return 0;
}

2.6 代码解释

  1. 状态机(FSM):定义了 DoorFSM 类,包含六个状态(LOCKED, UNLOCKED, CLOSED, OPENED, HALF_OPEN, JAMMED)和相应的方法。

  2. 行为树(BT):行为树在每个动作节点中捕获 fsm 对象的引用,确保在执行 lambda 函数时可以访问 fsm 对象。通过 [this, &fsm] 捕获 this 指针和 fsm 引用。

  3. 条件判断和随机性:行为树在每一步操作之前,先检查当前状态是否允许执行该操作,同时保留了随机性。

  4. 跳过步骤:如果当前步骤被随机选择跳过,则输出 Skipping step X due to random choice.,其中 X 是当前步骤的编号。

通过这种方式,我们可以确保行为树中的每个动作节点都能正确访问 fsm 对象,并根据当前状态执行相应的操作。再次尝试编译和运行程序,这些修改应该可以解决捕获问题。

3. 结果

行为树会在执行每一步之前检查当前状态,确保操作的逻辑一致性。这使得系统在引入随机性的同时,保持状态转换的合理性。现在运行程序时,会看到更加合理的状态变化输出:

Step 0: Attempting to unlock the door...
Door is now unlocked.
Step 1: Attempting to open the door...
Door is now opened.
Step 2: Attempting to half open the door...
Door is now half open.
Step 3: Attempting to jam the door...
Door is now jammed.
Step 4: Attempting to unjam the door...
Door is now unjammed and closed.
Step 5: Attempting to lock the door...
Door is now locked.
Skipping step 0 due to random choice.
Step 1: Attempting to open the door...
Step 2: Attempting to half open the door...
Step 3: Attempting to jam the door...
Skipping step 4 due to random choice.
Step 5: Attempting to lock the door...
Step 0: Attempting to unlock the door...
Door is now unlocked.
Skipping step 1 due to random choice.
Step 2: Attempting to half open the door...
Step 3: Attempting to jam the door...
Step 4: Attempting to unjam the door...
Step 5: Attempting to lock the door...

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

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

相关文章

客户有哪些封装案例,一句克服使用让PCBA工厂泪流满面

作者 | 高速先生成员--王辉东 天空下着雨&#xff0c;萧萧从窗前经过&#xff0c;看窗里。 翠萍那娇艳欲滴的脸上挂着两串泪滴。 萧萧一进去&#xff0c;问啥情况。 翠萍往电脑屏幕一指。 当萧萧看向屏幕一瞬间。 那些曾经以为早已遗忘的伤痛&#xff0c;会在某些时刻如潮…

Gradle学习-2 Groovy

1、Groovy基础语法 1.1、基本数据类型 Groovy支持数据类型&#xff1a;byte, short, int, long, float, double, char &#xff08;1&#xff09;创建一个Android Studio项目 &#xff08;2&#xff09;在根目录新建一个 leon.gradle&#xff0c;输入以下内容 leon.gradle…

突破Web3红海,DePIN如何构建创新生态系统?

撰文&#xff1a;TinTinLand 本文来源香港Web3媒体Techub News专栏作者TinTinLand 2023 年 DePIN 赛道的火热成为 Web3 行业的重点关注方向&#xff0c;当前如何以可扩展、去中心化、安全方式推动 DePIN 赛道赋能下的 AI 版图建设&#xff0c;寻找更多 Web3 行业创新机遇成为…

【已解决】Python报错:NameError: name ‘Image‘ is not defined

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…

QT拖放事件之七:子类化QMimeData,实现对多个自定义类型进行数据

1、前提说明 /*自定义的MIME类型数据存储在QMimeData对象中, 存在两种方法:1. setData(...)可以把自定义类型的数据以QByteArray的形式直接存储在QMimeData中,但是使用此方法一次只能对一个MIME类型进行处理(可参考 QT拖放事件六:自定义MIME类型的存储及读取demo ) 一文。…

udp Socket组播 服务器

什么是组播 组播也可以称之为多播这也是 UDP 的特性之一。组播是主机间一对多的通讯模式&#xff0c;是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址&#xff0c;组播地址不同于单播地址&#xff0c;它并不属于特定某个主机…

240621_Git初始配置及常用命令

Git初始配置及常用命令 初始配置 在安装Git后&#xff0c;我们应该首先设置修改、查看用户名及邮箱 运行Git Bash&#xff0c;使用以下命令设置本地Git工具的用户名及邮箱&#xff08;比如你的用户名是zhangsan&#xff0c;邮箱是zhangsan1123163.com&#xff09;&#xff1…

GPOPS-II教程(2): 可复用火箭再入大气层最优轨迹规划问题

问题描述 考虑一类可复用火箭再入大气层最优轨迹规划问题&#xff0c;其动力学方程为 { r ˙ v sin ⁡ γ , θ ˙ v cos ⁡ γ sin ⁡ ψ r cos ⁡ ϕ , ϕ ˙ v cos ⁡ γ cos ⁡ ψ r , v ˙ − F d m − F g sin ⁡ γ , γ ˙ F l cos ⁡ σ m v − ( F g v − v r …

解决chrome浏览器总是将对站点的http访问改为https的问题

问题&#xff1a;vue项目本地运行出来的地址是http开头的&#xff0c;但在chrome浏览器中无法访问&#xff0c;在Edge浏览器就可以&#xff0c;发现是chrome总是自动将http协议升级为https。 已试过的有效的方法&#xff1a; 一、无痕模式下访问 无痕模式下访问不会将http自…

推送电子邮箱与其他营销手段如何有效结合?

推送电子邮箱的效果如何&#xff1f;怎么优化邮件推送的策略&#xff1f; 将推送电子邮箱与其他营销手段有效结合&#xff0c;可以显著提升营销效果和用户体验。AokSend将探讨如何将推送电子邮箱与社交媒体营销、内容营销、搜索引擎优化&#xff08;SEO&#xff09;等手段相结…

Python 实现Excel转TXT,或TXT文本导入Excel

Excel是一种具有强大的数据处理和图表制作功能的电子表格文件&#xff0c;而TXT则是一种简单通用、易于编辑的纯文本文件。将Excel转换为TXT可以帮助我们将复杂的数据表格以文本的形式保存&#xff0c;方便其他程序读取和处理。而将TXT转换为Excel则可以将文本文件中的数据导入…

鸿蒙应用开发 - 软件安装 - DevEco

第一步 前往下载点下载安装包下载中心 | 华为开发者联盟-HarmonyOS开发者官网&#xff0c;共建鸿蒙生态https://developer.huawei.com/consumer/cn/download/ 根据自身需求下载对应安装包 第二步 点击打开安装包,配置安装路径 我个人选择放E盘,避免占用c盘空间 第三步 …

Linux集群自动化维护-Ansible

1.1Ansible概述 自动化运维&#xff1a;批量管理&#xff0c;批量分发&#xff0c;批量执行&#xff0c;维护。。是python写的 批量管理工具&#xff1a; Ansible&#xff08;无客户端&#xff09;&#xff1a;无客户端&#xff0c;基于ssh进行管理与维护 Saltstack &#…

RabbitMQ中lazyqueue队列

lazyqueue队列非常强悍 springboot注解方式开启 // 使用注解的方式lazy.queue队列模式 非常GoodRabbitListener(queuesToDeclare Queue(name "lazy.queue",durable "true",arguments Argument(name "x-queue-mode",value "lazy&…

RocketMQ源码学习笔记:Broker启动流程

这是本人学习的总结&#xff0c;主要学习资料如下 马士兵教育rocketMq官方文档 目录 1、Broker启动流程2、一些重要的类2.1、MappedFile2.2、MessgeStore2.3、MessageStore的加载启动流程 3、技术亮点3.1、 内存映射3.1.1、简介3.1.2、源码 1、Broker启动流程 Broker启动流程…

upload-labs第14关

upload-labs第14关 第十四关一、源代码分析代码审计 二、绕过分析a. 制作图片码首先需要一个照片&#xff0c;然后其次需要一个eval.php。 b.上传图片码上传成功 c.结合文件包含漏洞进行访问访问&#xff1a;http://192.168.1.110/upload-labs-master/include.php?filehttp://…

【Spine学习16】之 人物面部绑定

1、创建头部骨骼 一根头骨 以头骨为父结点创建一个面部控制器face-holder 2、创建头发和face面部控制结点的变换约束 左右头发的约束指向为face结点 3、设定后发的变换约束&#xff0c;约束指向为face结点&#xff0c;反方向移动 设置参数为-100 同理&#xff0c;耳朵也依…

6.25C高级

终端输入两个数&#xff0c;判断两数是否相等&#xff0c;如果不相等&#xff0c;判断大小关系 #!/bin/bash if [ $1 -eq $2 ] thenecho $1$2 elif [ $1 -gt $2 ] thenecho "$1>$2" elseecho "$1<$2" fi 2.已知网址www.hqyj.com&#xff0c;使用e…

Java-day01--基础知识

1、计算机基础知识&#xff1a; 计算机主要是由硬件和软件组成&#xff0c;软件指的是特定顺序的计算机指令&#xff0c;硬件主要可以看成是系统软件和应用软件等。 目前java主流分成三种&#xff1a;javase&#xff08;标准版&#xff09;、javame&#xff08;小型版&#x…

TCP: 传输控制协议

TCP: 传输控制协议 TCP的服务TCP 的首部小结 本系列文章旨在巩固网络编程理论知识&#xff0c;后续将结合实际开展深入理解的文章。 TCP的服务 T C P和U D P都使用相同的网络层&#xff08;I P&#xff09;&#xff0c;T C P却向应用层提供与U D P完全不同的服务。 T C P提供一…