├── .gitignore ├── .jshintrc ├── AUTHORS ├── LICENSE ├── Makefile ├── README-Chinese.md ├── README.md ├── index.js ├── lib ├── bt.js └── node │ ├── composite.js │ ├── condition.js │ ├── decorator.js │ ├── if.js │ ├── loop.js │ ├── node.js │ ├── parallel.js │ ├── select.js │ └── sequence.js ├── package.json └── test └── node ├── ifTest.js ├── loopTest.js ├── parallelTest.js ├── selectTest.js └── sequenceTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | node_modules/ 3 | coverage.html 4 | lib-cov/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "describe", 4 | "it", 5 | "before", 6 | "after", 7 | "window", 8 | "__resources__" 9 | ], 10 | "es5": true, 11 | "node": true, 12 | "eqeqeq": true, 13 | "undef": true, 14 | "curly": true, 15 | "bitwise": true, 16 | "immed": false, 17 | "newcap": true, 18 | "nonew": true 19 | } 20 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Yongchang Zhou -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012 Netease, Inc. and other pomelo contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC = $(shell find lib -type f -name "*.js") 2 | TESTS = test/* 3 | REPORTER = spec 4 | TIMEOUT = 5000 5 | 6 | test: 7 | @./node_modules/.bin/mocha \ 8 | --reporter $(REPORTER) --timeout $(TIMEOUT) $(TESTS) 9 | 10 | test-cov: lib-cov 11 | @JSCOV=1 $(MAKE) test REPORTER=html-cov > coverage.html && open coverage.html 12 | 13 | lib-cov: 14 | @rm -rf ./$@ 15 | @jscoverage lib $@ 16 | 17 | clean: 18 | rm -rf lib-cov 19 | rm -f coverage.html 20 | 21 | .PHONY: test test-cov 22 | -------------------------------------------------------------------------------- /README-Chinese.md: -------------------------------------------------------------------------------- 1 | #pomelo-bt - behavior tree for node.js 2 | pomelo-bt是pomelo项目中AI模块所依赖的行为树模块,提供了基本的行为树实现。 3 | 4 | + Tags: node.js 5 | 6 | ##安装 7 | ``` 8 | npm install pomelo-bt 9 | ``` 10 | 11 | ##行为树节点基类 12 | ###节点基类 Node 13 | 所有行为树节点都从该类派生,构造函数接受一个blackboard实例作为参数。 14 | 每个节点都提供一个执行的入口doAction方法。doAction执行完完毕后,向父节点返回执行结果: 15 | RES_SUCCESS, RES_FAIL, RES_WAIT分别代表当前执行成功,失败和仍在执行中。 16 | 父节点根据子节点的返回值再做后续流程决策。 17 | 18 | ###组合节点基类 Composite 19 | 所有组合节点都从该类派生,内部可以维护多个孩子节点。提供addChild接口,添加孩子节点。 20 | 21 | ###装饰节点基类 Decorator 22 | 所有装饰节点都从该类派生,提供setChild接口,添加唯一的孩子节点。 23 | 24 | ##组合节点 25 | ###Sequence 26 | 实现行为树sequence语义。 27 | ####构造函数Sequenec(opts) 28 | + opts.blackboard - 构造行为树节点的blackboard实例。 29 | 30 | ###Parallel 31 | 实现行为树parallel语义。 32 | ####构造函数Parallel(opts) 33 | + opts.blackboard - 构造行为树节点的blackboard实例。 34 | + opts.policy - Parallel节点失败策略,可选值:Parallel.POLICY_FAIL_ON_ONE(默认值), Parallel.POLICY_FAIL_ON_ALL。 35 | 36 | ###Selector 37 | 实现行为树selector语义。 38 | ####构造函数Selector(opts) 39 | + opts.blackboard - 构造行为树节点的blackboard实例。 40 | 41 | ##装饰节点 42 | ###Loop 43 | 循环节点。 44 | ####构造函数Loop(opts) 45 | + opts.blackboard - 构造行为树节点的blackboard实例。 46 | + opts.child - 孩子节点。 47 | + opts.loopCond(blackboard) - 循环条件判断函数。返回true表示循环条件成立,否则不成立。 48 | 49 | ##条件节点 50 | ###Condition 51 | 条件成立返回RES_SUCCESS, 反之返回RES_FAIL。 52 | ####构造函数Condition(opts) 53 | + opts.blackboard - 构造行为树节点的blackboard实例。 54 | + opts.cond(blackboard) - 条件判断函数,返回true表示条件成立,否则不成立。 55 | 56 | ##其他节点 57 | ###If 58 | 实现if语义,如果条件成立,则执行关联的孩子节点。 59 | ####构造函数If(opts) 60 | + opts.blackboard - 构造行为树节点的blackboard实例。 61 | + opts.action - 孩子节点。 62 | + opts.cond(blackboard) - 条件判断函数,返回true表示条件成立,否则不成立。 63 | 64 | ##用法 65 | ``` javascript 66 | var util = require('util'); 67 | var bt = require('pomelo-bt'); 68 | var Sequence = bt.Sequence; 69 | var Node = bt.Node; 70 | 71 | // define some action nodes 72 | var HelloNode = function(blackboard) { 73 | Node.call(this, blackboard); 74 | }; 75 | util.inherits(HelloNode, Node); 76 | 77 | HelloNode.prototype.doAction = function() { 78 | console.log('Hello '); 79 | return bt.RES_SUCCESS; 80 | }; 81 | 82 | 83 | var WorldNode = function(blackboard) { 84 | Node.call(this, blackboard); 85 | }; 86 | util.inherits(WorldNode, Node); 87 | 88 | WorldNode.prototype.doAction = function() { 89 | console.log('World'); 90 | return bt.RES_SUCCESS; 91 | }; 92 | 93 | var blackboard = {}; 94 | 95 | // composite your behavior tree 96 | var seq = new Sequence({blackboard: blackboard}); 97 | var hello = new HelloNode(blackboard); 98 | var world = new WorldNode(blackboard); 99 | 100 | seq.addChild(hello); 101 | seq.addChild(world); 102 | 103 | // run the behavior tree 104 | seq.doAction(); 105 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #pomelo-bt - behavior tree for node.js 2 | 3 | pomelo-bt is a Behavior-Tree module for pomelo project to implement AI. More information about Behavior-Tree please refer other articles from Internet, such as [Understanding Behavior Trees](http://aigamedev.com/open/article/bt-overview/). 4 | 5 | + Tags: node.js 6 | 7 | ##Installation 8 | ``` 9 | npm install pomelo-bt 10 | ``` 11 | 12 | ##Behavior tree nodes 13 | 14 | ###Node 15 | The base class of all the behavior tree node classes. Its constructor receives a blackboard instance as parameter. 16 | 17 | Each node class provides a `doAction` method to fire the behavior of current node instance. All the children should implement their own `doAction`. And the `doAction` method sould report a result code to the parent when it return. The result code is one of below: 18 | 19 | + RES_SUCCESS: the behavior finished successfully. 20 | + RES_FAIL: the behavior fails. 21 | + RES_WAIT: the behavior is running and should be continued in next tick. 22 | The parent node makes its decision based on the result code. 23 | 24 | ###Composite 25 | The base class of all the composite nodes. A composite node has arbitrary child nodes and it has a `addChild` method to add child node. 26 | 27 | ###Decorator 28 | The base class of all the decorator nodes. A decorator node has the ability to decorate the result for its child node. A decorator node has only one child node and has a `setChild` method to set the child node. 29 | 30 | Followings are some behavior node types provided in `pomelo-bt`. 31 | 32 | ##Composite nodes 33 | ###Sequence 34 | Implementation of `sequence` semantics. 35 | ####Sequence(opts) 36 | + opts.blackboard - blackboard instance for the behavior node. 37 | 38 | ###Parallel 39 | Implementation of parallel semantics. 40 | ####Parallel(opts) 41 | + opts.blackboard - blackboard instance for the behavior node. 42 | + opts.policy - Failure strategy for Parallel node: `Parallel.POLICY_FAIL_ON_ONE`(default) return `RES_FAIL` if one child node fail, `Parallel.POLICY_FAIL_ON_ALL` return `RES_FAIL` only on all the child nodes fail. 43 | 44 | ###Selector 45 | Implementation of selector semantics. 46 | ####Selector(opts) 47 | + opts.blackboard - blackboard instance for the behavior node. 48 | 49 | ###Decorator nodes 50 | ###Loop 51 | Implementation of loop semantics. 52 | ####Loop(opts) 53 | + opts.blackboard - blackboard instance for the behavior node. 54 | + opts.child - child node for the decorator node。 55 | + opts.loopCond(blackboard) - loop condition function. return true to continue the loop and false to break the loop. 56 | 57 | ###Condition 58 | Return `RES_SUCESS` if the condition is true otherwise return `RES_FAIL`. 59 | ####Condition(opts) 60 | + opts.blackboard - blackboard instance for the behavior node. 61 | + opts.cond(blackboard) - condition function, return true or false. 62 | 63 | ##Other nodes 64 | ###If 65 | Implementation of loop semantics. If the condition is true, then fire the child node. 66 | ####If(opts) 67 | + opts.blackboard - blackboard instance for the behavior node. 68 | + opts.action - child node. 69 | + opts.cond(blackboard) - condition function, return true or false. 70 | 71 | ##Usage 72 | ``` javascript 73 | var util = require('util'); 74 | var bt = require('pomelo-bt'); 75 | var Sequence = bt.Sequence; 76 | var Node = bt.Node; 77 | 78 | // define some action nodes 79 | var HelloNode = function(blackboard) { 80 | Node.call(this, blackboard); 81 | }; 82 | util.inherits(HelloNode, Node); 83 | 84 | HelloNode.prototype.doAction = function() { 85 | console.log('Hello '); 86 | return bt.RES_SUCCESS; 87 | }; 88 | 89 | 90 | var WorldNode = function(blackboard) { 91 | Node.call(this, blackboard); 92 | }; 93 | util.inherits(WorldNode, Node); 94 | 95 | WorldNode.prototype.doAction = function() { 96 | console.log('World'); 97 | return bt.RES_SUCCESS; 98 | }; 99 | 100 | var blackboard = {}; 101 | 102 | // composite your behavior tree 103 | var seq = new Sequence({blackboard: blackboard}); 104 | var hello = new HelloNode(blackboard); 105 | var world = new WorldNode(blackboard); 106 | 107 | seq.addChild(hello); 108 | seq.addChild(world); 109 | 110 | // run the behavior tree 111 | seq.doAction(); 112 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var bt = require('./lib/bt'); 2 | var exp = module.exports; 3 | 4 | exp.RES_SUCCESS = bt.RES_SUCCESS; 5 | exp.RES_FAIL = bt.RES_FAIL; 6 | exp.RES_WAIT = bt.RES_WAIT; 7 | 8 | exp.Node = require('./lib/node/node'); 9 | exp.Composite = require('./lib/node/composite'); 10 | exp.Condition = require('./lib/node/condition'); 11 | exp.Decorator = require('./lib/node/decorator'); 12 | exp.Sequence = require('./lib/node/sequence'); 13 | exp.Parallel = require('./lib/node/parallel'); 14 | exp.Select = require('./lib/node/select'); 15 | exp.Loop = require('./lib/node/loop'); 16 | exp.If = require('./lib/node/if'); -------------------------------------------------------------------------------- /lib/bt.js: -------------------------------------------------------------------------------- 1 | var exp = module.exports; 2 | 3 | exp.RES_SUCCESS = 0; 4 | exp.RES_FAIL = 1; 5 | exp.RES_WAIT = 2; -------------------------------------------------------------------------------- /lib/node/composite.js: -------------------------------------------------------------------------------- 1 | var BTNode = require('./node'); 2 | var util = require('util'); 3 | 4 | /** 5 | * Composite node: parent of nodes that have multi-children. 6 | */ 7 | var Node = function(blackboard) { 8 | BTNode.call(this, blackboard); 9 | this.blackboard = blackboard; 10 | this.children = []; 11 | }; 12 | util.inherits(Node, BTNode); 13 | 14 | module.exports = Node; 15 | 16 | var pro = Node.prototype; 17 | 18 | /** 19 | * Add a child to the node 20 | */ 21 | pro.addChild = function(node) { 22 | this.children.push(node); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/node/condition.js: -------------------------------------------------------------------------------- 1 | var bt = require('../bt'); 2 | var util = require('util'); 3 | var BTNode = require('./node'); 4 | 5 | /** 6 | * Condition node. 7 | * 8 | * @param opts {Object} 9 | * opts.blackboard {Object} blackboard object 10 | * opts.cond(blackboard) {Function} condition callback. Return true or false to decide the node return success or fail. 11 | * @return {Number} 12 | * bt.RES_SUCCESS if cond callback return true; 13 | * bt.RES_FAIL if cond undefined or return false. 14 | */ 15 | var Node = function(opts) { 16 | BTNode.call(this, opts.blackboard); 17 | this.cond = opts.cond; 18 | }; 19 | util.inherits(Node, BTNode); 20 | 21 | module.exports = Node; 22 | 23 | var pro = Node.prototype; 24 | 25 | pro.doAction = function() { 26 | if(this.cond && this.cond.call(null, this.blackboard)) { 27 | return bt.RES_SUCCESS; 28 | } 29 | 30 | return bt.RES_FAIL; 31 | }; 32 | -------------------------------------------------------------------------------- /lib/node/decorator.js: -------------------------------------------------------------------------------- 1 | var BTNode = require('./node'); 2 | var util = require('util'); 3 | 4 | /** 5 | * Decorator node: parent of nodes that decorate other node. 6 | */ 7 | var Node = function(blackboard, child) { 8 | BTNode.call(this, blackboard); 9 | this.child = child; 10 | }; 11 | util.inherits(Node, BTNode); 12 | 13 | module.exports = Node; 14 | 15 | var pro = Node.prototype; 16 | 17 | /** 18 | * set the child fo the node 19 | */ 20 | pro.setChild = function(node) { 21 | this.child = node; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/node/if.js: -------------------------------------------------------------------------------- 1 | var BTNode = require('./node'); 2 | var Condition = require('./condition'); 3 | var util = require('util'); 4 | var Sequence = require('./sequence'); 5 | 6 | /** 7 | * If node: invoke the action if the condition is true 8 | * 9 | * @param opts {Object} 10 | * opts.blackboard {Object} blackboard 11 | * opts.action {BTNode} action that would be invoked if cond return true 12 | * opts.cond(blackboard) {Function} condition callback, return true or false. 13 | */ 14 | var Node = function(opts) { 15 | BTNode.call(this, opts.blackboard); 16 | 17 | this.action = new Sequence({ 18 | blackboard: opts.blackboard 19 | }); 20 | 21 | var condition = new Condition({ 22 | blackboard: opts.blackboard, 23 | cond: opts.cond 24 | }); 25 | 26 | this.action.addChild(condition); 27 | this.action.addChild(opts.action); 28 | }; 29 | util.inherits(Node, BTNode); 30 | 31 | module.exports = Node; 32 | 33 | var pro = Node.prototype; 34 | 35 | /** 36 | * Move the current mob into patrol module and remove it from ai module. 37 | * 38 | * @return {Number} ai.RES_SUCCESS if everything ok; 39 | * ai.RES_FAIL if any error. 40 | */ 41 | pro.doAction = function() { 42 | return this.action.doAction(); 43 | }; 44 | -------------------------------------------------------------------------------- /lib/node/loop.js: -------------------------------------------------------------------------------- 1 | var bt = require('../bt'); 2 | var util = require('util'); 3 | var Decorator = require('./decorator'); 4 | 5 | /** 6 | * Loop node: a decorator node that invoke child in loop. 7 | * 8 | * @param opts {Object} 9 | * opts.blackboard {Object} blackboard object 10 | * opts.child {Object} origin action that is decorated 11 | * opts.loopCond(blackboard) {Function} loop condition callback. Return true to continue the loop. 12 | * @return {Number} 13 | * bt.RES_SUCCESS if loop finished successfully; 14 | * bt.RES_FAIL and break loop if child return fail; 15 | * bt.RES_WAIT if child return wait or loop is continue. 16 | */ 17 | var Node = function(opts) { 18 | Decorator.call(this, opts.blackboard, opts.child); 19 | this.loopCond = opts.loopCond; 20 | }; 21 | 22 | util.inherits(Node, Decorator); 23 | 24 | module.exports = Node; 25 | 26 | var pro = Node.prototype; 27 | 28 | pro.doAction = function() { 29 | var res = this.child.doAction(); 30 | if(res !== bt.RES_SUCCESS) { 31 | return res; 32 | } 33 | 34 | if(this.loopCond && this.loopCond.call(null, this.blackboard)) { 35 | //wait next tick 36 | return bt.RES_WAIT; 37 | } 38 | 39 | return bt.RES_SUCCESS; 40 | }; 41 | -------------------------------------------------------------------------------- /lib/node/node.js: -------------------------------------------------------------------------------- 1 | var BTNode = require('./node'); 2 | var util = require('util'); 3 | 4 | /** 5 | * Parent of all behavior tree nodes. 6 | */ 7 | var Node = function(blackboard) { 8 | this.blackboard = blackboard; 9 | }; 10 | 11 | module.exports = Node; 12 | -------------------------------------------------------------------------------- /lib/node/parallel.js: -------------------------------------------------------------------------------- 1 | var bt = require('../bt'); 2 | var util = require('util'); 3 | var Composite = require('./composite'); 4 | 5 | /** 6 | * Parallel node: a parent node that would invoke children in parallel. 7 | * The node would wait for all the children finished and return the result. 8 | * The result value would be decided by the policy. 9 | * POLICY_FAIL_ON_ONE stands for return fail if any fails; 10 | * POLICY_FAIL_ON_ALL stands for return fail if and only if all fail. 11 | */ 12 | var Node = function(opts) { 13 | Composite.call(this, opts.blackboard); 14 | this.policy = opts.policy||Node.POLICY_FAIL_ON_ONE; 15 | this.waits = []; 16 | this.succ = 0; 17 | this.fail = 0; 18 | }; 19 | util.inherits(Node, Composite); 20 | 21 | module.exports = Node; 22 | 23 | var pro = Node.prototype; 24 | 25 | Node.POLICY_FAIL_ON_ONE = 0; 26 | Node.POLICY_FAIL_ON_ALL = 1; 27 | 28 | /** 29 | * do the action 30 | */ 31 | pro.doAction = function() { 32 | if(!this.children.length) { 33 | //if no child 34 | return bt.RES_SUCCESS; 35 | } 36 | 37 | var res; 38 | var rest = []; 39 | var origin = this.waits.length ? this.waits : this.children; 40 | 41 | //iterate all the children and record the results 42 | for(var i=0, l=origin.length; i= this.children.length) { 29 | this.reset(); 30 | } 31 | 32 | var res; 33 | for(var l=this.children.length; this.index= this.children.length) { 29 | this.reset(); 30 | } 31 | 32 | var res; 33 | for(var l=this.children.length; this.index=0.0.1", 8 | "should": ">=0.0.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/node/ifTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var bt = require('../../'); 3 | var If = bt.If; 4 | 5 | var SNode = function(bb) { 6 | this.blackboard = bb; 7 | }; 8 | SNode.prototype = { 9 | doAction: function() { 10 | this.blackboard.scount++; 11 | return bt.RES_SUCCESS; 12 | } 13 | }; 14 | 15 | var FNode = function(bb) { 16 | this.blackboard = bb; 17 | }; 18 | FNode.prototype = { 19 | doAction: function() { 20 | this.blackboard.fcount++; 21 | return bt.RES_FAIL; 22 | } 23 | }; 24 | 25 | var WNode = function(bb) { 26 | this.blackboard = bb; 27 | }; 28 | WNode.prototype = { 29 | doAction: function() { 30 | if(this.blackboard.wcount < 2) { 31 | this.blackboard.wcount++; 32 | return bt.RES_WAIT; 33 | } else { 34 | this.blackboard.scount++; 35 | return bt.RES_SUCCESS; 36 | } 37 | } 38 | }; 39 | 40 | describe('If Test', function() { 41 | it('should invoke the action if condition return true', function() { 42 | var bb = { 43 | scount: 0, 44 | fcount: 0, 45 | wcount: 0 46 | }; 47 | 48 | var cond = function(bb) { 49 | return true; 50 | }; 51 | 52 | var i = new If({blackboard: bb, action: new SNode(bb), cond: cond}); 53 | 54 | var res = i.doAction(); 55 | res.should.equal(bt.RES_SUCCESS); 56 | bb.scount.should.equal(1); 57 | bb.fcount.should.equal(0); 58 | bb.wcount.should.equal(0); 59 | }); 60 | 61 | it('should return fail if condition return false', function() { 62 | var bb = { 63 | scount: 0, 64 | fcount: 0, 65 | wcount: 0 66 | }; 67 | 68 | var cond = function(bb) { 69 | return false; 70 | }; 71 | 72 | var i = new If({blackboard: bb, action: new SNode(bb), cond: cond}); 73 | 74 | var res = i.doAction(); 75 | res.should.equal(bt.RES_FAIL); 76 | bb.scount.should.equal(0); 77 | bb.fcount.should.equal(0); 78 | bb.wcount.should.equal(0); 79 | }); 80 | 81 | it('should return fail if action return false', function() { 82 | var bb = { 83 | scount: 0, 84 | fcount: 0, 85 | wcount: 0 86 | }; 87 | 88 | var cond = function(bb) { 89 | return true; 90 | }; 91 | 92 | var i = new If({blackboard: bb, action: new FNode(bb), cond: cond}); 93 | 94 | var res = i.doAction(); 95 | res.should.equal(bt.RES_FAIL); 96 | bb.scount.should.equal(0); 97 | bb.fcount.should.equal(1); 98 | bb.wcount.should.equal(0); 99 | }); 100 | 101 | it('should return wait if the child return wait and reenter the child directly in next tick', function() { 102 | var bb = { 103 | scount: 0, 104 | fcount: 0, 105 | wcount: 0 106 | }; 107 | 108 | var condCount = 0; 109 | var cond = function(bb) { 110 | condCount++; 111 | return true; 112 | }; 113 | 114 | var i = new If({blackboard: bb, action: new WNode(bb), cond: cond}); 115 | 116 | var res = i.doAction(); 117 | res.should.equal(bt.RES_WAIT); 118 | bb.scount.should.equal(0); 119 | bb.fcount.should.equal(0); 120 | bb.wcount.should.equal(1); 121 | condCount.should.equal(1); 122 | 123 | res = i.doAction(); 124 | res.should.equal(bt.RES_WAIT); 125 | bb.scount.should.equal(0); 126 | bb.fcount.should.equal(0); 127 | bb.wcount.should.equal(2); 128 | condCount.should.equal(1); 129 | 130 | res = i.doAction(); 131 | res.should.equal(bt.RES_SUCCESS); 132 | bb.scount.should.equal(1); 133 | bb.fcount.should.equal(0); 134 | bb.wcount.should.equal(2); 135 | condCount.should.equal(1); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/node/loopTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var bt = require('../../'); 3 | var Loop = bt.Loop; 4 | 5 | var SNode = function(bb) { 6 | this.blackboard = bb; 7 | }; 8 | 9 | SNode.prototype = { 10 | doAction: function() { 11 | this.blackboard.scount++; 12 | return bt.RES_SUCCESS; 13 | } 14 | }; 15 | 16 | var FNode = function(bb) { 17 | this.blackboard = bb; 18 | }; 19 | FNode.prototype = { 20 | doAction: function() { 21 | this.blackboard.fcount++; 22 | return bt.RES_FAIL; 23 | } 24 | }; 25 | 26 | var WNode = function(bb) { 27 | this.blackboard = bb; 28 | }; 29 | WNode.prototype = { 30 | doAction: function() { 31 | if(this.blackboard.wcount < 2) { 32 | this.blackboard.wcount++; 33 | return bt.RES_WAIT; 34 | } else { 35 | this.blackboard.scount++; 36 | return bt.RES_SUCCESS; 37 | } 38 | } 39 | }; 40 | 41 | describe('Loop Test', function() { 42 | it('should invoke the child in loop', function() { 43 | var bb = { 44 | scount: 0, 45 | fcount: 0, 46 | wcount: 0 47 | }; 48 | 49 | var loopConditionCount = 0; 50 | 51 | var lc = function(bb) { 52 | loopConditionCount++; 53 | return bb.scount <= 2; 54 | }; 55 | 56 | var loop = new Loop({blackboard: bb, child: new SNode(bb), loopCond: lc}); 57 | 58 | var res = loop.doAction(); 59 | res.should.equal(bt.RES_WAIT); 60 | bb.scount.should.equal(1); 61 | bb.fcount.should.equal(0); 62 | bb.wcount.should.equal(0); 63 | loopConditionCount.should.equal(1); 64 | 65 | res = loop.doAction(); 66 | res.should.equal(bt.RES_WAIT); 67 | bb.scount.should.equal(2); 68 | bb.fcount.should.equal(0); 69 | bb.wcount.should.equal(0); 70 | loopConditionCount.should.equal(2); 71 | 72 | res = loop.doAction(); 73 | res.should.equal(bt.RES_SUCCESS); 74 | bb.scount.should.equal(3); 75 | bb.fcount.should.equal(0); 76 | bb.wcount.should.equal(0); 77 | loopConditionCount.should.equal(3); 78 | }); 79 | 80 | it('should return fail and break loop if child return fail', function() { 81 | var bb = { 82 | scount: 0, 83 | fcount: 0, 84 | wcount: 0 85 | }; 86 | 87 | var loopConditionCount = 0; 88 | 89 | var lc = function(bb) { 90 | //should never enter here 91 | loopConditionCount++; 92 | return true; 93 | }; 94 | 95 | var loop = new Loop({blackboard: bb, child: new FNode(bb), loopCond: lc}); 96 | 97 | var res = loop.doAction(); 98 | res.should.equal(bt.RES_FAIL); 99 | bb.scount.should.equal(0); 100 | bb.fcount.should.equal(1); 101 | bb.wcount.should.equal(0); 102 | loopConditionCount.should.equal(0); 103 | 104 | res = loop.doAction(); 105 | res.should.equal(bt.RES_FAIL); 106 | bb.scount.should.equal(0); 107 | bb.fcount.should.equal(2); 108 | bb.wcount.should.equal(0); 109 | loopConditionCount.should.equal(0); 110 | }); 111 | 112 | it('should return wait when the child return wait', function() { 113 | var bb = { 114 | scount: 0, 115 | fcount: 0, 116 | wcount: 0 117 | }; 118 | 119 | var loopConditionCount = 0; 120 | 121 | var lc = function(bb) { 122 | loopConditionCount++; 123 | return false; 124 | }; 125 | 126 | var loop = new Loop({blackboard: bb, child: new WNode(bb), loopCond: lc}); 127 | var res = loop.doAction(); 128 | res.should.equal(bt.RES_WAIT); 129 | bb.scount.should.equal(0); 130 | bb.fcount.should.equal(0); 131 | bb.wcount.should.equal(1); 132 | loopConditionCount.should.equal(0); 133 | 134 | res = loop.doAction(); 135 | res.should.equal(bt.RES_WAIT); 136 | bb.scount.should.equal(0); 137 | bb.fcount.should.equal(0); 138 | bb.wcount.should.equal(2); 139 | loopConditionCount.should.equal(0); 140 | 141 | res = loop.doAction(); 142 | res.should.equal(bt.RES_SUCCESS); 143 | bb.scount.should.equal(1); 144 | bb.fcount.should.equal(0); 145 | bb.wcount.should.equal(2); 146 | loopConditionCount.should.equal(1); 147 | }); 148 | }); 149 | -------------------------------------------------------------------------------- /test/node/parallelTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var bt = require('../../'); 3 | var Parallel = bt.Parallel; 4 | 5 | var SNode = function(bb) { 6 | this.blackboard = bb; 7 | }; 8 | SNode.prototype = { 9 | doAction: function() { 10 | this.blackboard.scount++; 11 | return bt.RES_SUCCESS; 12 | } 13 | }; 14 | 15 | var FNode = function(bb) { 16 | this.blackboard = bb; 17 | }; 18 | FNode.prototype = { 19 | doAction: function() { 20 | this.blackboard.fcount++; 21 | return bt.RES_FAIL; 22 | } 23 | }; 24 | 25 | var WNode = function(bb) { 26 | this.blackboard = bb; 27 | }; 28 | WNode.prototype = { 29 | doAction: function() { 30 | if(this.blackboard.wcount < 2) { 31 | this.blackboard.wcount++; 32 | return bt.RES_WAIT; 33 | } else { 34 | this.blackboard.scount++; 35 | return bt.RES_SUCCESS; 36 | } 37 | } 38 | }; 39 | 40 | describe('Parallel Test', function() { 41 | it('should invoke the children in parallel', function() { 42 | var bb = { 43 | scount: 0, 44 | fcount: 0, 45 | wcount: 0 46 | }; 47 | var pl = new Parallel({blackboard: bb}); 48 | pl.addChild(new SNode(bb)); 49 | pl.addChild(new SNode(bb)); 50 | pl.addChild(new SNode(bb)); 51 | 52 | var res = pl.doAction(); 53 | res.should.equal(bt.RES_SUCCESS); 54 | bb.scount.should.equal(3); 55 | bb.fcount.should.equal(0); 56 | bb.wcount.should.equal(0); 57 | 58 | res = pl.doAction(); 59 | res.should.equal(bt.RES_SUCCESS); 60 | bb.scount.should.equal(6); 61 | bb.fcount.should.equal(0); 62 | bb.wcount.should.equal(0); 63 | }); 64 | 65 | it('should fail if any child fail in fail on one policy', function() { 66 | var bb = { 67 | scount: 0, 68 | fcount: 0, 69 | wcount: 0 70 | }; 71 | var pl = new Parallel({blackboard: bb, policy: Parallel.POLICY_FAIL_ON_ONE}); 72 | pl.addChild(new SNode(bb)); 73 | pl.addChild(new FNode(bb)); 74 | pl.addChild(new SNode(bb)); 75 | 76 | var res = pl.doAction(); 77 | res.should.equal(bt.RES_FAIL); 78 | bb.scount.should.equal(2); 79 | bb.fcount.should.equal(1); 80 | bb.wcount.should.equal(0); 81 | 82 | res = pl.doAction(); 83 | res.should.equal(bt.RES_FAIL); 84 | bb.scount.should.equal(4); 85 | bb.fcount.should.equal(2); 86 | bb.wcount.should.equal(0); 87 | }); 88 | 89 | it('should fail if and only if all children fail in fail on all policy', function() { 90 | var bb = { 91 | scount: 0, 92 | fcount: 0, 93 | wcount: 0 94 | }; 95 | var pl = new Parallel({blackboard: bb, policy: Parallel.POLICY_FAIL_ON_ALL}); 96 | pl.addChild(new FNode(bb)); 97 | pl.addChild(new FNode(bb)); 98 | pl.addChild(new FNode(bb)); 99 | 100 | var res = pl.doAction(); 101 | res.should.equal(bt.RES_FAIL); 102 | bb.scount.should.equal(0); 103 | bb.fcount.should.equal(3); 104 | bb.wcount.should.equal(0); 105 | 106 | bb.fcount = 0; 107 | pl.addChild(new SNode(bb)); 108 | res = pl.doAction(); 109 | res.should.equal(bt.RES_SUCCESS); 110 | bb.scount.should.equal(1); 111 | bb.fcount.should.equal(3); 112 | bb.wcount.should.equal(0); 113 | }); 114 | 115 | it('should wait if any child wait and reenter the wating child directly on next tick', function() { 116 | var bb = { 117 | scount: 0, 118 | fcount: 0, 119 | wcount: 0 120 | }; 121 | var pl = new Parallel({blackboard: bb}); 122 | pl.addChild(new SNode(bb)); 123 | pl.addChild(new WNode(bb)); 124 | pl.addChild(new SNode(bb)); 125 | 126 | var res = pl.doAction(); 127 | res.should.equal(bt.RES_WAIT); 128 | bb.scount.should.equal(2); 129 | bb.fcount.should.equal(0); 130 | bb.wcount.should.equal(1); 131 | 132 | res = pl.doAction(); 133 | res.should.equal(bt.RES_WAIT); 134 | bb.scount.should.equal(2); 135 | bb.fcount.should.equal(0); 136 | bb.wcount.should.equal(2); 137 | 138 | res = pl.doAction(); 139 | res.should.equal(bt.RES_SUCCESS); 140 | bb.scount.should.equal(3); 141 | bb.fcount.should.equal(0); 142 | bb.wcount.should.equal(2); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /test/node/selectTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var bt = require('../../'); 3 | var Select = bt.Select; 4 | 5 | var SNode = function(bb) { 6 | this.blackboard = bb; 7 | }; 8 | 9 | SNode.prototype = { 10 | doAction: function() { 11 | this.blackboard.scount++; 12 | return bt.RES_SUCCESS; 13 | } 14 | }; 15 | 16 | var FNode = function(bb) { 17 | this.blackboard = bb; 18 | }; 19 | FNode.prototype = { 20 | doAction: function() { 21 | this.blackboard.fcount++; 22 | return bt.RES_FAIL; 23 | } 24 | }; 25 | 26 | var WNode = function(bb) { 27 | this.blackboard = bb; 28 | }; 29 | WNode.prototype = { 30 | doAction: function() { 31 | if(this.blackboard.wcount < 2) { 32 | this.blackboard.wcount++; 33 | return bt.RES_WAIT; 34 | } else { 35 | this.blackboard.scount++; 36 | return bt.RES_SUCCESS; 37 | } 38 | } 39 | }; 40 | 41 | describe('Select Test', function() { 42 | it('should invoke one child only if success', function() { 43 | var bb = { 44 | scount: 0, 45 | fcount: 0, 46 | wcount: 0 47 | }; 48 | var sl = new Select({blackboard: bb}); 49 | sl.addChild(new SNode(bb)); 50 | sl.addChild(new SNode(bb)); 51 | sl.addChild(new SNode(bb)); 52 | 53 | var res = sl.doAction(); 54 | res.should.equal(bt.RES_SUCCESS); 55 | bb.scount.should.equal(1); 56 | bb.fcount.should.equal(0); 57 | bb.wcount.should.equal(0); 58 | 59 | res = sl.doAction(); 60 | res.should.equal(bt.RES_SUCCESS); 61 | bb.scount.should.equal(2); 62 | bb.fcount.should.equal(0); 63 | bb.wcount.should.equal(0); 64 | }); 65 | 66 | it('should fail if all child fail', function() { 67 | var bb = { 68 | scount: 0, 69 | fcount: 0, 70 | wcount: 0 71 | }; 72 | var sl = new Select({blackboard: bb}); 73 | sl.addChild(new FNode(bb)); 74 | sl.addChild(new FNode(bb)); 75 | sl.addChild(new FNode(bb)); 76 | 77 | var res = sl.doAction(); 78 | res.should.equal(bt.RES_FAIL); 79 | bb.scount.should.equal(0); 80 | bb.fcount.should.equal(3); 81 | bb.wcount.should.equal(0); 82 | 83 | res = sl.doAction(); 84 | res.should.equal(bt.RES_FAIL); 85 | bb.scount.should.equal(0); 86 | bb.fcount.should.equal(6); 87 | bb.wcount.should.equal(0); 88 | }); 89 | 90 | it('should success if and child success', function() { 91 | var bb = { 92 | scount: 0, 93 | fcount: 0, 94 | wcount: 0 95 | }; 96 | var sl = new Select({blackboard: bb}); 97 | sl.addChild(new FNode(bb)); 98 | sl.addChild(new SNode(bb)); 99 | sl.addChild(new FNode(bb)); 100 | 101 | var res = sl.doAction(); 102 | res.should.equal(bt.RES_SUCCESS); 103 | bb.scount.should.equal(1); 104 | bb.fcount.should.equal(1); 105 | bb.wcount.should.equal(0); 106 | 107 | res = sl.doAction(); 108 | res.should.equal(bt.RES_SUCCESS); 109 | bb.scount.should.equal(2); 110 | bb.fcount.should.equal(2); 111 | bb.wcount.should.equal(0); 112 | }); 113 | 114 | it('should wait if any child wait and reenter the wating child directly on next tick', function() { 115 | var bb = { 116 | scount: 0, 117 | fcount: 0, 118 | wcount: 0 119 | }; 120 | var sl = new Select({blackboard: bb}); 121 | sl.addChild(new FNode(bb)); 122 | sl.addChild(new WNode(bb)); 123 | sl.addChild(new SNode(bb)); 124 | 125 | var res = sl.doAction(); 126 | res.should.equal(bt.RES_WAIT); 127 | bb.scount.should.equal(0); 128 | bb.fcount.should.equal(1); 129 | bb.wcount.should.equal(1); 130 | 131 | res = sl.doAction(); 132 | res.should.equal(bt.RES_WAIT); 133 | bb.scount.should.equal(0); 134 | bb.fcount.should.equal(1); 135 | bb.wcount.should.equal(2); 136 | 137 | res = sl.doAction(); 138 | res.should.equal(bt.RES_SUCCESS); 139 | bb.scount.should.equal(1); 140 | bb.fcount.should.equal(1); 141 | bb.wcount.should.equal(2); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/node/sequenceTest.js: -------------------------------------------------------------------------------- 1 | var should = require('should'); 2 | var bt = require('../../'); 3 | var Sequence = bt.Sequence; 4 | 5 | var SNode = function(bb) { 6 | this.blackboard = bb; 7 | }; 8 | SNode.prototype = { 9 | doAction: function() { 10 | this.blackboard.scount++; 11 | return bt.RES_SUCCESS; 12 | } 13 | }; 14 | 15 | var FNode = function(bb) { 16 | this.blackboard = bb; 17 | }; 18 | FNode.prototype = { 19 | doAction: function() { 20 | this.blackboard.fcount++; 21 | return bt.RES_FAIL; 22 | } 23 | }; 24 | 25 | var WNode = function(bb) { 26 | this.blackboard = bb; 27 | }; 28 | WNode.prototype = { 29 | doAction: function() { 30 | if(this.blackboard.wcount < 2) { 31 | this.blackboard.wcount++; 32 | return bt.RES_WAIT; 33 | } else { 34 | this.blackboard.scount++; 35 | return bt.RES_SUCCESS; 36 | } 37 | } 38 | }; 39 | 40 | describe('Sequence Test', function() { 41 | it('should invoke the children one by one', function() { 42 | var bb = { 43 | scount: 0, 44 | fcount: 0, 45 | wcount: 0 46 | }; 47 | var sq = new Sequence({blackboard: bb}); 48 | sq.addChild(new SNode(bb)); 49 | sq.addChild(new SNode(bb)); 50 | sq.addChild(new SNode(bb)); 51 | 52 | var res = sq.doAction(); 53 | res.should.equal(bt.RES_SUCCESS); 54 | bb.scount.should.equal(3); 55 | bb.fcount.should.equal(0); 56 | bb.wcount.should.equal(0); 57 | 58 | res = sq.doAction(); 59 | res.should.equal(bt.RES_SUCCESS); 60 | bb.scount.should.equal(6); 61 | bb.fcount.should.equal(0); 62 | bb.wcount.should.equal(0); 63 | }); 64 | 65 | it('should fail if any child fail', function() { 66 | var bb = { 67 | scount: 0, 68 | fcount: 0, 69 | wcount: 0 70 | }; 71 | var sq = new Sequence({blackboard: bb}); 72 | sq.addChild(new SNode(bb)); 73 | sq.addChild(new FNode(bb)); 74 | sq.addChild(new SNode(bb)); 75 | 76 | var res = sq.doAction(); 77 | res.should.equal(bt.RES_FAIL); 78 | bb.scount.should.equal(1); 79 | bb.fcount.should.equal(1); 80 | bb.wcount.should.equal(0); 81 | 82 | res = sq.doAction(); 83 | res.should.equal(bt.RES_FAIL); 84 | bb.scount.should.equal(2); 85 | bb.fcount.should.equal(2); 86 | bb.wcount.should.equal(0); 87 | 88 | }); 89 | 90 | it('should wait if any child wait and reenter the waiting child directly on next tick', function() { 91 | var bb = { 92 | scount: 0, 93 | fcount: 0, 94 | wcount: 0 95 | }; 96 | var sq = new Sequence({blackboard: bb}); 97 | sq.addChild(new SNode(bb)); 98 | sq.addChild(new WNode(bb)); 99 | sq.addChild(new SNode(bb)); 100 | 101 | var res = sq.doAction(); 102 | res.should.equal(bt.RES_WAIT); 103 | bb.scount.should.equal(1); 104 | bb.fcount.should.equal(0); 105 | bb.wcount.should.equal(1); 106 | 107 | res = sq.doAction(); 108 | res.should.equal(bt.RES_WAIT); 109 | bb.scount.should.equal(1); 110 | bb.fcount.should.equal(0); 111 | bb.wcount.should.equal(2); 112 | 113 | res = sq.doAction(); 114 | res.should.equal(bt.RES_SUCCESS); 115 | bb.scount.should.equal(3); 116 | bb.fcount.should.equal(0); 117 | bb.wcount.should.equal(2); 118 | }); 119 | }); 120 | --------------------------------------------------------------------------------