java 行为树_使用行为树实现应用程序命令行解析工具

不知道是不是我一开始想多了,在做本科毕业论文的时候,为了更方便地运行程序,绞尽脑汁写了一个命令行解析器。而后又经过几次迭代,感觉成熟了不少,但是回过头发现好像并用不着这样的解析器。。。当然主体程序跟现在要讨论的内容不相关就不说了,只是花了精力实现的东西还是希望记录下思路。

行为树的原理,简单来说,系统的状态从根节点出发,接收外部信息,每接受一个信息,就按照预定义的跳转表进行状态跳转。当所有信息都处理完之后,最终停留的那个状态一般都有个动作,运行之后便完成了对程序的控制。

所以实际上要完成两个阶段的任务,首先是利用待实现的命令行语句构建一颗行为树,语句里的每个单词都是一个状态跳转指示。然后实现一个语句解析器,负责读取语句,并进行相应的状态跳转。

构造行为树

预定义一颗行为树的方法有很多种,主要是表现形式的问题。我这里使用json格式的配置文件来作说明如下:

{"START":{"transitions":{"cmd1":"CMD1","cmd2":"CMD2","cmd3":"CMD3"}}}

在上面的配置中,大写单词如”START”, “CMD1”, “CDM2”,”CMD3”,就是所谓的节点。在”transitions” 对象中定义了跳转条件,即从”START”开始,如果输入”cmd1”,则跳转到”CMD1”节点,依此类推。而对于后续的输入,当然还要继续定义,例如:

{"CMD1":{"transitions":{"a":"CMD1_A"}},"CMD2":{"transitions":{"b":"CMD2_B"}}}

使用这种方式可以将所有命令语句都用一颗行为树来表达,并且具有很好的灵活性,可以随时修改。这算是大体完成了第一阶段的任务,很简单。

接下来需要加载配置文件,那么首先要通过代码建立起行为树的模型,也就是规定其包含的内容与拥有的行为。像二叉树一样,只需包含根节点,然后通过各节点之间的链接关系就可以访问到树上的每个节点。行为树也不必完全拥有其树上所有节点的引用,而只需要知道最关键的信息,也就是当前节点和起始节点。行为树所拥有的动作当然就是执行命令语句,并且在语句执行完成后回复到起始状态,以便下一条语句的执行。粗略的代码如下:

public class BehaviorTree{

private Node start;

private Node current;

public BehaviorTree(Node start) {

this.start = start;

current = start;

}

public void execute(String command) {

//command execution code

}

public void reset() {

current = start;

}

}

而每个节点都包含一个跳转表,即在特定指令单词下,返回下一个状态节点

public class Node {

private HashMap transitions = new HashMap<>();

public Node next(String word) {

return transitions.get(word);

}

public void addTransition(String trigger, Node target) {

transitions.put(trigger, target);

}

public void run() {

//run

}

}

节点类的addTransition()方法需要在构造节点对象的时候调用,其依据就是前面所述的行为树配置文件中的跳转规则。而next()方法则根据传入的指令返回跳转表中的相应后续节点。最后还有个run()方法,这只在叶节点(又称为动作节点)中才实现,当然一般节点也可以输出些调试信息。

现在解释下命令语句的执行过程:当调用行为树的execute()方法时,传入命令,首先将语句分解成一个词组,然后调用current节点的next()方法,传入第一个单词,这将返回下一个节点,将其绑定为current。接着传入下一个单词,循环这一过程,直到所有单词都输入,最后得到的节点就是需要的动作节点,最后run()方法实现了这句指令想要执行的操作。

public void excute(String command) {

String[] words = command.trim().split("\\s+");

for(String word : words) {

current = current.next(word);

}

current.run();

current = start;

}

好了,介绍完大致思路之后,就要开始构建行为树了,一种很自然的想法是——为每个状态定义一个类(继承自Node类),例如我们可以定义 class Start extends Node{},class CMD1 extends Node{},class CMD2 extends Node{}等等,然后再为每个类创建实例,并配置跳转条件,类似下面这种

start.addTransition("cmd1", cmd1);

start.addTransition("cmd2", cmd2);

cmd1.addTransition("a", cmd1_a);

//....

想当初写着这样的代码的时候内心是崩溃的,还使用了好长时间。最后幡然醒悟,为何要为这些中间节点专门写个类,简直多此一举。简单的做法是创建很多Node实例作为状态集合,将这些对象放入哈希表map,其键值由到达这些状态所需要的字符串命令组成。例如,将对象cmd1_a的键值设为”cmd1_a”,这种方式规定的键值显然是唯一的。

前面提到的 json 配置文件就是生成行为树的一种很好的方式,因为它定义了所有的节点,以及节点间的跳转规则。于是就可以想办法把这种配置解析成 java 类,从而组装成一棵树。下面的代码首先将所有的节点找到,并以名称为键放入哈希表

String json = new String(new Files.readAllBytes(Paths.get(uri)));

JsonObject configJson = gson.fromJson(json, JsonObject.class);

HashMap nodes = new HashMap<>();

configJson.entrySet().forEach(e -> {

String name = e.getKey();

nodes.put(name, new Node());

});

其中的 gson 为 google 的 json 解析工具。

最后为每个节点配置跳转规则,便完成了配置文件的解析。

configJson.entrySet().forEach(e -> {

String name = e.getKey();

JsonObject properties = e.getValue().getAsJsonObject();

JsonElement transitions = properties.get("transitions");

if(transitions != null) {

transitions.getAsJsonObject().entrySet().forEach(t -> {

String trigger = t.getKey();

String targetName = t.getValue().getAsString();

nodes.get(name).addTransition(trigger, nodes.get(targetName));

});

}

});

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值