Skip to content

Instantly share code, notes, and snippets.

@begeeben
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save begeeben/da3eccb2b678612e400e to your computer and use it in GitHub Desktop.
Save begeeben/da3eccb2b678612e400e to your computer and use it in GitHub Desktop.
finite state machine example
.light {
display: inline-block;
position: relative;
background-color: black;
border: 3px solid #888;
border-radius: 50px;
width: 100px;
height: 100px;
margin: 3px;
}
.light .circle, .light .arrow {
visibility: hidden;
pointer-events: none;
}
.light.is-active .circle, .light.is-active .arrow {
visibility: visible;
}
.circle {
position: absolute;
top: 5px;
left: 5px;
border-radius: 50px;
width: 90px;
height: 90px;
}
.circle.red {
background-color: red;
}
.circle.yellow {
background-color: yellow;
}
.arrow {
position: absolute;
height: 0;
width: 0;
border: 40px solid transparent;
}
.arrow:after {
content: "";
background-color: green;
display: block;
height: 40px;
width: 40px;
}
.arrow.up {
top: -30px;
left: 10px;
border-bottom-color: green;
}
.arrow.up:after {
margin-left: -20px;
margin-top: 40px;
}
.arrow.left {
top: 10px;
left: -30px;
border-right-color: green;
}
.arrow.left:after {
margin-left: 40px;
margin-top: -20px;
}
.arrow.right {
top: 10px;
left: 50px;
border-left-color: green;
}
.arrow.right:after {
margin-left: -80px;
margin-top: -20px;
}
.control-panel {
margin: 5px;
}
.scene-switch {
font-size: 1rem;
padding: .5rem;
color: #999;
}
.scene-switch.is-active {
color: black;
}
@-webkit-keyframes blink {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-moz-keyframes blink {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@-o-keyframes blink {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes blink {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.light.is-blinking .circle {
-webkit-animation: blink .5s infinite alternate;
-moz-animation: blink .5s infinite alternate;
-o-animation: blink .5s infinite alternate;
animation: blink .5s infinite alternate;
}
<!DOCTYPE html>
<html>
<head>
<script src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>(function(){var a={VERSION:"2.3.2",Result:{SUCCEEDED:1,NOTRANSITION:2,CANCELLED:3,PENDING:4},Error:{INVALID_TRANSITION:100,PENDING_TRANSITION:200,INVALID_CALLBACK:300},WILDCARD:"*",ASYNC:"async",create:function(g,h){var j=(typeof g.initial=="string")?{state:g.initial}:g.initial;var f=g.terminal||g["final"];var e=h||g.target||{};var l=g.events||[];var i=g.callbacks||{};var c={};var k=function(m){var p=(m.from instanceof Array)?m.from:(m.from?[m.from]:[a.WILDCARD]);c[m.name]=c[m.name]||{};for(var o=0;o<p.length;o++){c[m.name][p[o]]=m.to||p[o]}};if(j){j.event=j.event||"startup";k({name:j.event,from:"none",to:j.state})}for(var d=0;d<l.length;d++){k(l[d])}for(var b in c){if(c.hasOwnProperty(b)){e[b]=a.buildEvent(b,c[b])}}for(var b in i){if(i.hasOwnProperty(b)){e[b]=i[b]}}e.current="none";e.is=function(m){return(m instanceof Array)?(m.indexOf(this.current)>=0):(this.current===m)};e.can=function(m){return !this.transition&&(c[m].hasOwnProperty(this.current)||c[m].hasOwnProperty(a.WILDCARD))};e.cannot=function(m){return !this.can(m)};e.error=g.error||function(o,s,r,n,m,q,p){throw p||q};e.isFinished=function(){return this.is(f)};if(j&&!j.defer){e[j.event]()}return e},doCallback:function(g,d,c,i,h,b){if(d){try{return d.apply(g,[c,i,h].concat(b))}catch(f){return g.error(c,i,h,b,a.Error.INVALID_CALLBACK,"an exception occurred in a caller-provided callback function",f)}}},beforeAnyEvent:function(d,c,f,e,b){return a.doCallback(d,d.onbeforeevent,c,f,e,b)},afterAnyEvent:function(d,c,f,e,b){return a.doCallback(d,d.onafterevent||d.onevent,c,f,e,b)},leaveAnyState:function(d,c,f,e,b){return a.doCallback(d,d.onleavestate,c,f,e,b)},enterAnyState:function(d,c,f,e,b){return a.doCallback(d,d.onenterstate||d.onstate,c,f,e,b)},changeState:function(d,c,f,e,b){return a.doCallback(d,d.onchangestate,c,f,e,b)},beforeThisEvent:function(d,c,f,e,b){return a.doCallback(d,d["onbefore"+c],c,f,e,b)},afterThisEvent:function(d,c,f,e,b){return a.doCallback(d,d["onafter"+c]||d["on"+c],c,f,e,b)},leaveThisState:function(d,c,f,e,b){return a.doCallback(d,d["onleave"+f],c,f,e,b)},enterThisState:function(d,c,f,e,b){return a.doCallback(d,d["onenter"+e]||d["on"+e],c,f,e,b)},beforeEvent:function(d,c,f,e,b){if((false===a.beforeThisEvent(d,c,f,e,b))||(false===a.beforeAnyEvent(d,c,f,e,b))){return false}},afterEvent:function(d,c,f,e,b){a.afterThisEvent(d,c,f,e,b);a.afterAnyEvent(d,c,f,e,b)},leaveState:function(f,e,h,g,d){var c=a.leaveThisState(f,e,h,g,d),b=a.leaveAnyState(f,e,h,g,d);if((false===c)||(false===b)){return false}else{if((a.ASYNC===c)||(a.ASYNC===b)){return a.ASYNC}}},enterState:function(d,c,f,e,b){a.enterThisState(d,c,f,e,b);a.enterAnyState(d,c,f,e,b)},buildEvent:function(b,c){return function(){var h=this.current;var g=c[h]||c[a.WILDCARD]||h;var e=Array.prototype.slice.call(arguments);if(this.transition){return this.error(b,h,g,e,a.Error.PENDING_TRANSITION,"event "+b+" inappropriate because previous transition did not complete")}if(this.cannot(b)){return this.error(b,h,g,e,a.Error.INVALID_TRANSITION,"event "+b+" inappropriate in current state "+this.current)}if(false===a.beforeEvent(this,b,h,g,e)){return a.Result.CANCELLED}if(h===g){a.afterEvent(this,b,h,g,e);return a.Result.NOTRANSITION}var f=this;this.transition=function(){f.transition=null;f.current=g;a.enterState(f,b,h,g,e);a.changeState(f,b,h,g,e);a.afterEvent(f,b,h,g,e);return a.Result.SUCCEEDED};this.transition.cancel=function(){f.transition=null;a.afterEvent(f,b,h,g,e)};var d=a.leaveState(this,b,h,g,e);if(false===d){this.transition=null;return a.Result.CANCELLED}else{if(a.ASYNC===d){return a.Result.PENDING}else{if(this.transition){return this.transition()}}}}}};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports){exports=module.exports=a}exports.StateMachine=a}else{if(typeof define==="function"&&define.amd){define(function(b){return a})}else{if(window){window.StateMachine=a}}}}());</script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div class="control-panel">
<button class="small-intersection">small intersection</button>
<button class="big-intersection">big intersection</button>
</div>
<div class="light red"><div class="circle red"></div></div>
<div class="light yellow"><div class="circle yellow"></div></div>
<div class="light left"><div class="arrow left"></div></div>
<div class="light up"><div class="arrow up"></div></div>
<div class="light right"><div class="arrow right"></div></div>
</body>
</html>
var timer;
var settings = {
stopped: { red: true, yellow: false, left: false, up: false, right: false },
warning: { red: false, yellow: true, left: false, up: false, right: false },
allgreen: { red: false, yellow: false, left: true, up: true, right: true },
left: { red: true, yellow: false, left: true, up: false, right: false },
up: { red: true, yellow: false, left: false, up: true, right: false },
right: { red: true, yellow: false, left: false, up: false, right: true },
upright: { red: true, yellow: false, left: false, up: true, right: true }
};
function switchLights(options) {
options = options || {};
options.red ? $('.light.red').addClass('is-active') : $('.light.red').removeClass('is-active');
options.yellow ? $('.light.yellow').addClass('is-active is-blinking') : $('.light.yellow').removeClass('is-active is-blinking');
options.left ? $('.light.left').addClass('is-active') : $('.light.left').removeClass('is-active');
options.up ? $('.light.up').addClass('is-active') : $('.light.up').removeClass('is-active');
options.right ? $('.light.right').addClass('is-active') : $('.light.right').removeClass('is-active');
}
function onFsmError(eventName, from, to, args, errorCode, errorMessage) {
console.log('event ' + eventName + ' was naughty :- ' + errorMessage);
}
var smallFsm = StateMachine.create({
initial: 'init',
// optional custom error handler
error: onFsmError,
events: [
{ name: 'stop', from: ['init', 'idle', 'warning' ], to: 'stopped' },
{ name: 'warn', from: ['init', 'idle', 'allgreen' ], to: 'warning' },
{ name: 'go', from: ['init', 'idle', 'stopped' ], to: 'allgreen' },
{ name: 'reset', from: '*', to: 'idle' }
],
callbacks: {
onenterstate: function(event, from, to) {
switchLights(settings[to]);
},
onstopped: function(event, from, to) {
timer = setTimeout(function() { smallFsm.go(); }, 3000);
},
onwarning: function(event, from, to) {
timer = setTimeout(function() { smallFsm.stop(); }, 2000);
},
onallgreen: function(event, from, to) {
timer = setTimeout(function() { smallFsm.warn(); }, 3000);
}
}
});
var bigFsm = StateMachine.create({
initial: 'init',
// optional custom error handler
error: onFsmError,
events: [
{ name: 'stop', from: ['init', 'idle', 'warning' ], to: 'stopped' },
{ name: 'warn', from: ['init', 'idle', 'left' ], to: 'warning' },
{ name: 'goLeft', from: ['init', 'idle', 'upright' ], to: 'left' },
{ name: 'goRight', from: ['init', 'idle', 'stopped' ], to: 'right' },
{ name: 'goUpRight', from: ['init', 'idle', 'right' ], to: 'upright' },
{ name: 'reset', from: '*', to: 'idle' }
],
callbacks: {
onenterstate: function(event, from, to) {
switchLights(settings[to]);
},
onstopped: function(event, from, to) {
timer = setTimeout(function() { bigFsm.goRight(); }, 2000);
},
onwarning: function(event, from, to) {
timer = setTimeout(function() { bigFsm.stop(); }, 2000);
},
onleft: function(event, from, to) {
timer = setTimeout(function() { bigFsm.warn(); }, 2000);
},
onright: function(event, from, to) {
timer = setTimeout(function() { bigFsm.goUpRight(); }, 2000);
},
onupright: function(event, from, to) {
timer = setTimeout(function() { bigFsm.goLeft(); }, 3000);
}
}
});
document.addEventListener('click', function (e) {
var $target = $(e.target);
if (e.target.tagName.toLowerCase() === 'button') {
if (timer) {
clearTimeout(timer);
}
if ($target.hasClass('small-intersection')) {
bigFsm.reset();
smallFsm.stop();
} else if ($target.hasClass('big-intersection')) {
smallFsm.reset();
bigFsm.stop();
}
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment