Section 5

Our next step is modify the Grid class definition. We need to add an instance variable, "movesStack".

Close any open LaserGame morphs you may have open.

Object subclass: #Grid
     instanceVariableNames: 'cells laserIsActive numberOfColumns numberOfRows laserBeamPath movesStack'
     classVariableNames: ''
     poolDictionaries: ''
     category: 'Laser-Game-Model'

Create some accessor methods for our new instance variable. I'm going to go ahead use lazy initialization with the getter method. These methods are instance methods on the Grid class.

movesStack: aCollection
     movesStack := aCollection

     movesStack isNil ifTrue: [self movesStack: OrderedCollection new].

The "movesStack" is where we will store every valid cell operation the user performs. When we want to perform an undo we remove the last entry from the stack.

Our next step is to refactor the push actions on our Grid class. We can reduce the work down to a single parameterized method. Make the following additions and changes to these Grid instance methods.

stackAction: aSymbol forCell: aCell
     self movesStack add: aSymbol->(aCell gridLocation)

pushCellAction: aDirectionSymbol fromLocation: aPoint
     | direction cell swappedCell |
     cell := self at: aPoint.
     direction := GridDirection directionFor: aDirectionSymbol.
     swappedCell := self pushCell: direction fromLocation: aPoint.
     swappedCell gridLocation = cell gridLocation ifFalse: [
         self stackAction: aDirectionSymbol forCell: cell].

pushCellEastFromLocation: aPoint
     ^self pushCellAction: #east fromLocation: aPoint

pushCellNorthFromLocation: aPoint
     ^self pushCellAction: #north fromLocation: aPoint

pushCellSouthFromLocation: aPoint
     ^self pushCellAction: #south fromLocation: aPoint

pushCellWestFromLocation: aPoint
     ^self pushCellAction: #west fromLocation: aPoint

rotateCellClockwiseAt: aPoint
     | cell |
     cell := self at: aPoint.
     self clearCellsInPath.
     (self at: aPoint) rotateClockwise.
     self laserIsActive ifTrue: [self activateCellsInPath].
     self stackAction: #clockwise forCell: cell

rotateCellCounterClockwiseAt: aPoint
     | cell |
     cell := self at: aPoint.
     self clearCellsInPath.
     (self at: aPoint) rotateCounterClockwise.
     self laserIsActive ifTrue: [self activateCellsInPath].
     self stackAction: #counterClockwise forCell: cell

Now we write the undo method.

     | actionAssociation symbol location reverseAction arguments |
     self movesStack isEmpty ifTrue: [^false].
     actionAssociation := self movesStack removeLast.
     symbol := actionAssociation key.
     location := actionAssociation value.
     reverseAction := ReverseLaserGameAction reverseActionSymbolFor: symbol.
     arguments := Array with: location.
     self perform: reverseAction withArguments: arguments.
     self movesStack removeLast.

Undo will answer a boolean value to indicate its success at doing an undo operation. The first check it makes is to see if the "movesStack" is empty. If it is, then there is no undo action possible. We then pull the last entry off the stack. The entries are Association objects placed there by the #stackAction:forCell: method we saw earlier. The association has the original action system and cell location for that action. We then utilize the ReverseLaserGameAction logic to get the instance method name we need to execute to perform the undo. Note that after we perform the action, the undo action is now on the stack. Since we don't want that to stay, we pop it off the stack before we answer "true" that we completed the undo request.

Index Page Next Page

Copyright © 2007, 2008, 2009, 2010 Stephan B Wessels