Repository: oguzeroglu/Ego Branch: master Commit: bf2c0af38947 Files: 30 Total size: 116.3 KB Directory structure: gitextract_frxhfo50/ ├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── js/ │ ├── Ego.js │ ├── decision-tree/ │ │ ├── Decision.js │ │ ├── DecisionTree.js │ │ ├── Knowledge.js │ │ ├── decision-methods/ │ │ │ ├── DecisionMethod.js │ │ │ ├── IsFalse.js │ │ │ ├── IsInRange.js │ │ │ └── IsTrue.js │ │ └── math/ │ │ └── Range.js │ ├── state-machine/ │ │ ├── State.js │ │ ├── StateMachine.js │ │ └── Transition.js │ └── util/ │ └── UUIDV4.js ├── package.json ├── rollup.config.js └── test/ ├── decision-tree/ │ ├── DecisionTest.js │ ├── DecisionTreeTest.js │ ├── KnowledgeTest.js │ ├── decision-methods/ │ │ ├── DecisionMethodTest.js │ │ ├── IsFalseTest.js │ │ ├── IsInRangeTest.js │ │ └── IsTrueTest.js │ └── math/ │ └── RangeTest.js └── state-machine/ ├── StateMachineTest.js ├── StateTest.js └── TransitionTest.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "es2015" ] } ================================================ FILE: .gitignore ================================================ build node_modules .DS_Store ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Oğuz Eroğlu Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Ego Ego is a lightweight decision making library for game AI. It provides decision trees and state machines (hierarchical and finite). Ego is originally designed to be used by the [ROYGBIV engine](https://github.com/oguzeroglu/ROYGBIV), however can easily be used outside of ROYGBIV as well. # Getting Started ## Index - [Including It](#including-it) - [Client-side Javascript](#client-side-javascript) - [NodeJS](#nodejs) - [Using Decision trees](#using-decision-trees) - [Creating the Knowledge](#creating-the-knowledge) - [Creating Decisions](#creating-decisions) - [Connecting the Decisions](#connecting-the-decisions) - [Creating the Decision Tree](#creating-the-decision-tree) - [Putting It All Together](#putting-it-all-together) - [Using State Machines](#using-state-machines) - [Creating the Knowledge](#creating-the-knowledge-1) - [Creating States](#creating-states) - [Creating Transitions](#creating-transitions) - [Creating the State Machine](#creating-the-state-machine) - [Putting It All Together](#putting-it-all-together-1) - [Note About Hierarchical State Machines](#note-about-hierarchical-state-machines) ## Including It ### Client-side Javascript Download the latest release from [releases page](https://github.com/oguzeroglu/Ego/releases). Then include the `ego.min.js` file into your project via `` ### NodeJS Install it via npm: `npm i @oguz.eroglu/ego-js` Then include the module into your project via `require`: ```javascript var Ego = require("@oguz.eroglu/ego-js"); ``` ## Using Decision Trees To get started with decision trees, let's create this decision tree with Ego: ![](/example-images/decision-tree.png?raw=true) ### Creating the Knowledge We'll start by creating the `Knowledge`. Knowledge class can contain boolean, numerical (integer/float) and vector (with x, y, z and length properties) information. For this decision tree, we need these informations to put into our knowledge: * isEnemyVisible (of type boolean) * isEnemyAudible (of type boolean) * isEnemyOnFlank (of type boolean) * distanceToEnemy (of type 3D vector) ```javascript // Instantiate a Knowledge var knowledge = new Ego.Knowledge(); // add isEnemyVisible information knowledge.addBooleanInformation("isEnemyVisible", false); // add isEnemyAudible information knowledge.addBooleanInformation("isEnemyAudible", false); // add isEnemyOnFlank information knowledge.addBooleanInformation("isEnemyOnFlank", false); // add distanceToEnemy information. We'll assume the enemy is 100 units far away on the axis x. knowledge.addVectorInformation("distanceToEnemy", 100, 0, 0); ``` ### Creating Decisions We'll continue by creating the decisions. Ego provides these three decision methods: * `IsTrue` to check if given boolean information is true. * `IsFalse` to check if given boolean information is false. * `IsInRange` to check if given numerical information or the length of given vector information is within given range. We can reuse the decision methods across several decisions. For this decision tree, we need `IsTrue` and `IsInRange` decision methods. Let's create them: ```javascript // create the IsTrue decision method var isTrue = new Ego.IsTrue(); // create the IsInRange decision method. We'll start by creating a Range object. // The first parameter is the lower bound and the second is the upper bound. // Note that Ego Range instances are inclusive by default. // Since we'd like to check if given distance is less than 10 units // we'll set the lower bound to -Infinity and the upper bound to 10. // [-Infinity, 10[ var range = new Ego.Range(-Infinity, 10); range.makeUpperBoundExclusive(); // Create the decision method var isInRange = new Ego.IsInRange(range); ``` ```javascript // the first parameter is the information name inside the Knowledge // the second is the type of the information // the third is the decision method var isEnemyVisible = new Ego.Decision("isEnemyVisible", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var isEnemyAudible = new Ego.Decision("isEnemyAudible", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var isEnemyOnFlank = new Ego.Decision("isEnemyOnFlank", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var distanceLessThan10Units = new Ego.Decision("distanceToEnemy", Ego.InformationTypes.TYPE_VECTOR, isInRange); ``` ### Connecting the Decisions We'll then connect the decisions that we've created. If we connect a decision with another decision, Ego will continue to perform the decision tests. If we connect it with any other type (such as a String or an Object), Ego will act as if the decision is done and return the connected object: ```javascript // If enemy is visible, we'll test if enemy is close. isEnemyVisible.setYesNode(distanceLessThan10Units); // If enemy is not visible, we'll test if enemy is audible. isEnemyVisible.setNoNode(isEnemyAudible); // If enemy is audible, we'll creep. We can simply return a string in this case. isEnemyAudible.setYesNode("creep"); // If enemy is not audible, we'll do nothing. We can simply return a string in this case. isEnemyAudible.setNoNode("doNothing"); // If enemy is less than 10 units away, we'll attack distanceLessThan10Units.setYesNode("attack"); // We'll perform isEnemyOnFlank test otherwise distanceLessThan10Units.setNoNode(isEnemyOnFlank); // If enemy is on flank, we'll move isEnemyOnFlank.setYesNode("move"); // We'll attack otherwise isEnemyOnFlank.setNoNode("attack"); ``` ### Creating the Decision Tree Now that we're all set, we can create our Decision Tree. We need to specifiy the root decision when creating the decision tree. In this case, the root decision is `isEnemyVisible`: ```javascript var decisionTree = new Ego.DecisionTree(isEnemyVisible); ``` We can now make decisions. Since given our knowledge the enemy is not visible and also not audible, the initial decision result would be to do nothing. ```javascript decisionTree.makeDecision(knowledge); // returns "doNothing" ``` We can always update our knowledge with new information and make new decisions. Let's update `isEnemyVisible` information. Since now enemy is visible and also the length of the distance vector is less than 10 units, we'll now chose to attack: ```javascript // update isEnemyVisible information knowledge.updateBooleanInformation("isEnemyVisible", true); decisionTree.makeDecision(knowledge); // returns "attack" ``` In the same way, if we put the enemy 500 units away on the axis X (the length would be than more than 10 units) and the enemy is now on flank, we'll chose to move instead. ```javascript // update knowledge knowledge.updateVectorInformation("distanceToEnemy", 500, 0, 0); knowledge.updateBooleanInformation("isEnemyOnFlank", true); decisionTree.makeDecision(knowledge); // return "move" ``` ### Putting It All Together ```javascript var Ego = require("@oguz.eroglu/ego-js"); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isEnemyVisible", false); knowledge.addBooleanInformation("isEnemyAudible", false); knowledge.addBooleanInformation("isEnemyOnFlank", false); knowledge.addVectorInformation("distanceToEnemy", 100, 0, 0); var isTrue = new Ego.IsTrue(); var range = new Ego.Range(-Infinity, 10); range.makeUpperBoundExclusive(); var isInRange = new Ego.IsInRange(range); var isEnemyVisible = new Ego.Decision("isEnemyVisible", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var isEnemyAudible = new Ego.Decision("isEnemyAudible", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var isEnemyOnFlank = new Ego.Decision("isEnemyOnFlank", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var distanceLessThan10Units = new Ego.Decision("distanceToEnemy", Ego.InformationTypes.TYPE_VECTOR, isInRange); isEnemyVisible.setYesNode(distanceLessThan10Units); isEnemyVisible.setNoNode(isEnemyAudible); isEnemyAudible.setYesNode("creep"); isEnemyAudible.setNoNode("doNothing"); distanceLessThan10Units.setYesNode("attack"); distanceLessThan10Units.setNoNode(isEnemyOnFlank); isEnemyOnFlank.setYesNode("move"); isEnemyOnFlank.setNoNode("attack"); var decisionTree = new Ego.DecisionTree(isEnemyVisible); knowledge.updateBooleanInformation("isEnemyVisible", true); knowledge.updateVectorInformation("distanceToEnemy", 500, 0, 0); knowledge.updateBooleanInformation("isEnemyOnFlank", true); console.log(decisionTree.makeDecision(knowledge)); ``` ## Using State Machines To get started with state machines, let's create this state machine with Ego: ![](/example-images/simple-state-machine.png?raw=true) ### Creating the Knowledge Just like Decision Trees, State Machines also use a Knowledge in order to decide if it's possible to switch from one state to another. For this example we'll need following informations to store in our Knowledge: * isWeakEnemySeen (of type boolean) * isStrongEnemySeen (of type boolean) * health (of type float) * distanceToEnemy (of type Vector) `isWeakEnemySeen` and `isStrongEnemySeen` boolean informations will be set to true when the player sees a weak or a strong enemy. We'll use `health` information to decide whether the player is losing the fight or not. `distanceToEnemy` will be used to decide if the player is successfully escaped from an enemy. ```javascript // create the knowledge var knowledge = new Ego.Knowledge(); // create isWeakEnemySeen information knowledge.addBooleanInformation("isWeakEnemySeen", false); // create isStrongEnemySeen information knowledge.addBooleanInformation("isStrongEnemySeen", false); // create health information. Initially we'll set this to 100. knowledge.addNumericalInformation("health", 100); // create distanceToEnemy information. Initially we'll assume the enemy is 500 units far away on the axis X. knowledge.addVectorInformation("distanceToEnemy", 500, 0, 0); ``` ### Creating States We'll move on by creating the states. For this example we need 3 states: `OnGuard`, `Fight` and `RunAway`: ```javascript // create OnGuard state var onGuardState = new Ego.State("OnGuard"); // create Fight state var fightState = new Ego.State("Fight"); // create RunAway state var runAwayState = new Ego.State("RunAway"); ``` ### Creating Transitions We then need to create Transitions. Transitions are a subclass of Decisions, so they work the same way. In addition to Decisions, we need to define the source state and the target state as well in order to create a Transition. We'll first define the decision methods just like we did while working with Decision Trees. For this example we need these decision methods: * `IsTrue` to check if strong or weak enemy is seen (`isStrongEnemySeen` and `isWeakEnemySeen` informations) * `IsInRange` to check if the player is losing the fight (`health` information is less than 20) * Another `IsInRange` to check if the player successfully escaped (The length of `distanceToEnemy` is greater than 100 units) ```javascript // create IsTrue decision method var isTrue = new Ego.IsTrue(); // create IsInRange decision method for the health information. // define the range var lessThan20Range = new Ego.Range(-Infinity, 20); lessThan20Range.makeUpperBoundExclusive(); // create the Decision method var lessThan20 = new Ego.IsInRange(lessThan20Range); // create IsInRange decision method for the distanceToEnemy information // define the range var greaterThan100Range = new Ego.Range(100, Infinity); greaterThan100Range.makeLowerBoundExclusive(); // create the Decision method var greaterThan100 = new Ego.IsInRange(greaterThan100Range); ``` Now that we have our decision methods ready, we can create our Transitions. The first parameter is the source state, the second is the target state, the third is the information name that we use inside our Knowledge, the fourth is the type of the information (either `Ego.InformationTypes.TYPE_BOOLEAN` or `Ego.InformationTypes.TYPE_NUMERICAL` or `Ego.InformationTypes.TYPE_VECTOR`) and the fifth is the decision method. ```javascript // Transition for: See weak enemy var seeWeakEnemyTransition = new Ego.Transition(onGuardState, fightState, "isWeakEnemySeen", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); // Transition for: See strong enemy var seeStrongEnemyTransition = new Ego.Transition(onGuardState, runAwayState, "isStrongEnemySeen", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); // Transition for: Losing fight var losingFightTransition = new Ego.Transition(fightState, runAwayState, "health", Ego.InformationTypes.TYPE_NUMERICAL, lessThan20); // Transition for: Escaped var escapedTransition = new Ego.Transition(runAwayState, onGuardState, "distanceToEnemy", Ego.InformationTypes.TYPE_VECTOR, greaterThan100); ``` ### Creating the State Machine Now that we have our states and transitions ready, we can create our State Machine. While creating the State Machine, we need to pass a name as the first argument, and the knowledge as the second one: ```javascript // create the state machine var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); // add states stateMachine.addState(onGuardState); stateMachine.addState(runAwayState); stateMachine.addState(fightState); // add transitions stateMachine.addTransition(seeWeakEnemyTransition); stateMachine.addTransition(seeStrongEnemyTransition); stateMachine.addTransition(losingFightTransition); stateMachine.addTransition(escapedTransition); // set the initial (entry) state stateMachine.setEntryState(onGuardState); ``` Now that we have our state machine ready, we want to update it and listen for state changes. Note that state machines are updated recursively, that's why it's important not to create circular transitions to avoid infinite loops. ```javascript // listen for state changes stateMachine.onStateChanged(function(newState){ console.log("New state is: " + newState.getName()); }); // update the state machine stateMachine.update(); ``` This will print out: `New state is: OnGuard`. Since none of our transition conditions are satisfied, we're stuck in the initial state. Now, let's update the knowledge to jump to other states: ```javascript // the player has seen a weak enemy knowledge.updateBooleanInformation("isWeakEnemySeen", true); stateMachine.update(); ``` Now that the player has seen a weak enemy, this will additionally print out: `New state is: Fight`. Let's lose the fight: ```javascript knowledge.updateNumericalInformation("health", 10); knowledge.updateVectorInformation("distanceToEnemy", 3, 0, 0); stateMachine.update(); // prints out: New state is: RunAway ``` ### Putting It All Together ```javascript var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isWeakEnemySeen", false); knowledge.addBooleanInformation("isStrongEnemySeen", false); knowledge.addNumericalInformation("health", 100); knowledge.addVectorInformation("distanceToEnemy", 500, 0, 0); var onGuardState = new Ego.State("OnGuard"); var fightState = new Ego.State("Fight"); var runAwayState = new Ego.State("RunAway"); var isTrue = new Ego.IsTrue(); var lessThan20Range = new Ego.Range(-Infinity, 20); lessThan20Range.makeUpperBoundExclusive(); var lessThan20 = new Ego.IsInRange(lessThan20Range); var greaterThan100Range = new Ego.Range(100, Infinity); greaterThan100Range.makeLowerBoundExclusive(); var greaterThan100 = new Ego.IsInRange(greaterThan100Range); var seeWeakEnemyTransition = new Ego.Transition(onGuardState, fightState, "isWeakEnemySeen", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var seeStrongEnemyTransition = new Ego.Transition(onGuardState, runAwayState, "isStrongEnemySeen", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var losingFightTransition = new Ego.Transition(fightState, runAwayState, "health", Ego.InformationTypes.TYPE_NUMERICAL, lessThan20); var escapedTransition = new Ego.Transition(runAwayState, onGuardState, "distanceToEnemy", Ego.InformationTypes.TYPE_VECTOR, greaterThan100); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); stateMachine.addState(onGuardState); stateMachine.addState(runAwayState); stateMachine.addState(fightState); stateMachine.addTransition(seeWeakEnemyTransition); stateMachine.addTransition(seeStrongEnemyTransition); stateMachine.addTransition(losingFightTransition); stateMachine.addTransition(escapedTransition); stateMachine.setEntryState(onGuardState); stateMachine.onStateChanged(function(newState){ console.log("New state is: " + newState.getName()); }); stateMachine.update(); knowledge.updateBooleanInformation("isWeakEnemySeen", true); stateMachine.update(); knowledge.updateNumericalInformation("health", 10); knowledge.updateVectorInformation("distanceToEnemy", 3, 0, 0); stateMachine.update(); ``` ### Note About Hierarchical State Machines Ego supports hierarchical state machines and cross hierarchy transitions. If a state machine is passed rather than a state to `StateMachine#addState` API, Ego automatically updates the child state machine if the current state of the parent state machine is the child state machine and the parent state machine is updated. # License Ego uses MIT license. ================================================ FILE: js/Ego.js ================================================ export { Knowledge } from "./decision-tree/Knowledge"; export { DecisionMethod } from "./decision-tree/decision-methods/DecisionMethod"; export { IsTrue } from "./decision-tree/decision-methods/IsTrue"; export { IsFalse } from "./decision-tree/decision-methods/IsFalse"; export { IsInRange } from "./decision-tree/decision-methods/IsInRange"; export { Range } from "./decision-tree/math/Range"; export { Decision, InformationTypes } from "./decision-tree/Decision"; export { DecisionTree } from "./decision-tree/DecisionTree"; export { State } from "./state-machine/State"; export { Transition } from "./state-machine/Transition"; export { StateMachine } from "./state-machine/StateMachine"; ================================================ FILE: js/decision-tree/Decision.js ================================================ var InformationTypes = { TYPE_BOOLEAN: "TYPE_BOOLEAN", TYPE_NUMERICAL: "TYPE_NUMERICAL", TYPE_VECTOR: "TYPE_VECTOR" }; var Decision = function(informationName, informationType, decisionMethod){ this._informationName = informationName; this._informationType = informationType; this._decisionMethod = decisionMethod; this._yesNode = null; this._noNode = null; } Decision.prototype.clone = function(){ var cloned = new Decision(this._informationName, this._informationType, this._decisionMethod.clone()); var yesNode = this.getYesNode(); var noNode = this.getNoNode(); if (yesNode instanceof Decision){ cloned.setYesNode(yesNode.clone()); }else{ cloned.setYesNode(yesNode); } if (noNode instanceof Decision){ cloned.setNoNode(noNode.clone()); }else{ cloned.setNoNode(noNode); } return cloned; } Decision.prototype.setYesNode = function(node){ this._yesNode = node; } Decision.prototype.getYesNode = function(){ return this._yesNode; } Decision.prototype.setNoNode = function(node){ this._noNode = node; } Decision.prototype.getNoNode = function(){ return this._noNode; } Decision.prototype.make = function(knowledge){ var informationType = this._informationType; var informationName = this._informationName; var information = null; if (informationType == InformationTypes.TYPE_BOOLEAN){ information = knowledge.getBooleanInformation(informationName); }else if (informationType == InformationTypes.TYPE_NUMERICAL){ information = knowledge.getNumericalInformation(informationName); }else if (informationType == InformationTypes.TYPE_VECTOR){ information = knowledge.getVectorInformation(informationName); }else{ throw new Error("No such information type."); } if (information == null){ throw new Error("No such information in knowledge: " + informationName); } if (this._decisionMethod.perform(information)){ return this._yesNode; } return this._noNode; } export { Decision }; export { InformationTypes }; ================================================ FILE: js/decision-tree/DecisionTree.js ================================================ import { Decision } from "./Decision"; var DecisionTree = function(rootNode){ if (!(rootNode instanceof Decision)){ throw new Error("Root node must be an instance of Ego.Decision"); } this._rootNode = rootNode; } DecisionTree.prototype.makeDecision = function(knowledge){ var currentNode = this._rootNode; while(currentNode instanceof Decision){ currentNode = currentNode.make(knowledge); } return currentNode; } DecisionTree.prototype.clone = function(){ return new DecisionTree(this._rootNode.clone()); } export { DecisionTree }; ================================================ FILE: js/decision-tree/Knowledge.js ================================================ var Knowledge = function(){ this._booleanMap = {}; this._numericalMap = {}; this._vectorMap = {}; } Knowledge.prototype.clone = function(){ var cloned = new Knowledge(); for (var infoName in this._booleanMap){ cloned.addBooleanInformation(infoName, this.getBooleanInformation(infoName)); } for (var infoName in this._numericalMap){ cloned.addNumericalInformation(infoName, this.getNumericalInformation(infoName)); } for (var infoName in this._vectorMap){ var vect = this.getVectorInformation(infoName); cloned.addVectorInformation(infoName, vect.x, vect.y, vect.z); } return cloned; } Knowledge.prototype.addBooleanInformation = function(name, booleanValue){ if (this._hasBooleanInformation(name)){ return false; } this._booleanMap[name] = !!booleanValue; return true; } Knowledge.prototype.addNumericalInformation = function(name, numericalValue){ if (this._hasNumericalInformation(name)){ return false; } var float = parseFloat(numericalValue); if (isNaN(float)){ return false; } this._numericalMap[name] = float; return true; } Knowledge.prototype.addVectorInformation = function(name, x, y, z){ if (this._hasVectorInformation(name)){ return false; } var parsedX = parseFloat(x); var parsedY = parseFloat(y); var parsedZ = parseFloat(z); if (isNaN(parsedX) || isNaN(parsedY) || isNaN(parsedZ)){ return false; } var xSqr = parsedX * parsedX; var ySqr = parsedY * parsedY; var zSqr = parsedZ * parsedZ; this._vectorMap[name] = {x: parsedX, y: parsedY, z: parsedZ, length: Math.sqrt(xSqr + ySqr + zSqr), isVector: true}; return true; } Knowledge.prototype.getBooleanInformation = function(name){ if (this._hasBooleanInformation(name)){ return this._booleanMap[name]; } return null; } Knowledge.prototype.getNumericalInformation = function(name){ if (this._hasNumericalInformation(name)){ return this._numericalMap[name]; } return null; } Knowledge.prototype.getVectorInformation = function(name){ if (this._hasVectorInformation(name)){ return this._vectorMap[name]; } return null; } Knowledge.prototype.updateBooleanInformation = function(name, booleanValue){ if (!this._hasBooleanInformation(name)){ return false; } this._booleanMap[name] = !!booleanValue; return true; } Knowledge.prototype.updateNumericalInformation = function(name, numericalValue){ if (!this._hasNumericalInformation(name)){ return false; } var float = parseFloat(numericalValue); if (isNaN(float)){ return false; } this._numericalMap[name] = float; return true; } Knowledge.prototype.updateVectorInformation = function(name, x, y, z){ if (!this._hasVectorInformation(name)){ return false; } var parsedX = parseFloat(x); var parsedY = parseFloat(y); var parsedZ = parseFloat(z); if (isNaN(parsedX) || isNaN(parsedY) || isNaN(parsedZ)){ return false; } var xSqr = parsedX * parsedX; var ySqr = parsedY * parsedY; var zSqr = parsedZ * parsedZ; var vect = this._vectorMap[name]; vect.x = parsedX; vect.y = parsedY; vect.z = parsedZ; vect.length = Math.sqrt(xSqr + ySqr + zSqr); return true; } Knowledge.prototype.deleteBooleanInformation = function(name){ if (!this._hasBooleanInformation(name)){ return false; } delete this._booleanMap[name]; return true; } Knowledge.prototype.deleteNumericalInformation = function(name){ if (!this._hasNumericalInformation(name)){ return false; } delete this._numericalMap[name]; return true; } Knowledge.prototype.deleteVectorInformation = function(name){ if (!this._hasVectorInformation(name)){ return false; } delete this._vectorMap[name]; return true; } Knowledge.prototype._hasBooleanInformation = function(name){ return !(typeof this._booleanMap[name] === "undefined"); } Knowledge.prototype._hasNumericalInformation = function(name){ return !(typeof this._numericalMap[name] === "undefined"); } Knowledge.prototype._hasVectorInformation = function(name){ return !(typeof this._vectorMap[name] === "undefined"); } export { Knowledge }; ================================================ FILE: js/decision-tree/decision-methods/DecisionMethod.js ================================================ var DecisionMethod = function(parameter){ this._parameter = parameter; } DecisionMethod.prototype.updateParameter = function(parameter){ this._parameter = parameter; } DecisionMethod.prototype.perform = function(val){ return true; } DecisionMethod.prototype.clone = function(){ var clonedParam = (this._parameter && this._parameter.clone)? this._parameter.clone(): this._parameter; return new this.constructor(clonedParam); } export { DecisionMethod }; ================================================ FILE: js/decision-tree/decision-methods/IsFalse.js ================================================ import { DecisionMethod } from "./DecisionMethod"; var IsFalse = function(){ DecisionMethod.call(this, null); } IsFalse.prototype = Object.create(DecisionMethod.prototype); IsFalse.prototype.perform = function(val){ return val == false; } Object.defineProperty(IsFalse.prototype, 'constructor', { value: IsFalse, enumerable: false, writable: true }); export { IsFalse }; ================================================ FILE: js/decision-tree/decision-methods/IsInRange.js ================================================ import { DecisionMethod } from "./DecisionMethod"; var IsInRange = function(range){ DecisionMethod.call(this, range); } IsInRange.prototype = Object.create(DecisionMethod.prototype); IsInRange.prototype.perform = function(val){ var range = this._parameter; if (val.isVector){ return range.testNumber(val.length); } return range.testNumber(val); } Object.defineProperty(IsInRange.prototype, 'constructor', { value: IsInRange, enumerable: false, writable: true }); export { IsInRange }; ================================================ FILE: js/decision-tree/decision-methods/IsTrue.js ================================================ import { DecisionMethod } from "./DecisionMethod"; var IsTrue = function(){ DecisionMethod.call(this, null); } IsTrue.prototype = Object.create(DecisionMethod.prototype); IsTrue.prototype.perform = function(val){ return val == true; } Object.defineProperty(IsTrue.prototype, 'constructor', { value: IsTrue, enumerable: false, writable: true }); export { IsTrue }; ================================================ FILE: js/decision-tree/math/Range.js ================================================ var Range = function(lowerBound, upperBound){ this._isLowerBoundInclusive = true; this._isUpperBoundInclusive = true; this._lowerBound = lowerBound; this._upperBound = upperBound; } Range.prototype.clone = function(){ var cloned = new Range(this._lowerBound, this._upperBound); if (this._isLowerBoundInclusive){ cloned.makeLowerBoundInclusive(); }else{ cloned.makeLowerBoundExclusive(); } if (this._isUpperBoundInclusive){ cloned.makeUpperBoundInclusive(); }else{ cloned.makeUpperBoundExclusive(); } return cloned; } Range.prototype.makeLowerBoundInclusive = function(){ this._isLowerBoundInclusive = true; } Range.prototype.makeLowerBoundExclusive = function(){ this._isLowerBoundInclusive = false; } Range.prototype.makeUpperBoundInclusive = function(){ this._isUpperBoundInclusive = true; } Range.prototype.makeUpperBoundExclusive = function(){ this._isUpperBoundInclusive = false; } Range.prototype.updateLowerBound = function(lowerBound){ this._lowerBound = lowerBound; } Range.prototype.updateUpperBound = function(upperBound){ this._upperBound = upperBound; } Range.prototype.testNumber = function(number){ var lower = this._lowerBound; var upper = this._upperBound; if (this._isUpperBoundInclusive){ if (number > upper){ return false; } }else{ if (number >= upper){ return false; } } if (this._isLowerBoundInclusive){ if (number < lower){ return false; } }else{ if (number <= lower){ return false; } } return true; } export { Range }; ================================================ FILE: js/state-machine/State.js ================================================ import uuidv4 from "../util/UUIDV4"; var State = function(name){ this._name = name; this._id = uuidv4(); this._parent = null; } State.prototype.clone = function(){ return new State(this.getName()); } State.prototype.setParent = function(parent){ if (this._parent){ return false; } this._parent = parent; return true; } State.prototype.removeParent = function(){ this._parent = null; } State.prototype.getID = function(){ return this._id; } State.prototype.getParent = function(){ return this._parent; } State.prototype.getName = function(){ return this._name; } export { State }; ================================================ FILE: js/state-machine/StateMachine.js ================================================ import { State } from "./State"; var StateMachine = function(name, knowledge){ State.call(this, name); this._knowledge = knowledge; this._statesByID = {}; this._transitionsByStateID = {}; this._stateChangedCallbackFunction = null; this._entryState = null; this._currentState = null; } StateMachine.prototype = Object.create(State.prototype); StateMachine.prototype.clone = function(overrideKnowledge, idsObj){ var clonedStateMachine = new StateMachine(this.getName(), overrideKnowledge || this._knowledge.clone()); var clonedIDs = idsObj || {}; for (var stateID in this._statesByID){ var state = this._statesByID[stateID]; var cloned = clonedIDs[stateID] || state.clone(overrideKnowledge || null, clonedIDs); clonedStateMachine.addState(cloned); clonedIDs[stateID] = cloned; if (this._entryState == state){ clonedStateMachine.setEntryState(cloned); } } for (var stateID in this._transitionsByStateID){ var transitions = this._transitionsByStateID[stateID]; for (var i = 0; i < transitions.length; i ++){ var transition = transitions[i]; var source = transition.getSourceNode(); var target = transition.getTargetNode(); var overrideSource = clonedIDs[source.getID()] || null; var overrideTarget = clonedIDs[target.getID()] || null; var cloned = transition.clone(overrideSource, overrideTarget, overrideKnowledge || null, clonedIDs); clonedStateMachine.addTransition(cloned); clonedIDs[source.getID()] = cloned.getSourceNode(); clonedIDs[target.getID()] = cloned.getTargetNode(); } } return clonedStateMachine; } StateMachine.prototype.hasState = function(state){ if (state.getID() == this.getID()){ return true; } var result = false; for (var stateID in this._statesByID){ var curState = this._statesByID[stateID]; result = result || curState.getID() == state.getID(); if (curState instanceof StateMachine){ result = result || curState.hasState(state); } } return result; } StateMachine.prototype.addState = function(state){ if (state instanceof StateMachine && state.hasState(this)){ return false; } if (state.setParent(this)){ this._statesByID[state.getID()] = state; this._transitionsByStateID[state.getID()] = []; return true; } return false; } StateMachine.prototype.removeState = function(state){ if (state.getParent() != this){ return false; } if (this._currentState && this._currentState.getID() == state.getID()){ throw new Error("Cannot remove the active state."); } if (this._entryState && this._entryState.getID() == state.getID()){ this._entryState = null; } state.removeParent(); delete this._statesByID[state.getID()]; delete this._transitionsByStateID[state.getID()]; return true; } StateMachine.prototype.addTransition = function(transition){ var sourceNode = transition.getSourceNode(); if (sourceNode.getParent() == this){ var existingTransitions = this._transitionsByStateID[sourceNode.getID()]; for (var i = 0; i < existingTransitions.length; i ++){ var existingTransition = existingTransitions[i]; if (existingTransition.getTargetNode() == transition.getTargetNode()){ return false; } } existingTransitions.push(transition); return true; } return false; } StateMachine.prototype.removeTransition = function(transition){ var sourceNode = transition.getSourceNode(); if (sourceNode.getParent() == this){ var transitions = this._transitionsByStateID[sourceNode.getID()]; var index = null; for (var i = 0; i < transitions.length; i ++){ if (transitions[i].getTargetNode() == transition.getTargetNode()){ index = i; break; } } if (index == null){ return false; } transitions.splice(index, 1); return true; } return false; } StateMachine.prototype.setEntryState = function(state){ if (!this._statesByID[state.getID()]){ return false; } this._entryState = state; return true; } StateMachine.prototype.onStateChanged = function(callbackFunction){ this._stateChangedCallbackFunction = callbackFunction; } StateMachine.prototype.reset = function(){ this._currentState = null; } StateMachine.prototype.onCrossHierarchyTransition = function(node){ this._changeState(node); this.update(); } StateMachine.prototype._onNewState = function(newState){ if (this._stateChangedCallbackFunction){ this._stateChangedCallbackFunction(newState); } } StateMachine.prototype._getNextState = function(currentState){ if (currentState == null){ return null; } var transitions = this._transitionsByStateID[currentState.getID()]; var knowledge = this._knowledge; for (var i = 0; i < transitions.length; i ++){ var transition = transitions[i]; if (transition.isPossible(knowledge)){ return transition; } } return null; } StateMachine.prototype._changeState = function(newState){ if (this._currentState && this._currentState instanceof StateMachine){ this._currentState.reset(); } this._currentState = newState; this._onNewState(newState); } StateMachine.prototype.update = function(){ if (!this._entryState){ throw new Error("Entry state not set. Cannot update the StateMachine."); } if (!this._currentState){ this._changeState(this._entryState); } var transition = this._getNextState(this._currentState); while (transition){ var targetNode = transition.getTargetNode(); var targetParent = targetNode.getParent(); if (targetParent == this){ this._changeState(transition.getTargetNode()); }else{ if (targetParent != null){ targetParent.onCrossHierarchyTransition(targetNode); } this.reset(); } transition = this._getNextState(this._currentState); } if (this._currentState instanceof StateMachine){ this._currentState.update(); } } Object.defineProperty(StateMachine.prototype, 'constructor', { value: StateMachine, enumerable: false, writable: true }); export { StateMachine }; ================================================ FILE: js/state-machine/Transition.js ================================================ import { Decision } from "../decision-tree/Decision"; var Transition = function(source, target, infoName, infoType, decisionMethod){ Decision.call(this, infoName, infoType, decisionMethod); this.setYesNode(target); this.setNoNode(source); } Transition.prototype = Object.create(Decision.prototype); Transition.prototype.clone = function(overrideSource, overrideTarget, overrideKnowledge, idsObj){ var source = null; var target = null; if (overrideSource){ source = overrideSource; }else{ source = this.getSourceNode().clone(overrideKnowledge || null, idsObj || null); } if (overrideTarget){ target = overrideTarget; }else{ target = this.getTargetNode().clone(overrideKnowledge || null, idsObj || null); } return new Transition(source, target, this._informationName, this._informationType, this._decisionMethod.clone()); } Transition.prototype.isPossible = function(knowledge){ var decisionResult = this.make(knowledge); if (decisionResult == this._yesNode){ return true; } return false; } Transition.prototype.getSourceNode = function(){ return this.getNoNode(); } Transition.prototype.getTargetNode = function(){ return this.getYesNode(); } Object.defineProperty(Transition.prototype, 'constructor', { value: Transition, enumerable: false, writable: true }); export { Transition }; ================================================ FILE: js/util/UUIDV4.js ================================================ var uuidv4 = function(){ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } export default uuidv4 ; ================================================ FILE: package.json ================================================ { "name": "@oguz.eroglu/ego-js", "version": "1.1.0", "description": "A lightweight decision making library for game AI.", "main": "build/Ego.js", "module": "build/Ego.mjs", "scripts": { "build": "rollup --config rollup.config.js --environment BUILD:production && minify ./build/Ego.js -o ./build/Ego.min.js", "test": "npm run build && mocha test/**/*" }, "repository": { "type": "git", "url": "git+https://github.com/oguzeroglu/ego.git" }, "keywords": [ "game-ai", "decision", "game-development", "roygbiv" ], "author": "Oguz Eroglu", "license": "MIT", "bugs": { "url": "https://github.com/oguzeroglu/ego/issues" }, "homepage": "https://github.com/oguzeroglu/ego#readme", "dependencies": { }, "devDependencies": { "babel-eslint": "^7.1.1", "babel-minify": "^0.5.1", "babel-plugin-external-helpers": "^6.18.0", "babel-preset-es2015": "^6.18.0", "babel-register": "^6.18.0", "babelrc-rollup": "^3.0.0", "eslint": "^4.1.1", "expect.js": "^0.3.1", "istanbul": "^0.4.5", "mocha": "^3.2.0", "rollup": "^0.43.0", "rollup-plugin-babel": "^2.7.1", "rollup-plugin-istanbul": "^1.1.0", "rollup-watch": "^4.3.1" } } ================================================ FILE: rollup.config.js ================================================ import babel from 'rollup-plugin-babel'; import babelrc from 'babelrc-rollup'; import istanbul from 'rollup-plugin-istanbul'; let pkg = require('./package.json'); let external = Object.keys(pkg.dependencies); let plugins = [ babel(babelrc()) ]; if (process.env.BUILD !== 'production') { plugins.push(istanbul({ exclude: ['test/**/*', 'node_modules/**/*'] })); } export default { entry: './js/Ego.js', plugins: plugins, external: external, targets: [ { dest: pkg.main, format: 'umd', moduleName: 'Ego', sourceMap: true }, { dest: pkg.module, format: 'umd', sourceMap: true, moduleName: "Ego" } ] }; ================================================ FILE: test/decision-tree/DecisionTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("Decision", function(){ it("should export information types", function(){ var informationTypes = Ego.InformationTypes; expect(informationTypes.TYPE_BOOLEAN).to.eql("TYPE_BOOLEAN"); expect(informationTypes.TYPE_NUMERICAL).to.eql("TYPE_NUMERICAL"); expect(informationTypes.TYPE_VECTOR).to.eql("TYPE_VECTOR"); }); it("should initialize", function(){ var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(decision._informationName).to.eql("isStuffHappening"); expect(decision._informationType).to.eql(Ego.InformationTypes.TYPE_BOOLEAN); expect(decision._decisionMethod).to.equal(decisionMethod); expect(decision._yesNode).to.eql(null); expect(decision._noNode).to.eql(null); }); it("should set yes node", function(){ var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var decision2 = new Ego.Decision("canSee", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setYesNode(decision2); expect(decision._yesNode).to.equal(decision2); }); it("should get yes node", function(){ var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var decision2 = new Ego.Decision("canSee", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setYesNode(decision2); expect(decision.getYesNode()).to.equal(decision2); }); it("should set no node", function(){ var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var decision2 = new Ego.Decision("canSee", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setNoNode(decision2); expect(decision._noNode).to.equal(decision2); }); it("should get no node", function(){ var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var decision2 = new Ego.Decision("canSee", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setNoNode(decision2); expect(decision.getNoNode()).to.equal(decision2); }); it("should make given boolean information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setYesNode("yes"); decision.setNoNode("no"); expect(decision.make(knowledge)).to.eql("yes"); knowledge.updateBooleanInformation("isStuffHappening", false); expect(decision.make(knowledge)).to.eql("no"); }); it("should make given numerical information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addNumericalInformation("health", 70); var range = new Ego.Range(0, 30); var decisionMethod = new Ego.IsInRange(range); var decision = new Ego.Decision("health", Ego.InformationTypes.TYPE_NUMERICAL, decisionMethod); decision.setYesNode("yes"); decision.setNoNode("no"); expect(decision.make(knowledge)).to.eql("no"); knowledge.updateNumericalInformation("health", 15); expect(decision.make(knowledge)).to.eql("yes"); }); it("should make given vector information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addVectorInformation("distance", 100, 100, 100); var range = new Ego.Range(0, 100); var decisionMethod = new Ego.IsInRange(range); var decision = new Ego.Decision("distance", Ego.InformationTypes.TYPE_VECTOR, decisionMethod); decision.setYesNode("yes"); decision.setNoNode("no"); expect(decision.make(knowledge)).to.eql("no"); range.updateUpperBound(300); expect(decision.make(knowledge)).to.eql("yes"); }); it("should throw an exception if information type unknown", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.UNKNOWN_INFORMATION, decisionMethod); decision.setYesNode("yes"); decision.setNoNode("no"); var catchedErr = null; try { decision.make(knowledge); }catch (err){ catchedErr = err; } expect(catchedErr).not.to.eql(null); expect(catchedErr.message).to.eql("No such information type."); }); it("should throw an exception if information non existent", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var decisionMethod = new Ego.IsTrue(); var decision = new Ego.Decision("stuff", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); decision.setYesNode("yes"); decision.setNoNode("no"); var catchedErr = null; try { decision.make(knowledge); }catch (err){ catchedErr = err; } expect(catchedErr).not.to.eql(null); expect(catchedErr.message).to.eql("No such information in knowledge: stuff") }); it("should clone", function(){ var decision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var cloned = decision.clone(); expect(decision).to.eql(cloned); expect(decision === cloned).to.eql(false); expect(decision._decisionMethod).to.eql(cloned._decisionMethod); expect(decision._decisionMethod === cloned._decisionMethod).to.eql(false); var decision2 = new Ego.Decision("age", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(new Ego.Range(70, Infinity))); var decision3 = new Ego.Decision("areYouOk", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); decision.setYesNode(decision2); decision.setNoNode(decision3); decision2.setYesNode("You're old."); decision2.setNoNode("You're not old."); decision3.setYesNode("You're ok."); decision3.setNoNode("You're not ok."); cloned = decision.clone(); expect(decision).to.eql(cloned); expect(decision === cloned).to.eql(false); }); }); ================================================ FILE: test/decision-tree/DecisionTreeTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("DecisionTree", function(){ it("should initialize", function(){ var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decisionTree = new Ego.DecisionTree(rootDecision); expect(decisionTree._rootNode).to.equal(rootDecision); }); it("should throw an error on construction if rootNode is not a Decision", function(){ var catchedErr = null; try { var decisionTree = new Ego.DecisionTree("not_a_decision"); }catch(err){ catchedErr = err; } expect(catchedErr).not.to.eql(null); expect(catchedErr.message).to.eql("Root node must be an instance of Ego.Decision") }); it("should clone", function(){ var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decisionTree = new Ego.DecisionTree(rootDecision); var cloned = decisionTree.clone(); expect(decisionTree).to.eql(cloned); expect(decisionTree === cloned).to.eql(false); expect(decisionTree._rootNode).to.eql(cloned._rootNode); expect(decisionTree._rootNode === cloned._rootNode).to.eql(false); }); describe("should make decision", function(){ it("root node only", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); rootDecision.setYesNode("chill."); rootDecision.setNoNode("make it happen."); var decisionTree = new Ego.DecisionTree(rootDecision); expect(decisionTree.makeDecision(knowledge)).to.eql("chill."); knowledge.updateBooleanInformation("isStuffHappening", false); expect(decisionTree.makeDecision(knowledge)).to.eql("make it happen."); }); it("two levels", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); knowledge.addBooleanInformation("doYouWantThis", true); var isTrueMethod = new Ego.IsTrue(); var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); var decision2 = new Ego.Decision("doYouWantThis", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); var decision3 = new Ego.Decision("doYouWantThis", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); decision2.setYesNode("chill"); decision2.setNoNode("stop it"); decision3.setYesNode("chill"); decision3.setNoNode("make it happen"); rootDecision.setYesNode(decision2); rootDecision.setNoNode(decision3); var decisionTree = new Ego.DecisionTree(rootDecision); expect(decisionTree.makeDecision(knowledge)).to.eql("chill"); knowledge.updateBooleanInformation("doYouWantThis", false); expect(decisionTree.makeDecision(knowledge)).to.eql("stop it"); knowledge.updateBooleanInformation("isStuffHappening", false); knowledge.updateBooleanInformation("doYouWantThis", true); expect(decisionTree.makeDecision(knowledge)).to.eql("chill"); knowledge.updateBooleanInformation("doYouWantThis", false); expect(decisionTree.makeDecision(knowledge)).to.eql("make it happen"); }); it("three levels", function(){ var knowledge = new Ego.Knowledge(); knowledge.addNumericalInformation("numberOfFamilyMembers", 4); knowledge.addNumericalInformation("salary", 100000); knowledge.addBooleanInformation("isMarried", true); var familyMemberRange = new Ego.Range(3, Infinity); familyMemberRange.makeLowerBoundInclusive(); var salaryRange1 = new Ego.Range(80000, Infinity); salaryRange1.makeLowerBoundInclusive(); var salaryRange2 = new Ego.Range(40000, Infinity); salaryRange2.makeLowerBoundInclusive(); var decision1 = new Ego.Decision("numberOfFamilyMembers", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(familyMemberRange)); var decision2 = new Ego.Decision("salary", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(salaryRange1)); var decision3 = new Ego.Decision("isMarried", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decision4 = new Ego.Decision("isMarried", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decision5 = new Ego.Decision("salary", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(salaryRange1)); decision1.setYesNode(decision3); decision1.setNoNode(decision2); decision2.setYesNode("4BHK"); decision2.setNoNode(decision4); decision3.setYesNode("3BHK"); decision3.setNoNode(decision5); decision4.setYesNode("3BHK"); decision4.setNoNode("2BHK"); decision5.setYesNode("2BHK"); decision5.setNoNode("1BHK"); var decisionTree = new Ego.DecisionTree(decision1); expect(decisionTree.makeDecision(knowledge)).to.eql("3BHK"); knowledge.updateBooleanInformation("isMarried", false); expect(decisionTree.makeDecision(knowledge)).to.eql("2BHK"); knowledge.updateNumericalInformation("salary", 20000); expect(decisionTree.makeDecision(knowledge)).to.eql("1BHK"); knowledge.updateNumericalInformation("numberOfFamilyMembers", 1); knowledge.updateNumericalInformation("salary", 100000); expect(decisionTree.makeDecision(knowledge)).to.eql("4BHK"); knowledge.updateNumericalInformation("salary", 50000); knowledge.updateBooleanInformation("isMarried", true); expect(decisionTree.makeDecision(knowledge)).to.eql("3BHK"); knowledge.updateBooleanInformation("isMarried", false); expect(decisionTree.makeDecision(knowledge)).to.eql("2BHK"); }); describe("clone", function(){ it("root node only", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); rootDecision.setYesNode("chill."); rootDecision.setNoNode("make it happen."); var decisionTree = new Ego.DecisionTree(rootDecision); var clone = decisionTree.clone(); expect(clone.makeDecision(knowledge)).to.eql("chill."); knowledge.updateBooleanInformation("isStuffHappening", false); expect(clone.makeDecision(knowledge)).to.eql("make it happen."); }); it("two levels", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); knowledge.addBooleanInformation("doYouWantThis", true); var isTrueMethod = new Ego.IsTrue(); var rootDecision = new Ego.Decision("isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); var decision2 = new Ego.Decision("doYouWantThis", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); var decision3 = new Ego.Decision("doYouWantThis", Ego.InformationTypes.TYPE_BOOLEAN, isTrueMethod); decision2.setYesNode("chill"); decision2.setNoNode("stop it"); decision3.setYesNode("chill"); decision3.setNoNode("make it happen"); rootDecision.setYesNode(decision2); rootDecision.setNoNode(decision3); var decisionTree = new Ego.DecisionTree(rootDecision); var clone = decisionTree.clone(); expect(clone.makeDecision(knowledge)).to.eql("chill"); knowledge.updateBooleanInformation("doYouWantThis", false); expect(clone.makeDecision(knowledge)).to.eql("stop it"); knowledge.updateBooleanInformation("isStuffHappening", false); knowledge.updateBooleanInformation("doYouWantThis", true); expect(clone.makeDecision(knowledge)).to.eql("chill"); knowledge.updateBooleanInformation("doYouWantThis", false); expect(clone.makeDecision(knowledge)).to.eql("make it happen"); }); it("three levels", function(){ var knowledge = new Ego.Knowledge(); knowledge.addNumericalInformation("numberOfFamilyMembers", 4); knowledge.addNumericalInformation("salary", 100000); knowledge.addBooleanInformation("isMarried", true); var familyMemberRange = new Ego.Range(3, Infinity); familyMemberRange.makeLowerBoundInclusive(); var salaryRange1 = new Ego.Range(80000, Infinity); salaryRange1.makeLowerBoundInclusive(); var salaryRange2 = new Ego.Range(40000, Infinity); salaryRange2.makeLowerBoundInclusive(); var decision1 = new Ego.Decision("numberOfFamilyMembers", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(familyMemberRange)); var decision2 = new Ego.Decision("salary", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(salaryRange1)); var decision3 = new Ego.Decision("isMarried", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decision4 = new Ego.Decision("isMarried", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var decision5 = new Ego.Decision("salary", Ego.InformationTypes.TYPE_NUMERICAL, new Ego.IsInRange(salaryRange1)); decision1.setYesNode(decision3); decision1.setNoNode(decision2); decision2.setYesNode("4BHK"); decision2.setNoNode(decision4); decision3.setYesNode("3BHK"); decision3.setNoNode(decision5); decision4.setYesNode("3BHK"); decision4.setNoNode("2BHK"); decision5.setYesNode("2BHK"); decision5.setNoNode("1BHK"); var decisionTree = new Ego.DecisionTree(decision1); var clone = decisionTree.clone(); expect(clone.makeDecision(knowledge)).to.eql("3BHK"); knowledge.updateBooleanInformation("isMarried", false); expect(clone.makeDecision(knowledge)).to.eql("2BHK"); knowledge.updateNumericalInformation("salary", 20000); expect(clone.makeDecision(knowledge)).to.eql("1BHK"); knowledge.updateNumericalInformation("numberOfFamilyMembers", 1); knowledge.updateNumericalInformation("salary", 100000); expect(clone.makeDecision(knowledge)).to.eql("4BHK"); knowledge.updateNumericalInformation("salary", 50000); knowledge.updateBooleanInformation("isMarried", true); expect(clone.makeDecision(knowledge)).to.eql("3BHK"); knowledge.updateBooleanInformation("isMarried", false); expect(clone.makeDecision(knowledge)).to.eql("2BHK"); }); }); }); }); ================================================ FILE: test/decision-tree/KnowledgeTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("Knowledge", function(){ it("should initialize", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge._booleanMap).to.eql({}); expect(knowledge._numericalMap).to.eql({}); expect(knowledge._vectorMap).to.eql({}); }); it("should add boolean information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.addBooleanInformation("isStuffHappening", true)).to.eql(true); expect(knowledge.addBooleanInformation("isStuffHappening", true)).to.eql(false); expect(knowledge.addBooleanInformation("isStuffHappening", false)).to.eql(false); expect(knowledge.addBooleanInformation("canSee", true)).to.eql(true); }); it("should add numerical information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.addNumericalInformation("health", 95.7)).to.eql(true); expect(knowledge.addNumericalInformation("health", 100)).to.eql(false); expect(knowledge.addNumericalInformation("ammo", "xx")).to.eql(false); expect(knowledge.addNumericalInformation("ammo", 30)).to.eql(true); expect(knowledge.addNumericalInformation("ammo", 40)).to.eql(false); }); it("should add vector information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.addVectorInformation("distance")).to.eql(false); expect(knowledge.addVectorInformation("distance", 10)).to.eql(false); expect(knowledge.addVectorInformation("distance", 10, 20)).to.eql(false); expect(knowledge.addVectorInformation("distance", 10, 20, 30)).to.eql(true); expect(knowledge.addVectorInformation("distance", 40, 50, 60)).to.eql(false); expect(knowledge.addVectorInformation("speed", 20.1, 30, 70.3)).to.eql(true); expect(knowledge.addVectorInformation("acceleration", "x", 70, "y")).to.eql(false); }); it("should get boolean information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.getBooleanInformation("isStuffHappening")).to.eql(null); knowledge.addBooleanInformation("isStuffHappening", true); expect(knowledge.getBooleanInformation("isStuffHappening")).to.eql(true); }); it("should get numerical information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.getNumericalInformation("health")).to.eql(null); knowledge.addNumericalInformation("health", 93.7); expect(knowledge.getNumericalInformation("health")).to.eql(93.7); }); it("should get vector information", function(){ var knowledge = new Ego.Knowledge(); expect(knowledge.getVectorInformation("velocity")).to.eql(null); knowledge.addVectorInformation("velocity", 100.9, 200.9, 300.9); expect(knowledge.getVectorInformation("velocity")).to.eql({ x: 100.9, y: 200.9, z: 300.9, length: Math.sqrt((100.9 * 100.9) + (200.9 * 200.9) + (300.9 * 300.9)), isVector: true }) }); it("should update boolean information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); expect(knowledge.updateBooleanInformation("isStuffHappening", false)).to.eql(true); expect(knowledge.getBooleanInformation("isStuffHappening")).to.eql(false); expect(knowledge.updateBooleanInformation("doesItWorthIt", true)).to.eql(false); }); it("should update numerical information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addNumericalInformation("health", 100); expect(knowledge.updateNumericalInformation("health", 93.7)).to.eql(true); expect(knowledge.getNumericalInformation("health")).to.eql(93.7); expect(knowledge.updateNumericalInformation("health", "xyz")).to.eql(false); expect(knowledge.getNumericalInformation("health")).to.eql(93.7); expect(knowledge.updateNumericalInformation("power", 94.7)).to.eql(false); }); it("should update vector information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addVectorInformation("speed", 100, 200, 300); expect(knowledge.updateVectorInformation("speed", 100)).to.eql(false); expect(knowledge.updateVectorInformation("speed", 100, 200)).to.eql(false); expect(knowledge.updateVectorInformation("speed", "x", "y", "z")).to.eql(false); expect(knowledge.updateVectorInformation("speed", 90.1, 80.2, 70.3)).to.eql(true); expect(knowledge.getVectorInformation("speed")).to.eql({ x: 90.1, y: 80.2, z: 70.3, length: Math.sqrt((90.1 * 90.1) + (80.2 * 80.2) + (70.3 * 70.3)), isVector: true }); expect(knowledge.updateVectorInformation("notSpeed", 100, 200, 300)).to.eql(false); }); it("should delete boolean information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); expect(knowledge.deleteBooleanInformation("isStuffHappening")).to.eql(true); expect(knowledge.getBooleanInformation("isStuffHappening")).to.eql(null); expect(knowledge.deleteBooleanInformation("isStuffHappening")).to.eql(false); }); it("should delete numerical information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addNumericalInformation("health", 100); expect(knowledge.deleteNumericalInformation("health")).to.eql(true); expect(knowledge.getNumericalInformation("health")).to.eql(null); expect(knowledge.deleteNumericalInformation("health")).to.eql(false); }); it("should delete vector information", function(){ var knowledge = new Ego.Knowledge(); knowledge.addVectorInformation("position", 100, 200, 300); expect(knowledge.deleteVectorInformation("position")).to.eql(true); expect(knowledge.getVectorInformation("position")).to.eql(null); expect(knowledge.deleteVectorInformation("position")).to.eql(false); }); it("should clone", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); knowledge.addNumericalInformation("myAge", 27); knowledge.addVectorInformation("dist", 100, 200, 300.5); var cloned = knowledge.clone(); expect(knowledge.getBooleanInformation("isStuffHappening")).to.eql(cloned.getBooleanInformation("isStuffHappening")); expect(knowledge.getNumericalInformation("myAge")).to.eql(cloned.getNumericalInformation("myAge")); expect(knowledge.getVectorInformation("dist")).to.eql(cloned.getVectorInformation("dist")); expect(knowledge).to.eql(cloned); expect(knowledge === cloned).to.eql(false); }) }); ================================================ FILE: test/decision-tree/decision-methods/DecisionMethodTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../../build/Ego"); describe("DecisionMethod", function(){ it("should initialize", function(){ var decisionMethod = new Ego.DecisionMethod(100); expect(decisionMethod._parameter).to.eql(100); }); it("should update parameter", function(){ var decisionMethod = new Ego.DecisionMethod(100); decisionMethod.updateParameter(200); expect(decisionMethod._parameter).to.eql(200); }); it("should perform", function(){ var decisionMethod = new Ego.DecisionMethod(100); expect(decisionMethod.perform()).to.eql(true); }); it("should clone", function(){ var decisionMethod = new Ego.DecisionMethod(300); var cloned = decisionMethod.clone(); expect(decisionMethod).to.eql(cloned); expect(decisionMethod === cloned).to.eql(false); var called = false; decisionMethod = new Ego.DecisionMethod({clone: function(){ called = true; return this; }}); cloned = decisionMethod.clone(); expect(decisionMethod).to.eql(cloned); expect(decisionMethod === cloned).to.eql(false); expect(called).to.eql(true); }); }); ================================================ FILE: test/decision-tree/decision-methods/IsFalseTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../../build/Ego"); describe("IsFalse", function(){ it("should initialize", function(){ var isFalse = new Ego.IsFalse(); expect(isFalse._parameter).to.eql(null); }); it("should perform", function(){ var isFalse = new Ego.IsFalse(); expect(isFalse.perform(false)).to.eql(true); expect(isFalse.perform(true)).to.eql(false); expect(isFalse.perform(123)).to.eql(false); expect(isFalse.perform({})).to.eql(false); }); it("should clone", function(){ var isFalse = new Ego.IsFalse(); var cloned = isFalse.clone(); expect(cloned instanceof Ego.IsFalse).to.eql(true); expect(isFalse).to.eql(cloned); expect(isFalse === cloned).to.eql(false); }); }); ================================================ FILE: test/decision-tree/decision-methods/IsInRangeTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../../build/Ego"); describe("IsInRange", function(){ it("should initialize", function(){ var range = new Ego.Range(10, 30); var isInRange = new Ego.IsInRange(range); expect(isInRange._parameter).to.equal(range); }); it("should perform for numbers", function(){ var range = new Ego.Range(10, 30); var isInRange = new Ego.IsInRange(range); expect(isInRange.perform(25)).to.equal(true); expect(isInRange.perform(45)).to.equal(false); expect(isInRange.perform(-10)).to.equal(false); }); it("should perform for vectors", function(){ var range = new Ego.Range(10, 30); var vec1 = {isVector: true, length: 25}; var vec2 = {isVector: true, length: 45}; var vec3 = {isVector: true, length: -10}; var isInRange = new Ego.IsInRange(range); expect(isInRange.perform(vec1)).to.equal(true); expect(isInRange.perform(vec2)).to.equal(false); expect(isInRange.perform(vec3)).to.equal(false); }); it("should clone", function(){ var range = new Ego.Range(10, 30); var isInRange = new Ego.IsInRange(range); var cloned = isInRange.clone(); expect(cloned instanceof Ego.IsInRange).to.eql(true); expect(cloned._parameter).to.eql(range); expect(cloned._parameter === range).to.eql(false); expect(isInRange).to.eql(cloned); expect(isInRange === cloned).to.eql(false); }); }); ================================================ FILE: test/decision-tree/decision-methods/IsTrueTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../../build/Ego"); describe("IsTrue", function(){ it("should initialize", function(){ var isTrue = new Ego.IsTrue(); expect(isTrue._parameter).to.eql(null); }); it("should perform", function(){ var isTrue = new Ego.IsTrue(); expect(isTrue.perform(true)).to.eql(true); expect(isTrue.perform(false)).to.eql(false); expect(isTrue.perform(123)).to.eql(false); expect(isTrue.perform({})).to.eql(false); }); it("should clone", function(){ var isTrue = new Ego.IsTrue(); var cloned = isTrue.clone(); expect(cloned instanceof Ego.IsTrue).to.eql(true); expect(isTrue).to.eql(cloned); expect(isTrue === cloned).to.eql(false); }); }); ================================================ FILE: test/decision-tree/math/RangeTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../../build/Ego"); describe("Range", function(){ it("should initialize", function(){ var range = new Ego.Range(20, 30); expect(range._isUpperBoundInclusive).to.eql(true); expect(range._isLowerBoundInclusive).to.eql(true); expect(range._upperBound).to.eql(30); expect(range._lowerBound).to.eql(20); }); it("should make lower bound exclusive", function(){ var range = new Ego.Range(20, 30); range.makeLowerBoundExclusive(); expect(range._isLowerBoundInclusive).to.eql(false); }); it("should make lower bound inclusive", function(){ var range = new Ego.Range(20, 30); range.makeLowerBoundExclusive(); range.makeLowerBoundInclusive(); expect(range._isLowerBoundInclusive).to.eql(true); }); it("should make upper bound exclusive", function(){ var range = new Ego.Range(20, 30); range.makeUpperBoundExclusive(); expect(range._isUpperBoundInclusive).to.eql(false); }); it("should make upper bound inclusive", function(){ var range = new Ego.Range(20, 30); range.makeUpperBoundExclusive(); range.makeUpperBoundInclusive(); expect(range._isUpperBoundInclusive).to.eql(true); }); it("should update lower bound", function(){ var range = new Ego.Range(20, 30); range.updateLowerBound(-100); expect(range._lowerBound).to.eql(-100); }); it("should update upper bound", function(){ var range = new Ego.Range(20, 30); range.updateUpperBound(300); expect(range._upperBound).to.eql(300); }); it("should test number", function(){ var range = new Ego.Range(20, 30); expect(range.testNumber(10)).to.eql(false); expect(range.testNumber(25)).to.eql(true); expect(range.testNumber(20)).to.eql(true); expect(range.testNumber(30)).to.eql(true); expect(range.testNumber(-10)).to.eql(false); expect(range.testNumber(40)).to.eql(false); range.makeLowerBoundExclusive(); expect(range.testNumber(20)).to.eql(false); range.makeUpperBoundExclusive(); expect(range.testNumber(30)).to.eql(false); range.updateLowerBound(-100); range.updateUpperBound(300); expect(range.testNumber(200)).to.eql(true); expect(range.testNumber(-85)).to.eql(true); }); it("should clone", function(){ var range1 = new Ego.Range(100, 300); var range2 = new Ego.Range(-100, 500); var range3 = new Ego.Range(200, 3000); var range4 = new Ego.Range(-12.5, 13.5); range1.makeLowerBoundExclusive(); range1.makeUpperBoundExclusive(); range2.makeLowerBoundExclusive(); range2.makeUpperBoundInclusive(); range3.makeLowerBoundInclusive(); range3.makeUpperBoundExclusive(); range4.makeLowerBoundInclusive(); range4.makeUpperBoundInclusive(); var range1Clone = range1.clone(); var range2Clone = range2.clone(); var range3Clone = range3.clone(); var range4Clone = range4.clone(); expect(range1).to.eql(range1Clone); expect(range1 === range1Clone).to.eql(false); expect(range2).to.eql(range2Clone); expect(range2 === range2Clone).to.eql(false); expect(range3).to.eql(range3Clone); expect(range3 === range3Clone).to.eql(false); expect(range4).to.eql(range4Clone); expect(range4 === range4Clone).to.eql(false); }); }); ================================================ FILE: test/state-machine/StateMachineTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("StateMachine", function(){ it("should initialize", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); expect(stateMachine instanceof Ego.State).to.eql(true); expect(stateMachine._knowledge).to.eql(knowledge); expect(stateMachine._statesByID).to.eql({}); expect(stateMachine._transitionsByStateID).to.eql({}); expect(stateMachine._entryState).to.eql(null); expect(stateMachine._currentState).to.eql(null); expect(stateMachine._stateChangedCallbackFunction).to.eql(null); expect(stateMachine._name).to.eql("stateMachine1"); expect(stateMachine._id).to.have.length(36); expect(stateMachine._parent).to.eql(null); }); it("should add state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine1 = new Ego.StateMachine("stateMachine1", knowledge); var stateMachine2 = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("idle"); var id1 = state1.getID(); var id2 = state2.getID(); var obj = {}; var obj2 = {}; obj[id1] = state1; obj2[id1] = []; expect(stateMachine1.addState(state1)).to.eql(true); expect(stateMachine1._statesByID).to.eql(obj); expect(stateMachine1._transitionsByStateID).to.eql(obj2); obj[id2] = state2; obj2[id2] = []; expect(stateMachine1.addState(state2)).to.eql(true); expect(stateMachine1._statesByID).to.eql(obj); expect(stateMachine1._transitionsByStateID).to.eql(obj2); expect(stateMachine1.addState(state1)).to.eql(false); expect(stateMachine1.addState(state2)).to.eql(false); expect(stateMachine1._statesByID).to.eql(obj); expect(stateMachine1._transitionsByStateID).to.eql(obj2); expect(stateMachine2.addState(state1)).to.eql(false); expect(stateMachine2.addState(state2)).to.eql(false); expect(stateMachine2._statesByID).to.eql({}); expect(stateMachine2._transitionsByStateID).to.eql({}); }); it("should remove state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state = new Ego.State("idle"); expect(stateMachine.removeState(state)).to.eql(false); stateMachine.addState(state); expect(stateMachine.removeState(state)).to.eql(true); expect(stateMachine._statesByID).to.eql({}); expect(stateMachine._transitionsByStateID).to.eql({}); }); it("should not add state if the added state contains the state", function(){ var knowledge = new Ego.Knowledge(); var sm1 = new Ego.StateMachine("sm1", knowledge); var sm2 = new Ego.StateMachine("sm2", knowledge); var sm3 = new Ego.StateMachine("sm3", knowledge); sm2.addState(sm1); expect(sm1.addState(sm2)).to.eql(false); sm1.addState(sm3); expect(sm3.addState(sm2)).to.eql(false); }) it("should check if has state", function(){ var knowledge = new Ego.Knowledge(); var sm1 = new Ego.StateMachine("sm1", knowledge); var sm2 = new Ego.StateMachine("sm2", knowledge); var state1 = new Ego.StateMachine("state1"); var state2 = new Ego.StateMachine("state2"); expect(sm1.hasState(sm1)).to.eql(true); expect(sm1.hasState(state1)).to.eql(false); expect(sm1.hasState(sm2)).to.eql(false); sm1.addState(state1); expect(sm1.hasState(state1)).to.eql(true); sm1.addState(sm2); expect(sm1.hasState(sm2)).to.eql(true); sm2.addState(state2); expect(sm2.hasState(state2)).to.eql(true); expect(sm1.hasState(state2)).to.eql(true); var sm3 = new Ego.StateMachine("sm3", knowledge); var state3 = new Ego.State("state3"); sm3.addState(state3); sm2.addState(sm3); expect(sm1.hasState(state3)).to.eql(true); }); it("should add transition", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); var state3 = new Ego.State("jumping"); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var transition2 = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var transition3 = new Ego.Transition(state1, state3, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); expect(stateMachine.addTransition(transition)).to.eql(false); stateMachine.addState(state1); var obj = {}; obj[state1.getID()] = [transition]; expect(stateMachine.addTransition(transition)).to.eql(true); expect(stateMachine._transitionsByStateID).to.eql(obj); expect(stateMachine.addTransition(transition)).to.eql(false); expect(stateMachine._transitionsByStateID).to.eql(obj); expect(stateMachine.addTransition(transition2)).to.eql(false); expect(stateMachine._transitionsByStateID).to.eql(obj); obj[state1.getID()].push(transition3); expect(stateMachine.addTransition(transition3)).to.eql(true); expect(stateMachine._transitionsByStateID).to.eql(obj); }); it("should remove transition", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); var state3 = new Ego.State("jumping"); var transition1 = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var transition2 = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var transition3 = new Ego.Transition(state1, state3, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); stateMachine.addState(state1); expect(stateMachine.removeTransition(transition1)).to.eql(false); expect(stateMachine.removeTransition(transition2)).to.eql(false); expect(stateMachine.removeTransition(transition3)).to.eql(false); stateMachine.addTransition(transition1); stateMachine.addTransition(transition3); var obj = {}; obj[state1.getID()] = [transition3]; expect(stateMachine.removeTransition(transition1)).to.eql(true); expect(stateMachine._transitionsByStateID).to.eql(obj); stateMachine.addTransition(transition1); obj = {}; obj[state1.getID()] = [transition1]; expect(stateMachine.removeTransition(transition3)).to.eql(true); expect(stateMachine._transitionsByStateID).to.eql(obj); obj = {}; obj[state1.getID()] = []; expect(stateMachine.removeTransition(transition1)).to.eql(true); expect(stateMachine._transitionsByStateID).to.eql(obj); }); it("should set entry state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("moving"); expect(stateMachine.setEntryState(state1)).to.eql(false); expect(stateMachine._entryState).to.eql(null); stateMachine.addState(state1); expect(stateMachine.setEntryState(state1)).to.eql(true); expect(stateMachine._entryState).to.eql(state1); }); it("should unset entry state if the removed state is the entry state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state = new Ego.State("idle"); stateMachine.addState(state); stateMachine.setEntryState(state); expect(stateMachine._entryState).to.eql(state); stateMachine.removeState(state); expect(stateMachine._entryState).to.eql(null); }); it("should set state change callback function", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var fn = function(newState){ console.log("State changed: ", newState); }; stateMachine.onStateChanged(fn); expect(stateMachine._stateChangedCallbackFunction).to.eql(fn); }); it("should throw an error if updating without an entry state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var catched = null; try{ stateMachine.update(); }catch (err){ catched = err; } expect(catched).not.to.eql(null); expect(catched.message).to.eql("Entry state not set. Cannot update the StateMachine."); }); it("should update", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", false); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addTransition(new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.setEntryState(state1); expect(stateMachine._currentState).to.eql(null); stateMachine.update(); expect(stateMachine._currentState).to.eql(state1); knowledge.updateBooleanInformation("isStuffHappening", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(state2); stateMachine.update(); expect(stateMachine._currentState).to.eql(state2); }); it("should invoke state change callback function", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addTransition(new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.setEntryState(state1); var param = null; stateMachine.onStateChanged(function(newState){ param = newState; }); stateMachine.update(); expect(param).to.eql(state2); }); it("should reset", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addTransition(new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.setEntryState(state1); stateMachine.update(); expect(stateMachine._currentState).to.eql(state2); stateMachine.reset(); expect(stateMachine._currentState).to.eql(null); stateMachine.update(); expect(stateMachine._currentState).to.eql(state2); stateMachine.reset(); knowledge.updateBooleanInformation("isStuffHappening", false); stateMachine.update(); expect(stateMachine._currentState).to.eql(state1); }); it("should update recursively", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); knowledge.addBooleanInformation("shouldJump", true); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); var state3 = new Ego.State("jumping"); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addState(state3); stateMachine.addTransition(new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.addTransition(new Ego.Transition(state2, state3, "shouldJump", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.setEntryState(state1); stateMachine.update(); expect(stateMachine._currentState).to.eql(state3); stateMachine.reset(); knowledge.updateBooleanInformation("shouldJump", false); stateMachine.update(); expect(stateMachine._currentState).to.eql(state2); }); it("should invoke state change callback function on recursive updates", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); knowledge.addBooleanInformation("shouldJump", true); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state1 = new Ego.State("idle"); var state2 = new Ego.State("moving"); var state3 = new Ego.State("jumping"); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addState(state3); stateMachine.addTransition(new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.addTransition(new Ego.Transition(state2, state3, "shouldJump", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); stateMachine.setEntryState(state1); var currentStates = []; stateMachine.onStateChanged(function(newState){ currentStates.push(newState); }); stateMachine.update(); expect(currentStates).to.eql([state1, state2, state3]); }); it("should update hierarchically", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("trashSeen", false); knowledge.addBooleanInformation("itemGot", false); knowledge.addBooleanInformation("trashDisposed", false); knowledge.addBooleanInformation("noPower", false); knowledge.addBooleanInformation("recharged", false); var decisionMethod = new Ego.IsTrue(); var stateMachine = new Ego.StateMachine("stateMachine", knowledge); var stateMachine2 = new Ego.StateMachine("cleanUp", knowledge); var state1 = new Ego.State("search"); var state2 = new Ego.State("headForTrash"); var state3 = new Ego.State("headForCompactor"); var state4 = new Ego.State("getPower"); stateMachine2.addState(state1); stateMachine2.addState(state2); stateMachine2.addState(state3); stateMachine.addState(stateMachine2); stateMachine.addState(state4); var transition1 = new Ego.Transition(state1, state2, "trashSeen", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var transition2 = new Ego.Transition(state2, state3, "itemGot", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var transition3 = new Ego.Transition(state3, state1, "trashDisposed", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(stateMachine2.addTransition(transition1)).to.eql(true); expect(stateMachine2.addTransition(transition2)).to.eql(true); expect(stateMachine2.addTransition(transition3)).to.eql(true); var transition4 = new Ego.Transition(stateMachine2, state4, "noPower", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var transition5 = new Ego.Transition(state4, stateMachine2, "recharged", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(stateMachine.addTransition(transition4)).to.eql(true); expect(stateMachine.addTransition(transition5)).to.eql(true); expect(stateMachine.setEntryState(stateMachine2)).to.eql(true); expect(stateMachine2.setEntryState(state1)).to.eql(true); expect(stateMachine._currentState).to.eql(null); expect(stateMachine2._currentState).to.eql(null); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state1); knowledge.updateBooleanInformation("trashSeen", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state2); knowledge.updateBooleanInformation("trashSeen", false); knowledge.updateBooleanInformation("itemGot", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state3); knowledge.updateBooleanInformation("itemGot", false); knowledge.updateBooleanInformation("trashDisposed", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state1); knowledge.updateBooleanInformation("trashDisposed", false); knowledge.updateBooleanInformation("trashSeen", true); knowledge.updateBooleanInformation("itemGot", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state3); knowledge.updateBooleanInformation("noPower", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(state4); expect(stateMachine2._currentState).to.eql(null); knowledge.updateBooleanInformation("trashSeen", true); knowledge.updateBooleanInformation("itemGot", true); knowledge.updateBooleanInformation("trashDisposed", false); stateMachine.update(); expect(stateMachine._currentState).to.eql(state4); expect(stateMachine2._currentState).to.eql(null); knowledge.updateBooleanInformation("noPower", false); knowledge.updateBooleanInformation("recharged", true); stateMachine.update(); expect(stateMachine._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state3); }); it("should not invoke state change callback function on cross-hierarchy transitions", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("seenTrash", false); var childStateMachine = new Ego.StateMachine("childStateMachine", knowledge); var parentStateMachine = new Ego.StateMachine("parentStateMachine", knowledge); var state1 = new Ego.State("search"); var state2 = new Ego.State("headForTrash"); var state3 = new Ego.State("getPower"); childStateMachine.addState(state1); childStateMachine.addState(state2); childStateMachine.setEntryState(state1); parentStateMachine.addState(childStateMachine); parentStateMachine.addState(state3); parentStateMachine.setEntryState(childStateMachine); childStateMachine.addTransition(new Ego.Transition(state1, state2, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); childStateMachine.addTransition(new Ego.Transition(state1, state3, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsFalse())); var callCount = 0; var lastState = null; childStateMachine.onStateChanged(function(newState){ callCount ++; lastState = newState; }); parentStateMachine.update(); expect(callCount).to.eql(1); expect(lastState).to.eql(state1); }); it("should update the current node of the other state machine on cross-hierarchy transitions", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("seenTrash", false); var childStateMachine = new Ego.StateMachine("childStateMachine", knowledge); var parentStateMachine = new Ego.StateMachine("parentStateMachine", knowledge); var state1 = new Ego.State("search"); var state2 = new Ego.State("headForTrash"); var state3 = new Ego.State("getPower"); childStateMachine.addState(state1); childStateMachine.addState(state2); childStateMachine.setEntryState(state1); parentStateMachine.addState(childStateMachine); parentStateMachine.addState(state3); parentStateMachine.setEntryState(childStateMachine); childStateMachine.addTransition(new Ego.Transition(state1, state2, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); childStateMachine.addTransition(new Ego.Transition(state1, state3, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsFalse())); var states = []; parentStateMachine.onStateChanged(function(newState){ states.push(newState); }); parentStateMachine.update(); expect(parentStateMachine._currentState).to.eql(state3); expect(states).to.eql([childStateMachine, state3]); }); it("should reset on cross-hierarchy transitions", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("seenTrash", false); var childStateMachine = new Ego.StateMachine("childStateMachine", knowledge); var parentStateMachine = new Ego.StateMachine("parentStateMachine", knowledge); var state1 = new Ego.State("search"); var state2 = new Ego.State("headForTrash"); var state3 = new Ego.State("getPower"); childStateMachine.addState(state1); childStateMachine.addState(state2); childStateMachine.setEntryState(state1); parentStateMachine.addState(childStateMachine); parentStateMachine.addState(state3); parentStateMachine.setEntryState(childStateMachine); childStateMachine.addTransition(new Ego.Transition(state1, state2, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); childStateMachine.addTransition(new Ego.Transition(state1, state3, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsFalse())); var stateChanged = false; childStateMachine.onStateChanged(function(state){ stateChanged = true; }); parentStateMachine.update(); expect(stateChanged).to.eql(true); expect(childStateMachine._currentState).to.eql(null); }); it("should keep updating the switched state machine after cross-hierarchy transition", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("seenTrash", false); knowledge.addBooleanInformation("isStuffHappening", true); var childStateMachine = new Ego.StateMachine("childStateMachine", knowledge); var parentStateMachine = new Ego.StateMachine("parentStateMachine", knowledge); var state1 = new Ego.State("search"); var state2 = new Ego.State("headForTrash"); var state3 = new Ego.State("getPower"); var state4 = new Ego.State("doStuff"); childStateMachine.addState(state1); childStateMachine.addState(state2); childStateMachine.setEntryState(state1); parentStateMachine.addState(childStateMachine); parentStateMachine.addState(state3); parentStateMachine.addState(state4); parentStateMachine.setEntryState(childStateMachine); childStateMachine.addTransition(new Ego.Transition(state1, state2, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); childStateMachine.addTransition(new Ego.Transition(state1, state3, "seenTrash", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsFalse())); parentStateMachine.addTransition(new Ego.Transition(state3, state4, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue())); parentStateMachine.update(); expect(parentStateMachine._currentState).to.eql(state4); }); it("should throw an error if trying to remove the active state", function(){ var knowledge = new Ego.Knowledge(); var stateMachine = new Ego.StateMachine("stateMachine1", knowledge); var state = new Ego.State("state1"); stateMachine.addState(state); stateMachine.setEntryState(state); stateMachine.update(); expect(stateMachine._currentState).to.eql(state); var thrownError = null try{ stateMachine.removeState(state); }catch (err){ thrownError = err; } expect(thrownError).not.to.eql(null); expect(thrownError.message).to.eql("Cannot remove the active state."); }); it("should not crash if target node has no parent", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var stateMachine = new Ego.StateMachine("sm1", knowledge); stateMachine.addState(state1); stateMachine.setEntryState(state1); stateMachine.addTransition(transition); var stateChangeCount = 0; stateMachine.onStateChanged(function(state){ stateChangeCount++; }); stateMachine.update(); expect(stateChangeCount).to.eql(1); expect(stateMachine._currentState).to.eql(null); }); it("should clone", function(){ var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", false); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var stateMachine = new Ego.StateMachine("sm1", knowledge); stateMachine.addState(state1); stateMachine.addState(state2); stateMachine.addTransition(transition); stateMachine.setEntryState(state1); var cloned = stateMachine.clone(); expect(stateMachine.getName()).to.eql(cloned.getName()); expect(stateMachine._knowledge === cloned._knowledge).to.eql(false); cloned.update(); expect(cloned._currentState.getName()).to.eql("state1"); cloned._knowledge.updateBooleanInformation("isStuffHappening", true); cloned.update(); expect(cloned._currentState.getName()).to.eql("state2"); cloned = stateMachine.clone(knowledge); expect(stateMachine._knowledge === cloned._knowledge).to.eql(true); }); describe("integration", function(){ it("case#1", function(){ var isTrue = new Ego.IsTrue(); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("info1", false); knowledge.addBooleanInformation("info2", false); knowledge.addBooleanInformation("info3", false); knowledge.addBooleanInformation("info4", false); knowledge.addBooleanInformation("info5", false); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var state3 = new Ego.State("state3"); var state4 = new Ego.State("state4"); var state5 = new Ego.State("state5"); var stateMachine1 = new Ego.StateMachine("stateMachine1", knowledge); var stateMachine2 = new Ego.StateMachine("stateMachine2", knowledge); var stateMachine3 = new Ego.StateMachine("stateMachine3", knowledge); stateMachine1.addState(stateMachine2); stateMachine1.addState(state3); stateMachine1.addTransition(new Ego.Transition(stateMachine2, state3, "info2", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine1.setEntryState(stateMachine2); stateMachine2.addState(state1); stateMachine2.addState(state2); stateMachine2.addTransition(new Ego.Transition(state1, state2, "info1", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine2.addTransition(new Ego.Transition(state2, state4, "info3", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine2.setEntryState(state1); stateMachine3.addState(stateMachine1); stateMachine3.addState(state4); stateMachine3.addState(state5); stateMachine3.addTransition(new Ego.Transition(state4, state5, "info4", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine3.addTransition(new Ego.Transition(state5, stateMachine1, "info5", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine3.setEntryState(stateMachine1); stateMachine3.update(); expect(stateMachine3._currentState).to.eql(stateMachine1); expect(stateMachine1._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state1); knowledge.updateBooleanInformation("info1", true); stateMachine3.update(); expect(stateMachine3._currentState).to.eql(stateMachine1); expect(stateMachine1._currentState).to.eql(stateMachine2); expect(stateMachine2._currentState).to.eql(state2); knowledge.updateBooleanInformation("info3", true); stateMachine3.update(); expect(stateMachine3._currentState).to.eql(state4); expect(stateMachine1._currentState).to.eql(null); expect(stateMachine2._currentState).to.eql(null); knowledge.updateBooleanInformation("info4", true); stateMachine3.update(); expect(stateMachine3._currentState).to.eql(state5); expect(stateMachine1._currentState).to.eql(null); expect(stateMachine2._currentState).to.eql(null); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info2", true); stateMachine3.update(); expect(stateMachine3._currentState).to.eql(stateMachine1); expect(stateMachine1._currentState).to.eql(state3); expect(stateMachine2._currentState).to.eql(null); }); it("case#2", function(){ var isTrue = new Ego.IsTrue(); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("info1", false); knowledge.addBooleanInformation("info2", false); knowledge.addBooleanInformation("info3", false); knowledge.addBooleanInformation("info4", false); knowledge.addBooleanInformation("info5", false); knowledge.addBooleanInformation("info6", false); knowledge.addBooleanInformation("info7", false); knowledge.addBooleanInformation("info8", false); knowledge.addBooleanInformation("info9", false); knowledge.addBooleanInformation("info10", false); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var state3 = new Ego.State("state3"); var state4 = new Ego.State("state4"); var state5 = new Ego.State("state5"); var state6 = new Ego.State("state6"); var state7 = new Ego.State("state7"); var sm1 = new Ego.StateMachine("sm1", knowledge); var sm2 = new Ego.StateMachine("sm2", knowledge); var sm3 = new Ego.StateMachine("sm3", knowledge); var transition1 = new Ego.Transition(state1, state2, "info1", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition2 = new Ego.Transition(state2, state4, "info2", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition3 = new Ego.Transition(state2, state3, "info3", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition4 = new Ego.Transition(state3, sm2, "info4", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition5 = new Ego.Transition(state6, state7, "info5", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition6 = new Ego.Transition(state7, state4, "info6", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition7 = new Ego.Transition(state4, state5, "info7", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition8 = new Ego.Transition(state5, sm1, "info8", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition9 = new Ego.Transition(sm1, sm2, "info9", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition10 = new Ego.Transition(sm2, sm1, "info10", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); sm1.addState(state1); sm1.addState(state2); sm1.addState(state3); sm1.addTransition(transition1); sm1.addTransition(transition2); sm1.addTransition(transition3); sm1.addTransition(transition4); sm1.setEntryState(state1); sm2.addState(state6); sm2.addState(state7); sm2.addTransition(transition5); sm2.addTransition(transition6); sm2.setEntryState(state6); sm3.addState(sm1); sm3.addState(sm2); sm3.addState(state4); sm3.addState(state5); sm3.addTransition(transition9); sm3.addTransition(transition10); sm3.addTransition(transition7); sm3.addTransition(transition8); sm3.setEntryState(sm1); sm3.update(); expect(sm3._currentState).to.eql(sm1); expect(sm1._currentState).to.eql(state1); expect(sm2._currentState).to.eql(null); knowledge.updateBooleanInformation("info9", true); sm3.update(); expect(sm3._currentState).to.eql(sm2); expect(sm1._currentState).to.eql(null); expect(sm2._currentState).to.eql(state6); knowledge.updateBooleanInformation("info9", false); knowledge.updateBooleanInformation("info10", true); sm3.update(); expect(sm3._currentState).to.eql(sm1); expect(sm1._currentState).to.eql(state1); expect(sm2._currentState).to.eql(null); knowledge.updateBooleanInformation("info10", false); knowledge.updateBooleanInformation("info9", true); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info6", true); knowledge.updateBooleanInformation("info7", true); sm3.update(); expect(sm3._currentState).to.eql(state5); expect(sm1._currentState).to.eql(null); expect(sm2._currentState).to.eql(null); knowledge.updateBooleanInformation("info7", false); knowledge.updateBooleanInformation("info8", true); knowledge.updateBooleanInformation("info1", true); knowledge.updateBooleanInformation("info2", true); sm3.update(); expect(sm3._currentState).to.eql(state4); expect(sm1._currentState).to.eql(null); expect(sm2._currentState).to.eql(null); knowledge.updateBooleanInformation("info1", false); knowledge.updateBooleanInformation("info2", false); knowledge.updateBooleanInformation("info3", false); knowledge.updateBooleanInformation("info4", false); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info6", false); knowledge.updateBooleanInformation("info7", true); knowledge.updateBooleanInformation("info8", true); knowledge.updateBooleanInformation("info9", true); knowledge.updateBooleanInformation("info10", false); sm3.update(); expect(sm3._currentState).to.eql(sm2); expect(sm1._currentState).to.eql(null); expect(sm2._currentState).to.eql(state7); }); it("clone#1", function(){ var isTrue = new Ego.IsTrue(); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("info1", false); knowledge.addBooleanInformation("info2", false); knowledge.addBooleanInformation("info3", false); knowledge.addBooleanInformation("info4", false); knowledge.addBooleanInformation("info5", false); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var state3 = new Ego.State("state3"); var state4 = new Ego.State("state4"); var state5 = new Ego.State("state5"); var stateMachine1 = new Ego.StateMachine("stateMachine1", knowledge); var stateMachine2 = new Ego.StateMachine("stateMachine2", knowledge); var stateMachine3 = new Ego.StateMachine("stateMachine3", knowledge); stateMachine1.addState(stateMachine2); stateMachine1.addState(state3); stateMachine1.addTransition(new Ego.Transition(stateMachine2, state3, "info2", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine1.setEntryState(stateMachine2); stateMachine2.addState(state1); stateMachine2.addState(state2); stateMachine2.addTransition(new Ego.Transition(state1, state2, "info1", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine2.addTransition(new Ego.Transition(state2, state4, "info3", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine2.setEntryState(state1); stateMachine3.addState(stateMachine1); stateMachine3.addState(state4); stateMachine3.addState(state5); stateMachine3.addTransition(new Ego.Transition(state4, state5, "info4", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine3.addTransition(new Ego.Transition(state5, stateMachine1, "info5", Ego.InformationTypes.TYPE_BOOLEAN, isTrue)); stateMachine3.setEntryState(stateMachine1); var cloned = stateMachine3.clone(knowledge); cloned.update(); expect(cloned._currentState.getName()).to.eql(stateMachine1.getName()); knowledge.updateBooleanInformation("info1", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(stateMachine1.getName()); knowledge.updateBooleanInformation("info3", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(state4.getName()); knowledge.updateBooleanInformation("info4", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(state5.getName()); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info2", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(stateMachine1.getName()); }); it("clone#2", function(){ var isTrue = new Ego.IsTrue(); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("info1", false); knowledge.addBooleanInformation("info2", false); knowledge.addBooleanInformation("info3", false); knowledge.addBooleanInformation("info4", false); knowledge.addBooleanInformation("info5", false); knowledge.addBooleanInformation("info6", false); knowledge.addBooleanInformation("info7", false); knowledge.addBooleanInformation("info8", false); knowledge.addBooleanInformation("info9", false); knowledge.addBooleanInformation("info10", false); var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var state3 = new Ego.State("state3"); var state4 = new Ego.State("state4"); var state5 = new Ego.State("state5"); var state6 = new Ego.State("state6"); var state7 = new Ego.State("state7"); var sm1 = new Ego.StateMachine("sm1", knowledge); var sm2 = new Ego.StateMachine("sm2", knowledge); var sm3 = new Ego.StateMachine("sm3", knowledge); var transition1 = new Ego.Transition(state1, state2, "info1", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition2 = new Ego.Transition(state2, state4, "info2", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition3 = new Ego.Transition(state2, state3, "info3", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition4 = new Ego.Transition(state3, sm2, "info4", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition5 = new Ego.Transition(state6, state7, "info5", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition6 = new Ego.Transition(state7, state4, "info6", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition7 = new Ego.Transition(state4, state5, "info7", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition8 = new Ego.Transition(state5, sm1, "info8", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition9 = new Ego.Transition(sm1, sm2, "info9", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); var transition10 = new Ego.Transition(sm2, sm1, "info10", Ego.InformationTypes.TYPE_BOOLEAN, isTrue); sm1.addState(state1); sm1.addState(state2); sm1.addState(state3); sm1.addTransition(transition1); sm1.addTransition(transition2); sm1.addTransition(transition3); sm1.addTransition(transition4); sm1.setEntryState(state1); sm2.addState(state6); sm2.addState(state7); sm2.addTransition(transition5); sm2.addTransition(transition6); sm2.setEntryState(state6); sm3.addState(sm1); sm3.addState(sm2); sm3.addState(state4); sm3.addState(state5); sm3.addTransition(transition9); sm3.addTransition(transition10); sm3.addTransition(transition7); sm3.addTransition(transition8); sm3.setEntryState(sm1); var cloned = sm3.clone(knowledge); cloned.update(); expect(cloned._currentState.getName()).to.eql(sm1.getName()); knowledge.updateBooleanInformation("info9", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(sm2.getName()); knowledge.updateBooleanInformation("info9", false); knowledge.updateBooleanInformation("info10", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(sm1.getName()); knowledge.updateBooleanInformation("info10", false); knowledge.updateBooleanInformation("info9", true); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info6", true); knowledge.updateBooleanInformation("info7", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(state5.getName()); knowledge.updateBooleanInformation("info7", false); knowledge.updateBooleanInformation("info8", true); knowledge.updateBooleanInformation("info1", true); knowledge.updateBooleanInformation("info2", true); cloned.update(); expect(cloned._currentState.getName()).to.eql(state4.getName()); knowledge.updateBooleanInformation("info1", false); knowledge.updateBooleanInformation("info2", false); knowledge.updateBooleanInformation("info3", false); knowledge.updateBooleanInformation("info4", false); knowledge.updateBooleanInformation("info5", true); knowledge.updateBooleanInformation("info6", false); knowledge.updateBooleanInformation("info7", true); knowledge.updateBooleanInformation("info8", true); knowledge.updateBooleanInformation("info9", true); knowledge.updateBooleanInformation("info10", false); cloned.update(); expect(cloned._currentState.getName()).to.eql(sm2.getName()); }); }); }); ================================================ FILE: test/state-machine/StateTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("State", function(){ it("should initialize", function(){ var state = new Ego.State("idle"); expect(state._name).to.eql("idle"); expect(state._id).to.have.length(36); expect(state._parent).to.eql(null); }); it("should set parent", function(){ var state = new Ego.State("idle"); var parent = new Ego.State("parent"); expect(state.setParent(parent)).to.eql(true); expect(state._parent).to.eql(parent); var parent2 = new Ego.State("parent2"); expect(state.setParent(parent2)).to.eql(false); expect(state._parent).to.eql(parent); }); it("should get parent", function(){ var state = new Ego.State("idle"); var parent = new Ego.State("parent"); state.setParent(parent); expect(state.getParent()).to.eql(parent); }); it("should removeParent", function(){ var state = new Ego.State("idle"); var parent = new Ego.State("parent"); state.setParent(parent); state.removeParent(); expect(state.getParent()).to.eql(null); }); it("should get ID", function(){ var state = new Ego.State("idle"); expect(state.getID()).to.eql(state._id); expect(state.getID()).to.have.length(36); }); it("should get name", function(){ var state = new Ego.State("testState"); expect(state.getName()).to.eql("testState"); }); it("should clone", function(){ var state = new Ego.State("state1"); var cloned = state.clone(); expect(state.getName()).to.eql(cloned.getName()); expect(state === cloned).to.eql(false); }) }); ================================================ FILE: test/state-machine/TransitionTest.js ================================================ var expect = require('expect.js'); var Ego = require("../../build/Ego"); describe("Transition", function(){ it("should initialize", function(){ var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var decisionMethod = new Ego.IsTrue(); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(transition instanceof Ego.Decision).to.eql(true); expect(transition._informationName).to.eql("isStuffHappening"); expect(transition._informationType).to.eql(Ego.InformationTypes.TYPE_BOOLEAN); expect(transition._decisionMethod).to.eql(decisionMethod); expect(transition._yesNode).to.eql(state2); expect(transition._noNode).to.eql(state1); }); it("should get source node", function(){ var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var decisionMethod = new Ego.IsTrue(); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(transition.getSourceNode()).to.eql(state1); }); it("should get target node", function(){ var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var decisionMethod = new Ego.IsTrue(); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); expect(transition.getTargetNode()).to.eql(state2); }); it("should check if possible", function(){ var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var decisionMethod = new Ego.IsTrue(); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, decisionMethod); var knowledge = new Ego.Knowledge(); knowledge.addBooleanInformation("isStuffHappening", true); expect(transition.isPossible(knowledge)).to.eql(true); knowledge.updateBooleanInformation("isStuffHappening", false); expect(transition.isPossible(knowledge)).to.eql(false); }); it("should clone", function(){ var state1 = new Ego.State("state1"); var state2 = new Ego.State("state2"); var transition = new Ego.Transition(state1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var cloned = transition.clone(); expect(cloned instanceof Ego.Transition).to.eql(true); expect(cloned._informationName).to.eql(transition._informationName); expect(cloned._informationType).to.eql(transition._informationType); expect(cloned._decisionMethod).to.eql(transition._decisionMethod); expect(cloned.getYesNode().getName()).to.eql(transition.getYesNode().getName()); expect(cloned.getNoNode().getName()).to.eql(transition.getNoNode().getName()); expect(cloned.getYesNode() === transition.getYesNode()).to.eql(false); expect(cloned.getNoNode() === transition.getNoNode()).to.eql(false); cloned = transition.clone(state1); expect(cloned._informationName).to.eql(transition._informationName); expect(cloned._informationType).to.eql(transition._informationType); expect(cloned._decisionMethod).to.eql(transition._decisionMethod); expect(cloned.getYesNode().getName()).to.eql(transition.getYesNode().getName()); expect(cloned.getNoNode().getName()).to.eql(transition.getNoNode().getName()); expect(cloned.getYesNode() === transition.getYesNode()).to.eql(false); expect(cloned.getNoNode() === transition.getNoNode()).to.eql(true); cloned = transition.clone(null, state2); expect(cloned._informationName).to.eql(transition._informationName); expect(cloned._informationType).to.eql(transition._informationType); expect(cloned._decisionMethod).to.eql(transition._decisionMethod); expect(cloned.getYesNode().getName()).to.eql(transition.getYesNode().getName()); expect(cloned.getNoNode().getName()).to.eql(transition.getNoNode().getName()); expect(cloned.getYesNode() === transition.getYesNode()).to.eql(true); expect(cloned.getNoNode() === transition.getNoNode()).to.eql(false); cloned = transition.clone(state1, state2); expect(cloned._informationName).to.eql(transition._informationName); expect(cloned._informationType).to.eql(transition._informationType); expect(cloned._decisionMethod).to.eql(transition._decisionMethod); expect(cloned.getYesNode().getName()).to.eql(transition.getYesNode().getName()); expect(cloned.getNoNode().getName()).to.eql(transition.getNoNode().getName()); expect(cloned.getYesNode() === transition.getYesNode()).to.eql(true); expect(cloned.getNoNode() === transition.getNoNode()).to.eql(true); var sm1 = new Ego.StateMachine("sm1", new Ego.Knowledge()) var tmpState = new Ego.State("tmpState"); sm1.addState(tmpState); sm1.setEntryState(tmpState) var transition2 = new Ego.Transition(sm1, state2, "isStuffHappening", Ego.InformationTypes.TYPE_BOOLEAN, new Ego.IsTrue()); var newKnowledge = new Ego.Knowledge(); var obj = {}; cloned = transition2.clone(null, null, newKnowledge, obj); expect(cloned.getSourceNode()._knowledge === newKnowledge).to.eql(true); expect(Object.keys(obj).length).to.eql(1); expect(cloned.getSourceNode()._entryState.getName()).to.eql("tmpState"); }); });