Class | FiniteStateMathing |
Parent: | Object |
Author: | Gavin Kistner (!@phrogz.net) |
Copyright: | Copyright ©2005 Gavin Kistner |
License: | See Phrogz.net/JS/_ReuseLicense.txt for details |
Version: | 0.6, 2005-May-26 |
Full Code: | FiniteStateMathing.rb |
Sort of a Finite State Machine, but using blocks as delegates to decide what state to switch to based on an input (and to perform various side effects).
To setup, subclass the FiniteStateMathing, and define a series of states and blocks to handle input during that state. Each block must return either a new state to transition to, or nil if an end state has been reached.
To use, instantiate your subclass, set the state property of the machine to your starting state, and then repeatedly invoke the transition method, passing in the trigger to be handled by the blocks.
The following example shows how to implement a simple elevator door state machine. It shows how to use instance variables to keep track of extra information during state transitions, (the @interruptions variable) and use that information to influence the reaction to a trigger. It also shows that you can use instance methods (sound_alarm) to assist in the state machine processing.
class ElevatorState < FiniteStateMathing def sound_alarm puts "***BUZZ!***" end handle_state( :doors_closed ) do |command| @interruptions = 0 case command when :open_doors then :opening_doors else :doors_closed end end handle_state( :opening_doors ) do |command| case command when :done_opening then :doors_opened when :close_doors then :closing_doors else :opening_doors end end handle_state( :closing_doors ) do |command| case command when :done_closing then :doors_closed when :hit_something, :open_doors if ( @interruptions += 1) > 4 sound_alarm :closing_doors else :opening_doors end else :closing_doors end end handle_state( :doors_opened ) do |command| case command when :close_doors, :open_timeout :closing_doors else :doors_opened end end end valid_commands_per_state = { :doors_closed => [ :open_doors, :close_doors ], :opening_doors => [ :open_doors, :close_doors, :done_opening ], :doors_opened => [ :open_doors, :close_doors, :open_timeout ], :closing_doors => [ :open_doors, :close_doors, :done_closing, :hit_something ] } door_controller = ElevatorState.new( :doors_closed ) 11.times{ valid_commands = valid_commands_per_state[ door_controller.state ] command = valid_commands[ rand( valid_commands.length ) ] print "#{door_controller.state}\n+ #{command} => " door_controller.transition( command ) } print "#{door_controller.state}" #=> doors_closed #=> + open_doors => opening_doors #=> + done_opening => doors_opened #=> + open_timeout => closing_doors #=> + open_doors => opening_doors #=> + close_doors => closing_doors #=> + open_doors => opening_doors #=> + done_opening => doors_opened #=> + open_timeout => closing_doors #=> + open_doors => ***BUZZ!*** #=> closing_doors #=> + open_doors => ***BUZZ!*** #=> closing_doors #=> + close_doors => closing_doors