UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

240 lines (192 loc) • 5.74 kB
import { assert } from "../../assert.js"; import { BitSet } from "../../binary/BitSet.js"; /** * Definition/Structure of a state machine. Used together with {@link SimpleStateMachine}. * @example * const def = new SimpleStateMachineDescription(); * const state_a = def.createState(); * const state_b = def.createState(); * * def.createEdge(state_a, state_b); */ export class SimpleStateMachineDescription { /** * * @type {BitSet} * @private */ __states = new BitSet(); /** * * @type {number[][]} * @private */ __edges = []; /** * @template T * @type {(function(T):number)[]} * @private */ __actions = []; /** * * @param {function(state:number)} visitor * @param {*} [thisArg] */ traverseStates(visitor, thisArg) { const states = this.__states; for (let i = states.nextSetBit(0); i !== -1; i = states.nextSetBit(i + 1)) { visitor.call(thisArg, i); } } /** * * @param {number} s * @returns {boolean} */ stateExists(s) { assert.isNumber(s, 'state'); assert.isNonNegativeInteger(s, 'state'); return this.__states.get(s); } /** * * @param {number} a * @param {number} b * @returns {boolean} */ edgeExists(a, b) { //assert.ok(this.stateExists(a), `starting state ${a} doesn't exist`); //assert.ok(this.stateExists(b), `ending state ${b} doesn't exist`); const targetStates = this.__edges[a]; if (targetStates === undefined) { return false; } return targetStates.indexOf(b) !== -1; } /** * @param {number} [id] * @returns {number} */ createState(id = this.__states.nextClearBit(0)) { assert.isNumber(id, 'id'); // assert.ok(!this.stateExists(id), `state ${id} already exist`); this.__states.set(id, true); return id; } /** * * @param {number} a * @param {number} b */ createEdge(a, b) { // assert.ok(this.stateExists(a), `state ${a} doesn't exist`); // assert.ok(this.stateExists(b), `state ${b} doesn't exist`); // assert.notOk(this.edgeExists(a, b), `Edge '${a}' -> '${b}' already exists`); const edges = this.__edges; const targetStates = edges[a]; if (targetStates === undefined) { edges[a] = [b] } else { targetStates.push(b); } } /** * @template T * @param {number} state * @param {function(T):number} logic */ setAction(state, logic) { assert.isNonNegativeInteger(state, 'state'); // assert.ok(this.stateExists(state), `state ${state} doesn't exist`); assert.isFunction(logic, 'logic'); this.__actions[state] = logic; } /** * * @param {number} state * @returns {number[]} state IDs */ getOutgoingStates(state) { // assert.ok(this.stateExists(state), `state ${state} doesn't exist`); const result = this.__edges[state]; if (result === undefined) { return []; } return result; } /** * * @param {number} state * @returns {number[]} state IDs */ getIncomingStates(state) { // assert.ok(this.stateExists(state), `state ${state} doesn't exist`); const result = []; for (let s in this.__edges) { const sId = parseInt(s); const targets = this.__edges[sId]; if (targets.indexOf(state) !== -1) { result.push(sId); } } return result; } /** * * @param {number} start * @param {number} goal * @returns {number[]|null} */ findPath(start, goal) { assert.ok(this.stateExists(start), `start state ${start} doesn't exist`); assert.ok(this.stateExists(goal), `goal state ${goal} doesn't exist`); const open = new Set(); open.add(start); const closed = new Set(); const cameFrom = new Map(); function constructPath() { const result = []; let c = goal; do { result.unshift(c); c = cameFrom.get(c); } while (c !== undefined); return result; } const graph = this; function expandNode(current) { const outgoingStates = graph.getOutgoingStates(current); const l = outgoingStates.length; for (let i = 0; i < l; i++) { const node = outgoingStates[i]; if (closed.has(node)) { continue; } if (open.has(node)) { continue; } open.add(node); cameFrom.set(node, current); } } while (open.size > 0) { const current = open.values().next().value; if (current === goal) { //reached the goal return constructPath(); } open.delete(current); closed.add(current); //expand node expandNode(current); } //no path found return null; } } /** * @readonly * @type {boolean} */ SimpleStateMachineDescription.prototype.isSimpleStateMachineDescription = true;