{"id":16579719,"url":"https://github.com/oguzeroglu/ego","last_synced_at":"2025-04-21T15:30:43.073Z","repository":{"id":43056785,"uuid":"290009052","full_name":"oguzeroglu/Ego","owner":"oguzeroglu","description":"A lightweight decision making library for game AI.","archived":false,"fork":false,"pushed_at":"2020-09-25T16:32:22.000Z","size":203,"stargazers_count":173,"open_issues_count":0,"forks_count":7,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-01T14:11:17.632Z","etag":null,"topics":["decision-making","decision-tree","game-ai","game-engine","javascript","state-machine","threejs","webgl"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/oguzeroglu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-24T18:35:28.000Z","updated_at":"2025-02-13T10:36:48.000Z","dependencies_parsed_at":"2022-09-03T11:42:42.887Z","dependency_job_id":null,"html_url":"https://github.com/oguzeroglu/Ego","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzeroglu%2FEgo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzeroglu%2FEgo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzeroglu%2FEgo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/oguzeroglu%2FEgo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/oguzeroglu","download_url":"https://codeload.github.com/oguzeroglu/Ego/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249954959,"owners_count":21351002,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decision-making","decision-tree","game-ai","game-engine","javascript","state-machine","threejs","webgl"],"created_at":"2024-10-11T22:19:05.089Z","updated_at":"2025-04-21T15:30:42.681Z","avatar_url":"https://github.com/oguzeroglu.png","language":"JavaScript","readme":"# Ego\n\nEgo is a lightweight decision making library for game AI. It provides decision trees and state machines (hierarchical and finite).\n\nEgo 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.\n\n# Getting Started\n\n## Index\n\n - [Including It](#including-it)\n   - [Client-side Javascript](#client-side-javascript)\n   - [NodeJS](#nodejs)    \n - [Using Decision trees](#using-decision-trees)\n   - [Creating the Knowledge](#creating-the-knowledge)\n   - [Creating Decisions](#creating-decisions)\n   - [Connecting the Decisions](#connecting-the-decisions)\n   - [Creating the Decision Tree](#creating-the-decision-tree)\n   -  [Putting It All Together](#putting-it-all-together)\n - [Using State Machines](#using-state-machines)\n   - [Creating the Knowledge](#creating-the-knowledge-1)  \n   - [Creating States](#creating-states)\n   - [Creating Transitions](#creating-transitions)\n   - [Creating the State Machine](#creating-the-state-machine)\n   - [Putting It All Together](#putting-it-all-together-1)\n   - [Note About Hierarchical State Machines](#note-about-hierarchical-state-machines)\n\n## Including It\n\n### Client-side Javascript\n\nDownload the latest release from [releases page](https://github.com/oguzeroglu/Ego/releases). Then include the `ego.min.js` file into your project via `\u003cscript\u003e` tag:\n\n`\u003cscript src=\"PATH_TO_ego.min.js\"\u003e\u003c/script\u003e`\n\n### NodeJS\n\nInstall it via npm:\n\n`npm i @oguz.eroglu/ego-js`\n\nThen include the module into your project via `require`:\n\n```javascript\nvar Ego = require(\"@oguz.eroglu/ego-js\");\n```\n\n## Using Decision Trees\n\nTo get started with decision trees, let's create this decision tree with Ego:\n\n![](/example-images/decision-tree.png?raw=true)  \n\n### Creating the Knowledge\n\nWe'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:\n\n* isEnemyVisible (of type boolean)\n* isEnemyAudible (of type boolean)\n* isEnemyOnFlank (of type boolean)\n* distanceToEnemy (of type 3D vector)\n\n```javascript\n// Instantiate a Knowledge\nvar knowledge = new Ego.Knowledge();\n\n// add isEnemyVisible information\nknowledge.addBooleanInformation(\"isEnemyVisible\", false);\n\n// add isEnemyAudible information\nknowledge.addBooleanInformation(\"isEnemyAudible\", false);\n\n// add isEnemyOnFlank information\nknowledge.addBooleanInformation(\"isEnemyOnFlank\", false);\n\n// add distanceToEnemy information. We'll assume the enemy is 100 units far away on the axis x.\nknowledge.addVectorInformation(\"distanceToEnemy\", 100, 0, 0);\n```\n\n### Creating Decisions\n\nWe'll continue by creating the decisions. Ego provides these three decision methods:\n\n* `IsTrue` to check if given boolean information is true.\n* `IsFalse` to check if given boolean information is false.\n* `IsInRange` to check if given numerical information or the length of given vector information is within given range.\n\nWe can reuse the decision methods across several decisions. For this decision tree, we need `IsTrue` and `IsInRange` decision methods. Let's create them:\n\n```javascript\n// create the IsTrue decision method\nvar isTrue = new Ego.IsTrue();\n\n// create the IsInRange decision method. We'll start by creating a Range object.\n// The first parameter is the lower bound and the second is the upper bound.\n// Note that Ego Range instances are inclusive by default.\n// Since we'd like to check if given distance is less than 10 units\n// we'll set the lower bound to -Infinity and the upper bound to 10.\n\n// [-Infinity, 10[\nvar range = new Ego.Range(-Infinity, 10);\nrange.makeUpperBoundExclusive();\n\n// Create the decision method\nvar isInRange = new Ego.IsInRange(range);\n```\n\n```javascript\n\n// the first parameter is the information name inside the Knowledge\n// the second is the type of the information\n// the third is the decision method\n\nvar isEnemyVisible = new Ego.Decision(\"isEnemyVisible\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar isEnemyAudible = new Ego.Decision(\"isEnemyAudible\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar isEnemyOnFlank = new Ego.Decision(\"isEnemyOnFlank\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar distanceLessThan10Units = new Ego.Decision(\"distanceToEnemy\", Ego.InformationTypes.TYPE_VECTOR, isInRange);\n```\n\n### Connecting the Decisions\nWe'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:\n\n```javascript\n// If enemy is visible, we'll test if enemy is close.\nisEnemyVisible.setYesNode(distanceLessThan10Units);\n\n// If enemy is not visible, we'll test if enemy is audible.\nisEnemyVisible.setNoNode(isEnemyAudible);\n\n// If enemy is audible, we'll creep. We can simply return a string in this case.\nisEnemyAudible.setYesNode(\"creep\");\n\n// If enemy is not audible, we'll do nothing. We can simply return a string in this case.\nisEnemyAudible.setNoNode(\"doNothing\");\n\n// If enemy is less than 10 units away, we'll attack\ndistanceLessThan10Units.setYesNode(\"attack\");\n\n// We'll perform isEnemyOnFlank test otherwise\ndistanceLessThan10Units.setNoNode(isEnemyOnFlank);\n\n// If enemy is on flank, we'll move\nisEnemyOnFlank.setYesNode(\"move\");\n\n// We'll attack otherwise\nisEnemyOnFlank.setNoNode(\"attack\");\n```\n\n### Creating the Decision Tree\nNow 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`:\n\n```javascript\nvar decisionTree = new Ego.DecisionTree(isEnemyVisible);\n```\n\nWe 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.\n\n```javascript\ndecisionTree.makeDecision(knowledge); // returns \"doNothing\"\n```\n\nWe 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:\n\n```javascript\n\n// update isEnemyVisible information\nknowledge.updateBooleanInformation(\"isEnemyVisible\", true);\n\ndecisionTree.makeDecision(knowledge); // returns \"attack\"\n```\n\nIn 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.\n\n```javascript\n\n// update knowledge\nknowledge.updateVectorInformation(\"distanceToEnemy\", 500, 0, 0);\nknowledge.updateBooleanInformation(\"isEnemyOnFlank\", true);\n\ndecisionTree.makeDecision(knowledge); // return \"move\"\n```\n\n### Putting It All Together\n\n```javascript\nvar Ego = require(\"@oguz.eroglu/ego-js\");\n\nvar knowledge = new Ego.Knowledge();\n\nknowledge.addBooleanInformation(\"isEnemyVisible\", false);\nknowledge.addBooleanInformation(\"isEnemyAudible\", false);\nknowledge.addBooleanInformation(\"isEnemyOnFlank\", false);\nknowledge.addVectorInformation(\"distanceToEnemy\", 100, 0, 0);\n\nvar isTrue = new Ego.IsTrue();\n\nvar range = new Ego.Range(-Infinity, 10);\nrange.makeUpperBoundExclusive();\n\nvar isInRange = new Ego.IsInRange(range);\n\nvar isEnemyVisible = new Ego.Decision(\"isEnemyVisible\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar isEnemyAudible = new Ego.Decision(\"isEnemyAudible\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar isEnemyOnFlank = new Ego.Decision(\"isEnemyOnFlank\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar distanceLessThan10Units = new Ego.Decision(\"distanceToEnemy\", Ego.InformationTypes.TYPE_VECTOR, isInRange);\n\nisEnemyVisible.setYesNode(distanceLessThan10Units);\nisEnemyVisible.setNoNode(isEnemyAudible);\n\nisEnemyAudible.setYesNode(\"creep\");\nisEnemyAudible.setNoNode(\"doNothing\");\n\ndistanceLessThan10Units.setYesNode(\"attack\");\ndistanceLessThan10Units.setNoNode(isEnemyOnFlank);\n\nisEnemyOnFlank.setYesNode(\"move\");\nisEnemyOnFlank.setNoNode(\"attack\");\n\nvar decisionTree = new Ego.DecisionTree(isEnemyVisible);\n\nknowledge.updateBooleanInformation(\"isEnemyVisible\", true);\nknowledge.updateVectorInformation(\"distanceToEnemy\", 500, 0, 0);\nknowledge.updateBooleanInformation(\"isEnemyOnFlank\", true);\n\nconsole.log(decisionTree.makeDecision(knowledge));\n```\n\n## Using State Machines\n\nTo get started with state machines, let's create this state machine with Ego:\n\n![](/example-images/simple-state-machine.png?raw=true) \n\n### Creating the Knowledge\n\nJust 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:\n\n* isWeakEnemySeen (of type boolean)\n* isStrongEnemySeen (of type boolean)\n* health (of type float)\n* distanceToEnemy (of type Vector)\n\n`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.\n\n```javascript\n// create the knowledge\nvar knowledge = new Ego.Knowledge();\n\n// create isWeakEnemySeen information\nknowledge.addBooleanInformation(\"isWeakEnemySeen\", false);\n\n// create isStrongEnemySeen information\nknowledge.addBooleanInformation(\"isStrongEnemySeen\", false);\n\n// create health information. Initially we'll set this to 100.\nknowledge.addNumericalInformation(\"health\", 100);\n\n// create distanceToEnemy information. Initially we'll assume the enemy is 500 units far away on the axis X.\nknowledge.addVectorInformation(\"distanceToEnemy\", 500, 0, 0);\n```\n\n### Creating States\nWe'll move on by creating the states. For this example we need 3 states: `OnGuard`, `Fight` and `RunAway`:\n\n```javascript\n// create OnGuard state\nvar onGuardState = new Ego.State(\"OnGuard\");\n\n// create Fight state\nvar fightState = new Ego.State(\"Fight\");\n\n// create RunAway state\nvar runAwayState = new Ego.State(\"RunAway\");\n```\n\n### Creating Transitions\nWe 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.\n\nWe'll first define the decision methods just like we did while working with Decision Trees. For this example we need these decision methods:\n\n* `IsTrue` to check if strong or weak enemy is seen (`isStrongEnemySeen` and `isWeakEnemySeen` informations)\n* `IsInRange` to check if the player is losing the fight (`health` information is less than 20)\n* Another `IsInRange` to check if the player successfully escaped (The length of `distanceToEnemy` is greater than 100 units)\n\n```javascript\n// create IsTrue decision method\nvar isTrue = new Ego.IsTrue();\n\n// create IsInRange decision method for the health information.\n\n// define the range\nvar lessThan20Range = new Ego.Range(-Infinity, 20);\nlessThan20Range.makeUpperBoundExclusive();\n\n// create the Decision method\nvar lessThan20 = new Ego.IsInRange(lessThan20Range);\n\n// create IsInRange decision method for the distanceToEnemy information\n\n// define the range\nvar greaterThan100Range = new Ego.Range(100, Infinity);\ngreaterThan100Range.makeLowerBoundExclusive();\n\n// create the Decision method\nvar greaterThan100 = new Ego.IsInRange(greaterThan100Range);\n```\n\nNow 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.\n\n```javascript\n// Transition for: See weak enemy\nvar seeWeakEnemyTransition = new Ego.Transition(onGuardState, fightState, \"isWeakEnemySeen\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\n\n// Transition for: See strong enemy\nvar seeStrongEnemyTransition = new Ego.Transition(onGuardState, runAwayState, \"isStrongEnemySeen\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\n\n// Transition for: Losing fight\nvar losingFightTransition = new Ego.Transition(fightState, runAwayState, \"health\", Ego.InformationTypes.TYPE_NUMERICAL, lessThan20);\n\n// Transition for: Escaped\nvar escapedTransition = new Ego.Transition(runAwayState, onGuardState, \"distanceToEnemy\", Ego.InformationTypes.TYPE_VECTOR, greaterThan100);\n```\n\n### Creating the State Machine\nNow 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:\n\n```javascript\n// create the state machine\nvar stateMachine = new Ego.StateMachine(\"stateMachine1\", knowledge);\n\n// add states\nstateMachine.addState(onGuardState);\nstateMachine.addState(runAwayState);\nstateMachine.addState(fightState);\n\n// add transitions\nstateMachine.addTransition(seeWeakEnemyTransition);\nstateMachine.addTransition(seeStrongEnemyTransition);\nstateMachine.addTransition(losingFightTransition);\nstateMachine.addTransition(escapedTransition);\n\n// set the initial (entry) state\nstateMachine.setEntryState(onGuardState);\n```\n\nNow 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.\n\n```javascript\n// listen for state changes\nstateMachine.onStateChanged(function(newState){\n  console.log(\"New state is: \" + newState.getName());\n});\n\n// update the state machine\nstateMachine.update();\n```\n\nThis 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:\n\n```javascript\n// the player has seen a weak enemy\nknowledge.updateBooleanInformation(\"isWeakEnemySeen\", true);\n\nstateMachine.update();\n```\n\nNow that the player has seen a weak enemy, this will additionally print out: `New state is: Fight`. Let's lose the fight:\n\n```javascript\nknowledge.updateNumericalInformation(\"health\", 10);\nknowledge.updateVectorInformation(\"distanceToEnemy\", 3, 0, 0);\n\nstateMachine.update(); // prints out: New state is: RunAway\n```\n\n### Putting It All Together\n\n```javascript\nvar knowledge = new Ego.Knowledge();\n\nknowledge.addBooleanInformation(\"isWeakEnemySeen\", false);\nknowledge.addBooleanInformation(\"isStrongEnemySeen\", false);\nknowledge.addNumericalInformation(\"health\", 100);\nknowledge.addVectorInformation(\"distanceToEnemy\", 500, 0, 0);\n\nvar onGuardState = new Ego.State(\"OnGuard\");\nvar fightState = new Ego.State(\"Fight\");\nvar runAwayState = new Ego.State(\"RunAway\");\nvar isTrue = new Ego.IsTrue();\n\nvar lessThan20Range = new Ego.Range(-Infinity, 20);\nlessThan20Range.makeUpperBoundExclusive();\nvar lessThan20 = new Ego.IsInRange(lessThan20Range);\n\nvar greaterThan100Range = new Ego.Range(100, Infinity);\ngreaterThan100Range.makeLowerBoundExclusive();\nvar greaterThan100 = new Ego.IsInRange(greaterThan100Range);\n\nvar seeWeakEnemyTransition = new Ego.Transition(onGuardState, fightState, \"isWeakEnemySeen\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar seeStrongEnemyTransition = new Ego.Transition(onGuardState, runAwayState, \"isStrongEnemySeen\", Ego.InformationTypes.TYPE_BOOLEAN, isTrue);\nvar losingFightTransition = new Ego.Transition(fightState, runAwayState, \"health\", Ego.InformationTypes.TYPE_NUMERICAL, lessThan20);\nvar escapedTransition = new Ego.Transition(runAwayState, onGuardState, \"distanceToEnemy\", Ego.InformationTypes.TYPE_VECTOR, greaterThan100);\n\nvar stateMachine = new Ego.StateMachine(\"stateMachine1\", knowledge);\n\nstateMachine.addState(onGuardState);\nstateMachine.addState(runAwayState);\nstateMachine.addState(fightState);\n\nstateMachine.addTransition(seeWeakEnemyTransition);\nstateMachine.addTransition(seeStrongEnemyTransition);\nstateMachine.addTransition(losingFightTransition);\nstateMachine.addTransition(escapedTransition);\n\nstateMachine.setEntryState(onGuardState);\n\nstateMachine.onStateChanged(function(newState){\n  console.log(\"New state is: \" + newState.getName());\n});\n\nstateMachine.update();\n\nknowledge.updateBooleanInformation(\"isWeakEnemySeen\", true);\n\nstateMachine.update();\n\nknowledge.updateNumericalInformation(\"health\", 10);\nknowledge.updateVectorInformation(\"distanceToEnemy\", 3, 0, 0);\n\nstateMachine.update();\n```\n\n### Note About Hierarchical State Machines\n\nEgo 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.\n\n# License\n\nEgo uses MIT license.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foguzeroglu%2Fego","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foguzeroglu%2Fego","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foguzeroglu%2Fego/lists"}