Now that we know how to play a game of Ticket to Ride, it's time to see if we can create the models and C# classes we need to play a game.

Hint: we can.

The Sample Project

As always, there is a sample project on GitHub that has the code we will use here. Check it out!

exceptionnotfound/TicketToRideModelingPractice
Contribute to exceptionnotfound/TicketToRideModelingPractice development by creating an account on GitHub.

The Game Board

Let's start our C# modeling practice by looking at the game board itself. We want to find the elements of the board that we can model using C# classes, enumerations, or other objects.

Cities

The board itself contains a collection of Cities, which are connected by Routes. The Cities do not appear to have any other information about them included in the board. In our demo, we will make the Cities an enumeration, with the following values:

public enum City
{
    Atlanta,
    Boston,
    Calgary,
    Charleston,
    Chicago,
    Dallas,
    Denver,
    Duluth,
    ElPaso,
    KansasCity,
    Helena,
    Houston,
    LasVegas,
    LittleRock,
    LosAngeles,
    Miami,
    Montreal,
    Nashville,
    NewOrleans,
    NewYork,
    OklahomaCity,
    Omaha,
    Phoenix,
    Pittsburgh,
    Portland,
    Raleigh,
    SaintLouis,
    SaltLakeCity,
    SanFrancisco,
    SantaFe,
    SaultSaintMarie,
    Seattle,
    Toronto,
    Vancouver,
    Washington,
    Winnipeg
}

Train Color

We must also consider two kinds of colors: train colors and player colors.

Each route on the board has a color, and each train card also has a color. Because these colors do not have any further properties, we can use another enumeration to represent train color:

public enum TrainColor
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple,
    Black,
    White,
    Locomotive, //Only used for cards
    Grey //Only used for board spaces
}

Note that we are including "Locomotive" as a color, despite the fact that it only ever appears on train cards (there are no spaces which required a locomotive on the board). We are also including "grey" as a color, even though it has the reverse problem: it only ever appears on the board, and there are no grey train cards.

Player Colors

Further, each player must have a distinct color which represents them, and we can use another enumeration for this value:

public enum PlayerColor
{
    Red,
    Blue,
    Yellow,
    Green,
    Black
}

Destination Cards

As a reminder, the destination cards look like this:

We need a class to represent these cards. Each card has an origin, a destination, and a point value. Here's that class:

public class DestinationCard
{
    public City Origin { get; set; }
    public City Destination { get; set; }
    public int PointValue { get; set; }

    public DestinationCard(City origin, City destination, int points)
    {
        Origin = origin;
        Destination = destination;
        PointValue = points;
    }
}

Train Cards

We also need a very simple class for a train card:

public class TrainCard
{
    public TrainColor Color { get; set; }
}

Board Routes

With classes for destination cards and train cards created, we must now determine how to model the routes between the cities. Here's the board again:

Note that each route has both an origin and a destination, and both of these must be cities. Further, each route has a color (which might be grey), and a length (or number of trains necessary to claim that route). Each route also has a point value, which can be calculated from the route length.

There's also one additional twist: each route can be "claimed" only by a single player, and if a route is already claimed, no one else can claim it. Therefore, we need our BoardRoute object to be aware if it is already claimed, and who claimed it.

Our object might look like this:

public class BoardRoute
{
    public City Origin { get; set; }
    public City Destination { get; set; }
    public TrainColor Color { get; set; }
    public int Length { get; set; }
    public bool IsOccupied { get; set; }
    public int PointValue
    {
        get
        {
            switch(Length)
            {
                case 1: return 1;
                case 2: return 2;
                case 3: return 4;
                case 4: return 7;
                case 5: return 10;
                case 6: return 15;
                default: return 1; //We do not expect this to ever be used.
            }
        }
    }
    public PlayerColor? OccupyingPlayerColor { get; set; }

    public BoardRoute(City origin, City destination, TrainColor color, int length)
    {
        Origin = origin;
        Destination = destination;
        Color = color;
        Length = length;
    }
}

The constructor method in this class will come in very handy in a short while.

Board Route Collection

In this simulation, we will need to be operating against a collection of routes much of the time. For example, we might need to find the ideal route between two distant cities so that a player can try to claim those routes, or we might need to determine if two cities are already connected so a player can decide if s/he has completed a destination card. To facilitate this, I chose to make a new class which will represent the collection of board routes as a whole:

public class BoardRouteCollection { }

This class will become important in the next part of this series, where we try to find ideal routes between two distant cities.

The Board Itself

We also need an object to represent the game board itself. This class will do double-duty, as it will also represent the game in progress. Question is, what kind of properties should this class contain?

We know we need the collection of board routes. The game requires a deck of train cards that can be drawn from, and a deck of destination cards, so we can include those in the Board object. We also need a "discard pile" for train cards that have been used to claim route and will eventually be shuffled back into the draw pile. Finally, we can include the "shown" cards, which are the face-up cards that players may choose to take during their turn.

All of this results in the following class:

public class Board
{
    public BoardRouteCollection Routes { get; set; } 
        = new BoardRouteCollection();

    public List<DestinationCard> DestinationCards { get; set; } 
        = new List<DestinationCard>();

    public List<TrainCard> Deck { get; set; } = new List<TrainCard>();

    public List<TrainCard> DiscardPile { get; set; } = new List<TrainCard>();

    public List<TrainCard> ShownCards { get; set; } = new List<TrainCard>();

    public Board()
    {
        CreateBoardRoutes();
        CreateDestinationCards();
        CreateTrainCardDeck();
    }
}

The constructor for this class is what will create a new board that players can use to play a game. To create this board, we need to define three things: the board routes, the destination cards, and the train card deck.

Defining Board Routes

There are a total of 102 possible routes on this board, ranging in size from 1 to 6 train cars, using various colors. A snippet of this method is here:

private void CreateBoardRoutes()
{
    #region Create Routes
    Routes.AddRoute(City.Vancouver, City.Calgary, TrainColor.Grey, 3);
    Routes.AddRoute(City.Calgary, City.Winnipeg, TrainColor.White, 6);
    Routes.AddRoute(City.Winnipeg, City.SaultSaintMarie, TrainColor.Grey, 6);
    Routes.AddRoute(City.SaultSaintMarie, City.Montreal, TrainColor.Black, 5);
    //...
    Routes.AddRoute(City.Houston, City.NewOrleans, TrainColor.Grey, 2);
    Routes.AddRoute(City.NewOrleans, City.Atlanta, TrainColor.Yellow, 4);
    Routes.AddRoute(City.NewOrleans, City.Atlanta, TrainColor.Orange, 4);
    Routes.AddRoute(City.NewOrleans, City.Miami, TrainColor.Red, 6);
    Routes.AddRoute(City.Atlanta, City.Miami, TrainColor.Blue, 5);
    Routes.AddRoute(City.Charleston, City.Miami, TrainColor.Purple, 4);
    #endregion
}

This method defines all 102 routes that can be claimed by the players. Note that the board routes are defined in a specific order: roughly west-to-east, then north-to-south. This is because that's how they might be read by a real player on a real board.

Creating Destination Cards

In a standard game of Ticket to Ride (e.g. with no expansions), there are 30 destination cards that can be drawn by the players. So we need to create the destination cards like this:

private void CreateDestinationCards()
{
    DestinationCards.Add(new DestinationCard(City.NewYork, City.Atlanta, 6));
    DestinationCards.Add(new DestinationCard(City.Boston, City.Miami, 12));
    DestinationCards.Add(new DestinationCard(City.LosAngeles, City.Chicago, 16));
    //...
    DestinationCards.Add(new DestinationCard(City.Winnipeg, City.Houston, 12));
    DestinationCards.Add(new DestinationCard(City.Denver, City.ElPaso, 4));
    DestinationCards.Add(new DestinationCard(City.LosAngeles, City.Miami, 20));
    DestinationCards.Add(new DestinationCard(City.Portland, City.Phoenix, 11));
}

However, we also have a slight problem. We cannot just deal the cards to the players in the order they were created, because that would result in the players always getting the same destination cards, which is no fun.

So instead we will implement a shuffling algorithm, specifically the Fisher-Yates Shuffling Algorithm, to randomize the order of the cards (I made this an extension method):

namespace TicketToRideModelingPractice.Extensions
{
    public static class CardExtensions
    {
        public static List<DestinationCard> Shuffle(this List<DestinationCard> cards)
        {
            Random r = new Random();
            //Step 1: For each unshuffled item in the collection
            for (int n = cards.Count - 1; n > 0; --n)
            {
                //Step 2: Randomly pick an item which has not been shuffled
                int k = r.Next(n + 1);

                //Step 3: Swap the selected item with the last
                //"unstruck" letter in the collection
                DestinationCard temp = cards[n];
                cards[n] = cards[k];
                cards[k] = temp;
            }

            return cards;
        }
    }
}

Which we then use to shuffle the destination cards:

using TicketToRideModelingPractice.Extensions;

private void CreateDestinationCards()
{
    //Define all destination cards

    DestinationCards = DestinationCards.Shuffle();
}

Creating the Train Card Deck

Similarly to the destination cards, we also need a deck of train cards that the players can draw from and the "shown" cards can be sourced from. The deck consists of 12 of each color card, and 14 locomotive cards.

First, let's create a method to create a number of cards in a single color:

private List<TrainCard> CreateSingleColorCollection(TrainColor color, int count)
{
    List<TrainCard> cards = new List<TrainCard>();
    for(int i = 0; i < count; i++)
    {
        cards.Add(new TrainCard() { Color = color });
    }
    return cards;
}

We also need another extension method to shuffle these cards (again using Fisher-Yates):

public static class CardExtensions
{
    //...Other methods

    public static List<TrainCard> Shuffle(this List<TrainCard> cards)
    {
        Random r = new Random();
        //Step 1: For each unshuffled item in the collection
        for (int n = cards.Count - 1; n > 0; --n)
        {
            //Step 2: Randomly pick an item which has not been shuffled
            int k = r.Next(n + 1);

            //Step 3: Swap the selected item with the last "unstruck" letter in the collection
            TrainCard temp = cards[n];
            cards[n] = cards[k];
            cards[k] = temp;
        }

        return cards;
    }
}

Next, we can use that method to create all the cards the deck needs:

private void CreateTrainCardDeck()
{
    var deck = new List<TrainCard>();
    deck.AddRange(CreateSingleColorCollection(TrainColor.Red, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Purple, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Blue, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Green, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Yellow, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Orange, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Black, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.White, 12));
    deck.AddRange(CreateSingleColorCollection(TrainColor.Locomotive, 14));

    deck.Shuffle();

    this.Deck = deck;
}

We now have our draw deck created, and by extension, the board object. Our game is ALMOST ready to play!

One Other Little Extension

There's one last little thing we need to make. For better or for worse, we are largely going to be dealing with List<T> objects in this implementation. Yes, I know it would be more appropriate to use a Stack<T> or Queue<T> instead, but I didn't. So, we need a method that will "pop" cards off a collection.

Here is that method, implemented for the List<TrainCard> class:

public static List<TrainCard> Pop(this List<TrainCard> cards, int count)
{
    List<TrainCard> returnCards = new List<TrainCard>();
    for(int i = 0; i < count; i++)
    {
        var selectedCard = cards[0];
        cards.RemoveAt(0);
        returnCards.Add(selectedCard);
    }

    return returnCards;
}

Summary

In this second part of our Ticket to Ride C# Modeling Practice series, we created objects for the destination cards, train cards, board routes, and the board itself. We also populated the destination card and train card decks with the appropriate cards. Finally, we created a Pop() extension to use on lists of cards.

In the next part of this series, we will start examining one of the most difficult problems we will face in this series: determining the ideal route between two distant cities.

With two more routes, Red will connect both LA and Portland with New York.

Don't forget to check out the sample project over on GitHub!

Happy Coding!