Allow me to represent how my cat was acting last night as a finite state machine.
Created
May 24, 2013 09:54
-
-
Save calvinmetcalf/5642502 to your computer and use it in GitHub Desktop.
Finite State Cat
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var finiteStateCat = StateMachine.create({ | |
initial: 'inside', | |
events: [ | |
{ name: 'letHerOut', from: 'inside', to: 'outside' }, | |
{ name: 'letHerIn', from: 'outside', to: 'inside' } | |
], | |
callbacks: { | |
oninside:function(){ | |
$("#cat").html("<p>Meows to be let out</p><button type='button' id='letHerOut'>Let Her Out</button>"); | |
$("#letHerOut").on("click",function(){finiteStateCat.letHerOut();}) | |
}, | |
onoutside:function(){ | |
$("#cat").html("<p>realizes it's raining meows to be let in</p><button type='button' id='letHerIn'>Let Her in</button>"); | |
$("#letHerIn").on("click",function(){finiteStateCat.letHerIn();}) | |
} | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<body> | |
<div id="cat"></div> | |
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script> | |
<script src="state-machine.js"></script> | |
<script src="finiteStateCat.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Javascript State Machine Library - https://github.com/jakesgordon/javascript-state-machine | |
Copyright (c) 2012, 2013 Jake Gordon and contributors | |
Released under the MIT license - https://github.com/jakesgordon/javascript-state-machine/blob/master/LICENSE | |
*/ | |
(function (window) { | |
var StateMachine = { | |
//--------------------------------------------------------------------------- | |
VERSION: "2.2.0", | |
//--------------------------------------------------------------------------- | |
Result: { | |
SUCCEEDED: 1, // the event transitioned successfully from one state to another | |
NOTRANSITION: 2, // the event was successfull but no state transition was necessary | |
CANCELLED: 3, // the event was cancelled by the caller in a beforeEvent callback | |
PENDING: 4 // the event is asynchronous and the caller is in control of when the transition occurs | |
}, | |
Error: { | |
INVALID_TRANSITION: 100, // caller tried to fire an event that was innapropriate in the current state | |
PENDING_TRANSITION: 200, // caller tried to fire an event while an async transition was still pending | |
INVALID_CALLBACK: 300 // caller provided callback function threw an exception | |
}, | |
WILDCARD: '*', | |
ASYNC: 'async', | |
//--------------------------------------------------------------------------- | |
create: function(cfg, target) { | |
var initial = (typeof cfg.initial == 'string') ? { state: cfg.initial } : cfg.initial; // allow for a simple string, or an object with { state: 'foo', event: 'setup', defer: true|false } | |
var terminal = cfg.terminal || cfg['final']; | |
var fsm = target || cfg.target || {}; | |
var events = cfg.events || []; | |
var callbacks = cfg.callbacks || {}; | |
var map = {}; | |
var add = function(e) { | |
var from = (e.from instanceof Array) ? e.from : (e.from ? [e.from] : [StateMachine.WILDCARD]); // allow 'wildcard' transition if 'from' is not specified | |
map[e.name] = map[e.name] || {}; | |
for (var n = 0 ; n < from.length ; n++) | |
map[e.name][from[n]] = e.to || from[n]; // allow no-op transition if 'to' is not specified | |
}; | |
if (initial) { | |
initial.event = initial.event || 'startup'; | |
add({ name: initial.event, from: 'none', to: initial.state }); | |
} | |
for(var n = 0 ; n < events.length ; n++) | |
add(events[n]); | |
for(var name in map) { | |
if (map.hasOwnProperty(name)) | |
fsm[name] = StateMachine.buildEvent(name, map[name]); | |
} | |
for(var name in callbacks) { | |
if (callbacks.hasOwnProperty(name)) | |
fsm[name] = callbacks[name] | |
} | |
fsm.current = 'none'; | |
fsm.is = function(state) { return (state instanceof Array) ? (state.indexOf(this.current) >= 0) : (this.current === state); }; | |
fsm.can = function(event) { return !this.transition && (map[event].hasOwnProperty(this.current) || map[event].hasOwnProperty(StateMachine.WILDCARD)); } | |
fsm.cannot = function(event) { return !this.can(event); }; | |
fsm.error = cfg.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; // default behavior when something unexpected happens is to throw an exception, but caller can override this behavior if desired (see github issue #3 and #17) | |
fsm.isFinished = function() { return this.is(terminal); }; | |
if (initial && !initial.defer) | |
fsm[initial.event](); | |
return fsm; | |
}, | |
//=========================================================================== | |
doCallback: function(fsm, func, name, from, to, args) { | |
if (func) { | |
try { | |
return func.apply(fsm, [name, from, to].concat(args)); | |
} | |
catch(e) { | |
return fsm.error(name, from, to, args, StateMachine.Error.INVALID_CALLBACK, "an exception occurred in a caller-provided callback function", e); | |
} | |
} | |
}, | |
beforeAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbeforeevent'], name, from, to, args); }, | |
afterAnyEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafterevent'] || fsm['onevent'], name, from, to, args); }, | |
leaveAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleavestate'], name, from, to, args); }, | |
enterAnyState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenterstate'] || fsm['onstate'], name, from, to, args); }, | |
changeState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onchangestate'], name, from, to, args); }, | |
beforeThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onbefore' + name], name, from, to, args); }, | |
afterThisEvent: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onafter' + name] || fsm['on' + name], name, from, to, args); }, | |
leaveThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onleave' + from], name, from, to, args); }, | |
enterThisState: function(fsm, name, from, to, args) { return StateMachine.doCallback(fsm, fsm['onenter' + to] || fsm['on' + to], name, from, to, args); }, | |
beforeEvent: function(fsm, name, from, to, args) { | |
if ((false === StateMachine.beforeThisEvent(fsm, name, from, to, args)) || | |
(false === StateMachine.beforeAnyEvent( fsm, name, from, to, args))) | |
return false; | |
}, | |
afterEvent: function(fsm, name, from, to, args) { | |
StateMachine.afterThisEvent(fsm, name, from, to, args); | |
StateMachine.afterAnyEvent( fsm, name, from, to, args); | |
}, | |
leaveState: function(fsm, name, from, to, args) { | |
var specific = StateMachine.leaveThisState(fsm, name, from, to, args), | |
general = StateMachine.leaveAnyState( fsm, name, from, to, args); | |
if ((false === specific) || (false === general)) | |
return false; | |
else if ((StateMachine.ASYNC === specific) || (StateMachine.ASYNC === general)) | |
return StateMachine.ASYNC; | |
}, | |
enterState: function(fsm, name, from, to, args) { | |
StateMachine.enterThisState(fsm, name, from, to, args); | |
StateMachine.enterAnyState( fsm, name, from, to, args); | |
}, | |
//=========================================================================== | |
buildEvent: function(name, map) { | |
return function() { | |
var from = this.current; | |
var to = map[from] || map[StateMachine.WILDCARD] || from; | |
var args = Array.prototype.slice.call(arguments); // turn arguments into pure array | |
if (this.transition) | |
return this.error(name, from, to, args, StateMachine.Error.PENDING_TRANSITION, "event " + name + " inappropriate because previous transition did not complete"); | |
if (this.cannot(name)) | |
return this.error(name, from, to, args, StateMachine.Error.INVALID_TRANSITION, "event " + name + " inappropriate in current state " + this.current); | |
if (false === StateMachine.beforeEvent(this, name, from, to, args)) | |
return StateMachine.Result.CANCELLED; | |
if (from === to) { | |
StateMachine.afterEvent(this, name, from, to, args); | |
return StateMachine.Result.NOTRANSITION; | |
} | |
// prepare a transition method for use EITHER lower down, or by caller if they want an async transition (indicated by an ASYNC return value from leaveState) | |
var fsm = this; | |
this.transition = function() { | |
fsm.transition = null; // this method should only ever be called once | |
fsm.current = to; | |
StateMachine.enterState( fsm, name, from, to, args); | |
StateMachine.changeState(fsm, name, from, to, args); | |
StateMachine.afterEvent( fsm, name, from, to, args); | |
return StateMachine.Result.SUCCEEDED; | |
}; | |
this.transition.cancel = function() { // provide a way for caller to cancel async transition if desired (issue #22) | |
fsm.transition = null; | |
StateMachine.afterEvent(fsm, name, from, to, args); | |
} | |
var leave = StateMachine.leaveState(this, name, from, to, args); | |
if (false === leave) { | |
this.transition = null; | |
return StateMachine.Result.CANCELLED; | |
} | |
else if (StateMachine.ASYNC === leave) { | |
return StateMachine.Result.PENDING; | |
} | |
else { | |
if (this.transition) // need to check in case user manually called transition() but forgot to return StateMachine.ASYNC | |
return this.transition(); | |
} | |
}; | |
} | |
}; // StateMachine | |
//=========================================================================== | |
if ("function" === typeof define) { | |
define(function(require) { return StateMachine; }); | |
} | |
else { | |
window.StateMachine = StateMachine; | |
} | |
}(this)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment