This is the log of a sample project from a small band of software engineers eager to learn better ways of working and new technologies. We've decided to use .NET 3.0 to create a Battleships game.

The Quest For The Perfect Project

Wednesday 2 May 2007

I've been here before - Lessons Learned

The past 6 weeks have not been very eventful against this project, though I have learned some serious and valuable lessons from it. Both Jon and I have changed jobs in this period and as a result we haven't been able to meet regularly to stay informed with the others activities on Battleships.

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.
Figure 2

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;
        }
    }
Figure 3

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:

3 Comments:

As you indicate, the challenge is not the class design as such, but the implementation of the game rules. For this, you first need to invest a bit more time in determining what those rules are and here your adaptation to a multiplayer game comes into play.
If you look at the 2 player version of the game, you should have noticed the boards are actually not related; you attack on your opponent's board and your own ships are not affected. So you are correct about giving each player his own board, on which he can place his ships.
The decision to have each attack affect all boards however is not automatic (and even counter-intuitive, I would say). You could have a player choose at each turn who he wants to attack and suddenly you get a completely different game (you could even make both options available through the game settings). Other things to think about in advance is whether all attacks and or all damage is shown to all players or not (e.g. only your own attacks and all sunken ships) since this will define how much information you need to store about the attacks.
There is more of course, but the point I am trying to make is that programming is a bit like playing a board game; you always have to think each decision through and only if you see all ramifications you can keep a few steps ahead :-)
Dirk
Dirk - you're absolutely right, and Emma and I have been discussing precisely those options. Completely separate boards would be my preference too at the moment, but it would be nice to have options.

I don't think it makes very much sense for other players to see the hits and misses, but again it could be an option. I suspect that'll come a bit later, as it's another event for the server and client to handle, effectively.

Jon
Hi Dirk,

Great comments, figuring out the game rules has been a challenge. The group first considered just doing a WPF version of the standard 2-player game but I really wanted to push the boat out and see what extra features this game could include. I'm very much looking to make the game as customisable as possible, as everybody has their preferred play styles.

When I used to play TA, we would either play all vs all or we'd group up into teams and play in a kind of cooperative mode. I think it would be good if Battleships offered these kinds of options as well.

After reading my blog entry Jon went into more depth about the attacks and you're both right in that maybe the same board area for everyone is not correct. Instead we could think of regions, imagine a battle in a command and conquer style, you'd have continents fighting each other. Battleships could model this and we'd have players occupying different areas and in that way, your attack method suggested would work better where the player would choose which region/player to attack.

Thanks for the feedback though,

Emma

Add a comment