当前位置: 首页 > news >正文

基于Qt有限状态机的一种实现方式和完善的人工智能方法

基于Qt有限状态机的一种实现方式和完善的人工智能方法

       人工智能在今年是一个非常火的方向,当然了。不不过今年,它一直火了非常多年,有关人工智能的一些算法层出不穷。人工智能在非常多领域都有应用,就拿我熟悉的游戏领域来说吧,一些寻路算法,比方说A*算法(我的《十日驱鬼记》就以前使用了A*算法进行寻路)。另一些高级的算法,比方说决策树等。都在游戏中得以了广泛的应用。我眼下想制作的项目和人工智能也有一定的关系,因此。我这个月開始学习搭建一些简单的人工智能框架。

蒋彩阳原创文章,首发地址:http://blog.csdn.net/gamesdev/article/details/46628447。欢迎同行前来探讨。

       Qt为了更加方便地在既有的GUI界面上增添更加复杂的逻辑,在4.6的时候引入了有限状态机这个概念。有限状态机指的是以限定个数的状态进行相互转换,而形成的一种有机的总体,它在游戏中用得或许多,我曾经在制作游戏项目的时候也见过自己制作有限状态机来处理复杂逻辑的。因此我開始又一次拾起有限状态机,看看能不能更深入地挖掘它的内容。

       假设你和我一样了解了QML的使用方法,那么一定会有印象,Qt 将有限状态机模块移植到了QML环境中来了。

要使用QML的有限状态机,须要来一句“import QtQml.StateMachine 1.0”这种声明。

Qt的文档非常丰富,在介绍有限状态机的时候甚至专门有一个章节,叫做“The Declarative State Machine Framework”,来介绍它的使用方法。假设大家还对QML的有限状态机不是非常熟悉的话。还是看看这篇Qt帮助文档吧!

     Qt的有限状态机。分为两个重要的内容。一个是“State”,指的是详细的某个状态,另外一个则是“Transition”,指的是两个状态之间的详细的转换。我在使用的时候发现。QML提供的有限状态机。仅仅提供了SignalTransition以及TimeoutTransition这种转换,并没有像Qt那样提供非常多有用的Transition。刚開始尝试简单的时候,认为还好,可是想到以后的状态机异常复杂,一旦涉及到的状态千变万化,就可能要写非常多的状态,实在是不方便。我拿我正在制作的项目打例如吧:

       上图是一个很easy的有限状态机。它仅仅有入口,没有出口。而且仅仅有三个状态。除了初始状态s1之外,仅仅是在s2和s3之间做切换。在图中,方框表示状态,箭头表示一个转换(transition)。那么不包含開始那个箭头。我们这里总共出现了6个状态,也是3×2个状态。

用QML代码表示的话,是这个样子:

 

QtObject
{
    id: root
    signal output( string text )
    property string input

    property var stateMachine: StateMachine
    {
        running: true
        initialState: s1

        State
        {
            id: s1
            onEntered: output( "你好,欢迎来到人工智能測试平台。" )

            SignalTransition
            {
                targetState: s2
                signal: root.inputChanged
                guard: root.input == "我喜欢你。"
            }
            SignalTransition
            {
                targetState: s3
                signal: root.inputChanged
                guard: root.input != "我喜欢你。"
            }
        }

        State
        {
            id: s2
            onEntered: output( "我也喜欢你。" )

            SignalTransition
            {
                targetState: s2
                signal: root.inputChanged
                guard: root.input == "我喜欢你。

" } SignalTransition { targetState: s3 signal: root.inputChanged guard: root.input != "我喜欢你。" } } State { id: s3 onEntered: output( "我刚来到这个世界,还不太懂人类的语言。可以教教我吗?" ) SignalTransition { targetState: s2 signal: root.inputChanged guard: root.input == "我喜欢你。" } SignalTransition { targetState: s3 signal: root.inputChanged guard: root.input != "我喜欢你。" } } } }

       这不过针对一个最小可运行的有限状态机而言,诸如Galgame这种游戏,它的分支情况是许多的,并且假设知道乘法原理的话,当x和y很大的时候,产生的转换(Transition)的个数也是很惊人的。

究其原因,是由于SignalTransition必须依附于State类作为它的sourceState。因此我们必须想办法缩小规模才行。

       因此我在研究Qt的有限状态机机制。幸运的是,在强大的Qt下。有限状态机的各个部分也是能够定制的。QState的祖先类是QAbstractState,QTransition的祖先类是QAbstractTransition,它们都是一定规模的抽象类,我们是须要实现它们的少数方法。就能够结合Qt的有限状态机做自己的处理了。于是我制作了一个名为AIState的状态类。它的作用是:

1、 保存它所发出的全部转换(Transition)的引用。当此状态激活时。统一让全部转换都为它服务。

相同地,我制作了一个名为ConditionalTransition的类,它有下面几个特性:

1、 能够设置sourceState,让其与State脱离父子关系,即能够定义在不论什么须要的位置。

2、 提供触发的条件属性;

3、 向状态机发送自己定义的事件。

以下是它们的代码:

AIState.h

#ifndef AISTATE_H
#define AISTATE_H

#include <QState>
#include <QQmlListProperty>
#include "ConditionalTransition.h"

class AIState : public QState
{
    Q_OBJECT
    Q_PROPERTY( QQmlListProperty<ConditionalTransition> conditions
                READ conditions )
public:
    explicit AIState( QState* parent = Q_NULLPTR );

    QQmlListProperty<ConditionalTransition> conditions( void );
private slots:
    void onActiveChanged( bool active );
protected:
    QList<ConditionalTransition*>           m_conditions;
};

#endif // AISTATE_H

AIState.cpp

#include "AIState.h"

AIState::AIState( QState* parent ): QState( parent )
{
    connect( this, SIGNAL( activeChanged( bool ) ),
             this, SLOT( onActiveChanged( bool ) ) );
}

QQmlListProperty<ConditionalTransition> AIState::conditions( void )
{
    return QQmlListProperty<ConditionalTransition>( this, m_conditions );
}

void AIState::onActiveChanged( bool active )
{
    // 将原来transition的sourceState设置为AIState。
    foreach ( ConditionalTransition* condition, m_conditions )
    {
        condition->setSourceState( active? this: Q_NULLPTR );
    }
}
 
  

ConditionalTransition.h

#ifndef CONDITIONALTRANSITION_H
#define CONDITIONALTRANSITION_H

#include <QObject>
#include <QObjectList>
#include <QEvent>
#include <QAbstractTransition>
#include <QQmlListProperty>

class ConditionalTransition: public QAbstractTransition
{
    Q_OBJECT
    Q_PROPERTY( QState* sourceState READ sourceState WRITE setSourceState NOTIFY sourceStateChanged )
    Q_PROPERTY( bool when READ condition WRITE setCondition NOTIFY conditionChanged )
public:
    explicit ConditionalTransition( QState* sourceState = Q_NULLPTR );

    inline bool condition( void ) { return m_condition; }
    void setCondition( bool condition );

    void setSourceState( QState* state );
signals:
    void sourceStateChanged( void );
    void conditionChanged( void );
protected:
    virtual bool eventTest( QEvent* event );
    virtual void onTransition( QEvent* event );

    bool                    m_condition;
};

class ConditionalEvent: public QEvent
{
public:
    explicit ConditionalEvent( bool condition,
                               ConditionalTransition* sourceTransition ):
        QEvent( QEvent::Type( ConditionalType ) ),
        m_condition( condition ),
        m_sourceTransition( sourceTransition ) { }
    inline bool condition( void )
    { return m_condition; }
    inline ConditionalTransition* sourceTransition( void )
    { return m_sourceTransition; }

    enum Type { ConditionalType = QEvent::User + 2079 };
private:
    bool                   m_condition;
    ConditionalTransition* m_sourceTransition;
};

#endif // CONDITIONALTRANSITION_H

ConditionalTransition.cpp

#include <QStateMachine>
#include "ConditionalTransition.h"

ConditionalTransition::ConditionalTransition(
        QState* sourceState ): QAbstractTransition( sourceState )
{
    m_condition = false;
}

void ConditionalTransition::setCondition( bool condition )
{
    m_condition = condition;
    emit conditionChanged( );
    if ( condition &&
         sourceState( ) != Q_NULLPTR &&
         sourceState( )->active( ) &&
         machine( )->isRunning( ) )
    {
        // 仅仅同意状态机正在执行而且源状态被激活的向状态机发送事件
        machine( )->postEvent( new ConditionalEvent( condition, this ) );
    }
}

void ConditionalTransition::setSourceState( QState* state )
{
    if ( sourceState( ) == state ) return;
    setParent( state );
    emit sourceStateChanged( );
}

bool ConditionalTransition::eventTest( QEvent* event )
{
    bool ret = false;
    if ( event->type( ) == QEvent::Type( ConditionalEvent::ConditionalType ) )
    {
        // 假设当前条件为真。而且源转换为其本身,那么通过,执行转换
        ConditionalEvent* ce = static_cast<ConditionalEvent*>( event );
        ret = ce->sourceTransition( ) == this;
    }

    return ret;
}

void ConditionalTransition::onTransition( QEvent* event )
{
    Q_UNUSED( event );
}
 
  

       接着将这几个类注冊到QML环境中,就能够在QML中定义这些类的实例了。

StateMachine
{
    id: machine
    running: true
    initialState: s1

    StateSettings
    {
        id: settings
    }
    property alias inputWord: settings.inputWord
    property alias outputWord: settings.outputWord

    Condition
    {
        id: c2
        objectName: "c2"
        when: inputWord.indexOf( "我喜欢你" ) != -1
        targetState: s2
    }

    Condition
    {
        id: c3
        objectName: "c3"
        when: inputWord.indexOf( "我喜欢你" ) == -1
        targetState: s3
    }

    AIState
    {
        id: s1
        objectName: "AI:s1"
        conditions: [ c2, c3 ]
        onEntered: outputWord = "你好。欢迎来到人工智能測试平台。"
    }

    AIState
    {
        id: s2
        objectName: "AI:s2"
        conditions: [ c2, c3 ]
        onEntered: outputWord = "我也喜欢你。"
    }

    AIState
    {
        id: s3
        objectName: "AI:s3"
        conditions: [ c2, c3 ]
        onEntered: outputWord = "我刚来到这个世界,还不太懂人类的语言。可以教教我吗?"
    }
}

       以下用状态机的图来分析一下:

       红色代表的是处于激活的状态,绿色则是处于激活的状态所拥有的转换。结合上面的QML代码我们能够知道,程序中总共仅仅定义了两个转换。而且转换定死的是targetState,而不是绑在了sourceState上,这么做能够把状态和转换进行解耦。

比曾经的实现少用了四个转换。假设有限状态机大起来了。这种效率提升是很可观的。

       演示程序的执行截图:

       源码下载地址:这里

版权声明:本文博主原创文章,博客,未经同意不得转载。

相关文章:

  • 【Android】高德地图 缩放级别及像素以及地图上的点转化成屏幕上的点
  • JAVA基础学习day26--正则表达式
  • css3中的伪元素
  • lua程序设计
  • yii2 model 给模型赋值(花了我将近30分钟)
  • CacheManager:–个通用缓存接口抽象类库
  • Map小结
  • Android中实现app版本更新
  • linux内核优化
  • 媒体类型(MIME类型)
  • AngularJS指令开发(1)——参数详解
  • [安卓] 15、用NFC解锁手机并自动打开应用
  • Delphi、C#之父Anders Hejlsberg首次访华 推广TypeScript
  • JavaScript 数据类型
  • code
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • 【React系列】如何构建React应用程序
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • ComponentOne 2017 V2版本正式发布
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • HomeBrew常规使用教程
  • nodejs调试方法
  • npx命令介绍
  • OSS Web直传 (文件图片)
  • PHP变量
  • Python_OOP
  • SQLServer插入数据
  • Vue全家桶实现一个Web App
  • 记一次删除Git记录中的大文件的过程
  • 线上 python http server profile 实践
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • 【云吞铺子】性能抖动剖析(二)
  • ​HTTP与HTTPS:网络通信的安全卫士
  • ###51单片机学习(2)-----如何通过C语言运用延时函数设计LED流水灯
  • (C#)一个最简单的链表类
  • (day 12)JavaScript学习笔记(数组3)
  • (保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示
  • (附源码)springboot教学评价 毕业设计 641310
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (七)c52学习之旅-中断
  • (一)python发送HTTP 请求的两种方式(get和post )
  • (转)Linq学习笔记
  • (转)关于多人操作数据的处理策略
  • (转)使用VMware vSphere标准交换机设置网络连接
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET Core 网络数据采集 -- 使用AngleSharp做html解析
  • .NET CORE使用Redis分布式锁续命(续期)问题
  • .NET Remoting学习笔记(三)信道
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET/C# 使用 ConditionalWeakTable 附加字段(CLR 版本的附加属性,也可用用来当作弱引用字典 WeakDictionary)
  • @Autowired 与@Resource的区别
  • [Excel VBA]单元格区域引用方式的小结
  • [HackMyVM]靶场 Wild