UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

145 lines (111 loc) 3.4 kB
import { randomFromArray } from "../../../core/math/random/randomFromArray.js"; import { seededRandom } from "../../../core/math/random/seededRandom.js"; /** * Hill climbing optimizer based on testing random moves one move at a time, if overall fitness improves - that move is committed, otherwise it's rejected * Very dumb, but quite effective. Requires very little code to write a competent optimizer, especially when state space is relatively small * @template S * @class */ export class RandomOptimizer { /** * * @type {S|null} */ state = null; /** * * @type {function(S):S} */ cloneState = null; /** * * @type {function(S):Function[]} */ computeValidActions = null; /** * * @type {function(S):number} */ scoreFunction = null; /** * * @type {function(S,random:function():number)|null} */ randomAction = null; /** * * @type {function():number} */ random = seededRandom(0); /** * * @param {S} state * @param {function(S):Function[]} [computeValidActions] * @param {function(S):S} cloneState * @param {function(S):number} scoreFunction * @param {function(S,random:function():number)} [randomAction] */ initialize( { state, computeValidActions = null, cloneState, scoreFunction, randomAction = null } ) { if (computeValidActions === null && randomAction === null) { throw new Error(`Either computeValidActions or randomAction must be supplied`); } this.state = state; this.computeValidActions = computeValidActions; this.cloneState = cloneState; this.scoreFunction = scoreFunction; this.randomAction = randomAction; } /** * Perform a single optimization step * @returns {boolean} True if state was improved, false if no change has occurred */ step() { const tempState = this.cloneState(this.state); const currentScore = this.scoreFunction(this.state); //mutate state if (this.randomAction !== null) { this.randomAction(tempState, this.random); } else { const actions = this.computeValidActions(tempState); const numActions = actions.length; if (numActions === 0) { return false; } const action = randomFromArray(this.random, actions); action(tempState); } const newScore = this.scoreFunction(tempState); if (Number.isNaN(newScore)) { console.error('Score function returned NaN'); return false; } if (newScore <= currentScore) { //score not improved return false; } //swap current state with the new one this.state = tempState; return true; } /** * * @param {number} tries * @returns {boolean} */ stepThrough(tries) { for (let i = 0; i < tries; i++) { if (this.step()) { return true; } } return false; } }