Ludo Game with first-class moves


In order to browse the state of the game over time, we make moves first-class objects.


Motivation: our test examples all consist of a setup example and the test case itself.

We would like to be able to see explicitly what the moves are and visualize them.

The driver for the change is the need for visualization.

Views are useful because they document what developers found interesting about the domain objects and how to query them.

Move objects

We start by creating a Move object that encapsulates a token to move and the rolled number of of steps to move.

game := GtLudoGame new.
game roll: 6.

move := GtLudoMove new
	        roll: 6;
	        token: (game tokenNamed: 'a');


We have changed players so they know their game, and now we can navigate back and forth.

We want to reuse the BoardElement to highlight a token on the board.

token := move token.
boardElement := token player game boardElement.
(boardElement children select: [ :each | 
	 (each isKindOf: GtLudoSquareElement) and: [ 
		 each square token = token ] ]) do: #highlight.

From a token we can navigate to its player, and highlight the player's tokens.

token player

We introduce GtLudoRecordingGame GtLudoGame subclass: #GtLudoRecordingGame instanceVariableNames: 'moves' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' as a subclass of GtLudoGame Object subclass: #GtLudoGame instanceVariableNames: 'players squares startSquares goalSquares die announcer feedback winner needToRollDie lastDieRolled playerQueue routeCache' classVariableNames: '' package: 'GToolkit-Demo-Ludo-Model' , with first-class moves.

GtLudoRecordingGame new

We add numerous views with highlights for the locations of tokens, squares and players.

A RecordingGame records its moves, and has a view showing the list of recorded moves.

To automatically update views, we should use GtPhlowView>>#updateWhen:in:

For example, see GtLudoGame>>#gtPlayersFor: gtPlayersFor: aView <gtView> ^ (aView columnedList) title: 'Players'; priority: 20; items: [ self players ]; column: 'Player' text: [ :player | player ]; column: 'Token' text: [ :player | player tokens first ]; column: 'State' text: [ :player | player tokens first state ]; column: 'Token' text: [ :player | player tokens second ]; column: 'State' text: [ :player | player tokens second state ]; updateWhen: GtLudoGameUpdated in: self announcer , which is automatically updated when the game state is updated.

Recorded Game views

Moves should also record the from and to squares, and the before and after feedback.

This can be displayed in the game view.

We can subclass the GameExamples class to create the same examples for the recorded games.

game := GtLudoRecordingGame new.
game roll: 6.
game moveTokenNamed: 'a'.
game roll: 6.
game moveTokenNamed: 'A'.


We introduce GtLudoRecordingGame>>#autoPlay: autoPlay: numberOfMoves "NB: For demo purposes we only implement autoPlay: in the GtLudoRecordingGame class, not in GtLudoGame." self assert: numberOfMoves > 0. (1 to: numberOfMoves) do: [ :n | self isOver ifTrue: [ ^ self ]. self playerToRoll ifTrue: [ self die roll ] ifFalse: [ self moveTokenNamed: self tokensToMove atRandom name ] ] and storeOn:

game := GtLudoRecordingGame new.