基于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上,这么做能够把状态和转换进行解耦。
比曾经的实现少用了四个转换。假设有限状态机大起来了。这种效率提升是很可观的。
演示程序的执行截图:
源码下载地址:这里
版权声明:本文博主原创文章,博客,未经同意不得转载。
基于Qt有限状态机的一种实现方式和完善的人工智能方法的更多相关文章
- 二、spring Boot构建的Web应用中,基于MySQL数据库的几种数据库连接方式进行介绍
包括JDBC.JPA.MyBatis.多数据源和事务. 一.JDBC 连接数据库 1.属性配置文件(application.properties) spring.datasource.url=jdbc ...
- [Python]基于权重的随机数2种实现方式
问题: 比如我们要选从不同省份选取一个号码.每一个省份的权重不一样,直接选随机数肯定是不行的了,就须要一个模型来解决问题. 简化成以下的问题: 字典的key代表是省份,value代表的是权重,我们如今 ...
- 基于ftp服务的三种登录方式及其相关的访问控制和优化
ftp(简单文件传输协议),是一种应用广泛的网络文件传输协议和服务,占用20和21号端口,主要用于资源的上传和下载. 在linux对于ftp同widows一样具有很多的种类,这里主要介绍vsfptd( ...
- synchronized三种使用方式,及锁的类型验证
Synchronized常用三种使用方式 1.修饰普通方法:锁对象即为当前对象 2.修饰静态方法:锁对象为当前Class对象 3.修饰代码块:锁对象为synchronized紧接着的小括号内的对象 一 ...
- spring事务——try{...}catch{...}中事务不回滚的几种处理方式(转载)
转载自 spring事务——try{...}catch{...}中事务不回滚的几种处理方式 当希望在某个方法中添加事务时,我们常常在方法头上添加@Transactional注解 @Respon ...
- 基于Metronic的Bootstrap开发框架经验总结(11)--页面菜单的几种呈现方式
在常规的后台管理系统或者前端界面中,一般都有一个导航菜单提供给用户,方便选择所需的内容.基于Metronic的Bootstrap开发框架,是整合了Metroinc样式,以及Boostrap组件模块的内 ...
- Qt 2D绘图 渐变填充(三种渐变方式)
在qt中提供了三种渐变方式,分别是线性渐变,圆形渐变和圆锥渐变.如果能熟练应用它们,就能设计出炫目的填充效果. 线性渐变: 1.更改函数如下: void Dialog::paintEvent(QPai ...
- 一种基于Qt的可伸缩的全异步C/S架构服务器实现(流浪小狗,六篇,附下载地址)
本文向大家介绍一种基于Qt的伸缩TCP服务实现.该实现针对C/S客户端-服务集群应用需求而搭建.连接监听.数据传输.数据处理均在独立的线程池中进行,根据特定任务不同,可安排负责监听.传输.处理的线程数 ...
- 一种基于Qt的可伸缩的全异步C/S架构server实现(一) 综述
本文向大家介绍一种基于Qt的伸缩TCP服务实现.该实现针对C/Sclient-服务集群应用需求而搭建. 连接监听.传输数据.数据处理均在独立的线程池中进行,依据特定任务不同,可安排负责监听.传输.处理 ...
随机推荐
- 怎样从host之外连接到docker container
启动docker的时候的指令使用 sudo docker -H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock -d & 这样就能使dock ...
- 《Linux设备驱动开发具体解释(第3版)》进展同步更新
本博实时更新<Linux设备驱动开发具体解释(第3版)>的最新进展. 2015.2.26 差点儿完毕初稿. 本书已经rebase到开发中的Linux 4.0内核,案例多数基于多核CORTE ...
- 第一章 andrid visdio 安装
第一章 andrid visdio 安装与环境搭建 一.Android Studio简介 Android Studio是Google新发布的Android应用程序开发环境,Android Stud ...
- leetcode第一刷_Convert Sorted List to Binary Search Tree
好,二叉搜索树粉末登场,有关他的问题有这么几个,给你一个n,如何求全部的n个节点的二叉搜索树个数?能不能把全部的这些二叉搜索树打印出来? 这道题倒不用考虑这么多,直接转即可了,我用的思想是分治,每次找 ...
- JS学习笔记-OO疑问之对象创建
问一.引入工厂,解决反复代码 前面已经提到,JS中创建对象的方法,不难发现,主要的创建方法中,创建一个对象还算简单,假设创建多个类似的对象的话就会产生大量反复的代码. 解决:工厂模式方法(加入一个专门 ...
- 用python输出汉字字库
问题1:如果我们知道汉字编码范围是0x4E00到0x9FA5,怎么从十六进制的编码转成人类可读的字呢? 问题2:怎么把unicode编码的字写入文件呢,假设直接用open()的话,会提示Unicode ...
- linux sed命令详解(转)
简介 sed 是一种在线编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的 ...
- Android他们控制的定义(一)
培养自己的控制步骤定义: 1.要理解View作品 2. 分享到继承View子类 3. 要定义自己的View类添加属性 4. 绘制控件 5. 响应用户消息 6 .自己定义回调函数 一.View ...
- 使用QT 4.8.6 + Cmake 3.0.0 编译 最新版本OpenCv3.0.0
mingw32 (x32) gcc g++ qt opencv- -- cmake -rc1 windows x64 参考文章: http://blog.csdn.net/qiurisuixiang/ ...
- Gradle构建多模块项目(转)
废话不多说,直接进入主题. 1. 创建项目 首先创建项目,名称为 test: mkdir test && cd test gradle init 这时候的项目结构如下: ➜ test ...