Transitions


When Flash developers hear the word transistion, they first think of animations between screens or view states. However, in the context of the State pattern, a transition is simply what triggers our application to move from one state to another. This is not necessarily a visual thing, it's simply how a state gets changed. You probably noticed in the previous examples that we had no way to move from one state to the other. The state was defined in the main class. In this section, we'll look at two ways to handle transitions within the abstract state example we just completed.

Transitions Defined in the State Machine

The first implementation is the easiest. We set the current state in the state machine; therefore, why not also manage when that state changes? In this example, we switch the state after each shot. We do this by iterating through an array of the three state objects.

Our state machine is getting two new properties: _stateList and _index. The stateList property is an array that holds all the state names available to our state machine, and it holds them in the order in which they should be run. The index holds the current position of the state machine in the stateList array.

Inside the constructor, set the index to 0 and populate the stateList with the state names. Because we want the state machine to manage when the state is set, we're removing the setState() call from our main class. Instead, we put the initial setState() call at the end of the state machine's constructor.

The only other change is in the shoot() method. Now, instead of just returning the value of the current state's shot, it also sets the next state.

package com.peachpit.aas3wdp.stateexample {    import com.peachpit.aas3wdp.stateexample.AbstractShooterState;    import com.peachpit.aas3wdp.stateexample.LayUpState;    import com.peachpit.aas3wdp.stateexample.FreeThrowState;    import com.peachpit.aas3wdp.stateexample.ThreePointerState;    public class ShooterStateMachine {       private var _state:AbstractShooterState;       private var _stateList:Array;       private var _index:Number;       public static const LAY_UP_STATE:String = "lay_up_state";       public static const FREE_THROW_STATE:String = "free_throw_state";       public static const THREE_POINTER_STATE:String = "three_pointer_state";       public function ShooterStateMachine() {          _index = 0;          _stateList = new Array();          _stateList.push(ShooterStateMachine.LAY_UP_STATE);          _stateList.push(ShooterStateMachine.FREE_THROW_STATE);          _stateList.push(ShooterStateMachine.THREE_POINTER_STATE);          setState(_stateList[_index]);       }       public function getAccuracy():Number {          return _state.getAccuracy();       }       public function getPointValue():Number {          return _state.getPointValue();        }       public function shoot():Number {          var shotResult:Number = state.shoot();          if(++_index >= _stateList.length) _index = 0;          setState(_stateList[_index]);          return shotResult;       }       public function setState(stateDesc:String):void {          switch(stateDesc) {             case LAY_UP_STATE:               _state = new LayUpState();               break;             case FREE_THROW_STATE:               _state = new FreeThrowState();               break;             case THREE_POINTER_STATE:               _state = new ThreePointerState();               break;             default:               _state = null;          }        }      }    }


Transitions Defined in the State Objects

The other option for where to put the transition logic is a little more involved because it requires putting the transition logic inside the state objects. For this implementation, we'll revert back to the state machine used in the original abstract state example, with two minor changes. We're going to pass a reference to the state machine into each of the state objects' constructors, and we're going to set the first state in the state machine's constructor.

package com.peachpit.aas3wdp.stateexample {    import com.peachpit.aas3wdp.stateexample.AbstractShooterState;    import com.peachpit.aas3wdp.stateexample.LayUpState;    import com.peachpit.aas3wdp.stateexample.FreeThrowState;    import com.peachpit.aas3wdp.stateexample.ThreePointerState;    public class ShooterStateMachine {       private var _state:AbstractShooterState;       public static const LAY_UP_STATE:String = "lay_up_state";       public static const FREE_THROW_STATE:String = "free_throw_state";       public static const THREE_POINTER_STATE:String = "three_pointer_state";       public function ShooterStateMachine() {          setState(ShooterStateMachine.LAY_UP_STATE);        }        public function getAccuracy():Number {           return _state.getAccuracy();        }        public function getPointValue():Number {           return _state.getPointValue();         }        public function shoot():Number {           return _state.shoot();        }        public function setState(stateDesc:String):void {           switch(stateDesc) {              case LAY_UP_STATE:                _state = new LayUpState(this);                break;              case FREE_THROW_STATE:                _state = new FreeThrowState(this);                break;              case THREE_POINTER_STATE:                _state = new ThreePointerState(this);                break;              default:                _state = null;          }       }    } }


The state objects do most of the work for us. Each stores a reference to the state machine and calls its setState() method when the state object wants to move to the next state. We're still triggering that move on the shoot() method call, so we need to override that method. We still call the abstract state's shoot() method using the super keyword, but we need to override it so that we can add the setState() call to it. The following are the new state objects with these changes:

package com.peachpit.aas3wdp. stateexample {    import com.peachpit.aas3wdp. stateexample.AbstractShooterState;    internal class LayUpState extends AbstractShooterState {       private var _stateMachine:ShooterStateMachine;       public function LayUpState(_stateMachine:ShooterStateMachine) {          this._stateMachine = _stateMachine;       }       public override function getAccuracy():Number {          return 0.9;       }       public override function getPointValue():Number {          return 2;       }       public override function shoot():Number {          var pointValue:Number = super.shoot();          _stateMachine.setState(ShooterStateMachine.FREE_THROW_STATE);          return pointValue;       }     } } package com.peachpit.aas3wdp.stateexample {    import com.peachpit.aas3wdp.stateexample.AbstractShooterState;    import com.peachpit.aas3wdp.stateexample.ShooterStateMachine;    internal class FreeThrowState extends AbstractShooterState {       private var _stateMachine:ShooterStateMachine;       public function FreeThrowState(_stateMachine:ShooterStateMachine) {          this._stateMachine = _stateMachine;       }       public override function getAccuracy():Number {          return 0.7;       }       public override function getPointValue():Number {          return 1;        }        public override function shoot():Number {          var pointValue:Number = super.shoot();          _stateMachine.setState(ShooterStateMachine.THREE_POINTER_STATE);          return pointValue;        }     } } package com.peachpit.aas3wdp.stateexample {    import com.peachpit.aas3wdp.stateexample.AbstractShooterState;    internal class ThreePointerState extends AbstractShooterState {       private var _stateMachine:ShooterStateMachine;       public function ThreePointerState(_stateMachine:ShooterStateMachine) {          this._stateMachine = _stateMachine;       }       public override function getAccuracy():Number {          return 0.4;       }       public override function getPointValue():Number {          return 3;       }       public override function shoot():Number {          var pointValue:Number = super.shoot();          _stateMachine.setState(ShooterStateMachine.LAY_UP_STATE);          return pointValue;       }            } }


This logic could have gone into the abstract class, but it's left on the state subclasses because it demonstrates the flexibility of defining the transitions this way. If you're going to treat all the transitions the same, and you have a set methodology for how you trigger the transitions, then the state machine should manage that. However, if each state has different criteria for triggering a transition, defining the transition on the state object provides the most flexibility.

For example, maybe we want to change the way the LayUpState transitions. We'll require a shooter to make a lay-up before we move on to the free throw. All we need to do is add a condition to the shoot() method. The following code shows the simple change:

package com.peachpit.aas3wdp.stateexample {    import com.peachpit.aas3wdp.stateexample.AbstractShooterState;    internal class LayUpState extends AbstractShooterState {       private var _stateMachine:ShooterStateMachine;       public function LayUpState(stateMachine:ShooterStateMachine) {          this._stateMachine = _stateMachine;       }       public override function getAccuracy():Number {          return 0.9;       }       public override function getPointValue():Number {          return 2;       }       public override function shoot():Number {          var pointValue:Number = super.shoot();          if(pointValue > 0) {            _stateMachine.setState(ShooterStateMachine.FREE_THROW_STATE);          }          return pointValue;       }     } }





Advanced ActionScript 3 with Design Patterns
Advanced ActionScript 3 with Design Patterns
ISBN: 0321426568
EAN: 2147483647
Year: 2004
Pages: 132

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net