基于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。刚开始尝试简单的时候,觉得还好,但是想到以后的状态机异常复杂,一旦涉及到的状态千变万化,就可能要写很多的状态,实在是不方便。我拿我正在制作的项目打比方吧:

上图是一个非常简单的有限状态机,它只有入口,没有出口,并且只有三个状态。除了初始状态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上,这么做可以把状态和转换进行解耦。比以前的实现少用了四个转换。如果有限状态机大起来了,这样的效率提升是非常可观的。

演示程序的运行截图:

源代码下载地址:这里

时间: 2024-10-26 10:14:38

基于Qt有限状态机人工智能的一种实现及改进方法的相关文章

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

基于Qt有限状态机的一种实现方式和完善的人工智能方法 人工智能在今年是一个非常火的方向,当然了.不不过今年,它一直火了非常多年,有关人工智能的一些算法层出不穷.人工智能在非常多领域都有应用,就拿我熟悉的游戏领域来说吧,一些寻路算法,比方说A*算法(我的<十日驱鬼记>就以前使用了A*算法进行寻路).另一些高级的算法,比方说决策树等.都在游戏中得以了广泛的应用.我眼下想制作的项目和人工智能也有一定的关系,因此.我这个月開始学习搭建一些简单的人工智能框架. 蒋彩阳原创文章,首发地址:http://b

一种基于Qt的可伸缩的全异步C/S架构服务器实现(流浪小狗,六篇,附下载地址)

本文向大家介绍一种基于Qt的伸缩TCP服务实现.该实现针对C/S客户端-服务集群应用需求而搭建.连接监听.数据传输.数据处理均在独立的线程池中进行,根据特定任务不同,可安排负责监听.传输.处理的线程数目,从而在高传输负荷.高计算符合上达成取舍.数据处理采用流水线结构,以避免少量客户的密集计算请求影响其他客户端的处理.本文对应的代码符合LGPL协议,可直接从https://github.com/goldenhawking/zpserver下载. 也可从http://download.csdn.ne

一种基于Qt的可伸缩的全异步C/S架构服务器实现(一) 综述

本文向大家介绍一种基于Qt的伸缩TCP服务实现.该实现针对C/S客户端-服务集群应用需求而搭建.连接监听.数据传输.数据处理均在独立的线程池中进行,根据特定任务不同,可安排负责监听.传输.处理的线程数目,从而在高传输负荷.高计算符合上达成取舍.数据处理采用流水线结构,以避免少量客户的密集计算请求影响其他客户端的处理.本文对应的代码符合LGPL协议,可直接从https://github.com/goldenhawking/zpserver下载. 也可从http://download.csdn.ne

一种基于Qt的可伸缩的全异步C/S架构server实现(一) 综述

本文向大家介绍一种基于Qt的伸缩TCP服务实现.该实现针对C/Sclient-服务集群应用需求而搭建. 连接监听.传输数据.数据处理均在独立的线程池中进行,依据特定任务不同,可安排负责监听.传输.处理的线程数目,从而在高传输负荷.高计算符合上达成取舍.数据处理採用流水线结构.以避免少量客户的密集计算请求影响其它client的处理. 本文相应的代码符合LGPL协议,可直接从https://github.com/goldenhawking/zpserver下载. 也可从http://download

一种基于Qt的可伸缩的全异步C/S架构服务器实现(六) 整合各个模块实现功能

在前面的章节中,介绍了网络传输.任务线程池.数据库和集群四个主要功能模块.到现在为止,这些模块都还只是一种资源,没有产生实际的运行效果.对一个具备真实功能的应用来说,需要有一个整合的过程.整合方法很多,这里以典型的客户 -客户通信来举例说明. (一)类结构 1."客户端" 这个概念被抽象为一个节点类st_clientNode,每个客户端连接对应了该类的一个实例.这个类不但存储了有关该连接的所有背景信息(比如聊天程序中的用户名等),还提供了正确解释数据流的代码实现.由于想分开传输层和应用

一种基于Qt的可伸缩的全异步C/S架构服务器实现(三)

三.流水线结构线程池设计 为了无阻塞地实现并发通信及处理,传统的小规模服务器采用每用户一线程的多线程技术,称为"任务伴随者"模式.该模式示意图如下: 然而,当客户端很多时,开启上百组线程,远远超过计算机的物理线程规模,导致大量计算资源浪费在线程上下文切换和环境恢复等维护工作中,有效计算能力显著降低. 在多线程并行计算技术中,能够有效利用CPU物理核心,避免上下文频繁切换的经典模式是线程池模式.系统仅开启与CPU核心数相等的工作线程,形成线程池(ThreadPool).各个任务在队列中排

基于Qt的跨平台应用开发

1 Qt简介 Qt是1991年奇趣科技开发的一个跨平台的C++图形用户界面应用程序框架.它提供给应用程序开发者建立艺术级的图形用户界面所需的所有功能.Qt很容易扩展,并且允许真正地组件编程.基本上,Qt 同 X Window 上的 Motif,Openwin,GTK 等图形界 面库和 Windows 平台上的 MFC,OWL,VCL,ATL 是同类型的东西. 2008年,奇趣科技被诺基亚公司收购,QT也因此成为诺基亚旗下的编程语言工具.2012年,Qt被Digia收购.2014年4月,跨平台集成

基于QT和OpenCV的人脸识别系统

1 系统方案设计 1.1 引言 人脸是一个常见而复杂的视觉模式,人脸所反映的视觉信息在人与人的交流和交往中有着重 要的作用和意义,对人脸进行处理和分析在视频监控.出入口控制.视频会议以及人机交互等领 域都有着广泛的应用前景,因此是模式识别和计算机视觉领域持续的研究热点. 本系统在 FriendlyARM Tiny6410 开发板基础上,利用 OpenCV 计算机视觉库和 QT 图形库,通 过普通的 USB 摄像头实现了自动人脸识别,准确率较高,方便易用. 1.2 系统总体架构 "人脸识别&quo

基于Qt的类QQ气泡聊天的界面开发

最近在写IM 聊天界面,想设计出一个类似QQ气泡聊天的样式 使用了几种办法 1:使用Qt下面的QListview来实现QQ类似效果,差强人意 2:使用QWebview加载html css样式来完成,发现效果不错,但是毕竟webview占用巨大的内存 3:使用QTextBrower加载css,但是好像只支持css2.1版本,css3完全不支持,这样的话,花哨的样式应该是无法实现 基于以上三种思路 最后发现还是QML实现比较好,但是qml基于文本与动画图片混合显示没找到好的办法,有好的办法的希望可以