This post is part of a series exploring the (very) basics of iterative development using the example of a simple Snakes and Ladders-like game. Links to each post in the series will be added to the index page.
After having a think about top-down and bottom-up design, I concluded that delivering complete vertical slices was more important than whether you started at the top or bottom of the slice. As most of the work in previous iterations has been closer to the bottom of our design, it now seems a good time to start writing some tests around the GUI. This work will form the basis of this iteration.
Project status and work for iteration 3
Based on the previous two iterations, all we have left from the original list is the “feature squares” (the snakes and ladders style squares):
A player can roll the die, and then move that many spaces along the board.- A player that ends his or her turn on a “feature square” (a square containing a creature or obstacle), will be moved to the square connected with that feature.
There can be 1-4 players, and each player has their turn in sequence.A player that reaches the final square wins the game.
We’ll leave the pending story on hold for now so we can get to the GUI bits. One thing to notice is that none of these stories, strictly speaking, demand a GUI. So let’s add one, in consultation with our customers (i.e. firstborn and I):
- Each player’s position will be displayed on an 8 x 8 grid of squares.
This should give us enough to get rid of the command line demo code and give the customer something closer to a usable product. It will probably be enough to fill the whole iteration as well, as we may have to revisit some of the original stories and make sure we have exposed them all through our GUI (story 1 for example). I’ve also managed to miss a fairly major part of story 3 – there is currently no way to choose how many players there are in the game.
I think this indicates a problem with the approach so far. Imagine having to revisit every story after implementing 50 or so due to a change to the front end. One big thing we are missing that could help us avoid this are acceptance tests. If we had those, the relevant ones would fail and we could test drive out our way back to green. It could also be an indication that we have too much logic in the untested demo app. We were aware of this when we wrote the demo app though, and never intended to keep it around long enough for it to become a problem, so manually rechecking our 3 finished stories shouldn’t be too big a deal.
Let’s start off using WPF for this, as it’s shiny and new (well, compared with standard WinForms), and comes standard with our current .NET 3.5 platform. We’d obviously want to do some more research if doing this for real. I’ve done practically nothing with WPF to date, so I’m looking forward to publicly humiliating myself throughout the remainder of this post. (Why don’t my regular readers look surprised? ;))
Where to start?
I’d like to replace our untested demo code with tested, production code that we can use as a foundation for a GUI. We need to make sure the functionality of our original stories are exposed via the GUI, rather than the demo app. The two things that jump out at me is that we need a way for a player to roll a die, and we also need some way to indicate the end of the game. The actual rules of the game are hopefully captured in our Game
class, so with a bit of luck we’ll only need to verify our UI’s interaction with that class. Then to complete our story we’ll need to make sure each player’s position is displayed on the game board.
I’d love to avoid jumping straight to an MVP-style pattern here, in favour of starting from first principles and refactoring to patterns like that when it becomes painfully obvious we need to. However I have absolutely no idea how to do that test-first, so let’s try the standard route of creating a GamePresenter
that will mediate between our Game
model and our UI. We’ll start off with the basics of rolling the die and move on from there.
Rollin’, rollin’, rollin’
What should happen when a player interacts with the view to roll the die? Well for starters our model class, Game
, should probably be updated to reflect the fact the player is having their turn. We don’t have an actual GUI button to press, but we know that we can fire off an event when a GUI button is clicked. If we add this event to a view interface, and our real GUI implements that interface, then we’ll be able to write automated tests for large number of interactions with our GUI via its interface. Our GUI implementation itself should be fairly basic and concentrate on rendering and widgets rather than application logic (see the Humble Dialog Box [PDF]).
public class GamePresenterSpec { [Fact] public void Game_should_update_when_roll_die_is_clicked() { var fakeGameView = MockRepository.GenerateStub<IGameView>(); var fakeGame = MockRepository.GenerateMock<IGame>(); var gamePresenter = new GamePresenter(fakeGameView, fakeGame); fakeGameView.Raise(view => view.RollClicked += null, this, EventArgs.Empty); fakeGame.AssertWasCalled(game => game.Roll(Arg<int>.Is.Anything)); } }
This test isn’t really saying much, just that game.Roll(...)
should be called with any argument, which is how a player currently has their turn in our model. We need to extract an IGame
interface for this, and also create an IGameView
and a GamePresenter
.
public class GamePresenter { public GamePresenter(IGameView view, IGame game){} } public interface IGameView { event EventHandler RollClicked; } public interface IGame { void Roll(int dieValue); }
Our test compiles, but fails. We’ll fix that now.
public class GamePresenter { private readonly IGame _game; public GamePresenter(IGameView view, IGame game) { _game = game; view.RollClicked += view_RollClicked; } void view_RollClicked(object sender, EventArgs e) { _game.Roll(1); } }
Our test now passes, and as I can’t see much refactoring to do, let’s try the next test. Looking at what we have so far, the most obvious deficiency to me seems to be that we are just rolling 1 in the game. We really need a die roll here (1d6 :)), but that would involve random numbers running around our tests causing havoc. So let’s fake a DieRoller
that we can use to get known values during tests, and random values during the actual game.
public class GamePresenterSpec { private IGameView fakeGameView; private IGame fakeGame; private IDieRoller fakeDieRoller; private GamePresenter CreateGamePresenterAndDependencies() { fakeGameView = MockRepository.GenerateStub<IGameView>(); fakeGame = MockRepository.GenerateMock<IGame>(); fakeDieRoller = MockRepository.GenerateStub<IDieRoller>(); return new GamePresenter(fakeGameView, fakeGame, fakeDieRoller); } /* ... snip ... */ [Fact] public void Game_should_roll_value_from_die_when_roll_die_is_clicked() { CreateGamePresenterAndDependencies(); int dieFace = 3; fakeDieRoller.Stub(die => die.Roll()).Return(dieFace); RaiseRollClickedEventOnView(); fakeGame.AssertWasCalled(game => game.Roll(dieFace)); } private void RaiseRollClickedEventOnView() { fakeGameView.Raise(view => view.RollClicked += null, this, EventArgs.Empty); } }
The new test stubs out a known value for IDieRoller.Roll()
, then makes sure that will get passed to our Game
. I’ve also shown that we’ve extracted the common fixture setup code into a CreateGamePresenterAndDependencies()
method, although we’d normally do this refactoring after all the tests get to green (unfortunately this stuff is surprisingly difficult to get into blog-form, so please excuse me taking some licence with presentation).
public class GamePresenter { private readonly IGame game; private readonly IDieRoller roller; public GamePresenter(IGameView view, IGame game, IDieRoller roller) { this.game = game; this.roller = roller; view.RollClicked += view_RollClicked; } void view_RollClicked(object sender, EventArgs e) { game.Roll(roller.Roll()); } } public interface IDieRoller { int Roll(); }
And we’re back at green. What else can we look at? Well we should probably display the result of the roll to the player. Like our view.RollClicked
event, we’ll just make our view interface have a method for setting the result of a die roll (say, ShowRollResult(dieFace)
), and we’ll let our actual GUI implementation worry about translating this message to the display.
[Fact] public void View_should_show_result_of_roll() { CreateGamePresenterAndDependencies(); int dieFace = 2; fakeDieRoller.Stub(die => die.Roll()).Return(dieFace); RaiseRollClickedEventOnView(); fakeGameView.AssertWasCalled(view => view.ShowRollResult(dieFace)); }
public class GamePresenter { private readonly IGameView view; /* ... snip ... */ void view_RollClicked(object sender, EventArgs e) { var dieValue = roller.Roll(); game.Roll(dieValue); view.ShowRollResult(dieValue); } }
Whose turn is it anyway?
Tests pass, and can’t see any refactoring to do. It is probably important to show whose turn it is, so let’s try that now. First up, we need to show the current player when the game is first started.
[Fact] public void View_should_show_current_player_when_game_is_created() { CreateGamePresenterAndDependencies(); var currentPlayer = 1; fakeGame.Stub(game => game.CurrentPlayer).Return(currentPlayer); fakeGameView.AssertWasCalled(view => view.SetCurrentPlayer(currentPlayer)); }
Oops, this won’t work. We are using CreateGamePresenterAndDependencies()
to create everything in one step, but then we go on to stub out the game.CurrentPlayer
call. Let’s separate the dependency creation from the creation of the subject under test.
[Fact] public void View_should_show_current_player_when_game_is_created() { CreateGameDependencies(); var currentPlayer = 1; fakeGame.Stub(game => game.CurrentPlayer).Return(currentPlayer); new GamePresenter(fakeGameView, fakeGame, fakeDieRoller); fakeGameView.AssertWasCalled(view => view.SetCurrentPlayer(currentPlayer)); }
To get this passing we’ll need to pull up Game.CurrentPlayer
to the IGame
interface, then update the GamePresenter
constructor to pass this information to the view.
public GamePresenter(IGameView view, IGame game, IDieRoller roller) { this.view = view; this.game = game; this.roller = roller; view.RollClicked += view_RollClicked; view.SetCurrentPlayer(game.CurrentPlayer); }
That handles the start of the game, but we also need to change whose turn it is after each roll.
[Fact] public void View_should_show_current_player_after_a_roll() { CreateGamePresenterAndDependencies(); var player = 2; fakeGame.Stub(game => game.CurrentPlayer).Return(player); RaiseRollClickedEventOnView(); fakeGameView.AssertWasCalled(view => view.SetCurrentPlayer(player)); } /* In GamePresenter: */ void view_RollClicked(object sender, EventArgs e) { var dieValue = roller.Roll(); game.Roll(dieValue); view.ShowRollResult(dieValue); view.SetCurrentPlayer(game.CurrentPlayer); }
Where am I?
Ostensibly the story we are working on is to display each player’s position on the grid. It should not be too hard to update each player’s position after they take their turn. Because I haven’t done enough of a spike to see how our view should work, let’s just assume we have a MovePlayerMarker(...)
method on the view that will handle any animation or display stuff we need.
[Fact] public void Should_update_players_position_after_roll() { CreateGamePresenterAndDependencies(); var player = 1; var newSquare = 10; var oldSquare = 5; fakeGame.Stub(game => game.CurrentPlayer).Return(player); fakeGame.Stub(game => game.GetSquareFor(player)).Return(oldSquare); fakeGame.Stub(game => game.GetSquareFor(player)).Return(newSquare); RaiseRollClickedEventOnView(); fakeGameView.AssertWasCalled(view => view.MovePlayerMarker(player, oldSquare, newSquare)); } /* In GamePresenter: */ void view_RollClicked(object sender, EventArgs e) { var dieValue = roller.Roll(); var player = game.CurrentPlayer; var startingSquare = game.GetSquareFor(player); game.Roll(dieValue); view.ShowRollResult(dieValue); view.SetCurrentPlayer(player); view.MovePlayerMarker(player, startingSquare, game.GetSquareFor(player)); }
We are really starting to run into some of the limitations of our Game
class now. As soon as a player rolls the die in the game, the positions and current player changed, so we need to save this information prior to calling game.Roll(...)
. This may indicate we may have an overly intimate implementation. We need to know all kinds of stuff about the Game
implementation to use it, which is making our view_RollClicked(...)
code fairly ugly as it steps through the procedure of running the game. Maybe we should instead expose a list of player positions that we could bind to instead?
It looks like we are due for some refactoring, but I’m not really sure how to proceed with that. Instead of letting that hold us up as we worry about all the potential solutions we could pick, let’s put that off and whack up a quick view implementation and see if that helps at all.
This time I’m playing to win!
Before we make that final step to the GUI implementation we still need to handle one more case from our original console app – winning the game. Here’s two tests and an implementation that passes them both (although written one at a time, of course!).
[Fact] public void Should_show_winner_when_game_is_finished() { CreateGamePresenterAndDependencies(); int player = 3; fakeGame.Stub(game => game.CurrentPlayer).Return(player); fakeGame.Stub(game => game.IsFinished).Return(true); RaiseRollClickedEventOnView(); fakeGameView.AssertWasCalled(view => view.ShowWinner(player)); } [Fact] public void Should_disable_die_roll_when_game_is_finished() { CreateGamePresenterAndDependencies(); fakeGame.Stub(game => game.IsFinished).Return(true); RaiseRollClickedEventOnView(); fakeGameView.AssertWasCalled(view => view.DisableDieRolls()); } /* In GamePresenter: */ void view_RollClicked(object sender, EventArgs e) { var dieValue = roller.Roll(); var player = game.CurrentPlayer; var startingSquare = game.GetSquareFor(player); game.Roll(dieValue); view.ShowRollResult(dieValue); view.SetCurrentPlayer(player); view.MovePlayerMarker(player, startingSquare, game.GetSquareFor(player)); if (game.IsFinished) { view.DisableDieRolls(); view.ShowWinner(player); } }
The worst, most blatant misuse of WPF in history!!!1!
I’m not proud of what you are about to see. In fact, I’m rarely proud of any of the crud I write on this blog, but if my normal stuff is a 2 out of 10, this is about a -30 * 1012. My local check-in comment for this stuff is "Embarrassingly bad GUI using WPF controls", which is pretty accurate. I’m using WPF controls, but saying this is WPF is like wrapping a 1000 line main method in a class declaration and calling it OO. But I did promise a GUI of sorts, so let’s try and get something graphical working.
First, I’ve created a new DaveSquared.GardenRace.Gui
WPF project to house this hideous monstrosity. I’ve created a new WPF form called GardenRaceView
. Here’s the XAML.
<Window x:Class="DaveSquared.GardenRace.Gui.GardenRaceView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="GardenRaceView" Height="526" Width="716"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="528*" /> <ColumnDefinition Width="166*" /> </Grid.ColumnDefinitions> <UniformGrid Name="gameBoard" Rows="8" Columns="8" /> <Label Grid.Column="1" Height="28" Margin="0,12,46,0" Name="currentPlayer" VerticalAlignment="Top">currentPlayer</Label> <Button Grid.Column="1" Height="23" Margin="46,46,46,0" Name="rollDieButton" VerticalAlignment="Top" Click="rollDieButton_Click">Roll</Button> <Label Grid.Column="1" Height="28" Margin="0,86,46,0" Name="rollResult" VerticalAlignment="Top">rollResult</Label> <Ellipse Visibility="Hidden" Name="player1Marker" Stroke="Black" Height="30" Margin="5, 0, 0, 0" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Red" Width="30" /> <Ellipse Visibility="Hidden" Height="30" HorizontalAlignment="Left" Name="player2Marker" Stroke="Black" Fill="Blue" VerticalAlignment="Top" Width="30" /> </Grid> </Window>
This gives us two columns to work with: The left hand column to hold the board, and the right hand column for status and game controls. Let’s wire this thing up. Here is the xaml.cs file. This was originally wired up piece by piece, manually testing in between (we don’t have automated tests for the view remember).
public partial class GardenRaceView : Window, IGameView { public GardenRaceView() { InitializeComponent(); FillSquares(); MoveToStartingPositions(); var gameModel = new Game(64, 2); new GamePresenter(this, gameModel, new DieRoller()); } private void MoveToStartingPositions() { MovePlayerMarker(1, 0, 0); MovePlayerMarker(2, 0, 0); } private void FillSquares() { for (var squareNumber=1; squareNumber <= 64; squareNumber++) { var square = new StackPanel(); var squareLabel = new Label(); squareLabel.Content = squareNumber; square.Children.Add(squareLabel); gameBoard.Children.Add(square); } } public event EventHandler RollClicked; private void OnRollClicked() { EventHandler rollClickedHandler = RollClicked; if (rollClickedHandler != null) rollClickedHandler(this, EventArgs.Empty); } public void ShowRollResult(int dieFace) { rollResult.Content = "You rolled a " + dieFace; } public void SetCurrentPlayer(int player) { currentPlayer.Content = "Player " + player + "'s turn."; } public void MovePlayerMarker(int player, int fromSquare, int toSquare) { var markerForPlayer = GetMarkerForPlayer(player); markerForPlayer.Visibility = Visibility.Visible; var containerForMarker = (Panel) markerForPlayer.Parent; containerForMarker.Children.Remove(markerForPlayer); if (toSquare >= gameBoard.Children.Count) toSquare = gameBoard.Children.Count-1; var newSquare = (StackPanel) gameBoard.Children[toSquare]; newSquare.Children.Add(markerForPlayer); } private Shape GetMarkerForPlayer(int player) { if (player == 1) { return player1Marker; } if (player == 2) { return player2Marker; } throw new ArgumentOutOfRangeException(); } public void ShowWinner(int winningPlayer) { MessageBox.Show("Player " + winningPlayer + " wins! Nice work!"); } public void DisableDieRolls() { rollDieButton.IsEnabled = false; } private void rollDieButton_Click(object sender, RoutedEventArgs e) { OnRollClicked(); } }
If you haven’t been blinded yet, the first thing you’ll have seen is that we make our form implement our IGameView
. The majority of these implementations are trivial: ShowRollResult(...)
, SetCurrentPlayer(...)
, ShowWinner(...)
, DisableDieRolls()
, and firing the RollClicked
event. The MovePlayerMarker(...)
is fairly hideous, but it is pretty much all view-specific logic (except the highlighted code that does bound checking on the player’s positions).
There is also some code in the constructor to fill our UniformGrid
(in the incorrect order mind you), and then the code to instantiate our presenter and model code. I don’t really see a pressing need for an IoC container here yet, do you? :)
The only other implementation is the pseudo-random DieRoller
which we pass through to our presenter:
internal class DieRoller : IDieRoller { Random random = new Random(); public int Roll() { return random.Next(1, 6); } }
Somewhat surprisingly, this manages to actually work (er, well, somewhat work :)).
On what poor, pitiful, defenseless customers has my monstrosity been unleashed?
So what have we done? Besides unsubscribing from Dave’s blog, we also managed to test drive a presenter and unleash a hideous GUI on our unsuspecting customers. We’ve done a very rough job on this story (my fault, not yours), but we are now displaying players’ positions on a grid. The grid is not in the correct order for a snakes and ladders-style game (it is meant to snake around, starting at the bottom of the board and winding its way up to the top), but we can refine this later. We’ve also replaced our untested, console-only demo app with a tested, hideous bastardisation of WPF.
We’ve also found lots of new tasks to do. First, learn WPF. Next, refactor this to have a more useful Game
class, and maybe change the presenter into more of a presentation model approach so we can use some WPF goodness. We also need to do something about our incomplete story about being able to play with 1 - 4 players – at present we have 2 players hard coded in. And we also should put in some snake and/or ladder-like squares.
Despite the obvious problems with the current code, I’d like to try and salvage some small glimmer of positivity from this post. We managed to test drive a whole host of GUI-specific functionality, before we even had a forms project. We managed to hook in a thin view on top of that foundation that, despite being ugly, just worked. (Really it did! If I was going to start lieing to you it would have been in an attempt to hide my incompetence during the rest of the post, not for something trivial like this ;)). And last but not least – um, no, actually they’re the only positives I can think of. :)
I hope you can get some value from this post, even if its just a laugh or two at my expense. :) I’d love to hear your thoughts as to whether what we’ve got here is salvageable, and if so then how you would start evolving it in the right direction. In the meantime, I’m off to read up on WPF.