6
\$\begingroup\$

I am trying to write a simple domino (five-game) game with 2 players. I must say I am very bad at OOP design, and so I don't want to get myself too deep into coding then realize I have a bad design to start with. I'd appreciate if someone shed some light on how to better design this.

Here is summary of the game:

  • there are 28 dominos
  • there will be 2 players
  • initially, each player receives 7 random tiles, the remaining 14 tiles are left to the bone yard pool
  • initially, whichever player got the heaviest tile gets to play first
  • whoever scores x points first wins the game (e.g: 100,150,etc)

Here is what I have so far:

Player class:

public class Player
{
    //properties
    string ID { get; set; } //player id  
    int Wins { get; set; }
    int Losses { get; set; }
    bool IsMyTurn { get; set; } 
    List<Domino> Hand { get; set; } //each player will have a list of domino tiles

    void ShowHand(); //just display what's in their hands if needed
    void PlayerStatistic();  //get user's historical wins/losses, number game played, etc.

}

Game class:

public class Game
{
    //properties
    int ID { get; set; } //game id, assuming each player can play multiple games as once
    Player PlayerTurn { get; set; } //signifies if it is his turn
    Dominos DominoSet { get; set; }
    List<string> Board { get; set; }

    void Move(Player player, Domino domino); 

    public Game(Player player1, Player player2)
    { 
        ID = 0; //TODO
        DominoSet = new Dominos();  //create a set of dominos
        Board = new List<string>(); //create a new game board that store all the moves 
        Domino curDomino = InitializePlayers(player1, player2, DominoSet.AllDominos);
        DominoSet.AllDominos.RemoveRange(0, 14); //remove all dominos that have been assigned to 2 players
        //...and other good stuffs
    }

}

Domino class, which was intended to describe a single domino:

public class Domino 
{ 
    public string Name { get; set; } // "0,1", "0,2", etc.
    public int Head { get; set; } //each domino has 2 side, i called it head and tail
    public int Tail { get; set; }
    public int Weight { get; set; } //a 0-27, e.g: a "6,6" domino would have the highest predifined weight of 27
    public bool IsDouble { get; set; }  //whether the tile is double or not

    public Domino(string name, int head, int tail, int weight){
       Name = name;
       Head = head;
       Tail = tail;
       Weight = weight;
       IsDouble = (head == tail ? true : false);
    }
}

Dominos class, which was intended for use to store a list of 28 domino set, and perform other actions such as shuffle dominos, track domino, etc.:

public class Dominos 
{  
       private string[] _tiles = {"0,1","0,2","0,3","0,4","0,5","0,6",
                               "1,2","1,3","1,4","1,5","1,6",
                               "2,3","2,4","2,5","2,6",
                               "3,4","3,5","3,6",
                               "4,5","4,6",
                               "5,6",
                               "0,0","1,1","2,2", "3,3", "4,4","5,5","6,6"};
       public List<Domino> AllDominos { get; set; }       
       public void Shuffle(){} //radomize the dominos

       public Dominos()
       {
           AllDominos = new List<Domino>();

           for (int i = 0; i < _tiles.Length; i++)
           {
               string[] word = _tiles[i].Split(',');
               Domino d = new Domino(_tiles[i], int.Parse(word[0]), int.Parse(word[1]), i);
               AllDominos.Add(d);  
           }
           Shuffle();//randomize the domino list
       }        
}

Main program to wire things together:

class Program
{
    static void Main(string[] args)
    {
        Player _player1 = new Player("Player1");
        Player _player2 = new Player("Player2"); 
        Game _game = new Game(_player1, _player2);
    }
}
\$\endgroup\$
5
\$\begingroup\$

I think this is a good exercise for trying out OOP design. When I was learning OOP I found that working with real-life objects helped a lot and slowly I started getting use to objects that represented abstract concepts. In this example you have a mixture, real-life dominoes and abstract concepts such as the 'Game' object.

The Domino Class is a good candidate for an immutable object. It is often a good idea to create immutable objects when you can as this can be a powerful mechanism to stop erroneous state from being accidentally recycled.

There seems to be quite a bit of duplication going on within the Domino Class. This can sometimes lead to confusion when debugging. If you are debugging, and the name of a domino says "3,2" but the Head and Tail show 4 and 1 then you are left not sure about what happened. Best to try and avoid this from happening.

I think it is also a good idea to make sure that all the logic that represents dominoes is wrapped up in the Domino class. This will help to avoid abstraction leakage. It is fairly obvious that a domino has a head and a tail and this is exposed via your properties but it may not be obvious how the domino name should be formatted (e.g. should it use a comma 3,4 or a colon 3:4) or how a domino's weight is determined. If you have to evaluate the weight of a domino outside of the domino class then this might be considered as abstraction leakage.

Putting these concepts together, here is how I would program the Domino Class:

// Represents a tile used in a domino game.
// This class is designed to be immutable.
public class Domino
{
    // Private constructor means that dominos can
    // only be crated from a static method in this class.
    private Domino(string name, int weight)
    {
        // domino name can either be in the format H,T or H:T
        string[] nameParts = name.Split(',', ':');
        if (nameParts.Length != 2)
        {
            throw new ArgumentException("Invalid name format.");
        }

        try
        {
            Head = Int32.Parse(nameParts[0]);
            Tail = Int32.Parse(nameParts[1]);
        }
        catch
        {
            throw new ArgumentException("Invalid name format.");
        }

        Weight = weight;
    }

    // The properties of the domino class have a private set
    // because this class is designed to be immutable.
    public int Head { get; private set; }
    public int Tail { get; private set; }
    public int Weight { get; private set; }

    public bool IsDouble { get { return (Head == Tail); } }

    // Avoid duplicating the head and tail values by
    // generating the name every time it is needed.
    // This will be quick enough that it won't impact
    // performance.  Only worry about performance once
    // it starts to become a problem.
    public string Name { get { return Head.ToString() + "," + Tail.ToString(); } }

    public override string ToString() { return Name; }

    // Returns a full collection of shuffled dominos
    // this static method is part of the domino class
    // and so it can use the private constructor.
    public static DominoCollection GetAll()
    {
        string[] tiles = new string[]
        {
            "0,1","0,2","0,3","0,4","0,5","0,6",
            "1,2","1,3","1,4","1,5","1,6",
            "2,3","2,4","2,5","2,6",
            "3,4","3,5","3,6",
            "4,5","4,6",
            "5,6",
            "0,0","1,1","2,2", "3,3", "4,4","5,5","6,6"
        };

        DominoCollection dominos = new DominoCollection();
        for (int i = 0; i < tiles.Length; i++)
        {
            dominos.Add(new Domino(tiles[i], i));
        }

        dominos.Shuffle();
        return dominos;
    }
}

I would define the DominoCollection class to make as much use of the built-in features as possible by inheriting it from the generic collection class:

public class DominoCollection : Collection<Domino>
{
    // shuffels the dominos in this collection
    public void Shuffle()
    {
        Random r = new Random();

        int n = Count;
        while (n-- > 1)
        {
            int k = r.Next(n + 1);
            Swap(k, n);
        }
    }

    // swap the domino in index 1 with the domino in index 2
    private void Swap(int i1, int i2)
    {
        Domino temp = this[i1];
        this[i1] = this[i2];
        this[i2] = temp;
    }
}

Now you can get the full set of dominoes like this:

DominoCollection dominos = Domino.GetAll();

This still might not be considered ideal and there maybe some who suggest that the Domino.GetAll() method should not be a static method of Domino but instead should be in the collection class (similar to your original code) but at least you will have considered the alternatives and thought about the concept of abstraction leakage.

\$\endgroup\$
  • \$\begingroup\$ Thanks Ben, lot of good stuffs here, let me take time to digest it first then post follow up soon - Thanks a lot \$\endgroup\$ – HaiNguyen Jul 30 '14 at 15:51
  • \$\begingroup\$ Do you meant? public class DominoCollection : ICollection<Domino> Also, do I have to add an ADD public method in the DominoCollection class or is it available in ICollection? Sorry as stated previously I am pretty bad in OOP. \$\endgroup\$ – HaiNguyen Jul 30 '14 at 16:01
  • \$\begingroup\$ @HaiNguyen using the ICollection<Domino> would leave you a lot of extra work to do (like adding the Add() method). Instead try using System.Collections.ObjectModel; and then you should have access to the Collection<T> class. This should do most of the work for you and you can just define the DominoCollection as I did in my example. \$\endgroup\$ – Ben Jul 30 '14 at 16:09
  • \$\begingroup\$ @HaiNguyen based on your code (which now seems to have disappeared) I added the implementation of the Shuffle method in the DominoCollection class. The method does not need to be generic since you know you are dealing with Dominoes. (see this post for a more detailed explanation of the random method. \$\endgroup\$ – Ben Jul 30 '14 at 18:11
  • 1
    \$\begingroup\$ Actually I didn't realize that I already have shuffle method implemented in the public static class MyExtensions within the same namespace that is why it works. \$\endgroup\$ – HaiNguyen Jul 30 '14 at 18:58
2
\$\begingroup\$

Without nit-picking the details, here are few high-level architecture points to consider:

Player

A player class should encapsulate a player's identity in the context of your program. A player class should contain: username, ID, and user info. Wins, losses and player statistics should be part of a class dedicated to tracking game statistics and results in your program. Hand information, and IsMyTurn are part of game-state and belong in the Game class.

Game

This should be called GameState. This class should represent the state of a particular game at any given point in time. You should have two properties, Player1 and Player2 for the two players and a boolean variable:

bool IsPlayer1Turn { get; private set; }

Also, you should have properties for each player's hand at any given time. This way, the Player class is insulated from the vicissitudes of ever changing game state which is handled by the GameState class.

Domino

There is a lot of redundant data here which means that your objects can achieve inconsistent state and you'll have to do bookkeeping to keep everything in sync. It's important that your object model makes it impossible to ever achieve an inconsistent state. The C# language allows you to define properties with getters and no setters, take advantage of this feature to derive the Name, Weight and IsDouble properties from the Head and Tail properties, eg:

public string Name { 
    get {
        return string.Format("{0},{1}", this.Head, this.Tail);
    } 
}
\$\endgroup\$
  • \$\begingroup\$ on player class comment, I do have properties related to player info. I just stripped them out when posting because I think they are not critical elements of the design and left them out to reduce the length of the question. Thanks for the all other advises. I learned a lot from it. \$\endgroup\$ – HaiNguyen Jul 30 '14 at 15:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.