UNPKG

aimjs

Version:

A js library for artificial intelligence creation

647 lines (582 loc) 23.4 kB
(function () { var AIM = (function () { var AIM = {} AIM.test = 'hello world' /** * Return if var1 is equals to var2. * @param {*} var1 - the variable to compare. * @param {*} var2 - the variable to compare with. * @return {boolean} If both variable are equals. */ AIM.equals = function (var1, var2) { var equals = typeof var1 === typeof var2 if (equals) { if (typeof var1 === 'object') { var equals = true for (var i in var1) { if (!this.equals(var1[i], var2[i])) { equals = false break } } } else { equals = var1 === var2 } } return equals } /** * Clone an object. * @param {*} obj - The object to clone. * @return {*} A clone of obj. */ AIM.clone = function (obj) { var clone = null if (typeof obj === 'object') { if (Array.isArray(obj)) { clone = [] var i = 0 for (var i = 0; i < obj.length; i++) { clone[i] = this.clone(obj[i]) } } else { clone = {} for (var p in obj) { clone[p] = this.clone(obj[p]) } } } else { clone = obj } return clone } /** * Return a string that represents a specified object. * @param {*} obj - The object to represents. * @return {string} A string that represents obj. */ AIM.toString = function (obj, depth) { depth = typeof depth === 'undefined' ? 0 : depth var str = '' if (typeof obj === 'object') { if (Array.isArray(obj)) { str += '[' for (var i = 0; i < obj.length; i++) { if (i > 0) str += ', ' str += this.toString(obj[i]) } str += ']' } else { str += '{' var first = true for (var i in obj) { if (first) { first = false } else { str += ',' } str += i + ': ' + this.toString(obj[i], depth + 1) } str += '}' } } else if (typeof obj === 'function') { str += 'function (...) { ... }' } else if (typeof obj === 'string') { str += "'" + obj + "'" } else { str += obj } return str } /** * Return the index in an array of a specified state. * @param {State} state - The state to search for. * @param {array} array - The array to search in. * @return {number} Index of state in array, -1 if not inside. */ AIM.indexOfState = function (state, array) { var index = -1 for (var i = 0; i < array.length; i++) { if (state.equals(array[i])) { index = i break } } return index } /** * Return a html string to display an array of states in the DOM. * @param {State[]} array - The array to display. * @param {string} title - The array's title. * @return {string} A html string that represents an array of states. */ AIM.stateArrayToHTML = function (array, title) { var html = "<div class='state-array'><div class='state-array-title'>" + title + "</div><div class='state-array-content'>" if (array.length == 0) { html += 'empty' } else { for (var i in array) { html += "<div class='state'>" + array[i].toHTML() + "</div>" } } html += '</div></div>' return html } /**************************************************************************************************************************************************************************************************/ // AI /** * An AI. * @constructor * @param {object} config - An object that contains params to build the AI * @param {*} initState - The initial state, can be anything who choosed to represent it exept a function. * @param {*} finalState - The final state, has to be of the same type as initState OR a function that take a state (same type as initState) as parameter and return a boolean at true if it's a final state. * @param {function} getSuccessors - A function that take a state (same type as initState) as parameter and return an array with all of his successors. * @param {function} equals - A function that compare two states (same type as initState) return if they are equals, if not specified use AIM.equals instead. * @param {function} toHTML - A function that return a HTML String to represents a specified state of type State (type specified by user is in state.struct), if not specified use AIM.toString on state.struct. * @property {State} initState - The initial state. * @property {function} heuristic - The heuristic function. */ AIM.AI = function (config) { if (typeof config !== 'object') throw new Error('config is undefined or not of type object') var ai = {} if (typeof config.initState === 'undefined') throw new Error('config.initState is undefined') ai.initState = AIM.State(ai, config.initState) if (typeof config.finalState === 'undefined') throw new Error('config.finalState is undefined') ai.finalState = config.finalState if (typeof config.getSuccessors !== 'function') throw new Error('config.getSuccessors is not a function') ai.getSuccessors = config.getSuccessors ai.equals = typeof config.equals === 'function' ? config.equals : null ai._stateToHTML = typeof config.toHTML === 'function' ? config.toHTML : null ai.heuristic = typeof config.heuristic === 'function' ? config.heuristic : null /** * Check if a state is final. * @param {State} state - A state to check. * @return {boolean} If state is final. */ ai.isFinal = function (state) { if (typeof ai.finalState === 'function') { return ai.finalState(state.struct) } return ai.areEquals(state.struct, ai.finalState) } /** * Check if two states are equals. * @param {*} state1 - A state (same type that user's type, State.struct) to compare with state2. * @param {*} state2 - A state (same type that user's type, State.struct) to compare with state1. * @return {boolean} If both states are equals. */ ai.areEquals = function (state1, state2) { return ai.equals == null ? AIM.equals(state1, state2) : ai.equals(state1, state2) } /** * Calculate heuristic for a specified state. * @param {State} state - The state. * @return {number} The heuristic of state. */ ai.h = function (state) { if (ai.heuristic == null) { throw new Error('No heuristic specified') } return ai.heuristic(state.struct) } /** * Return a HTML string that represents a specified State. * @param {State} state - The state to represent. * @return {string} The representation. */ ai.stateToHTML = function (state) { return ai._stateToHTML != null ? ai._stateToHTML(state) : AIM.toString(state.struct) } /** * Execute the AI using a specified algorithm. * @param {string} algorithm - Algorithm to use : breadth-first, depth-first, A* (more comming soon). * @param {number} maxDepth - The maximum depth to search, optional but highly recommended. * @param {function} heuristic - A function that return an heuristic (number => 0) for a specified state (same type than initState). * @return {Result} An object of type Result. */ ai.execute = function (algorithm, maxDepth) { var result = null if (algorithm === 'breadth-first') { result = ai.breadthFirst(maxDepth) } else if (algorithm === 'depth-first') { result = ai.depthFirst(maxDepth) } else if (algorithm === 'A*') { result = ai.aStar(maxDepth) } else if (typeof algorithm === 'undefined') { throw new Error('No algorithm specified') } else { throw new Error('Unknown algorithm ' + algorithm + ' check documentation to know implemented algorithms') } return result } /** * Execute a breadth-first algorithm. * @param {number} maxDepth - The maximum depth to search, optional but highly recommended. * @return {Result} An object that contains everything you need to know about execution. */ ai.breadthFirst = function (maxDepth) { var start = (new Date()).getTime() var open = [ai.initState] var close = [] var success = false var turn = 0 while (open.length > 0 && !success) { turn++ var state = open[0] if (state.isFinal()) { success = true } else if (typeof maxDepth === 'undefined' || state.depth < maxDepth) { var successors = state.getSuccessors() close.push(open.shift()) for (var s in successors) { var successor = successors[s] if (AIM.indexOfState(successor, open) === -1 && AIM.indexOfState(successor, close) === -1) { open.push(successor) } } } else { close.push(open.shift()) } } var time = (new Date()).getTime() - start return AIM.Result(success, 'breadth-first', time, turn, open[0], open, close) } /** * Execute a depth-first algorithm. * @param {number} maxDepth - The maximum depth to search, optional but highly recommended. * @return {Result} An object that contains everything you need to know about execution. */ ai.depthFirst = function (maxDepth) { var start = (new Date()).getTime() var open = [ai.initState] var close = [] var success = false var turn = 0 while (open.length > 0 && !success) { turn++ var state = open[open.length - 1] if (state.isFinal()) { success = true } else if (typeof maxDepth === 'undefined' || state.depth < maxDepth) { var successors = state.getSuccessors() close.push(open.pop()) for (var s in successors) { var successor = successors[s] var indexInOpen = AIM.indexOfState(successor, open) var indexInClose = AIM.indexOfState(successor, close) if (indexInOpen === -1 && (indexInClose === -1 || successor.depth < close[indexInClose].depth)) { open.push(successor) } else if (indexInOpen !== -1 && successor.depth < open[indexInOpen].depth) { open[indexInOpen].father = state } } } else { open.pop() } } var time = (new Date()).getTime() - start return AIM.Result(success, 'depth-first', time, turn, open[open.length - 1], open, close) } /** * Execute a A* algorithm. * @param {function} heuristic - A function that take a state as parameter (same type than initState) and return an heuristic (number). * @param {number} maxDepth - The maximum depth to search, optional but highly recommended. * @return {Result} An object that contains everything you need to know about execution. */ ai.aStar = function (maxDepth) { if (ai.heuristic == null) throw new Error('An heuristic is needed for A*') var start = (new Date()).getTime() var open = [ai.initState] var close = [] var success = false var turn = 0 var solution = null while (open.length > 0 && !success) { turn++ var index = -1 var state = null //var f = 0 for (var i = 0; i < open.length; i++) { if (state == null || open[i].f < state.f) { index = i state = open[i] } } //console.log(state) if (state.isFinal()) { success = true solution = state } else if (typeof maxDepth === 'undefined' || state.depth < maxDepth) { var successors = state.getSuccessors() close.push(open.splice(index, 1)[0]) for (var s in successors) { var successor = successors[s] var indexInOpen = AIM.indexOfState(successor, open) var indexInClose = AIM.indexOfState(successor, close) if (indexInOpen === -1 && (indexInClose === -1 || successor.depth < close[indexInClose].depth)) { open.push(successor) } else if (indexInOpen !== -1 && successor.depth < open[indexInOpen].depth) { open[indexInOpen].father = state } } } else { open.splice(index, 1) } } var time = (new Date()).getTime() - start return AIM.Result(success, 'A*', time, turn, solution, open, close) } return ai } /**************************************************************************************************************************************************************************************************/ // State /** * Represents a state, you are not suposed to instanciate it in your app. * @constructor * @property {AI} ai - The ai. * @property {*} struct - State as specified by the user. * @property {State} father - Father of the state, equals to null for the initial state. * @property {number} depth - The depth of the state in the graph, equals to 0 for the initial state. */ AIM.State = function (ai, struct, father) { var state = {} state.ai = ai state.struct = struct state._father = null state.depth = 0 state._h = null /** * The father of the state, if father change depth is updated. */ Object.defineProperty(state, 'father', { set: function (father) { state._father = father state.depth = father != null ? father.depth + 1 : 0 }, get: function () { return state._father } }) state.father = typeof father === 'undefined' ? null : father /** * h is the heuristic of the state */ Object.defineProperty(state, 'h', { get: function () { if (state._h == null) { state._h = state.ai.h(state) } return state._h } }) /** * f of the state (State.f = State.depth + AI.h(state)) */ Object.defineProperty(state, 'f', { get: function () { return state.depth + state.h } }) /** * Return a HTML string that srepresent the state. * @return {string} The HTML string. */ state.toHTML = function () { return state.ai.stateToHTML(state) } /** * Return if the state is final using AI.isFinal. * @return {boolean} If the state is final. */ state.isFinal = function () { return state.ai.isFinal(state) } /** * Return an array that contains all successors of the state. * @return {State[]} An array that contains all successors of the state. */ state.getSuccessors = function () { var successorsStruct = state.ai.getSuccessors(state.struct) var successors = [] for (var s in successorsStruct) { successors.push(AIM.State(state.ai, successorsStruct[s], state)) } return successors } /** * Check if the state is equals to another state. * @param {State} stateToComp - The state to compare with. * @return {boolean} If both states are equals. */ state.equals = function (stateToComp) { return state.ai.areEquals(state.struct, stateToComp.struct) } return state } /**************************************************************************************************************************************************************************************************/ // Result /** * Data returned after running an algorithm, you are not supposed to instanciate it in your app. * @constructor * @property {boolean} success - If the sun is a success. * @property {string} algorithm - Algorithm used. * @property {number} time - Number of ms to execute the algorithm. * @property {number} turn - Number of loop turn. * @property {State} solution - The final state reached. * @property {State[]} open - Every opened states (not explored). * @property {State[]} close - Every closed states (explored). * @property {State[]} resolution - An array that contains the succession of states to reach the solution (index 0 is the initial state). */ AIM.Result = function (success, algorithm, time, turn, solution, open, close) { var result = {} result.success = success result.algorithm = algorithm result.time = time result.turn = turn result.solution = solution result.open = open result.close = close result.resolution = [] if(result.success){ var state = result.solution while (state != null) { result.resolution.unshift(state) state = state.father } result.depth = result.solution.depth } else { result.depth = 0 for (var i = 0; i < result.open.length; i++) { if (result.open[i].depth > result.depth) result.depth = result.open[i].depth } for (var i = 0; i < result.close.length; i++) { if (result.close[i].depth > result.depth) result.depth = result.close[i].depth } } /** * Print the result in the console. * @param {boolean} printArrays - To display resolution, open and close state's arrays (can flood the console in nodejs, better in a modern browser). */ result.print = function (printArrays) { printArrays = typeof printArrays === 'undefined' ? false : printArrays var str = result.success ? 'Success' : 'Failure' str += ' with algorithm ' + result.algorithm + ' in ' + result.time + ' ms after ' + result.turn + ' turns at depth ' + result.depth console.log(str) if (printArrays) { if (result.success) { console.log('Resolution : ') console.log(result.resolution) } console.log('Opened states :') console.log(result.open) console.log('Closed states :') console.log(result.close) } } /** * Return a html string to display result in the DOM, use the toHTML function specified by the user in AI constructor to represent a state. * @param {boolean} open - True to display the opened states array. * @param {boolean} close - True to display the closed states array. * @return {string} A html string that represents the result. */ result.html = function (toHTML, open, close) { open = typeof open === 'undefined' ? false : open close = typeof close === 'undefined' ? false : close var html = "<div class='result-success-value " html += result.success ? "success'>Success" : "failure'>Failure" html += '</div>' html += "<div class='result-info'><div class='result-info-name'>Algorithm :</div> " + result.algorithm + '</div>' html += "<div class='result-info'><div class='result-info-name'>Time :</div> " + result.time + ' ms</div>' html += "<div class='result-info'><div class='result-info-name'>Turns :</div> " + result.turn + '</div>' html += "<div class='result-info'><div class='result-info-name'>Depth :</div> " + result.depth + '</div>' if (result.success) { html += AIM.stateArrayToHTML(result.resolution, 'Resolution') } if (open) { html += AIM.stateArrayToHTML(result.open, 'Opened states') } if (close) { html += AIM.stateArrayToHTML(result.close, 'Closed states') } return html } /** * Draw a tree of all States in the specified container using Trean.js. * @param {string} container - A string to identify the container with jQuery. */ result.drawTree = function (container) { if (typeof Treant !== 'function') throw new Error('Treant.js is required to draw tree (Trean.js require raphael.js)') var treeConfig = { chart: { container: container }, nodeStructure: null } //Create all nodes for (var i = 0; i < result.close.length; i++) { result.close[i].node = { innerHTML: result.close[i].toHTML(), children: [] } } for (var i = 0; i < result.open.length; i++) { result.open[i].node = { innerHTML: result.open[i].toHTML(), children: [] } } //Push all nodes into father node for (var i = 0; i < result.close.length; i++) { if (result.close[i].father != null) { result.close[i].father.node.children.push(result.close[i].node) } else { treeConfig.nodeStructure = result.close[i].node } } for (var i = 0; i < result.open.length; i++) { if (result.open[i].father != null) { result.open[i].father.node.children.push(result.open[i].node) } else { treeConfig.nodeStructure = result.open[i].node } } new Treant(treeConfig) } return result } return AIM })() if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = AIM } else if (typeof define === 'function' && define.amd) { define([], function() { return AIM }) } else { window.AIM = AIM; } })()