Using Mementos to Make Actions Undoable in the Proximity Game


Often, mementos are used in conjunction with commands in order to implement complex undoable and redoable commands. The following application applies mementos to the Proximity game application you created in the previous chapter, "Command Pattern"; the mementos will make the commands undoable in the Proximity game.

Defining the Memento Type

The first thing we'll do is define a memento class. The class com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento serves as the memento type used to store game piece state.

     package com.peachpit.aas3wdp.proximity.mementos {         import com.peachpit.aas3wdp.proximity.data.GamePlayer;         public class GamePieceMemento {            private var _count:uint;            private var _owner:GamePlayer;            public function get count():uint {               return _count;            }            public function get owner():GamePlayer {               return _owner;            }            public function GamePieceMemento(count:uint, owner:GamePlayer) {               _count = count;               _owner = owner;            }         }      }


You can see that the memento in this case stores values for count and owner. These values represent state for a PieceData object.

Creating the Originator

In the Proximity game, the mementos we want to store are for PieceData objects. Therefore, we'll need to make the PieceData class an originator by adding getMemento() and setMemento() methods. Here's PieceData with the new methods (we've omitted some of the code here just for the purposes of saving printed space):

     package com.peachpit.aas3wdp.proximity.data {         import flash.events.EventDispatcher;         import flash.events.Event;         import com.peachpit.aas3wdp.proximity.data.GamePlayer;         import com.peachpit.aas3wdp.proximity.data.NullOwner;         import com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento;         public class PieceData extends EventDispatcher {            // Existing code goes here.            public function getMemento():GamePieceMemento {              return new GamePieceMemento(_count, _owner);            }            public function setMemento(memento:GamePieceMemento):void {               _count = memento.count;               _owner = memento.owner;               dispatchEvent(new Event(Event.CHANGE));            }               }     }


You can see that the getMemento() method simply constructs and returns a new GamePieceMemento object. The setMemento() method takes a GamePieceMemento instance, restores the PieceData state, and dispatches an event to notify listeners that the data model has changed.

Defining the Undoable Command Type

Next we'll define an undoable command type. The undoable command should inherit from the standard command type (GamePlayCommand). In addition, the command needs to implement the IUndoableCommand interface. The class, com.peachpit.aas3wdp.proximity.commands.UndoableGamePlayCommand is as follows:

     package com.peachpit.aas3wdp.proximity.commands {         import com.peachpit.aas3wdp.proximity.data.PieceData;         import com.peachpit.aas3wdp.proximity.data.GamePlayer;         import com.peachpit.aas3wdp.proximity.data.GamePlayers;         import com.peachpit.aas3wdp.proximity.data.GameboardData;         import com.peachpit.aas3wdp.proximity.commands.GamePlayCommand;         import com.peachpit.aas3wdp.iterators.IIterator;         import com.peachpit.aas3wdp.proximity.mementos.GamePieceMemento;         import com.peachpit.aas3wdp.proximity.data.GameboardData;         import com.peachpit.aas3wdp.proximity.data.NullOwner;         import com.peachpit.aas3wdp.proximity.data.PieceData;         import com.peachpit.aas3wdp.commands.CommandStack;         import com.peachpit.aas3wdp.commands.IUndoableCommand;         public class UndoableGamePlayCommand extends GamePlayCommand implements            IUndoableCommand {            protected var _gamePieceMementos:Array;            protected var _gameboardMemento:GamePieceMemento;            public function UndoableGamePlayCommand(piece:PieceData) {               super(piece);               _gamePieceMementos = new Array();            }            override public function execute():void {               var gameboard:GameboardData = GameboardData.getInstance();               if(_piece.owner is NullOwner) {                  // Get the memento for the clicked game piece.                  _gamePieceMementos.push({object:                  _piece, memento: _piece.getMemento()});                  var iterator:IIterator = gameboard.getProximityPieces(_piece);                  var piece:PieceData;                  while(iterator.hasNext()) {                     piece = PieceData(iterator.next());                     // Add a memento for the adjacent                      // game piece.                     _gamePieceMementos.push({object: piece, memento: piece.getMemento()});                  }                  // Add a memento for the new game piece and for the gameboard.                  _gameboardMemento = gameboard.getMemento();               }               super.execute();               CommandStack.getInstance().putCommand(this);            }            public function undo():void {              for(var i:uint = 0; i < _gamePieceMementos.length; i++) {                 _gamePieceMementos[i].object.setMemento(_gamePieceMementos[i].memento);              }              GameboardData.getInstance().setMemento(_gameboardMemento);           }         }      }


This class overrides the execute() method so that it can retrieve all the mementos for the affected game pieces before changing their state. Then the undo() method loops through all the mementos and restores the originator state for each affected game piece.

Updating the Command Factory

Next we want to edit CommandFactory so that it returns a UndoableGamePlayCommand instance when the option is set correctly. Here's the updated getGamePlayCommand() method:

       public static function getGamePlayCommand(data:PieceData):ICommand {           if(_type == NORMAL) {              return new GamePlayCommand(data);           }           else if(_type == UNDOABLE) {             return new UndoableGamePlayCommand(data);           }           return null;       }


Updating the Main Class

Next we can edit the main class to enable undoable commands in the game. The behavior we are striving for is to undo commands when the user presses the left-arrow key.

The first thing we need to do in the main class is edit the constructor and assign UNDOABLE rather than NORMAL to the CommandFactory.type property:

     CommandFactory.type = CommandFactory.UNDOABLE


Next we'll add keyboard control. To do this the class must import the Keyboard and KeyboardEvent classes:

     import flash.events.KeyboardEvent;      import flash.ui.Keyboard;


Then we'll add the following line of code to the main class constructor to listen for keyboard events:

     stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboard);


Add an onKeyboard() method to the main class. Define the method as follows:

     private function onKeyboard(event:KeyboardEvent):void {         var stack:CommandStack = CommandStack.getInstance();         var command:ICommand;         // If the user pressed the left arrow and there are         // previous commands in the stack,         // and if the command is undoable, call undo().         if(event.keyCode == Keyboard.LEFT && stack.hasPreviousCommands()) {            command = stack.previous();            if(command is IUndoableCommand) {               IUndoableCommand(command).undo();            }            else {              stack.next();            }         }      }


Those few changes make the game's play actions undoable. When you test the Proximity application now, you can use the left-arrow key to undo the actions you have applied.




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