BoardGame: A MiniDraw extension Henrik Bærbak Christensen Status: Draft. November 29, 2010
Chapter 1 Boardgame Architecture The MiniDraw framework is described in Chapter 30 in Flexible, Reliable Software. The Boardgame extension is a set of classes defined in the minidraw.boardgame package and defines an augmented framework in itself for the more specialized domain of supporting graphical user interfaces for board games. It does so by providing implementations of some of MiniDraw s hotspots (essentially turning these into frozen spots) while providing new hotspots to be defined by developers of board games. Boardgame assumes a well-defined decoupling between the game itself (called the domain) and the graphical user interface (the GUI) which of course is a classic architecture. Boardgame provides hotspots to support the two flows of information and control: From GUI to game domain: That is, when a user manipulate some graphical object, this action is converted into an action in the domain. The archetypical example is the user dragging a token from one square of the board game board to another which must be translated into a call to a move() sor similar method in the game domain. From game domain to GUI: That is, when the state changes in board game, then the GUI must be updated to reflect this. For instance if a chess piece hits an opponent piece, then the opponent piece must disappear or move to a place outside the chess board. Boardgame provides a number of hotspots to support this flow more directly than does MiniDraw. [DISCUSSION PENDING] 1.1 Notes on the Process Note that the focus here is on the GUI and no domain code exists beforehand. 2
Chapter 2 Snakes and Ladders by TDD The snakes and ladders game is a very old childrens game. The game s logic is extremely simple: roll a die and move forward accoring to the die value, if you hit a square with a ladder you will move (forward) to the square at the end of the ladder and if you hit a snake you will move (backward) to the square at the end of the snake. The first player to reach the end square wins. From the extension package boardgame s perspective it is ideal because it requires all the behaviour supported by the framework: Generic type LOCATION: The squares on which the players tokens rest. BoardFigure: Moveable objects with a graphical appearance (representing the tokens of the players) that are moved by the user using the mouse. Props: Static objects with an appearance that can be clicked by the user (the die, clicking telling it to roll) PositioningStrategy: Once a tokens has been put on a square we would like to adjust its position to appear nice. To start the project I devise a short test list ] Show the board and a die ] Make tokens and die into BoardFigures ] Move a token invokes the game s move method 2.1 Iteration 1: Show Board and Die A Ant build script is written and a standard folder setup is created like that in FRS chapter 6. A simple snakes and ladder board is found on wikipedia and its size is increased a bit to make room for a die. Together with die images it is copied to the resource folder. 3
4 Snakes and Ladders by TDD In package snakesandladders.visual I create a simple test program, just to load the board background and show a single die (and target show). This is basically just a copy of a similar show graphics test program from the HotGammon project from FRS. Note that it only uses the standard MiniDraw API. package snakesladders. v i s u a l ; import minidraw. standard. ; import minidraw. framework. ; import java. awt. ; import javax. swing. ; / Show v i s u a l a p p e a r a n c e o f game. <# i f t y p e == " c o d e "> <# i n c l u d e " / d a t a / a u t h o r. t x t "> </# i f > / public c l a s s ShowLayout { public s t a t i c void main ( S t r i n g [ ] args ) { DrawingEditor e d i t o r = new MiniDrawApplication ( " Show Layout... ", new SnakesAndLaddersFactory ( ) ) ; e d i t o r. open ( ) ; Figure die = new ImageFigure ( " die4 ", new Point ( 6 9 0, 4 0 ) ) ; e d i t o r. drawing ( ). add ( die ) ; e d i t o r. settool ( new S e l e c t i o n T o o l ( e d i t o r ) ) ; c l a s s SnakesAndLaddersFactory implements Factory { public DrawingView createdrawingview ( DrawingEditor e d i t o r ) { DrawingView view = new StdViewWithBackground ( editor, " snakes and ladders background " ) ; return view ; public Drawing createdrawing ( DrawingEditor e d i t o r ) { return new StandardDrawing ( ) ; public J T e x t F i e l d c r e a t e S t a t u s F i e l d ( DrawingEditor e d i t o r ) { J T e x t F i e l d s t a t u s F i e l d = new J T e x t F i e l d ( " Hello Snakes... " ) ; s t a t u s F i e l d. s e t E d i t a b l e ( f a l s e ) ; return s t a t u s F i e l d ; The resulting graphics is shown in Figure 2.1.
Iteration 2: Making BoardFigures 5 Figure 2.1: Visual test case of iteration 1. 2.2 Iteration 2: Making BoardFigures Next I want to make a One Step Test to start building up the boardgame extension stuff. The first one is getting boardgame to understand the three BoardFigures that must be on the GUI. This involves creating BoardFigures using the FigureFactory. I start by making a new test case program ShowFigures as a copy of iteration 1 program. Unfortunately, the next step is really a several step process: 1) I have to create a FigureFactory which 2) require generic type LOCATION and 3) configure MiniDraw with a BoardDrawing instance instead of just a StandardDrawing (see method createdrawing in the previous listing). Furthermore, step 3 s BoardDrawing requires a PositioningStrategy which is then step 4! To get to this point I have to Fake It a lot. It appears that point 3) is actually the proper first step, so I need to fake both LOCA- TION and FigureFactory. This leads to the following code change (TDD rhythm step 1): public Drawing createdrawing ( DrawingEditor e d i t o r ) { return new BoardDrawing<Square >(new SnakeLadderPieceFactory ( game ), new SnakeLadderPositioningStrategy ( ), null / wait with t h e d i e prop / ) ; which of course does not compile at all! I hurry up to define some fake it implementations as local classes in the same file.
6 Snakes and Ladders by TDD c l a s s Square { c l a s s SnakeLadderPositioningStrategy implements P o s i t i o n i n g S t r a t e g y <Square > { public Point calculatefigurecoordinatesindexedforlocation ( Square l o c a t i o n, i n t index ) { return new Point ( 8 0, 3 0 0 ) ; public Point calculatefigurecoordinatesforprops ( S t r i n g keyofprop ) { return null ; c l a s s SnakeLadderPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { return null ; public Map< S t r i n g, BoardFigure > generatepropmap ( ) { return null ; Now it at least compiles but I get an exception from boardgame extension at runtime: BoardGame contract violation: buildpiecemap assumes a non-null map is return from the FigureFactory. So to get at Step 3 I add the images of two tokens, one for blue and one for red. I start by adding just one token, as I then do not have to add any implementation of Square. The graphics I found on the net and it is licensed under the Free Art License. c l a s s SnakeLadderPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { Map<Square, L ist <BoardFigure >> m = new HashMap<Square, List <BoardFigure > >(); Square square1 = new Square ( ) ; BoardFigure redtoken = new BoardFigure ( " game token red ", true, null ) ; L i s t s q u a r e 1 l i s t = new ArrayList ( ) ; s q u a r e 1 l i s t. add ( redtoken ) ; m. put ( square1, s q u a r e 1 l i s t ) ; return m; public Map< S t r i n g, BoardFigure > generatepropmap ( ) { return null ; I get to step 4 which looks like in Figure2.2. Why is the red token positioned there? At this stage I still lack to add the blue token and the die. The die is really a prop that is an unmoveable object so I postpone that into a new test on the test list.
Iteration 2: Making BoardFigures 7 Figure 2.2: Iteration 2. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token invokes the game s move method So I need to add the blue token as well. As it is also located on square 1, this is easy. c l a s s SnakeLadderPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { Map<Square, L ist <BoardFigure >> m = new HashMap<Square, List <BoardFigure > >(); Square square1 = new Square ( ) ; BoardFigure redtoken = new BoardFigure ( " game token red ", true, null ) ; BoardFigure bluetoken = new BoardFigure ( " game token blue ", true, null ) ; L i s t s q u a r e 1 l i s t = new ArrayList ( ) ; s q u a r e 1 l i s t. add ( redtoken ) ; s q u a r e 1 l i s t. add ( bluetoken ) ; m. put ( square1, s q u a r e 1 l i s t ) ; return m; public Map< S t r i n g, BoardFigure > generatepropmap ( ) { return null ;
8 Snakes and Ladders by TDD Visually, the only change I see is that the visible token is blue now as it fully covers the red token. If you solved the reflection exercise above you know why: it is because the PositioningStrategy always returns graphical position (80,300). Thus I once again extend the test list. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token invokes the game s move method Arrange tokens on the correct square Arrange non-overlapping if on the same square End of iteration. 2.3 Iteration 3: Tokens Appear On Correct Square This again involves multiple steps: 1) implement a PositioningStrategy that works correctly given the square a token is on 2) implement some domain code so tokens can actually be moved around. The latter point is a consequence of the way I have choosen to structure my process here: normally I use TDD to develop a quality domain implementation first and then fit a GUI afterwards. Here, however, I have choosen to go the other way, but I still have to provide some stub behaviour of the domain code. I realize that I can add these modifications with the context of the previous test case program: no need to define new java files! The changes are minimal but require a tiny bit of domain design: I have to have a way to distinguish one square from the other. I decide on the obvious choice inspired by the numbers on the game board. I add a method int index() to Square. Furthermore I fake this method as well as add fake-it code to the PositioningStrategy: only square 1 is possible and recognized. c l a s s Square { public i n t index ( ) { return 1 ; c l a s s SnakeLadderPositioningStrategy implements P o s i t i o n i n g S t r a t e g y <Square > { public Point calculatefigurecoordinatesindexedforlocation ( Square l o c a t i o n, i n t index ) { i f ( l o c a t i o n. index ( ) == 1) { return new Point ( 2 0, 4 0 0 ) ; return new Point ( 8 0, 3 0 0 ) ; public Point calculatefigurecoordinatesforprops ( S t r i n g keyofprop ) { return null ;
Iteration 3: Tokens Appear On Correct Square 9 I am lucky, the graphical position (20,400) is actually quite good for square 1 on the board. The question is how to drive the proper algorithm into place for squares other than 1? I realize that I can come some of the way by putting red and blue token on different squares in their configuration in the figure factory: c l a s s SnakeLadderPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { Map<Square, L ist <BoardFigure >> m = new HashMap<Square, List <BoardFigure > >(); Square square1 = new Square ( ) ; BoardFigure redtoken = new BoardFigure ( " game token red ", true, null ) ; L i s t s q u a r e 1 l i s t = new ArrayList ( ) ; s q u a r e 1 l i s t. add ( redtoken ) ; m. put ( square1, s q u a r e 1 l i s t ) ; Square square20 = new Square ( ) ; BoardFigure bluetoken = new BoardFigure ( " game token blue ", true, null ) ; L i s t s q u a r e 2 0 l i s t = new ArrayList ( ) ; s q u a r e 2 0 l i s t. add ( bluetoken ) ; m. put ( square20, s q u a r e 2 0 l i s t ) ; return m; public Map< S t r i n g, BoardFigure > generatepropmap ( ) { return null ; Note that this is still fake-it code but serves to triangulate a better PositioningStrategy into existence. Step 2: It compiles but blue token is still not on square 20, as all squares return index 1. I have to add the understanding of index into class Square. c l a s s Square { private i n t index ; public Square ( i n t index ) { t h i s. index = index ; public i n t index ( ) { return index ; and update the constructor calls in the SnakeLadderPieceFactory. Now I compile and run - and the two tokens finally appear in different places like seen in Figure 2.3. Finally, I triangulate a positioning strategy in a number of edit-compile-run cycles. (I was pretty confused by wrong calculations until I discovered that the board is actually jumping a square after square 17!) c l a s s SnakeLadderPositioningStrategy implements P o s i t i o n i n g S t r a t e g y <Square > { public Point calculatefigurecoordinatesindexedforlocation ( Square l o c a t i o n, i n t index ) { i n t sqindex = l o c a t i o n. index ( ) ; / / Note t h e t r i c k y b o a r d l a y o u t what i s s q u a r e 18 i s v i s u a l l y
10 Snakes and Ladders by TDD Figure 2.3: Iteration 3a. / / s q u a r e 1 9!!! i f ( sqindex > 17) { sqindex ++; / / c a l c u l a t e t h e row and column i n t row = ( sqindex 1) / 7 ; i n t column = ( sqindex 1) % 7 ; / / System. out. p r i n t l n ( " r, c = "+row +","+ column ) ; return new Point (20+ column 92,400 row 9 2 ) ; public Point calculatefigurecoordinatesforprops ( S t r i n g keyofprop ) { return null ; The result appears quite ok for the moment, see Figure 2.4. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token invokes the game s move method Arrange tokens on the correct square Arrange non-overlapping if on the same square
Iteration 4: Move a Token 11 Figure 2.4: Iteration 3 at the end. 2.4 Iteration 4: Move a Token I would like to see things move! I want to move the tokens. This entails activating the proper tool of boardgame, namely BoardActionTool. To ensure that it will not interfere with the present test cases I create a new test case program: MoveToken. I create this as a copy of the ShowFigures class just developed. The compiler now complains highly that there are duplicate classes. Thus I have to refactor the class structure and put all the local classes I developed into separate java files. Thus I put this iteration on hold and do iteration 3 s step 5: refactoring. 2.5 Iteration 3:... Refactored This is a simple exercise in slicing up the java source file and put the local classes into separate java source files and in the proper folders. I create two new folders in the src folder: snakesladders.domain and snakesladders.view and put Square in the first and put classes SnakesAndLaddersFactory, SnakesAndLaddersPieceFactory, and SnakesAndLaddersPositioningStrategy in the latter. I refactor ShowFigures to use these classes instead of defining them locally. When it runs the refactoring step is complete.
12 Snakes and Ladders by TDD 2.6 Iteration 4: Move a Token Continued Back to MoveToken. Step 1: I tell MiniDraw to use the frozen spot tool supplied by boardgame, namely BoardActionTool. public c l a s s MoveToken { public s t a t i c void main ( S t r i n g [ ] args ) { DrawingEditor e d i t o r = new MiniDrawApplication ( "Move a token... ", new SnakesAndLaddersFactory ( ) ) ; e d i t o r. open ( ) ; e d i t o r. settool ( new BoardActionTool ( e d i t o r ) ) ; Step 2 it runs and I can move a token, but... When I release it I get a null pointer exception. This is because I did not define any COMMMAND pattern object to be associated with the token figures: BoardFigure redtoken = new BoardFigure ( " game token red ", true, null ) ; We need to define some tool. First, let us just get rid of the exception: BoardFigure redtoken = new BoardFigure ( " game token red ", true, new NullCommand ( ) ) ; Great I can move tokens and there is no exception. But of course nothing happens in the game domain. The command object is the one responsible for telling the domain code that some action happened: here that a token has moved. As always I fake-it and triangulate, so my first implementation of a MoveCommand simply writes something on the console. public MoveCommand implements Command { public boolean execute ( ) { System. out. p r i n t l n ( " Moving from ( "+fx+", "+fy+" ) to ( "+ tx+", "+ty+" ) " ) ; return valid ; private i n t fx, fy, tx, ty ; public void setfromcoordinates ( i n t fromx, i n t fromy ) { f x = fromx ; fy = fromy ; public void settocoordinates ( i n t tox, i n t toy ) { tx = tox ; ty = toy ; No thing happens??? Ahh, I forgot to associate command objects with the tokens in the factory. I realize that I only need one command object for both tokens.
Iteration 5: Move a Token Continued 13 public c l a s s SnakesAndLaddersPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { Map<Square, L ist <BoardFigure >> m = new HashMap<Square, List <BoardFigure > >(); Square square1 = new Square ( 1 ) ; BoardFigure redtoken = new BoardFigure ( " game token red ", true, new MoveCommand ( ) ) ; L i s t s q u a r e 1 l i s t = new ArrayList ( ) ; s q u a r e 1 l i s t. add ( redtoken ) ; m. put ( square1, s q u a r e 1 l i s t ) ; Square square20 = new Square ( 2 0 ) ; BoardFigure bluetoken = new BoardFigure ( " game token blue ", true, new MoveCommand ( ) ) ; L i s t s q u a r e 2 0 l i s t = new ArrayList ( ) ; s q u a r e 2 0 l i s t. add ( bluetoken ) ; m. put ( square20, s q u a r e 2 0 l i s t ) ; return m; public Map< S t r i n g, BoardFigure > generatepropmap ( ) { return null ; It works, every time I move a token I get output written on the console: move: [java] Moving from (605,253) to (431,427) [java] Moving from (429,429) to (337,245) [java] Moving from (338,243) to (431,342) [java] Moving from (431,341) to (535,418) [java] Moving from (534,419) to (607,331) [java] Moving from (607,336) to (623,438) [java] Moving from (623,437) to (166,150) [java] Moving from (164,150) to (59,249) So this iteration ends in success I can move tokens but also in the realization that it is not quite enough. I have to convert the graphical coordinates into the square indices in order to call a move(from,to) method in the game domain. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token Move a token invokes the game s move method Arrange tokens on the correct square Arrange non-overlapping if on the same square 2.7 Iteration 5: Move a Token Continued Several One Step Tests are possible at this point in time. As the focus is on the boardgame abstractions I will pick the item that shows the special handling of props: Make die into
14 Snakes and Ladders by TDD a Prop BoardFigure. I will do this in terms of the ShowFigures test case as props are also figures and thus the test case name is a cohesive name for what it does. Basically, the die is just a BoardFigure that cannot be moved, which is indicated by a boolean value in the constructor. public c l a s s SnakesAndLaddersPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { [... ] public Map< S t r i n g, BoardFigure > generatepropmap ( ) { BoardFigure die = new BoardFigure ( " die0 ", false, new NullCommand ( ) ) ; Map<String, BoardFigure > m = new HashMap<String, BoardFigure > ( ) ; m. put ( " die ", die ) ; return m; When run I get figures: [java] Exception in thread "main" java.lang.runtimeexception: BoardDrawing:PositionStrategy returns null for Prop with key die... (This requires MiniDraw version 1.4 or later, earlier versions just throw a null pointer exception) Alas, I need to define the position strategy for the die as well. public c l a s s SnakesAndLaddersPositioningStrategy implements P o s i t i o n i n g S t r a t e g y <Square > { public Point calculatefigurecoordinatesindexedforlocation ( Square l o c a t i o n, i n t index ) { [... ] public Point calculatefigurecoordinatesforprops ( S t r i n g keyofprop ) { i f ( keyofprop. equals ( " die " ) ) { return new Point ( 6 9 0, 4 0 ) ; return null ; And I get to Step 4: Run all tests and see them all succeed. In Step 5: Refactor to remove duplication I remove the ugly magic constant of string value die by defining the constant instead in a introduced class Constant. package snakesladders. view ; public c l a s s Constant { public s t a t i c f i n a l S t r i n g diepropname=" die " ;
Iteration 5: Move a Token Continued 15 One final test case remains: Can I move the die? I run move target that runs the MoveToken test case class and verify that I can still move tokens, but the die remains impossible to move. I can strike out a test case on the test list but quite a few new test items appear on my mind because when I move the tokens they are not neatly arranged on the squares as you can see in Figure??. Also we must create the coupling from the game domain state changes to the GUI. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token Move a token invokes the game s move method Arrange tokens on the correct square Arrange non-overlapping if on the same square Position tokens neatly on the squares Update die prop when domain die is rolled Update token when domain token is moved Figure 2.5: Iteration 5 at the end.
16 Snakes and Ladders by TDD 2.8 Iteration 6: Update Die Prop When Domain Die Rolled In this iteration the focus is on introducing the coupling from the domain to the GUI. This is (of course) via an OBSERVER pattern. In Boardgame it is known that the domain is a board game and therefore a special BoardGameObserver class and protocol is provided having two methods: one for tokens/pieces and one for information objects/props. I need a test program that can call a method to roll a die in order to verify that the GUI shows the new die face. I first create a test program, note the use of a very specialized tool just to call the roll die method. public c l a s s ShowDomainUpdate { public s t a t i c void main ( S t r i n g [ ] args ) { Game game = new GameImpl ( ) ; DrawingEditor e d i t o r = new MiniDrawApplication ( "Move a token... ", new SnakesAndLaddersFactory ( ) ) ; e d i t o r. open ( ) ; e d i t o r. settool ( new DomainUpdateTool ( game ) ) ; c l a s s DomainUpdateTool extends NullTool { private Game game ; public DomainUpdateTool (Game game ) { t h i s. game = game ; public void mousedown( MouseEvent e, i n t x, i n t y ) { game. r o l l D i e ( ) ; Of course, this does not work - there is no Game defined. I will just introduce one that seems feasible for a snakes and ladders game, again only focusing at the test item at hand, namely the die and getting the observer protocol running. public i n t e r f a c e Game { public void r o l l D i e ( ) ; public i n t getdievalue ( ) ; public void addobserver ( BoardGameObserver<Square > observer ) ; with a pretty Obvious Implementation: package snakesladders. domain ; import minidraw. boardgame. BoardGameObserver ; import snakesladders. view. Constant ; public c l a s s GameImpl implements Game { private i n t die = 1 ; public void r o l l D i e ( ) { die = ( i n t ) ( Math. random ( ) 6 + 1 ) ;
Iteration 6: Update Die Prop When Domain Die Rolled 17 Sidebar 2.1: Polution of Domain Code PENDING observer. propchangeevent ( Constant. diepropname ) ; public i n t getdievalue ( ) { return die ; p r i v a t e BoardGameObserver<Square > observer ; public void addobserver ( BoardGameObserver<Square > observer ) { t h i s. observer = observer ; Of course, this is also a fake-it implementation as there can only be one Observer at a time. Note that the domain code actually now contains strains of GUI oriented code. I am not too happy about that, see 2.1. The GUI appears and pressing the mouse throws a null pointer exception: Step 2: Run all tests and see the new one fail. Ahh - I forgot to register the GUI as observer. This is one of the places where Boardgame is a bit tedious still. public s t a t i c void main ( S t r i n g [ ] args ) { Game game = new GameImpl ( ) ; DrawingEditor e d i t o r = new MiniDrawApplication ( "Move a token... ", new SnakesAndLaddersFactory ( ) ) ; e d i t o r. open ( ) ; e d i t o r. settool ( new DomainUpdateTool ( game ) ) ; BoardDrawing<Square > drawing = ( BoardDrawing<Square >) e d i t o r. drawing ( ) ; game. addobserver ( drawing ) ; Better now a get an exception telling me what is wrong: update: [java] Exception in thread "AWT-EventQueue-0" java.lang.runtimeexception: BoardGame contract violation: The PropAppearanceStrategy must be defined if propchangeevents are posted. The PropAppearanceStrategy is an algorithm that based upon the string valued key of a prop calculates which image name to use for the graphical prop. Alas, if the die rolled is value two, then we need to show the image named die2. Alas I define one: public c l a s s SnakesAndLaddersPropAppearanceStrategy implements PropAppearanceStrategy { private Game game ; public SnakesAndLaddersPropAppearanceStrategy (Game game ) { t h i s. game = game ;
18 Snakes and Ladders by TDD public S t r i n g calculateimagenameforpropwithkey ( S t r i n g key ) { return " die "+game. getdievalue ( ) ; And I also have to tell BoardGame to use it which require changing the Factory: public c l a s s SnakesAndLaddersFactory implements Factory { private Game game ; public SnakesAndLaddersFactory (Game game ) { t h i s. game = game ; public DrawingView createdrawingview ( DrawingEditor e d i t o r ) { DrawingView view = new StdViewWithBackground ( e d i t o r, " snakes and ladders background " ) ; return view ; public Drawing createdrawing ( DrawingEditor e d i t o r ) { return new BoardDrawing<Square >(new SnakesAndLaddersPieceFactory ( ), new SnakesAndLaddersPositioningStrategy ( ), new SnakesAndLaddersPropAppearanceStrategy ( game ) ) ; This required a few refactorings of all the test cases so I run them all, one after the other, to test that none of them fails. All works. And also the ShowUpdate program whenever I click the die changes face to new, random, values. Show the board and a die Make tokens into BoardFigures Make die into a Prop BoardFigure Move a token Move a token invokes the game s move method Arrange tokens on the correct square Arrange non-overlapping if on the same square Position tokens neatly on the squares Update die prop when domain die is rolled Update token when domain token is moved 2.9 Iteration 7: Update Token Figure When Domain Token Is Moved I can actually easily extend the previous update test case program by modifying the specialized tool I developed: c l a s s DomainUpdateTool extends NullTool { private Game game ; public DomainUpdateTool (Game game ) { t h i s. game = game ; i n t clickcount = 0 ; public void mousedown( MouseEvent e, i n t x, i n t y ) { switch ( clickcount ) {
Iteration 7: Update Token Figure When Domain Token Is Moved 19 / / move p l a y e r 0 s t o k e n from s q u a r e 1 t o 3 case 0 : game. move ( 0, new Square ( 1 ), new Square ( 3 ) ) ; break ; case 1 : game. move ( 0, new Square ( 3 ), new Square ( 1 8 ) ) ; break ; default : game. r o l l D i e ( ) ; clickcount ++; Which does not compile as there is no move() method. Again, extreme use of Fake It makes me go fast and keep focus: public c l a s s GameImpl implements Game { [... ] private Square tokensquare = new Square ( 1 ) ; public boolean move( i n t player, Square fromsquare, Square tosquare ) { System. out. p r i n t l n ( " Moving to "+tosquare. index ( ) ) ; tokensquare = tosquare ; return true ; OK, I see output in the console from the print statement but no visual action. I have to get the Observer going. public c l a s s GameImpl implements Game { [... ] private Square tokensquare = new Square ( 1 ) ; public boolean move( i n t player, Square fromsquare, Square tosquare ) { System. out. p r i n t l n ( " Moving to "+tosquare. index ( ) ) ; tokensquare = tosquare ; observer. piecemovedevent ( fromsquare, tosquare ) ; return true ; which generates an exception from boardgame that states the following: update : [ java ] Moving to 3 [ j a v a ] Exception in thread "AWT EventQueue 0" j a v a. lang. RuntimeException : B oarddrawing i n v a r i a n t not e s t a b l i s e d : The FigureFactory has not defined a l i s t of BoardFigures for l o c a t i o n snakesladders. domain. Square@169e11 Looking over the JavaDoc for FigureFactory I see that I have missed an important aspect of the multimap returned: POSTCONDITION: The multimap MUST contain a non-null list for every LOCATION on the board game, even if the list is empty! When I inspect the SnakesAndLaddersPieceFactory I see there is quite a lot of Fake It code around the proper design would be to read the starting position of the game. I note it one the test list as yet another item Generate tokens based upon domain contents, but for now I will keep faking to keep focus on my iteration goal.
20 Snakes and Ladders by TDD public c l a s s SnakesAndLaddersPieceFactory implements FigureFactory <Square > { public Map<Square, L i s t <BoardFigure >> generatepiecemultimap ( ) { Map<Square, L ist <BoardFigure >> m = new HashMap<Square, List <BoardFigure > >(); for ( i n t i = 0 ; i < 3 5 ; i ++ ) { Square s = new Square ( i ) ; L ist <BoardFigure > s l = new ArrayList ( ) ; m. put ( s, s l ) ; Square square1 = new Square ( 1 ) ; BoardFigure redtoken = new BoardFigure ( " game token red ", true, new MoveCommand ( ) ) ; L ist <BoardFigure > s q u a r e 1 l i s t = new ArrayList ( ) ; s q u a r e 1 l i s t. add ( redtoken ) ; m. put ( square1, s q u a r e 1 l i s t ) ; Square square20 = new Square ( 2 0 ) ; BoardFigure bluetoken = new BoardFigure ( " game token blue ", true, new MoveCommand ( ) ) ; L ist <BoardFigure > s q u a r e 2 0 l i s t = new ArrayList ( ) ; s q u a r e 2 0 l i s t. add ( bluetoken ) ; m. put ( square20, s q u a r e 2 0 l i s t ) ; return m; [... ] However, this leads to the same boarddrawing invariant exception as before? The answer lies again in the JavaDoc for FigureFactory s generatepiecemultimap method: POSTCONDITON: The LOCATION type must implement methods equals() and possibly hashcode() so lookups can be made in the returned map. After implementing these two methods in class Square, I get to Step 4: Run all tests and see them all succeed: the ShowDomainUpdate class now moves the red token nicely upon the first two mouse clicks, whereafter the die rolls.