The Quest For The Perfect Project
Wednesday 9 May 2007
Mocking helps test in isolation
As you are probably already aware, I've been spending most of my time expanding the domain classes that we've come up with. It's been a roller coaster ride going backwards and forwards over several ideas of how it's all implemented. I've actually made forward progress this week in that I'm stepping on territory that hasn't already been visited.
I now have what I see as the fundamentals behind the domain classes ironed out and I decided to write a few tests that exercised game play. Our Game class is somewhat simple at the moment [see figure 1] and at this moment in time we're not 100% sure on how the server will interact with the clients, though I suspect this will become more of an issue sooner rather than later.
public sealed class Game { static Game theGame = null; readonly static object locker = new object(); IList<Board> boards = new List<Board>(); private int currentTurn; private IGameController gameController; private Game() { } public static Game TheGame { get { if (theGame == null) { lock (locker) { if (theGame == null) { theGame = new Game(); } } } return theGame; } } public IGameController GameController { get { return gameController; } set { gameController = value; } } public Board this[Player player] { get { foreach (Board board in boards) { if (board.Player == player) { return board; } } return null; } } public void AddBoard(Board board) { boards.Add(board); } public int NumberOfPlayers() { return boards.Count; } public void NextTurn() { if (currentTurn >= boards.Count) { // Reset to player 1 again. currentTurn = 0; } Board playersBoard = boards[currentTurn]; Attack attack = gameController.TakeTurn(playersBoard.Player); Board targetsBoard = this[attack.Target]; bool result = targetsBoard.ProcessAttack(attack); currentTurn++; } public static void Reset() { lock (locker) { theGame = null; } } } |
Figure 1.
Whilst I'm in the domain area all that I am interested in at the moment is my domain objects. I'm also trying to be truly Agile with our design in that, yes I'm aware that we'll need some form of communication layer and interface into that layer, but at the moment it's not essential that I know the nitty gritty of how it will be implemented. I just assumed that I'd have some interface, with a mechanism for taking a turn. I'd expect this interface to undergo changes when we actually get to the design/implementation of that area.
public interface IGameController { Attack TakeTurn(Player target); } |
Figure 2.
This issue in the past would have caused us problems at this early stage in development when it comes to testing the game play. We could have written a test stub class that we could talk to that provided us with responses to exercise the object. These days you'll often hear people refer to mocking instead. Mocking is not an easy concept to grasp - it took me a while and it's probably a lot harder to explain. We're using Rhino.Mocks for mocking as it is probably the closest mocking library to the one we've used when developing Java. At first I wasn't entirely sure how I was going to test the game play and my first attempt resulted in me writing the steps that I wanted to do, along with what I'd expect to be returned as comments. I then went to bed...
The following evening, I started to create the mock with a little help from some sample code that Jon happened to produce for a presentation to our colleagues on mocking using Rhino.Mocks, figure 3 shows the test in all it's glory.
[Test] public void VerifyMultipleShipHealthBars() { // Test the game with 3 separate attacks to only 2 targets. // Create and program the mocks to do this. MockRepository factory = new MockRepository(); IGameController controller = factory.CreateMock<IGameController>(); Expect.Call(controller.TakeTurn(evan)) .Return(new Attack(evan, emma, 2, 3)); // should be a hit for ship1 Expect.Call(controller.TakeTurn(emma)) .Return(new Attack(emma, caroline, 3, 2)); // should be a miss Expect.Call(controller.TakeTurn(caroline)) .Return(new Attack(caroline, emma, 2, 3)); // should be a hit for ship1 factory.ReplayAll(); // Create the component under test (CUT) and proceed with behaviour. Game game = Game.TheGame; game.AddBoard(evansBoard); game.AddBoard(emmasBoard); game.AddBoard(carolinesBoard); game.GameController = controller; game.NextTurn(); game.NextTurn(); game.NextTurn(); Assert.AreEqual(2, ship1.GetHealth(evan)); Assert.AreEqual(2, ship1.GetHealth(caroline)); // Verify that the mock executed as expected. factory.VerifyAll(); } |
Figure 3.
Mocking will only get you so far and this test is far from complete because it's now highlighted the need to move onto the IGameController interface. As you can see from the NextTurn method in the Game class, we're losing the result from the ProcessAttack method call. This return value would be valuable to the client so that it could update the EnemyBoard for the player. Although the Game.NextTurn() is incomplete, it's still functioning at the moment and it has helped immensely to have a unit test mocking the IGameController concept at this early stage in development. It served it's purpose as it found one or two bugs in my Board logic.
I envisage my next steps to be designing the GameController interface some more, looking at the possibility of having the following calls to complete the next turn functionality.
Figure 4.
Once this communication has been established, I can then revisit my unit test around gameplay and modify that accordingly to exercise the game class with the expanded IGameController implementation. It was however a great exercise and it gave me a chance to play with mocking in .NET.
Wednesday 2 May 2007
I've been here before - Lessons Learned
Communication is key
You may remember from my last post that I was happily developing the domain classes for Battleships when the niggling issue regarding multi player capabilities and how we handled it emerged. I was always thinking about it, but I never really sat down until then to work out the fundamentals. All I did was try to work out how a second player could place a ship. This little thought just opened up a whole load of issues and I suddenly realised that my grid of ships was not going to work because I'd need to allow several ships to be placed at the same location.
Jon did raise a few concerns and we did have a few brief conversations over email. I've learned that it is vital that we communicate more regularly. This applies more so with designs, as it's best to identify issues in the early stages to avoid writing code that will eventually get thrown away.
Do you ever think you're going round in circles?
We'll use the example around ships being placed on a board as this will demonstrate when I have experienced a design/implementation where I have just completed a 360 degree over a period of 2 months.
I thought I had designed it at the beginning, but obviously not in the depth that it required; I naively ignored the multi player aspect because I thought we could do something with that later. A big red flashing light should have been going off in my head, but I'll blame the lack of batteries :).
Design 1: Single Ship Locations
Figure 1 shows the grid that the player has configured with his ships.
Figure 1
Figure 2 shows the relationship between the domain classes. The ship knows about the player that it belongs to and the board will have a list of Guid's that have been inserted into the list at an offset corresponding to the real location in the grid.
Offset Calculation Example:
Take a grid that has dimensions of 4x4; you place a ship with a length of 3 going in a vertical direction from coordinate 0,0. In the locations list in the Board it occupied elements [0], [4] and [8]. I'm not sure why I chose to do it this way, I've had bad experiences with multi dimensional arrays in my C++ days and they've haunted me ever since.
This all appeared to work fine, until I started to wonder about the multi player aspect and how the board could cope with another player's ships and not alert to them that a ship already occupied a square.
Design 2: Multiple Ship Locations
After a brief chat with Jon, he convinced me that using a multi dimensional array was much better than my weird offset list. He did say he had concerns about the direction I was going but we never really managed to find time to get together to discuss this any further. Again, alarm bells should have been ringing and I think they were but I wanted to continue my work with Battleships and I didn't want to have to stop work on it.
My board class underwent massive life saving surgery and ended up being something like figure 3.
public sealed class Board { int width; public int Width { get { return width; } } int height; public int Height { get { return height; } } /// <summary> /// The collection of ships on this board. /// </summary> IDictionary<Guid, Ship> ships = null; public IDictionary<Guid, Ship> Ships { get { return ships; } } /// <summary> /// A representation of the grid to store the attacks made on this grid. /// </summary> bool[,] grid; /// <summary> /// A mirror of the grid but this time detailing the location of the ships. /// </summary> IList<Ship>[,] locations = null; public Board(int width, int height) { this.width = width; this.height = height; // Initialise the bit array. grid = new bool[width, height]; locations = new List<Ship>[width, height]; // Populate the list with empty placeholders as we will need to // be able to insert into the middle of the list. for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { List<Ship> emptyList = new List<Ship>(); locations[i, j] = emptyList; } } } public bool ProcessAttack(Attack attack) { bool hit = false; IList<Ship> ships = locations[attack.X, attack.Y]; // Tell all the ships that they have taken damage. foreach (Ship ship in ships) { // If we've hit any ships, the result is a successful attack hit = true; ship.TakeDamage(attack); } // Tell the grid we've had an attack at some coordinates. grid[attack.X, attack.Y] = true; return hit; } } |
Luckily I had issues with the multi-dimensional array syntax when trying to initialise it with empty list's of Guid's, probably the reason I avoided them in the first place. This issue held me up for a month and I went through a period of time where I didn't work on the project because I knew I had a compile error waiting for me when I returned. This is the sort of issue you dread when working on a solo project; you don't have other people on hand to help you resolve silly user errors like this and Battleships was starting to feel like a solo project.
I finally managed to get a 30 minute slot with Jon; I knew he'd be able to fix it. I also wanted to use this opportunity to talk him through my concerns. I'd become too focused on this issue around multiple ships at one location that the overall design that I had chosen became horridly broken. I had this idea of a BoardView into the game board that would show a player the grid from his perspective. Figure 4 shows an overview of how I envisaged this working.
Figure 4
The BoardView was going to have 2 board's, one with the player's ships and the one that he will attack on. With everything going on and the refactoring of the Board to manage multiple ships I lost sight of this design and one day I realised that everything was broken. I had a task to unit test the BoardView and when I couldn't think of anything to test, I realised that I had a flawed design.
I identified the following issues with the design as it stood:
* As it stands the Board had no way of given the Player it's BoardView.
* The BoardView wasn't being utilised at all and we weren't doing anything with the enemyboard.
Design 3: Single Ships Per Board - Again
Rightly so, after my session with Jon we identified several things that neither of us were happy with. We've decided to revert back to a simpler board design like we had in Design 1. I had Guid's everywhere to act as ID values. Too many Guid's obfuscate how the data is linked and sometimes a Guid as an identifier is just unnecessary. It may be easier to transport a Guid remotely but we're moving towards a new design that includes having a Player object as the unique identifier.
Figure 5 shows the new relationships between the classes. I tried to treat the enemy board and player board as the same object previously and this was not necessary. The enemy board will not need to be a domain class, this is something that can remain local to the client as the server will not need to know about these, only the attacks being made.
Figure 5
Jon and I now envisage the Game consisting of a sorted collection of boards (so that we know what the turn order should be for each player). A Board will know which player owns it. The Board will now revert back to it's simpler design of just monitoring 1 set of a ships with no more than one ship occupying a location. When an attack is made, the game would ask all the boards to process the attack, figure 6 demonstrates visually how we envisage this happening.
Figure 6
Orientation had also been made complicated by me in that I was taking the start coordinate of a ship and when placing it on the board, I knew what direction the ship was facing. Depending on the direction the ship was facing, the ship occupied a different set of cells on the board. Jon talked me through how the UI might look for a player and you could see him possibly dragging this ship object over the grid, highlighting all the cells it would occupy. The UI could easily pass the most left set of coordinates meaning that we wouldn't need a complicated orientation algorithm, we'd just need to know if it was facing a horizontal or vertical direction. It's at times like this when you benefit from more heads than one, there was nothing wrong with my design, but it was easily made simpler.
All I have to do now is implement this new design and hopefully this one will manage to stick around longer than the rest.
Labels: design
Thursday 1 March 2007
Of Layers and Designs
Steven Nagy pointed out that we hadn't said what we meant when we mentioned that Emma was working on the Domain objects. This post will hopefully correct this, give an idea of the design we'll be following, and provide a good opportunity for those of you who are really hot on n-tier programming to not only rubbish the design, but also my description of n-tier (and "n-tier plus domain"; I don't know of an official name for it) designs.
Traditional 3-tier design
The traditional 3 tiers are the UI layer, the "business object" layer (BOL) and the "data access layer" (DAL). Here's a graphical representation:
Each layer only "knows about" itself and the layer directly beneath it - so the UI layer doesn't have direct access to the DAL. That way, the theory goes, the DAL can change completely, and only the BOL should care. Although I haven't done any "straight" business applications, in my experience things tend to leak a bit - the nature of the DAL affects the interfaces the BOL exposes, so radical changes to the DAL may well cause a ripple effect. In addition, because the UI and the DAL have nothing in common, you can end up with a lot of duplication, where very similar data structures exist in the BOL for the UI/BOL to manipulate, and in the DAL for the BOL/DAL to manipulate. Removing this duplication leads to what I think of as a "n-tier plus domain" design, or possibly "n+1"-tier.
3-tier plus domain design
Here we still have separation of the UI, business objects, and data access, but there are two kinds of business objects involved: domain objects, which tend to have the actual data (so in an online shopping system, domain objects may be customers, items available for purchase, etc) and service objects which are able to do things such as searching (via the DAL). Sometimes the boundary may not be very clear - should the shopping basket class know how to commit itself (in terms of the order being submitted) or should that be a service? No doubt different people have different views, and I'm not going to give one here. Anyway, the important difference between this design and a normal, strictly vertical n-tier design is that all layers have access to the domain objects. That means the UI can create (say) a shopping basket, and ask the business service layer to store it for the duration of the session. The business service layer can then (after applying appropriate business rules) pass the same object to the data access layer for actual persistence. That means less repetition, and is particularly important when using an ORM (Object Relational Mapping) system (where frankly, it can be quite tricky sometimes to work out whether a class is part of the business layer or the data layer, sometimes). Here's a diagram illustrating this design:
This approach, or at least one approximating it, has worked pretty well in a couple of projects I've worked on, and I believe it's gaining favour in the world in general. Now, what about the Battleships project itself?
Battleships
Battleships doesn't really fit into a BOL/DAL design at all, so rather than trying to squeeze a square peg into a round hole, we're not even going to try to claim it's a normal n-tier design. However, the idea of having different layers which all have their own jobs and have commonality in terms of domain objects. With the caveat that we haven't actually written any of the code yet or even designed the interfaces, here's our current idea of the overall design/architecture:
There will always be a server involved, even in a straight two player game, even if we ever support two players on the same client, taking turns having looked away while setting up the board. What's not clear is whether a communications layer is always required. It's possible that the interface between the server and the communications layer is the same as between the UI layer and the communications layer, in which case when the client is in the same process (and application domain) as the server, there's no need for an extra layer of code at all. It's likely that the arrows going "up" in the diagram will actually just be in terms of events: the server may well supply an interface which includes various methods to be called (e.g. for the player to make a move) and events to be handled by the layer above (e.g. player X has moved; it's now the turn of player Y). There's not very much difference between that and the communications layer passing in an implementation of an interface for the server to call at appropriate times, but it makes it easier to add logging/debugging hooks etc with the normal .NET eventing model.
Quite where the MVC/MVP (Model View Controller, Model View Presenter) patterns come into this remains to be seen. (One aim is to investigate MVP thoroughly enough to know how appropriate it is to this game.) The UI layer will need to maintain a model of some description, partly comprised of the common domain objects, and partly comprised of its own view of things. Likewise the server's model won't be entirely comprised of common objects. This is because the server needs to know everything, whereas we're taking a paranoid approach so that clients only know as much as they need to, rather than having a complete view of all game boards.
Separating out the communications layer from the UI layer is not only an obvious thing to do, but it should allow us to play around with different technologies pretty easily. We can try a "straight sockets" comms layer with our own protocol, or various different types of web service (SOAP, Hessian etc), or different .NET remoting technologies ("vanilla" remoting and WCF). We could even go exotic and implement an email comms layer - it should just work (although obviously the lag would be horrible). The comms layer is likely to break down into two halves in most cases - the client side (e.g. the player's computer making web requests to either do something or poll for events) and the server side (e.g. responding to the web requests). How exactly this split happens is likely to depend on the comms layer in question though - I don't think there's any need to standardise there.
Similarly, lots of different UI layer should be possible without too much work. We may find it easiest to write a small console UI first, which after each turn just displays "our" board and how damaged we are, and how we've done against the opponent's board so far. Very little work involved in that, even if it would look ghastly. We can then move on to WPF, WinForms or even UIs on other platforms.
It's probably that many UIs will want the same model code, so we may well break the UI layer up into "genuinely presentation-specific" and "generic UI model". I don't see that as another vertical layer, so much as breaking up the single UI layer into two (or more) parts.
Conclusion
Hopefully that's explained a bit of what we're trying to do, as well as my understanding of a couple of architectures for traditional business applications. I'm sure many people interpret the architectures in different ways, and bend them to whatever they happen to be doing - which is fine. Having said that, I hope I haven't misrepresented them too badly!
Having written up the architecture, fairly soon I think I need to start putting fingers to keyboard and actually working out what the interfaces between the layers look like... more on that when it happens.
Friday 23 February 2007
Evolutionary Design - keep it simple
I'm starting to see the benefits of this way of thinking already. At the beginning of a project, you can't really expect to fully understand all the implications that lie ahead so having a mammoth design task to plan the whole system is often flawed. I'm using the technique to have evolutionary design and already I've started to see why this is better than spending weeks planning and designing early on. I'm new to writing games, especially games that have the added complexity of being multiplayer. The last game I wrote was some little console C++ horse racing game 10 years ago when I was learning to program (whoa, I feel old!).
I'm starting to realise that you need to think in a different mindset when trying to develop a multiplayer game and I'm very fortunate that my other half loves developing games, so I've got someone to bounce my ideas off of. Last night we had a chat because I keep finding that I'm having to refactor continually because something I initially thought was a great implementation, suddenly started to fall over when I considered how it would work in a multiplayer environment. I've identified that my design has been subtly flawed for a while, so it's great that I had designed the whole game from day 1 because at the end of the day, I've got several learning curves going on here. I'm starting to think my lack of knowledge around multiplayer games is the biggest.
For example:
I came up with this basic idea about a ship having health and whenever it received an attack, the health would decrease. I was happily testing the process attack code when I started to dream up more complex tests that verified several players attacking the same ship. I needed a way to model this in code and as it currently stood, my design was flawed. It wasn't a terrible design, but it was just wrong for the multiplayer aspect.
A similar issue arose when I started thinking about the board representation. I couldn't stop players from placing ships on certain coordinates because it would alert them to the probability of there being another ship there. As a result, ships can occupy the same coordinates in Multiplayer Battleships. I'd initially not really thought about this and found myself once again revisiting the design board to make this possible in a multiplayer environment.
The list goes on, but I'm finally turning the corner and realising that I need to approach my evolving design from a multiplayer perspective early on, otherwise I'll spend a lot more of my limited time rewriting and rewriting.
A great article that has helped me along the way is: "Is Design Dead by Martin Fowler"
Thursday 8 February 2007
Getting into the swing of it
I've just started fleshing out the domain classes, it's very early stages - but it's coding at last! I was determined to do things properly though. I've not really experienced much TDD in .NET it wasn't until I started writing Java that I became an Agile adopter. TestDriven.NET has various nuances to the TDD framework that I was used to using in Java but I'm slowly picking it all up.
When you're up against deadlines at work, you sometimes take the easy option and start coding straight away not really doing the whole TDD properly, just tacking on the unit tests afterwards. I did however make more of an effort to write a test first when we came across bugs because it was a great way to verify that you'd actually fixed it.
One simple TDD example from Battleships:
I've identified one of the domain classes to be a ship and before I even created the Ship class, I created the test class initialising x and y coordinates for the ship.
[TestFixture] public class ShipTest { [Test] public void ConstructShip() { Ship ship = new Ship(6, 8); Assert.AreEqual(6, ship.X); Assert.AreEqual(8, ship.Y); } } |
- Construct the ship.
- Fix compile errors by creating a ship class and basic constructor.
- Test for some values (x and y coordinates).
- Modify the ship to give you the coordinates.
public class Ship { /// <summary> /// The X coordinate on the board for this ship /// </summary> int x; public int X { get { return x; } } /// <summary> /// The Y coordinate on the board for this ship /// </summary> int y; public int Y { get { return y; } } public Ship(int x, int y) { this.x = x; this.y = y; } } |
I am really starting to see the benefits of doing it properly though. I've been quite strict on myself, even with these straightforward domain classes that I'm creating at the moment. I'm writing a test even before I've created the class and although at first I felt a bit silly, I stuck at it and it's now really good fun. I'm seeing the benefits already and I'm confident that the code I'm producing is of a higher standard than if I'd not had unit tests at all. You spot the errors quickly because it's staring you right in the eyes when you get failed unit tests.
The last time I looked at C#, it was in .NET 1.1. I'm actually finding it a lot easier to works with C# 2 and the .NET 2.0 framework after my brief experiences with Java. I'd completely forgotten about the old way of using the .NET collections and I slipped right into the groove with C# generics - they're just fabulous.
I'm so glad Microsoft have taken a few hints from Eclipse, the IDE is much better than 2003, very slick - though I do miss the organise imports. So if you know any of the Microsoft IDE developers, give them a little nudge from me ;).
All in all, it's lovely to be back in .NET land and I can't wait until this project really gets going.
Emma
Labels: tdd
Friday 19 January 2007
Welcome
Welcome to the blog for my latest project. This is a joint project between myself and a couple of colleagues. We're interested in brushing up our C# (having not had much cause to use it professionally recently) as well as learning about some new technologies (e.g. XAML and Windows Presentation Foundation).
We want to do a project right, from the start. That doesn't mean the code will be perfect, but we're going to try to use appropriate patterns, tools, best practices etc to do the best we can. We have no time limits to worry about, so we can experiment with different techniques without risk of anything other than looking stupid.
This blog is meant to be a sort of diary of the project. If we're conscientious in keeping it up to date, we should be able to look back and see where we've made mistakes, where up-front work has paid off and where we we've wasted time by thinking we know more than we do, etc. In short, don't expect this blog to be a guide to "doing it right" - only expect it to be the experiences of people trying to "do it right" in a recreational context.
The project we've chosen is the age-old game of battleships. We don't need to be innovative in creating a new and marvellous game, just implementing existing ideas - any innovation (and don't expect too much) will be in the software itself. We're using Windows Presentation Foundation (part of .NET 3.0) for the presentation layer, and may well end up using Windows Communication Foundation (another part) for one of the supported protocols.
The project is open source, under a BSD licence (we were going to go with public domain, but when I was creating the project I was slightly concerned about binaries in the source repository which weren't in the public domain). We're using SourceForge for hosting, and Subversion for source control. Feel free to look at the project home page - but there's not a lot there yet.
We've no idea how fast the project will go, but we'll keep anyone interested posted here...