7.1 Command

Among the design patterns it is likely one that impact the most the end-user: it lets you implement the undo and redo operations.

The template repository CuisApp14 demonstrates the design of a picture viewer, as a simple Cuis application example. The user can rotate, flip and scale a picture

Its undo and redo actions are implemented with three classes:

ch07-commandDiagram

Figure 7.1: Command pattern diagram (for ease of reading the App prefix is removed)

7.1.1 Memory game with undo/redo

What will be the implication to implement the undo and redo actions within our game? Our game is simple, the user can only click one card at a time, therefore we want to record this and also the consequences on the game state, if any. The consequences can be none, matching cards or non matching cards; each resulting in a different changed game state.

When implementing the user interaction with a command, the execute method will deal with these three possible outcomes. Its unexecute counterpart will have to reverse the game to its original state.

If the game state has a small memory footprint, it is less cumbersome to just save its state before executing each user action. The game state is the collection of each card’s status: the done and flipped Boolean values.

Our Memory game just need to be flanked with the command classes we described earlier, unchanged. Then the Command hierarchy will have one subclass PlayCardCommand:

Command subclass: #PlayCardCommand
   tanceVariableNames: 'status position'
   ...

It captures the game state in its status attribute, of the same nature as the cards array in the Memory game model:

initialize
status := Array2D newSize: presenter model cards size

At command execution,

execute
self backupModels.
presenter flip: position

the game state is backed up before flipping the card:

backupModels
| size |
size := presenter model cards size.
1 to: size y do: [:y |
   1 to: size x do: [:x | | card |
      card := presenter model cards at: x@y.
      status at: x@y put: (Array with: card isFlipped with: card isDone) ]]

The undo action restores the game state before execution:

unexecute
" Restore the status of the card models "
| size |
size := status size.
1 to: size y do: [:y |
   1 to: size x do: [:x | | cardStatus card |
      card := presenter model cards at: x@y.
      cardStatus := status at: x@y.
      card 
         flip: cardStatus first;
         done: cardStatus second ] ]

The card models’s flip: and done: methods are refactored to trigger events propagated to the card view:

MemoryCardModel>>flip: boolean
" Set my flip state and trigger a color event for my view accordingly to my flip state "
| newColor |
flipped = boolean ifTrue: [^ self].
flipped := boolean.
newColor := flipped ifTrue: [color] ifFalse: [self backColor].
self triggerEvent: #color with: newColor

and

done: boolean
done = boolean ifTrue: [^ self].
done := boolean.
self triggerEvent: (done ifTrue: [#lock] ifFalse: [#unlock])

In Memory Game v3, you will find the complete source of the modified Memory game: toolbar with undo and redo buttons.


Footnotes

(14)

https://github.com/hilaire/CuisApp