├── examples └── boid │ ├── server.bat │ ├── js │ ├── main.js │ ├── Scene.js │ ├── Agent.js │ └── states.js │ ├── game.html │ ├── index.html │ └── libs │ └── statejs-0.1.0dev.js ├── docs └── statejs-0.1.0.zip ├── bower.json ├── .gitignore ├── package.json ├── LICENSE ├── README.md ├── tests ├── tests │ ├── Blackboard.js │ ├── FSM.js │ ├── Utility.js │ └── Subsumption.js ├── tests.html ├── mocha.css └── sinon-chai.js ├── libs ├── statejs-0.1.0.min.js └── statejs-0.1.0.js └── src ├── State.js ├── statejs.js ├── FSM.js ├── Utility.js ├── Subsumption.js └── Blackboard.js /examples/boid/server.bat: -------------------------------------------------------------------------------- 1 | python -m SimpleHTTPServer 8000 -------------------------------------------------------------------------------- /docs/statejs-0.1.0.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renatopp/statejs/HEAD/docs/statejs-0.1.0.zip -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statejs", 3 | "version": "0.1.0", 4 | "homepage": "https://github.com/renatopp/statejs", 5 | "authors": [ 6 | {"name":"Renato Pereira", "email":"renato.ppontes@gmail.com", "homepage":"http://guineashots.com"}, 7 | ], 8 | "description": "Behavior tree library", 9 | "license": "MIT", 10 | "keywords": [ 11 | "state", 12 | "state machine", 13 | "finitie state machine", 14 | "subsumption", 15 | "utility", 16 | "ai", 17 | "artificial intelligence", 18 | "statejs", 19 | ], 20 | "main": [ "libs/statejs-0.1.0.min.js" ], 21 | "ignore": [ 22 | "node_modules", 23 | "bower_components", 24 | "build", 25 | "docs", 26 | "src", 27 | "tests", 28 | "tools", 29 | "examples" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Commenting this out is preferred by some people, see 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 25 | node_modules 26 | 27 | # Users Environment Variables 28 | .lock-wscript 29 | 30 | # IDE configurations 31 | .idea 32 | 33 | # -------------- 34 | docs/statejs 35 | -------------------------------------------------------------------------------- /examples/boid/js/main.js: -------------------------------------------------------------------------------- 1 | var game = { 2 | canvas : null, 3 | stage : null, 4 | scene : null, 5 | machine : null, 6 | fdelta : 0 7 | }; 8 | var settings = { 9 | SHEEP_ROTATION_SPEED: 1, 10 | SHEEP_MOVE_SPEED: 1, 11 | SHEEP_OBEY_DISTANCE: 100, 12 | SHEEP_NEIGHBOR_DISTANCE: 100, 13 | SHEEP_MIN_SAFE_DISTANCE: 40, 14 | SHEEP_MAX_VELOCITY: 100, 15 | SHEEP_VELOCITY_DECAY: 0.999, 16 | SHEEP_FORGET_TIME: 1000, 17 | } 18 | 19 | 20 | function main() { 21 | game.canvas = document.getElementById('game'); 22 | game.stage = new createjs.Stage(game.canvas); 23 | game.machine = new statejs.FSM() 24 | .add('idle', new IdleState()) 25 | .add('obey', new ObeyState()) 26 | .add('stopping', new StoppingState()) 27 | game.scene = new MainScene(); 28 | 29 | createjs.Ticker.framerate = 60; 30 | } 31 | main(); -------------------------------------------------------------------------------- /examples/boid/game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 24 | 25 | 26 | 27 | Javascript not working. 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "statejs", 3 | "version": "0.1.0", 4 | "description": "A behavior tree library for JavaScript", 5 | "main": "libs/statejs-0.1.0.min.js", 6 | "directories": { 7 | "doc" : "docs", 8 | "example" : "examples", 9 | "test" : "tests" 10 | }, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git://github.com/renatopp/statejs.git" 17 | }, 18 | "keywords": [ 19 | "state", 20 | "state machine", 21 | "finitie state machine", 22 | "subsumption", 23 | "utility", 24 | "ai", 25 | "artificial intelligence", 26 | "statejs", 27 | ], 28 | "author": "Renato Pereira (http://guineashots.com/)", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/renatopp/statejs/issues" 32 | }, 33 | "homepage": "http://statejs.guineashots.com" 34 | } 35 | -------------------------------------------------------------------------------- /examples/boid/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 25 | 26 | 27 | 28 | Javascript not working. 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Renato de Pontes Pereira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/boid/js/Scene.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | function MainScene() { 4 | this.Container_constructor(); 5 | 6 | this.agents = []; 7 | for (var i=0; i<10; i++) { 8 | var agent = new Agent(this); 9 | this.agents.push(agent); 10 | this.addChild(agent); 11 | } 12 | 13 | game.stage.addChild(this); 14 | createjs.Ticker.on('tick', this.update, this); 15 | } 16 | var p = createjs.extend(MainScene, createjs.Container); 17 | 18 | p.updateMovement = function(e) { 19 | var fdelta = e.delta/1000.; 20 | 21 | for (var i=0; i 5) { 26 | var x = agent.x; 27 | var y = agent.y; 28 | agent.x += settings.SHEEP_MOVE_SPEED*agent.dx*fdelta; 29 | agent.y += settings.SHEEP_MOVE_SPEED*agent.dy*fdelta; 30 | } 31 | } 32 | } 33 | 34 | p.update = function(e) { 35 | game.fdelta = e.delta/1000.; 36 | 37 | for (var i=0; i 30 | 31 | It is very recommended that you take a look into the API where you will find 32 | examples and descriptions of features in the library: 33 | 34 | - http://docs.guineashots.com/statejs 35 | 36 | 37 | ------------------ 38 | Within the Library 39 | ------------------ 40 | 41 | - Finite State Machine 42 | - Utility based 43 | - SubSumption architecture 44 | 45 | 46 | ----------------- 47 | Looking for More? 48 | ----------------- 49 | 50 | Take a look into Behavior3JS: 51 | 52 | - http://behavior3js.guineashots.com 53 | -------------------------------------------------------------------------------- /tests/tests/Blackboard.js: -------------------------------------------------------------------------------- 1 | /* BLACKBOARD ============================================================== */ 2 | suite('Blackboard', function() { 3 | test('Basic Read & Write operations', function() { 4 | var blackboard = new statejs.Blackboard(); 5 | 6 | blackboard.set('var1', 'this is some value'); 7 | blackboard.set('var2', 999888); 8 | 9 | assert.equal(blackboard.get('var1'), 'this is some value'); 10 | assert.equal(blackboard.get('var2'), 999888); 11 | assert.equal(blackboard.get('var3'), undefined); 12 | }); 13 | 14 | test('Tree memory initialization', function() { 15 | var blackboard = new statejs.Blackboard(); 16 | 17 | blackboard.set('var1', 'value', 'tree1'); 18 | 19 | assert.isNotUndefined(blackboard.get('var1', 'tree1')); 20 | assert.isNotUndefined(blackboard.get('stateMemory', 'tree1')); 21 | }); 22 | 23 | test('Read & Write operations within Tree Scope', function() { 24 | var blackboard = new statejs.Blackboard(); 25 | 26 | blackboard.set('var1', 'this is some value', 'tree 1'); 27 | blackboard.set('var2', 999888, 'tree 2'); 28 | 29 | assert.equal(blackboard.get('var1', 'tree 1'), 'this is some value'); 30 | assert.equal(blackboard.get('var2', 'tree 2'), 999888); 31 | 32 | assert.equal(blackboard.get('var1', 'tree 2'), undefined); 33 | assert.equal(blackboard.get('var2', 'tree 1'), undefined); 34 | }); 35 | 36 | test('Read & Write operations within Tree and Node Scopes', function() { 37 | var blackboard = new statejs.Blackboard(); 38 | 39 | blackboard.set('var1', 'value 1', 'tree 1'); 40 | blackboard.set('var2', 'value 2', 'tree 1', 'state 1'); 41 | blackboard.set('var3', 'value 3', 'tree 1', 'state 2'); 42 | blackboard.set('var4', 999888, 'tree 2'); 43 | 44 | assert.equal(blackboard.get('var2', 'tree 1', 'state 1'), 'value 2'); 45 | assert.equal(blackboard.get('var3', 'tree 1', 'state 2'), 'value 3'); 46 | assert.equal(blackboard.get('var2', 'tree 1', 'state 2'), undefined); 47 | assert.equal(blackboard.get('var3', 'tree 1', 'state 1'), undefined); 48 | assert.equal(blackboard.get('var2', 'tree 1'), undefined); 49 | assert.equal(blackboard.get('var1', 'tree 1', 'state 1'), undefined); 50 | 51 | assert.equal(blackboard.get('var2', 'tree 2', 'state 1'), undefined); 52 | }); 53 | }); 54 | /* ========================================================================= */ 55 | -------------------------------------------------------------------------------- /tests/tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mocha Tests 6 | 7 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /tests/tests/FSM.js: -------------------------------------------------------------------------------- 1 | suite('Finite State Machine', function() { 2 | test('Add states', function() { 3 | var machine = new statejs.FSM(); 4 | 5 | var state_a = StateStub(); 6 | var state_b = StateStub(); 7 | machine.add('a', state_a); 8 | machine.add('b', state_b); 9 | 10 | assert.equal(state_a.machine, machine); 11 | assert.equal(state_b.machine, machine); 12 | }); 13 | 14 | test('Add states (Error)', function() { 15 | var machine = new statejs.FSM(); 16 | 17 | var state_a = StateStub(); 18 | var state_b = StateStub(); 19 | machine.add('a', state_a); 20 | 21 | assert.throw(function() {machine.add('a', state_b)}, Error); 22 | }); 23 | 24 | 25 | test('List states', function() { 26 | var machine = new statejs.FSM(); 27 | 28 | var state_a = StateStub(); 29 | var state_b = StateStub(); 30 | machine.add('a', state_a); 31 | machine.add('b', state_b); 32 | 33 | var states = machine.list(); 34 | assert.equal(states.length, 2); 35 | assert.notEqual(states.indexOf('a'), -1); 36 | assert.notEqual(states.indexOf('b'), -1); 37 | }); 38 | 39 | test('Get states', function() { 40 | var machine = new statejs.FSM(); 41 | 42 | var state_a = StateStub(); 43 | var state_b = StateStub(); 44 | machine.add('a', state_a); 45 | machine.add('b', state_b); 46 | 47 | assert.equal(machine.get('a'), state_a); 48 | assert.equal(machine.get('b'), state_b); 49 | }); 50 | 51 | test('Current state name', function() { 52 | var machine = new statejs.FSM(); 53 | machine.id = '1'; 54 | 55 | var memory = BlackboardStub(); 56 | assert.notEqual(machine.name(memory), 'a'); 57 | memory.get.withArgs('name', '1').returns('a'); 58 | assert.equal(machine.name(memory), 'a'); 59 | }); 60 | 61 | test('State transition', function() { 62 | var machine = new statejs.FSM(); 63 | machine.id = '1'; 64 | 65 | var state_a = StateStub(); 66 | var state_b = StateStub(); 67 | machine.add('a', state_a); 68 | machine.add('b', state_b); 69 | 70 | var target = null; 71 | var memory = BlackboardStub(); 72 | 73 | // first transition 74 | machine.to('a', target, memory); 75 | assert.isTrue(memory.set.withArgs('name', 'a', '1').calledOnce); 76 | assert.isTrue(state_a.enter.withArgs(target, memory).calledOnce); 77 | 78 | // second transition 79 | memory.get.withArgs('name', '1').returns('a'); 80 | 81 | machine.to('b', target, memory); 82 | assert.isTrue(memory.set.withArgs('name', 'b', '1').calledOnce); 83 | assert.isTrue(state_a.exit.withArgs(target, memory).calledOnce); 84 | assert.isTrue(state_b.enter.withArgs(target, memory).calledOnce); 85 | }); 86 | 87 | test('Ticking state', function() { 88 | var machine = new statejs.FSM(); 89 | machine.id = '1'; 90 | 91 | var state_a = StateStub(); 92 | var state_b = StateStub(); 93 | machine.add('a', state_a); 94 | machine.add('b', state_b); 95 | 96 | var target = null; 97 | var memory = BlackboardStub(); 98 | 99 | // ticking 100 | memory.get.withArgs('name', '1').returns('a'); 101 | machine.tick(target, memory); 102 | 103 | assert.isTrue(state_a.tick.withArgs(target, memory).calledOnce); 104 | assert.isFalse(state_b.tick.withArgs(target, memory).calledOnce); 105 | }); 106 | }); -------------------------------------------------------------------------------- /libs/statejs-0.1.0.min.js: -------------------------------------------------------------------------------- 1 | /*! StateJS 2015-04-02 */ 2 | this.statejs=this.statejs||{},function(){"use strict";statejs.createUUID=function(){for(var a=[],b="0123456789abcdef",c=0;36>c;c++)a[c]=b.substr(Math.floor(16*Math.random()),1);a[14]="4",a[19]=b.substr(3&a[19]|8,1),a[8]=a[13]=a[18]=a[23]="-";var d=a.join("");return d},statejs.Class=function(a){var b=function(a){this.initialize(a)};return a&&(b.prototype=Object.create(a.prototype),b.prototype.constructor=b),b.prototype.initialize||(b.prototype.initialize=function(){}),b}}(),this.statejs=this.statejs||{},function(){"use strict";var a=statejs.Class(),b=a.prototype;b.initialize=function(){this._baseMemory={},this._machineMemory={}},b._getMachineMemory=function(a){return this._machineMemory[a]||(this._machineMemory[a]={stateMemory:{}}),this._machineMemory[a]},b._getStateMemory=function(a,b){var c=a.stateMemory;return c[b]||(c[b]={}),c[b]},b._getMemory=function(a,b){var c=this._baseMemory;return a&&(c=this._getMachineMemory(a),b&&(c=this._getStateMemory(c,b))),c},b.set=function(a,b,c,d){var e=this._getMemory(c,d);e[a]=b},b.get=function(a,b,c){var d=this._getMemory(b,c);return d[a]},statejs.Blackboard=a}(),this.statejs=this.statejs||{},function(){"use strict";var a=statejs.Class(),b=a.prototype;this.id=null,this.machine=null,b.initialize=function(){this.id=statejs.createUUID(),this.machine=null},b.enter=function(){},b.potential=function(){console&&console.log&&console.log("Warning: potential not implemented.")},b.tick=function(){},b.exit=function(){}}(),this.statejs=this.statejs||{},function(){"use strict";var a=statejs.Class(),b=a.prototype;this.id=null,b.initialize=function(){this.id=statejs.createUUID(),this._states={}},b.add=function(a,b){if("undefined"!=typeof this._states[a])throw new Error('State "'+a+'" already on the FSM.');return this._states[a]=b,b.machine=this,this},b.get=function(a){return this._states[a]},b.list=function(){var a=[];for(var b in this._states)a.push(b);return a},b.name=function(a){return a.get("name",this.id)},b.to=function(a,b,c){if("undefined"==typeof this._states[a])throw new Error('State "'+a+'" does not exist.');var d=c.get("name",this.id),e=this.get(d);e&&e.exit(b,c);var f=this._states[a];return c.set("name",a,this.id),f.enter(b,c),this},b.tick=function(a,b){var c=b.get("name",this.id),d=this.get(c);d&&d.tick(a,b)},statejs.FSM=a}(),this.statejs=this.statejs||{},function(){"use strict";var a=statejs.Class(),b=a.prototype;this.id=null,b.initialize=function(){this.id=statejs.createUUID(),this._states=[]},b.add=function(a,b){for(var c=0;cd&&(d=g,e=c)}e&&(this._to(e.name,a,b),e.state.tick(a,b))},b._to=function(a,b,c){if(null===a)return void c.set("name",null,this.id);if(a!==this.name){var d=this.get(a);if("undefined"==typeof d)throw new Error('State "'+a+'" does not exist.');var e=c.get("name",this.id),f=this.get(e);return f&&f.exit(b,c),c.set("name",a,this.id),d.enter(b,c),this}},statejs.Utility=a}(); -------------------------------------------------------------------------------- /src/State.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State 3 | * 4 | * Copyright (c) 2015 Renato de Pontes Pereira. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | **/ 24 | 25 | /** 26 | * @module StateJS 27 | **/ 28 | 29 | // namespace: 30 | this.statejs = this.statejs || {}; 31 | 32 | (function() { 33 | "use strict"; 34 | 35 | /** 36 | * The State class represents generic states that works for all machines. 37 | * Some machines require the implementation of specific methods in this 38 | * class. Please consult the documentation of the machine you want to use and 39 | * the description of each method here. 40 | * 41 | * To extend the State class you can use the `statejs.Class` function: 42 | * 43 | * var MyState = statejs.Class(statejs.State); 44 | * 45 | * MyState.prototype.tick = function(target, memory) { 46 | * console.log('My Implementation') 47 | * } 48 | * 49 | * @class State 50 | **/ 51 | var State = statejs.Class(); 52 | var p = State.prototype; 53 | 54 | /** 55 | * State unique ID. 56 | * 57 | * @property id 58 | * @type {String} 59 | * @readonly 60 | **/ 61 | this.id = null; 62 | 63 | /** 64 | * The reference to the machine in which this state was added. 65 | * 66 | * @property machine 67 | * @type {Object} 68 | * @readonly 69 | **/ 70 | this.machine = null; 71 | 72 | /** 73 | * Initialization method. 74 | * 75 | * @method initialize 76 | * @constructor 77 | */ 78 | p.initialize = function() { 79 | this.id = statejs.createUUID(); 80 | this.machine = null; 81 | } 82 | 83 | /** 84 | * Enter method, override this to use. It is called when the machine assume 85 | * this state as the current one 86 | * 87 | * @method enter 88 | * @param {Object} target A target object, commonly an agent. 89 | * @param {Object} memory A blackboard object. 90 | */ 91 | p.enter = function(target, memory) {} 92 | 93 | /** 94 | * The potential method is used to some machines for different things. 95 | * Consult the machine documentation to know how to implement this. 96 | * 97 | * In general, this method is used by machines to verify if the state is able 98 | * to execute at a given moment or not. 99 | * 100 | * @method potential 101 | * @param {Object} target A target object, commonly an agent. 102 | * @param {Object} memory A blackboard object. 103 | */ 104 | p.potential = function(target, memory) { 105 | if (console && console.log) { 106 | console.log('Warning: potential not implemented.'); 107 | } 108 | } 109 | 110 | /** 111 | * Tick method is called every time a machine is asked to update. Depending 112 | * on the machine, the State tick may only be called if it is the current 113 | * executing state. Consult the machine documentation to known more. 114 | * 115 | * @method tick 116 | * @param {Object} target A target object, commonly an agent. 117 | * @param {Object} memory A blackboard object. 118 | */ 119 | p.tick = function(target, memory) {} 120 | 121 | /** 122 | * Exit method is called when the state is replaced by another on the 123 | * machine. 124 | * 125 | * @method exit 126 | * @param {Object} target A target object, commonly an agent. 127 | * @param {Object} memory A blackboard object. 128 | */ 129 | p.exit = function(target, memory) {} 130 | })(); -------------------------------------------------------------------------------- /tests/tests/Utility.js: -------------------------------------------------------------------------------- 1 | suite('Utility', function() { 2 | test('Add states', function() { 3 | var machine = new statejs.Utility(); 4 | 5 | var state_a = StateStub(); 6 | var state_b = StateStub(); 7 | machine.add('a', state_a); 8 | machine.add('b', state_b); 9 | 10 | assert.equal(state_a.machine, machine); 11 | assert.equal(state_b.machine, machine); 12 | }); 13 | 14 | test('Add states (Error)', function() { 15 | var machine = new statejs.Utility(); 16 | 17 | var state_a = StateStub(); 18 | var state_b = StateStub(); 19 | machine.add('a', state_a); 20 | 21 | assert.throw(function() {machine.add('a', state_b)}, Error); 22 | }); 23 | 24 | test('List states', function() { 25 | var machine = new statejs.Utility(); 26 | 27 | var state_a = StateStub(); 28 | var state_b = StateStub(); 29 | machine.add('a', state_a); 30 | machine.add('b', state_b); 31 | 32 | var states = machine.list(); 33 | assert.equal(states.length, 2); 34 | assert.notEqual(states.indexOf('a'), -1); 35 | assert.notEqual(states.indexOf('b'), -1); 36 | }); 37 | 38 | test('Get states', function() { 39 | var machine = new statejs.Utility(); 40 | 41 | var state_a = StateStub(); 42 | var state_b = StateStub(); 43 | machine.add('a', state_a); 44 | machine.add('b', state_b); 45 | 46 | assert.equal(machine.get('a'), state_a); 47 | assert.equal(machine.get('b'), state_b); 48 | }); 49 | 50 | test('Checking potential state', function() { 51 | var machine = new statejs.Utility(); 52 | machine.id = '1'; 53 | 54 | var state_a = StateStub(); 55 | var state_b = StateStub(); 56 | machine.add('a', state_a); 57 | machine.add('b', state_b); 58 | 59 | var target = null; 60 | var memory = BlackboardStub(); 61 | 62 | // ticking 63 | machine.tick(target, memory); 64 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 65 | assert.isTrue(state_b.potential.withArgs(target, memory).calledOnce); 66 | }); 67 | 68 | test('Current state name', function() { 69 | var machine = new statejs.Utility(); 70 | machine.id = '1'; 71 | 72 | var memory = BlackboardStub(); 73 | 74 | assert.notEqual(machine.name(memory), 'a'); 75 | memory.get.withArgs('name', '1').returns('a'); 76 | assert.equal(machine.name(memory), 'a'); 77 | }); 78 | 79 | test('State transition', function() { 80 | var machine = new statejs.Utility(); 81 | machine.id = '1'; 82 | 83 | var state_a = StateStub(); 84 | var state_b = StateStub(); 85 | machine.add('a', state_a); 86 | machine.add('b', state_b); 87 | 88 | var target = null; 89 | var memory = BlackboardStub(); 90 | 91 | // ticking 92 | state_a.potential.returns(15); 93 | state_b.potential.returns(10); 94 | machine.tick(target, memory); 95 | assert.isTrue(memory.set.withArgs('name', 'a', '1').calledOnce); 96 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 97 | assert.isTrue(state_a.enter.withArgs(target, memory).calledOnce); 98 | 99 | assert.isTrue(state_b.potential.withArgs(target, memory).calledOnce); 100 | assert.isFalse(state_b.enter.withArgs(target, memory).calledOnce); 101 | }); 102 | 103 | test('State transition (cont)', function() { 104 | var machine = new statejs.Utility(); 105 | machine.id = '1'; 106 | 107 | var state_a = StateStub(); 108 | var state_b = StateStub(); 109 | machine.add('a', state_a); 110 | machine.add('b', state_b); 111 | 112 | var target = null; 113 | var memory = BlackboardStub(); 114 | // ticking 115 | state_a.potential.returns(15); 116 | state_b.potential.returns(25); 117 | machine.tick(target, memory); 118 | assert.isTrue(memory.set.withArgs('name', 'b', '1').calledOnce); 119 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 120 | assert.isFalse(state_a.enter.withArgs(target, memory).calledOnce); 121 | 122 | assert.isTrue(state_b.potential.withArgs(target, memory).calledOnce); 123 | assert.isTrue(state_b.enter.withArgs(target, memory).calledOnce); 124 | }); 125 | 126 | test('Ticking current state', function() { 127 | var machine = new statejs.Utility(); 128 | machine.id = '1'; 129 | 130 | var state_a = StateStub(); 131 | var state_b = StateStub(); 132 | machine.add('a', state_a); 133 | machine.add('b', state_b); 134 | 135 | var target = null; 136 | var memory = BlackboardStub(); 137 | // ticking 138 | state_a.potential.returns(false); 139 | state_b.potential.returns(true); 140 | machine.tick(target, memory); 141 | 142 | assert.isFalse(state_a.tick.withArgs(target, memory).calledOnce); 143 | assert.isTrue(state_b.tick.withArgs(target, memory).calledOnce); 144 | }); 145 | }); -------------------------------------------------------------------------------- /src/statejs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * statejs 3 | * 4 | * Copyright (c) 2015 Renato de Pontes Pereira. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | this.statejs = this.statejs || {}; 25 | 26 | /** 27 | * StateJS 28 | * ======= 29 | * 30 | * * * * 31 | * 32 | * **StateJS** is a Javascript library that provides state-based models such 33 | * as Finite State Machines and the Subsumption architecture. It aims speed, 34 | * low memory consuption and multi-agent control. Check out some features 35 | * of StateJS: 36 | * 37 | * - StateJS provides **Finite State Machines**, **Subsumption Architecture** 38 | * and **Utility Functions**; 39 | * - **Extensible and Flexible**, create new Machines or your own version of 40 | * FSM or Utilities; 41 | * - **Optimized to control multiple agents**, you can use a single machine 42 | * instance to handle hundreds of agents; 43 | * - **Completely free**, StateJS is published under the MIT License, which 44 | * means that you can use it for your open source and commercial projects; 45 | * - **Lightweight**, only 5KB! 46 | * 47 | * Visit http://statejs.guineashots.com to know more! 48 | * 49 | * @module StateJS 50 | * @main StateJS 51 | */ 52 | (function() { 53 | "use strict"; 54 | 55 | /** 56 | * List of internal and helper functions in StateJS. 57 | * 58 | * @class Utils 59 | **/ 60 | 61 | /** 62 | * This function is used to create unique IDs for machines and states. 63 | * 64 | * (consult http://www.ietf.org/rfc/rfc4122.txt). 65 | * 66 | * @method createUUID 67 | * @return {String} A unique ID. 68 | */ 69 | statejs.createUUID = function() { 70 | var s = []; 71 | var hexDigits = "0123456789abcdef"; 72 | for (var i = 0; i < 36; i++) { 73 | s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); 74 | } 75 | // bits 12-15 of the time_hi_and_version field to 0010 76 | s[14] = "4"; 77 | 78 | // bits 6-7 of the clock_seq_hi_and_reserved to 01 79 | s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); 80 | 81 | s[8] = s[13] = s[18] = s[23] = "-"; 82 | 83 | var uuid = s.join(""); 84 | return uuid; 85 | } 86 | 87 | /** 88 | * Class is a meta-factory function to create classes in JavaScript. It is a 89 | * shortcut for the CreateJS syntax style. By default, the class created by 90 | * this function have an initialize function (the constructor). Optionally, you 91 | * can specify the inheritance by passing another class as parameter. 92 | * 93 | * By default, all classes created using this function, may receives only a 94 | * settings parameter as argument. This pattern is commonly used by jQuery and 95 | * its plugins. 96 | * 97 | * Usage 98 | * ----- 99 | * 100 | * // Creating a simple class 101 | * var BaseClass = statejs.Class(); 102 | * 103 | * // Using inheritance 104 | * var ChildClass = statejs.Class(BaseClass); 105 | * 106 | * // Defining the constructor 107 | * ChildClass.prototype.initialize = function(settings) { ... } 108 | * 109 | * @method Class 110 | * @param {Object} baseClass The super class. 111 | * @return {Object} A new class. 112 | */ 113 | statejs.Class = function(baseClass) { 114 | // create a new class 115 | var cls = function(params) { 116 | this.initialize(params); 117 | }; 118 | 119 | // if base class is provided, inherit 120 | if (baseClass) { 121 | cls.prototype = Object.create(baseClass.prototype); 122 | cls.prototype.constructor = cls; 123 | } 124 | 125 | // create initialize if does not exist on baseClass 126 | if(!cls.prototype.initialize) { 127 | cls.prototype.initialize = function() {}; 128 | } 129 | 130 | return cls; 131 | } 132 | 133 | })(); 134 | -------------------------------------------------------------------------------- /tests/mocha.css: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | padding: 0px 50px; 6 | } 7 | 8 | #mocha ul, #mocha li { 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | #mocha ul { 14 | list-style: none; 15 | } 16 | 17 | #mocha h1, #mocha h2 { 18 | margin: 0; 19 | } 20 | 21 | #mocha h1 { 22 | margin-top: 15px; 23 | font-size: 1em; 24 | font-weight: 200; 25 | } 26 | 27 | #mocha h1 a { 28 | text-decoration: none; 29 | color: inherit; 30 | } 31 | 32 | #mocha h1 a:hover { 33 | text-decoration: underline; 34 | } 35 | 36 | #mocha .suite .suite h1 { 37 | margin-top: 0; 38 | font-size: .8em; 39 | } 40 | 41 | .hidden { 42 | display: none; 43 | } 44 | 45 | #mocha h2 { 46 | font-size: 12px; 47 | font-weight: normal; 48 | cursor: pointer; 49 | } 50 | 51 | #mocha .suite { 52 | margin-left: 15px; 53 | } 54 | 55 | #mocha .test { 56 | margin-left: 15px; 57 | overflow: hidden; 58 | } 59 | 60 | #mocha .test.pending:hover h2::after { 61 | content: '(pending)'; 62 | font-family: arial; 63 | } 64 | 65 | #mocha .test.pass.medium .duration { 66 | background: #C09853; 67 | } 68 | 69 | #mocha .test.pass.slow .duration { 70 | background: #B94A48; 71 | } 72 | 73 | #mocha .test.pass::before { 74 | content: '✓'; 75 | font-size: 12px; 76 | display: block; 77 | float: left; 78 | margin-right: 5px; 79 | color: #00d6b2; 80 | } 81 | 82 | #mocha .test.pass .duration { 83 | font-size: 9px; 84 | margin-left: 5px; 85 | padding: 2px 5px; 86 | color: white; 87 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 88 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 89 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 90 | -webkit-border-radius: 5px; 91 | -moz-border-radius: 5px; 92 | -ms-border-radius: 5px; 93 | -o-border-radius: 5px; 94 | border-radius: 5px; 95 | } 96 | 97 | #mocha .test.pass.fast .duration { 98 | display: none; 99 | } 100 | 101 | #mocha .test.pending { 102 | color: #0b97c4; 103 | } 104 | 105 | #mocha .test.pending::before { 106 | content: '◦'; 107 | color: #0b97c4; 108 | } 109 | 110 | #mocha .test.fail { 111 | color: #c00; 112 | } 113 | 114 | #mocha .test.fail pre { 115 | color: black; 116 | } 117 | 118 | #mocha .test.fail::before { 119 | content: '✖'; 120 | font-size: 12px; 121 | display: block; 122 | float: left; 123 | margin-right: 5px; 124 | color: #c00; 125 | } 126 | 127 | #mocha .test pre.error { 128 | color: #c00; 129 | max-height: 300px; 130 | overflow: auto; 131 | } 132 | 133 | #mocha .test pre { 134 | display: block; 135 | float: left; 136 | clear: left; 137 | font: 12px/1.5 monaco, monospace; 138 | margin: 5px; 139 | padding: 15px; 140 | border: 1px solid #eee; 141 | border-bottom-color: #ddd; 142 | -webkit-border-radius: 3px; 143 | -webkit-box-shadow: 0 1px 3px #eee; 144 | -moz-border-radius: 3px; 145 | -moz-box-shadow: 0 1px 3px #eee; 146 | } 147 | 148 | #mocha .test h2 { 149 | position: relative; 150 | } 151 | 152 | #mocha .test a.replay { 153 | position: absolute; 154 | top: 3px; 155 | right: 0; 156 | text-decoration: none; 157 | vertical-align: middle; 158 | display: block; 159 | width: 15px; 160 | height: 15px; 161 | line-height: 15px; 162 | text-align: center; 163 | background: #eee; 164 | font-size: 15px; 165 | -moz-border-radius: 15px; 166 | border-radius: 15px; 167 | -webkit-transition: opacity 200ms; 168 | -moz-transition: opacity 200ms; 169 | transition: opacity 200ms; 170 | opacity: 0.3; 171 | color: #888; 172 | } 173 | 174 | #mocha .test:hover a.replay { 175 | opacity: 1; 176 | } 177 | 178 | #mocha-report.pass .test.fail { 179 | display: none; 180 | } 181 | 182 | #mocha-report.fail .test.pass { 183 | display: none; 184 | } 185 | 186 | #mocha-error { 187 | color: #c00; 188 | font-size: 1.5 em; 189 | font-weight: 100; 190 | letter-spacing: 1px; 191 | } 192 | 193 | #mocha-stats { 194 | position: fixed; 195 | top: 15px; 196 | left: 10px; 197 | background: #D2D2D2; 198 | border: 1px solid #999; 199 | text-align: center; 200 | padding: 5px 0px; 201 | width: 55px; 202 | font-size: 12px; 203 | margin: 0; 204 | color: #333; 205 | } 206 | 207 | #mocha-stats .progress { 208 | /*float: lef;*/ 209 | /*padding-top: 15;*/ 210 | } 211 | 212 | #mocha-stats em { 213 | color: #000; 214 | font-weight: bold; 215 | } 216 | 217 | #mocha-stats a { 218 | text-decoration: none; 219 | color: inherit; 220 | } 221 | 222 | #mocha-stats a:hover { 223 | border-bottom: 1px solid #333; 224 | } 225 | 226 | #mocha-stats li { 227 | display: inline-block; 228 | margin: 0 0px; 229 | list-style: none; 230 | padding: 5px; 231 | } 232 | #mocha-stats li:first-child { 233 | padding-top: 10px; 234 | } 235 | #mocha-stats li:last-child { 236 | padding-bottom: 10px; 237 | } 238 | 239 | code .comment { color: #ddd } 240 | code .init { color: #2F6FAD } 241 | code .string { color: #5890AD } 242 | code .keyword { color: #8A6343 } 243 | code .number { color: #2F6FAD } 244 | -------------------------------------------------------------------------------- /src/FSM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * State 3 | * 4 | * Copyright (c) 2015 Renato de Pontes Pereira. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | **/ 24 | 25 | /** 26 | * @module StateJS 27 | **/ 28 | 29 | // namespace: 30 | this.statejs = this.statejs || {}; 31 | 32 | (function() { 33 | "use strict"; 34 | 35 | /** 36 | * A Finite State Machine implementation. 37 | * 38 | * The FSM does not use the `potential` method in the state, and it only call 39 | * the `tick` method of the current state. 40 | * 41 | * @class FSM 42 | */ 43 | var FSM = statejs.Class(); 44 | var p = FSM.prototype; 45 | 46 | /** 47 | * Machine unique ID. 48 | * 49 | * @property id 50 | * @type {String} 51 | * @readonly 52 | **/ 53 | this.id = null; 54 | 55 | /** 56 | * Initialization method. 57 | * 58 | * @method initialize 59 | * @constructor 60 | */ 61 | p.initialize = function() { 62 | this.id = statejs.createUUID(); 63 | this._states = {}; 64 | } 65 | 66 | /** 67 | * Adds a new state to the FSM. The state is identified by a name, which must 68 | * be unique. 69 | * 70 | * @method add 71 | * @param {String} name The unique name that identifies the state. 72 | * @param {State} state The state object. 73 | */ 74 | p.add = function(name, state) { 75 | if (typeof this._states[name] !== 'undefined') { 76 | throw new Error('State "'+name+'" already on the FSM.'); 77 | } 78 | 79 | this._states[name] = state; 80 | state.machine = this; 81 | 82 | return this; 83 | } 84 | 85 | /** 86 | * Returns a registered state instance by name. 87 | * 88 | * @method get 89 | * @param {String} name The state name. 90 | * @return {State} The state. 91 | */ 92 | p.get = function(name) { 93 | return this._states[name]; 94 | } 95 | 96 | /** 97 | * Returns a list of all state names registered in this machine. 98 | * 99 | * @method list 100 | * @return {Array} An array of state names. 101 | */ 102 | p.list = function() { 103 | var result = []; 104 | for (var name in this._states) { 105 | result.push(name); 106 | } 107 | 108 | return result; 109 | } 110 | 111 | /** 112 | * Return the name of the current state. Requires a blackboard instance. 113 | * 114 | * @method name 115 | * @param {statejs.Blackboard} memory A Blackboard instance. 116 | * @return {String} the name of the current state or `null` if none. 117 | */ 118 | p.name = function(memory) { 119 | return memory.get('name', this.id); 120 | } 121 | 122 | /** 123 | * Change the machine to the new state. 124 | * 125 | * @method to 126 | * @param {String} name The state name. 127 | * @param {Object} target A target object. 128 | * @param {statejs.Blackboard} memory A Blackboard instance. 129 | */ 130 | p.to = function(name, target, memory) { 131 | if (typeof this._states[name] === 'undefined') { 132 | throw new Error('State "'+name+'" does not exist.'); 133 | } 134 | 135 | // exit current state 136 | var fromStateName = memory.get('name', this.id); 137 | var fromState = this.get(fromStateName); 138 | if (fromState) { 139 | fromState.exit(target, memory); 140 | } 141 | 142 | // change to the next state 143 | var state = this._states[name]; 144 | memory.set('name', name, this.id); 145 | state.enter(target, memory); 146 | 147 | return this; 148 | } 149 | 150 | /** 151 | * Propagates the update to current state. 152 | * 153 | * @method tick 154 | * @param {Object} target A target object. 155 | * @param {statejs.Blackboard} memory A Blackboard instance. 156 | */ 157 | p.tick = function(target, memory) { 158 | var stateName = memory.get('name', this.id); 159 | var state = this.get(stateName); 160 | if (state) { 161 | state.tick(target, memory); 162 | } 163 | } 164 | 165 | statejs.FSM = FSM; 166 | })(); -------------------------------------------------------------------------------- /examples/boid/js/states.js: -------------------------------------------------------------------------------- 1 | var dist = function(x1, y1, x2, y2) { 2 | return Math.sqrt(Math.pow(x1-x2, 2) + Math.pow(y1-y2, 2)); 3 | } 4 | var norm2d = function(x, y) { 5 | return Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) 6 | } 7 | 8 | function _avoidMouse(target, neighbors) { 9 | var px = 0; 10 | var py = 0; 11 | var mx = game.stage.mouseX; 12 | var my = game.stage.mouseY; 13 | 14 | if (dist(target.x, target.y, mx, my) < settings.SHEEP_OBEY_DISTANCE) { 15 | var px = (mx - target.x); 16 | var py = (my - target.y); 17 | } 18 | 19 | return [-px, -py]; 20 | } 21 | function _centering(target, neighbors) { 22 | var px = 0; 23 | var py = 0; 24 | 25 | if (neighbors.length) { 26 | for (var i=0; i settings.SHEEP_MAX_VELOCITY) { 87 | target.dx = (target.dx/norm)*settings.SHEEP_MAX_VELOCITY; 88 | target.dy = (target.dy/norm)*settings.SHEEP_MAX_VELOCITY; 89 | } 90 | } 91 | 92 | var IdleState = statejs.Class(statejs.State); 93 | IdleState.prototype.enter = function(target, memory) {} 94 | IdleState.prototype.exit = function(target, memory) {} 95 | IdleState.prototype.tick = function(target, memory) { 96 | var mx = game.stage.mouseX; 97 | var my = game.stage.mouseY; 98 | 99 | if (dist(target.x, target.y, mx, my) < settings.SHEEP_OBEY_DISTANCE) { 100 | this.machine.to('obey', target, memory); 101 | } 102 | 103 | flock(target, memory.get('neighbors'), [0.0, 0.0, 0.3, 0.1]) 104 | } 105 | 106 | var ObeyState = statejs.Class(statejs.State); 107 | ObeyState.prototype.enter = function(target, memory) {} 108 | ObeyState.prototype.exit = function(target, memory) { 109 | memory.set('starttime', false, this.machine.id, this.id) 110 | } 111 | ObeyState.prototype.tick = function(target, memory) { 112 | var mx = game.stage.mouseX; 113 | var my = game.stage.mouseY; 114 | 115 | if (dist(target.x, target.y, mx, my) > settings.SHEEP_OBEY_DISTANCE) { 116 | var starttime = memory.get('starttime', this.machine.id, this.id); 117 | var curtime = new Date().getTime(); 118 | if (!starttime) { 119 | memory.set('starttime', curtime, this.machine.id, this.id) 120 | starttime = curtime; 121 | } 122 | // console.log(curtime, starttime, curtime-starttime, settings.SHEEP_FORGET_TIME) 123 | if (curtime-starttime > settings.SHEEP_FORGET_TIME) { 124 | this.machine.to('stopping', target, memory); 125 | } 126 | } else { 127 | memory.set('starttime', false, this.machine.id, this.id) 128 | } 129 | 130 | flock(target, memory.get('neighbors'), [1.0, 0.6, 1.0, 1.0]) 131 | } 132 | 133 | var StoppingState = statejs.Class(statejs.State); 134 | StoppingState.prototype.enter = function(target, memory) { 135 | memory.set('starttime', new Date().getTime(), this.machine.id, this.id); 136 | } 137 | StoppingState.prototype.exit = function(target, memory) {} 138 | StoppingState.prototype.tick = function(target, memory) { 139 | var mx = game.stage.mouseX; 140 | var my = game.stage.mouseY; 141 | 142 | if (dist(target.x, target.y, mx, my) < settings.SHEEP_OBEY_DISTANCE) { 143 | this.machine.to('obey', target, memory); 144 | } 145 | 146 | var starttime = memory.get('starttime', this.machine.id, this.id); 147 | var curtime = new Date().getTime(); 148 | if (curtime - starttime > 3000) { 149 | this.machine.to('idle', target, memory); 150 | } 151 | 152 | flock(target, memory.get('neighbors'), [0.0, 0.1, 1.0, 0.4]) 153 | } 154 | -------------------------------------------------------------------------------- /tests/tests/Subsumption.js: -------------------------------------------------------------------------------- 1 | suite('Subsumption', function() { 2 | test('Add states', function() { 3 | var machine = new statejs.Subsumption(); 4 | 5 | var state_a = StateStub(); 6 | var state_b = StateStub(); 7 | machine.add('a', state_a); 8 | machine.add('b', state_b); 9 | 10 | assert.equal(state_a.machine, machine); 11 | assert.equal(state_b.machine, machine); 12 | }); 13 | 14 | test('Add states (Error)', function() { 15 | var machine = new statejs.Subsumption(); 16 | 17 | var state_a = StateStub(); 18 | var state_b = StateStub(); 19 | machine.add('a', state_a); 20 | 21 | assert.throw(function() {machine.add('a', state_b)}, Error); 22 | }); 23 | 24 | test('List states', function() { 25 | var machine = new statejs.Subsumption(); 26 | 27 | var state_a = StateStub(); 28 | var state_b = StateStub(); 29 | machine.add('a', state_a); 30 | machine.add('b', state_b); 31 | 32 | var states = machine.list(); 33 | assert.equal(states.length, 2); 34 | assert.notEqual(states.indexOf('a'), -1); 35 | assert.notEqual(states.indexOf('b'), -1); 36 | }); 37 | 38 | test('Get states', function() { 39 | var machine = new statejs.Subsumption(); 40 | 41 | var state_a = StateStub(); 42 | var state_b = StateStub(); 43 | machine.add('a', state_a); 44 | machine.add('b', state_b); 45 | 46 | assert.equal(machine.get('a'), state_a); 47 | assert.equal(machine.get('b'), state_b); 48 | }); 49 | 50 | test('Checking potential state', function() { 51 | var machine = new statejs.Subsumption(); 52 | machine.id = '1'; 53 | 54 | var state_a = StateStub(); 55 | var state_b = StateStub(); 56 | machine.add('a', state_a); 57 | machine.add('b', state_b); 58 | 59 | var target = null; 60 | var memory = BlackboardStub(); 61 | 62 | // ticking 63 | machine.tick(target, memory); 64 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 65 | assert.isTrue(state_b.potential.withArgs(target, memory).calledOnce); 66 | }); 67 | 68 | test('Current state name', function() { 69 | var machine = new statejs.Subsumption(); 70 | machine.id = '1'; 71 | 72 | var memory = BlackboardStub(); 73 | 74 | assert.notEqual(machine.name(memory), 'a'); 75 | memory.get.withArgs('name', '1').returns('a'); 76 | assert.equal(machine.name(memory), 'a'); 77 | }); 78 | 79 | test('State transition', function() { 80 | var machine = new statejs.Subsumption(); 81 | machine.id = '1'; 82 | 83 | var state_a = StateStub(); 84 | var state_b = StateStub(); 85 | machine.add('a', state_a); 86 | machine.add('b', state_b); 87 | 88 | var target = null; 89 | var memory = BlackboardStub(); 90 | 91 | // ticking 92 | state_a.potential.returns(true); 93 | machine.tick(target, memory); 94 | assert.isTrue(memory.set.withArgs('name', 'a', '1').calledOnce); 95 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 96 | assert.isTrue(state_a.enter.withArgs(target, memory).calledOnce); 97 | 98 | assert.isFalse(state_b.potential.withArgs(target, memory).calledOnce); 99 | assert.isFalse(state_b.enter.withArgs(target, memory).calledOnce); 100 | }); 101 | 102 | test('State transition (cont)', function() { 103 | var machine = new statejs.Subsumption(); 104 | machine.id = '1'; 105 | 106 | var state_a = StateStub(); 107 | var state_b = StateStub(); 108 | machine.add('a', state_a); 109 | machine.add('b', state_b); 110 | 111 | var target = null; 112 | var memory = BlackboardStub(); 113 | // ticking 114 | state_a.potential.returns(false); 115 | state_b.potential.returns(true); 116 | machine.tick(target, memory); 117 | assert.isTrue(memory.set.withArgs('name', 'b', '1').calledOnce); 118 | assert.isTrue(state_a.potential.withArgs(target, memory).calledOnce); 119 | assert.isFalse(state_a.enter.withArgs(target, memory).calledOnce); 120 | 121 | assert.isTrue(state_b.potential.withArgs(target, memory).calledOnce); 122 | assert.isTrue(state_b.enter.withArgs(target, memory).calledOnce); 123 | }); 124 | 125 | test('Transition to null', function() { 126 | var machine = new statejs.Subsumption(); 127 | machine.id = '1'; 128 | 129 | var state_a = StateStub(); 130 | var state_b = StateStub(); 131 | machine.add('a', state_a); 132 | machine.add('b', state_b); 133 | 134 | var target = null; 135 | var memory = BlackboardStub(); 136 | // ticking 137 | state_a.potential.returns(false); 138 | state_b.potential.returns(false); 139 | machine.tick(target, memory); 140 | 141 | assert.isTrue(memory.set.withArgs('name', null, '1').calledOnce); 142 | assert.isFalse(state_a.enter.withArgs(target, memory).calledOnce); 143 | assert.isFalse(state_b.enter.withArgs(target, memory).calledOnce); 144 | }); 145 | 146 | 147 | 148 | test('Ticking current state', function() { 149 | var machine = new statejs.Subsumption(); 150 | machine.id = '1'; 151 | 152 | var state_a = StateStub(); 153 | var state_b = StateStub(); 154 | machine.add('a', state_a); 155 | machine.add('b', state_b); 156 | 157 | var target = null; 158 | var memory = BlackboardStub(); 159 | // ticking 160 | state_a.potential.returns(false); 161 | state_b.potential.returns(true); 162 | machine.tick(target, memory); 163 | 164 | assert.isFalse(state_a.tick.withArgs(target, memory).calledOnce); 165 | assert.isTrue(state_b.tick.withArgs(target, memory).calledOnce); 166 | }); 167 | }); -------------------------------------------------------------------------------- /tests/sinon-chai.js: -------------------------------------------------------------------------------- 1 | (function (sinonChai) { 2 | "use strict"; 3 | 4 | // Module systems magic dance. 5 | 6 | if (typeof require === "function" && typeof exports === "object" && typeof module === "object") { 7 | // NodeJS 8 | module.exports = sinonChai; 9 | } else if (typeof define === "function" && define.amd) { 10 | // AMD 11 | define(function () { 12 | return sinonChai; 13 | }); 14 | } else { 15 | // Other environment (usually