forked from vincent/machinejs
-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
283 lines (242 loc) · 10.1 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/*
Machine.js
by mary rose cook
http://github.com/maryrosecook/machinejs
Make behaviour trees in JavaScript.
See index.html for an example.
Uses Base.js by Dean Edwards. Thanks, Dean.
UMD by rfox90
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
} else if (typeof exports === "object") {
module.exports = factory();
} else {
root.Machine = factory();
}
}(this, function () {
// this is where I defined my module implementation
var Base=function(){if(arguments.length)this==window?Base.prototype.extend.call(arguments[0],arguments.callee.prototype):this.extend(arguments[0])};Base.version="1.0.2"; Base.prototype={extend:function(b,d){var g=Base.prototype.extend;if(arguments.length==2){var e=this[b],h=this.constructor?this.constructor.prototype:null;if(e instanceof Function&&d instanceof Function&&e.valueOf()!=d.valueOf()&&/\bbase\b/.test(d)){var a=d;d=function(){var k=this.base,l=this;this.base=e;this.baseClass=h;var m=a.apply(this,arguments);this.base=k;this.baseClass=l;return m};d.valueOf=function(){return a};d.toString=function(){return String(a)}}return this[b]=d}else if(b){var i={toSource:null}, c=["toString","valueOf"];if(Base._prototyping)c[2]="constructor";for(var j=0;f=c[j];j++)b[f]!=i[f]&&g.call(this,f,b[f]);for(var f in b)i[f]||g.call(this,f,b[f])}return this},base:function(){}}; Base.extend=function(b,d){var g=Base.prototype.extend;b||(b={});Base._prototyping=true;var e=new this;g.call(e,b);var h=e.constructor;e.constructor=this;delete Base._prototyping;var a=function(){Base._prototyping||h.apply(this,arguments);this.constructor=a};a.prototype=e;a.extend=this.extend;a.implement=this.implement;a.create=this.create;a.getClassName=this.getClassName;a.toString=function(){return String(h)};a.isInstance=function(c){return typeof c!="undefined"&&c!==null&&c.constructor&&c.constructor.__ancestors&& c.constructor.__ancestors[a.getClassName()]};g.call(a,d);b=h?a:e;b.init instanceof Function&&b.init();if(!a.__ancestors){a.__ancestors={};a.__ancestors[a.getClassName()]=true;var i=function(c){if(c.prototype&&c.prototype.constructor&&c.prototype.constructor.getClassName){a.__ancestors[c.prototype.constructor.getClassName()]=true;i(c.prototype.constructor)}};i(a)}if(a.getClassName)b.className=a.getClassName();return b};Base.implement=function(b){if(b instanceof Function)b=b.prototype;this.prototype.extend(b)}; Base.create=function(){};Base.getClassName=function(){return"Base"};Base.isInstance=function(){};
/*
The tree generator. Instantiate and then call generateTree(),
passing the JSON definition of the tree and the object the tree controls.
*/
var Machine = Base.extend({
constructor: function() { },
// makes behaviour tree from passed json and returns the root node
generateTree: function(treeJson, actor, states) {
states = states || actor;
return this.read(treeJson, null, actor, states);
},
// reads in all nodes in passed json, constructing a tree of nodes as it goes
read: function(subTreeJson, parent, actor, states) {
var node = null;
if (subTreeJson.pointer == true)
node = new Pointer(subTreeJson.identifier,
subTreeJson.test,
subTreeJson.strategy,
parent,
actor,
states);
else
node = new State(subTreeJson.identifier,
subTreeJson.test,
subTreeJson.strategy,
parent,
actor,
states);
node.report = subTreeJson.report;
if(subTreeJson.children !== undefined)
for (var i = 0; i < subTreeJson.children.length; i++)
node.children[node.children.length] = this.read(subTreeJson.children[i],
node, actor, states);
return node;
}
}, {
getClassName: function() {
return "Machine";
}
});
/*
The object for nodes in the tree.
*/
var Node = Base.extend({
identifier: null,
test: null,
strategy: null,
parent: null,
children: null,
actor: null,
states: null,
report: null,
constructor: function(identifier, test, strategy, parent, actor, states) {
this.identifier = identifier;
this.test = test;
this.strategy = strategy;
this.parent = parent;
this.actor = actor;
this.states = states;
this.children = [];
},
// A tick of the clock. Returns the next state.
tick: function() {
if (this.isAction()) // run an actual action
this.run();
var potentialNextState = this.nextState();
var actualNextState = null;
if (potentialNextState !== null)
actualNextState = potentialNextState.transition();
else if (this.can()) // no child state, try and stay in this one
actualNextState = this;
else // can't stay in this one, so back up the tree
actualNextState = this.nearestRunnableAncestor().transition();
return actualNextState;
},
// gets next state that would be moved to from current state
nextState: function() {
var strategy = this.strategy;
if (strategy === undefined) {
var ancestor = this.nearestAncestorWithStrategy();
if (ancestor !== null)
strategy = ancestor.strategy;
}
if (strategy !== null)
return this[strategy].call(this);
else
return null;
},
isTransition: function() {
return this.children.length > 0 || this instanceof Pointer;
},
isAction: function() {
return !this.isTransition();
},
// returns true if actor allowed to enter this state
can: function() {
var functionName = this.test; // can specify custom test function name
if (functionName === undefined) // no override so go with default function name
functionName = "can" + this.identifier[0].toUpperCase() + this.identifier.substring(1, this.identifier.length);
if (this.states[functionName] !== undefined)
return this.states[functionName].call(this.actor);
else // no canX() function defined - assume can
return true;
},
// switches state to direct child of root state with passed identifier
// use very sparingly - really only for important events that
// require machine to temporarily relinquish control over actor
// e.g. a soldier who is mostly autonomous, but occasionally receives orders
warp: function(identifier) {
var rootNodeChildren = this.getRootNode().children;
for(var i = 0; i < rootNodeChildren.length; i++)
if(rootNodeChildren[i].identifier == identifier)
return rootNodeChildren[i];
return this; // couldn't find node - stay in current state
},
// returns first child that can run
prioritised: function() {
return this.nextRunnable(this.children);
},
// gets next runnable node in passed list
nextRunnable: function(nodes) {
for (var i = 0; i < nodes.length; i++)
if (nodes[i].can())
return nodes[i];
return null;
},
// runs all runnable children in order, then kicks up to children's closest runnable ancestor
sequential: function() {
var nextState = null;
if (this.isAction()) // want to get next runnable child or go back up to grandparent
{
var foundThis = false;
for (var i = 0; i < this.parent.children.length; i++) {
var sibling = this.parent.children[i];
if (this.identifier == sibling.identifier)
foundThis = true;
else if (foundThis && sibling.can())
return sibling;
}
}
else // at a sequential parent so try to run first runnable child
{
var firstRunnableChild = this.nextRunnable(this.children);
if (firstRunnableChild !== null)
return firstRunnableChild;
}
return this.nearestRunnableAncestor(); // no more runnable children in the sequence so return first runnable ancestor
},
// returns first namesake forebear encountered when going directly up tree
nearestAncestor: function(test) {
if (this.parent === null)
return null;
else if (test.call(this.parent) === true)
return this.parent;
else
return this.parent.nearestAncestor(test);
},
// returns root node of whole tree
getRootNode: function() {
if(this.parent === null)
return this;
else
return this.parent.getRootNode();
},
nearestAncestorWithStrategy: function() {
return this.nearestAncestor(function() {
return this.strategy !== undefined && this.strategy !== null;
});
},
// returns nearest ancestor that can run
nearestRunnableAncestor: function() {
return this.nearestAncestor(function() {
return this.can();
});
},
nearestNamesakeAncestor: function(identifier) {
return this.nearestAncestor(function() {
return this.identifier == identifier;
});
}
}, {
getClassName: function() {
return "Node";
}
});
/*
A normal state in the tree.
*/
var State = Node.extend({
transition: function() {
return this;
},
// run the behaviour associated with this state
run: function() {
this.states[this.identifier].call(this.actor); // run the action
}
}, {
getClassName: function() {
return "State";
}
});
/*
A pointer state in the tree. Directs the actor to a synonymous state
further up the tree. Which synonymous state the actor transitions to
is dependent on the pointer's strategy.
*/
var Pointer = Node.extend({
// transition out of this state using the state's strategy
transition: function() {
return this[this.strategy].call(this);
},
// a strategy that moves to the first synonymous ancestor
hereditory: function() {
return this.nearestNamesakeAncestor(this.identifier);
}
}, {
getClassName: function() {
return "Pointer";
}
});
return Machine;
}));