Last active
August 29, 2015 14:02
-
-
Save begeeben/da3eccb2b678612e400e to your computer and use it in GitHub Desktop.
finite state machine example
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
.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; | |
} |
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> | |
<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> |
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 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