export class StateMachine { public readonly name : string ; /**
     * Debug level of this state machine.
     * 0 = off; 1 = enter, exit; 2 = transitions, 3 = update
     */ public debugLevel : number = 0 ; /**
     * Map of available states. state id -> State.
     */ private _states : { [ id ] : State ; } = { } ; /**
     * Map of transition hash code -> state id
     */ private _transitions : { [ transitionHash ] : string ; } = { } ; /**
     * The currently active state's id.
     */ private _curStateId : string ; /**
     * The state id to change to next.
     */ private _changeStateId : string ; private _curFrame : number ; private _lastUpdateFrame : number ; private _active : boolean ; get currentState ( ) : State { return this . _states [ this . _curStateId ] ; } /**
     * Create a new state machine. Setup the state machine using `addState` and `addStateTransition`.
     * Start the state machine using the `start` method. Manually call `update` every frame in order to update the state machine.
     */ constructor ( name ) { this . name = name ; } public start ( startingStateId , curFrame ) { if ( this . _active ) return ; this . _active = true ; this . _curFrame = curFrame ; this . _changeState ( startingStateId ) ; this . update ( curFrame ) ; } public pause ( ) { if ( ! this . _active ) return ; this . _active = false ; } public resume ( curFrame ) { if ( this . _active ) return ; this . _active = true ; this . _curFrame = curFrame ; this . update ( curFrame ) ; } public addState ( state ) { if ( this . _active ) throw new Error ( 'Cannot add states to state machine after it has started.' ) ; this . _states [ state . id ] = state ; } public addStateTransition ( transition , nextStateId ) { if ( this . _active ) throw new Error ( 'Cannot add state transitions after the state machine has been started.' ) ; if ( ! this . _states [ nextStateId ] ) throw new Error ( `Can't add transition for state '${ nextStateId }'. This state does not exist on the state machine.` ) ; let transitionHash = computeTransitionHash ( transition ) ; this . _transitions [ transitionHash ] = nextStateId ; } public getState ( stateId ) { return this . _states [ stateId ] ; } public update ( curFrame ) { if ( ! this . _active ) return ; if ( this . _lastUpdateFrame === curFrame ) return ; this . _curFrame = curFrame ; this . _lastUpdateFrame = curFrame ; if ( this . _changeStateId ) { let curState = this . _states [ this . _curStateId ] ; if ( curState ) { if ( this . debugLevel >= 1 ) console . log ( `${ this . toString ( ) } ${ curState . id } onStateExit. frame: ${ curFrame }` ) ; curState . onStateExit ( ) ; } curState = this . _states [ this . _changeStateId ] ; if ( curState ) { if ( this . debugLevel >= 1 ) console . log ( `${ this . toString ( ) } ${ curState . id } onStateEnter. frame: ${ curFrame }` ) ; curState . onStateEnter ( ) ; } } else { throw new Error ( `${ this . toString ( ) } No state with id '${ this . _changeStateId }' found.` ) ; } let curState = this . _states [ this . _curStateId ] ; if ( curState ) { if ( this . debugLevel >= 3 ) console . log ( `${ this . toString ( ) } ${ curState . id } onStateUpdate. frame: ${ curFrame }` ) ; let command = curState . onStateUpdate ( ) ; if ( command ) { if ( this . debugLevel >= 2 ) console . log ( `${ this . toString ( ) } ${ curState . id } command: ${ command }, nextState: ${ this . getNextStateId ( command ) }, frame: ${ curFrame }` ) ; this . _changeState ( this . getNextStateId ( command ) ) ; } } this . _changeStateId = undefined ; } public toString ( ) { return `[StateMachine::${ this . name }]` ; } /**
     * Call this when getting rid of the state machine.
     */ public dispose ( ) { throw new Error ( "StateMachine's dispose is not implemented yet." ) ; } /**
     * Usually this state machine should be controlled with state transitions, but in some cases we may want to force a state change.
     */ public forceChangeState ( stateId ) { this . _changeState ( stateId ) ; } private _changeState ( stateId ) { this . _changeStateId = stateId ; this . _lastUpdateFrame = - 1 ; this . update ( this . _curFrame ) ; } private getNextStateId ( command ) { if ( this . _transitions ) return ; let transition = { stateId : this . _curStateId , command : command , } ; let transitionHash = computeTransitionHash ( transition ) ; let nextStateId = this . _transitions [ transitionHash ] ; if ( ! nextStateId ) throw new Error ( `No transition found for State '${ this . _curStateId }' with command '${ command }'` ) ; return nextStateId ; } } export abstract class State { private _id : string ; get id ( ) : string { return this . _id ; } constructor ( id ) { this . _id = id ; } public abstract onStateEnter ( ) ; public abstract onStateUpdate ( ) ; public abstract onStateExit ( ) ; } interface Transition { stateId : string ; command : string ; } param transition function computeTransitionHash ( transition ) { let hashCode = ( s ) => { let h = 0 , l = s . length , i = 0 ; if ( l > 0 ) { while ( i < l ) { h = ( ( h << 5 ) - h + s . charCodeAt ( i ++ ) ) | 0 ; } } return h ; } ; return ( 17 + 31 * hashCode ( transition . stateId ) + 31 * hashCode ( transition . command ) ) ; }