Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

renaming to coffee-machine

  • Loading branch information...
commit fc715ea90f46a4880c1cc9759e9ec1ce9218cf42 1 parent 844542b
Stephen Blankenship authored
2  Cakefile
View
@@ -8,5 +8,5 @@ task 'build', 'Build project from src/*.coffee to lib/*.js', ->
console.log "build complete. #{stdout} #{stderr}"
task 'test', 'Runs vowsjs test suite', ->
- exec './node_modules/vows/bin/vows test/test_state_machine.coffee --spec', (error, stdout, stderr) ->
+ exec './node_modules/vows/bin/vows test/test_coffee_machine.coffee --spec', (error, stdout, stderr) ->
console.log stdout
18 README.md
View
@@ -6,15 +6,15 @@ A simple state machine written in CoffeeScript.
Sample Usage:
------
-A "StateMachine" class is provided that can be used as the basis of your state machine implementation.
+A "CoffeeMachine" class is provided that can be used as the basis of your state machine implementation.
The object passed in to the constructor has an expected format that will define the state machine.
The sample stuff below will use a chess game as a basic example.
Step one will always be to require the state machine:
- {StateMachine} = require 'state_machine'
+ {CoffeeMachine} = require 'coffee_machine'
-The StateMachine class' constructor takes in an object that defines the entire state machine.
+The CoffeeMachine class' constructor takes in an object that defines the entire state machine.
Here's what it looks like:
states:
@@ -33,7 +33,7 @@ Here's what it looks like:
If you don't need anything fancy on the states, then you can use a basic Array setup:
- game = new StateMachine states: ['whiteToMove', 'blackToMove']
+ game = new CoffeeMachine states: ['whiteToMove', 'blackToMove']
game.availableStates()
# outputs: [ 'whiteToMove', 'blackToMove' ]
@@ -43,7 +43,7 @@ If you don't need anything fancy on the states, then you can use a basic Array s
But, you should really define some *events* that will trigger state changes. Each
defined event gives you a method you can call to trigger the state change.
- class ChessGame extends StateMachine
+ class ChessGame extends CoffeeMachine
switchSides: ->
# ...
console.log "switchSides called."
@@ -61,10 +61,10 @@ defined event gives you a method you can call to trigger the state change.
game.whiteMoved()
# outputs: switchSides called.
-You can also pass the states definition to the defineStateMachine method. So, a more custom
+You can also pass the states definition to the defineCoffeeMachine method. So, a more custom
and comprehensive implementation may look like:
- class ChessGame extends StateMachine
+ class ChessGame extends CoffeeMachine
constructor: (@board, @pieces) ->
@defineStateMachine
states:
@@ -148,3 +148,7 @@ Note that each callback method (onEnter, onExit, guard, and onStateChange) gets
has a "from", "to", and "event" key, providing the previous state, new state, and the
event that triggered the state change.
+Tests
+------
+ cake test
+
159 lib/coffee_machine.js
View
@@ -0,0 +1,159 @@
+(function() {
+ var CoffeeMachine, root;
+ var __hasProp = Object.prototype.hasOwnProperty, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ root = typeof exports !== "undefined" && exports !== null ? exports : window;
+ root.CoffeeMachine = CoffeeMachine = (function() {
+ function CoffeeMachine(stateMachine) {
+ this.stateMachine = stateMachine != null ? stateMachine : {
+ states: {},
+ events: {}
+ };
+ this.defineStateMachine(this.stateMachine);
+ }
+ CoffeeMachine.prototype.defineStateMachine = function(stateMachine) {
+ var activeStates, event, eventDef, state, stateDef, states, _i, _j, _len, _len2, _ref, _ref2, _results;
+ this.stateMachine = stateMachine != null ? stateMachine : {
+ states: {},
+ events: {}
+ };
+ if (this.stateMachine.states.constructor.toString().indexOf('Array') !== -1) {
+ states = this.stateMachine.states;
+ this.stateMachine.states = {};
+ for (_i = 0, _len = states.length; _i < _len; _i++) {
+ state = states[_i];
+ this.stateMachine.states[state] = {
+ active: state === states[0]
+ };
+ }
+ }
+ activeStates = (function() {
+ var _ref, _results;
+ _ref = this.stateMachine.states;
+ _results = [];
+ for (state in _ref) {
+ if (!__hasProp.call(_ref, state)) continue;
+ stateDef = _ref[state];
+ if (stateDef.active) {
+ _results.push(state);
+ }
+ }
+ return _results;
+ }).call(this);
+ if (activeStates.length === 0) {
+ _ref = this.stateMachine.states;
+ for (state in _ref) {
+ if (!__hasProp.call(_ref, state)) continue;
+ stateDef = _ref[state];
+ stateDef.active = true;
+ break;
+ }
+ } else if (activeStates.length > 1) {
+ for (_j = 0, _len2 = activeStates.length; _j < _len2; _j++) {
+ state = activeStates[_j];
+ if (state === activeStates[0]) {
+ continue;
+ }
+ stateDef.active = false;
+ }
+ }
+ _ref2 = this.stateMachine.events;
+ _results = [];
+ for (event in _ref2) {
+ eventDef = _ref2[event];
+ _results.push(__bind(function(event, eventDef) {
+ return this[event] = function() {
+ return this.changeState(eventDef.from, eventDef.to, event);
+ };
+ }, this)(event, eventDef));
+ }
+ return _results;
+ };
+ CoffeeMachine.prototype.currentState = function() {
+ var state, stateDef;
+ return ((function() {
+ var _ref, _results;
+ _ref = this.stateMachine.states;
+ _results = [];
+ for (state in _ref) {
+ if (!__hasProp.call(_ref, state)) continue;
+ stateDef = _ref[state];
+ if (stateDef.active) {
+ _results.push(state);
+ }
+ }
+ return _results;
+ }).call(this))[0];
+ };
+ CoffeeMachine.prototype.availableStates = function() {
+ var state, _ref, _results;
+ _ref = this.stateMachine.states;
+ _results = [];
+ for (state in _ref) {
+ if (!__hasProp.call(_ref, state)) continue;
+ _results.push(state);
+ }
+ return _results;
+ };
+ CoffeeMachine.prototype.availableEvents = function() {
+ var event, _ref, _results;
+ _ref = this.stateMachine.events;
+ _results = [];
+ for (event in _ref) {
+ if (!__hasProp.call(_ref, event)) continue;
+ _results.push(event);
+ }
+ return _results;
+ };
+ CoffeeMachine.prototype.changeState = function(from, to, event) {
+ var args, enterMethod, exitMethod, fromStateDef, guardMethod, toStateDef;
+ if (event == null) {
+ event = null;
+ }
+ if (from.constructor.toString().indexOf('Array') !== -1) {
+ if (from.indexOf(this.currentState()) !== -1) {
+ from = this.currentState();
+ } else {
+ throw "Cannot change from states " + (from.join(' or ')) + "; none are the active state!";
+ }
+ }
+ fromStateDef = this.stateMachine.states[from];
+ toStateDef = this.stateMachine.states[to];
+ if (toStateDef === void 0) {
+ throw "Cannot change to state '" + to + "'; it is undefined!";
+ }
+ enterMethod = toStateDef.onEnter, guardMethod = toStateDef.guard;
+ if (from !== 'any') {
+ if (fromStateDef === void 0) {
+ throw "Cannot change from state '" + from + "'; it is undefined!";
+ }
+ if (fromStateDef.active !== true) {
+ throw "Cannot change from state '" + from + "'; it is not the active state!";
+ }
+ }
+ if (from === 'any') {
+ fromStateDef = this.stateMachine.states[this.currentState()];
+ }
+ exitMethod = fromStateDef.onExit;
+ args = {
+ from: from,
+ to: to,
+ event: event
+ };
+ if (guardMethod !== void 0 && guardMethod.call(this, args) === false) {
+ return false;
+ }
+ if (exitMethod !== void 0) {
+ exitMethod.call(this, args);
+ }
+ if (enterMethod !== void 0) {
+ enterMethod.call(this, args);
+ }
+ if (this.stateMachine.onStateChange !== void 0) {
+ this.stateMachine.onStateChange.call(this, args);
+ }
+ fromStateDef.active = false;
+ return toStateDef.active = true;
+ };
+ return CoffeeMachine;
+ })();
+}).call(this);
6 package.json
View
@@ -1,13 +1,13 @@
{
"author": "Stephen Blankenship",
- "name": "state_machine",
+ "name": "coffee-machine",
"description": "A simple state machine written in CoffeeScript.",
- "version": "0.0.1",
+ "version": "0.0.2",
"repository": {
"type": "git",
"url": "git://github.com/stephenb/state_machine.git"
},
- "main": "./lib/state_machine",
+ "main": "./lib/coffee_machine",
"scripts": {
"test": "cake test"
},
4 src/state_machine.coffee → src/coffee_machine.coffee
View
@@ -1,6 +1,6 @@
root = exports ? window
-# new StateMachine
+# new CoffeeMachine
# states:
# stateName:
# active: true/false (optional, the 1st state defaults to true)
@@ -13,7 +13,7 @@ root = exports ? window
# to: toState (should be a defined state)
# onStateChange: changeMethod (called on any state change)
#
-root.StateMachine = class StateMachine
+root.CoffeeMachine = class CoffeeMachine
constructor: (@stateMachine = {states:{}, events:{}}) ->
this.defineStateMachine(@stateMachine)
20 test/test_state_machine.coffee → test/test_coffee_machine.coffee
View
@@ -1,11 +1,11 @@
vows = require 'vows'
assert = require 'assert'
-{StateMachine} = require '../src/state_machine'
+{CoffeeMachine} = require '../src/coffee_machine'
-vow = vows.describe('StateMachine')
+vow = vows.describe('CoffeeMachine')
vow.addBatch
'State setup using an array':
- topic: new StateMachine {
+ topic: new CoffeeMachine {
states: ['state1', 'state2', 'state3']
}
@@ -27,7 +27,7 @@ vow.addBatch
vow.addBatch
'State setup using full object':
- topic: new StateMachine
+ topic: new CoffeeMachine
states:
state1:
onEnter: -> 'onEnter state1'
@@ -61,7 +61,7 @@ vow.addBatch
vow.addBatch
'onExit':
topic: ->
- new StateMachine
+ new CoffeeMachine
states:
state1:
onExit: -> throw 'onExitCalled'
@@ -78,7 +78,7 @@ vow.addBatch
'onEnter':
topic: ->
- new StateMachine
+ new CoffeeMachine
states:
state1: {}
state2:
@@ -95,7 +95,7 @@ vow.addBatch
'guard':
topic: ->
- new StateMachine
+ new CoffeeMachine
states:
state1: {}
state2:
@@ -110,7 +110,7 @@ vow.addBatch
'onStatechange':
topic: ->
- new StateMachine
+ new CoffeeMachine
states:
state1: {}
state2: {}
@@ -127,7 +127,7 @@ vow.addBatch
'Callbacks should contain state and event info':
topic: ->
- new StateMachine
+ new CoffeeMachine
states:
state1:
onExit: (args) -> this.returnedArgs = args
@@ -159,7 +159,7 @@ vow.addBatch
vow.addBatch
'Events':
topic: ->
- new StateMachine
+ new CoffeeMachine
states: ['state1', 'state2', 'state3']
events:
state1to2: {from:'state1', to:'state2'}
Please sign in to comment.
Something went wrong with that request. Please try again.