This is the second part of a two-part series detailing how to simulate the Hasbro board game Candy Land as a C# console project. Click here for Part 1

Now that we've got our basic classes defined and ready to go, we can start programming the game. However, the first thing we need to do is figure out how many Cards and BoardSpaces a standard game as.

The Space In Between

Of the two, the more difficult problem is the BoardSpaces. Here's my board again for reference:
The game board for Candy Land, showing all spaces

Note that the standard spaces always appear in a pattern: red, purple, yellow, blue, orange, green. Further, on careful counting I observed that this pattern repeats itself 21 times, resulting in 126 spaces that are a standard color (including the licorice and shortcut spaces).

But what about the specialty spaces? It turns out that whenever they appear, they are just inserted into the main pattern, and do not change it.

All of this lends itself to an easily-implemented library. Now we just need a class to represent the entire Board.

Room and Board

So let's create that Board class we need:

public class Board  
{
    public List<BoardSpace> Spaces { get; set; }
    public Board() { }
}

When the board gets created, we want to create the spaces in it in the same order every time. Remember that the basic spaces go in that standard pattern, and we can just insert the speciality, licorice, and shortcut spaces where they go.

The code that creates the spaces into their collection looks like this:

public Board()  
{
    Spaces = new List<BoardSpace>();

    //Add the standard colors
    for(int i = 1; i<= 21; i++)
    {
        Spaces.Add(new BoardSpace() { Color = CandyColor.Red });
        Spaces.Add(new BoardSpace() { Color = CandyColor.Purple });
        Spaces.Add(new BoardSpace() { Color = CandyColor.Yellow });
        Spaces.Add(new BoardSpace() { Color = CandyColor.Blue });
        Spaces.Add(new BoardSpace() { Color = CandyColor.Orange });
        Spaces.Add(new BoardSpace() { Color = CandyColor.Green });
    }

    //Add the speciality spaces
    Spaces.Insert(8, new BoardSpace() { Color = CandyColor.Cupcake });
    Spaces.Insert(19, new BoardSpace() { Color = CandyColor.IceCream });
    Spaces.Insert(41, new BoardSpace() { Color = CandyColor.Star });
    Spaces.Insert(68, new BoardSpace() { Color = CandyColor.Gingerbread });
    Spaces.Insert(91, new BoardSpace() { Color = CandyColor.Lollipop });
    Spaces.Insert(101, new BoardSpace() { Color = CandyColor.IcePop });
    Spaces.Insert(116, new BoardSpace() { Color = CandyColor.Chocolate });
    Spaces.Insert(132, new BoardSpace() { Color = CandyColor.Rainbow });

    //Replace the Licorice spaces
    Spaces.RemoveAt(44);
    Spaces.Insert(44, new BoardSpace() { Color = CandyColor.Green, IsLicorice = true });

    Spaces.RemoveAt(75);
    Spaces.Insert(75, new BoardSpace() { Color = CandyColor.Green, IsLicorice = true });

    //Replace the Shortcut spaces
    Spaces.RemoveAt(3);
    Spaces.Insert(3, new BoardSpace() { Color = CandyColor.Blue, ShortcutDestination = 59 });

    Spaces.RemoveAt(28);
    Spaces.Insert(28, new BoardSpace() { Color = CandyColor.Yellow, ShortcutDestination = 40 });

    int location = 0;
    foreach(var space in Spaces)
    {
        space.Location = location;
        location++;
    }
}

Now the Board will be ready to play once it is created.

Stacking the Deck

With the Board ready to go, we also need to get the Cards ready, and we can do this by creating a CardDeck class:

public class CardDeck  
{
    private List<Card> Cards { get; set; }

    public CardDeck()
    {
        CreateDeck();
        Shuffle();
    }
    private void CreateDeck()
    {
    }
    public void Shuffle()
    {
    }

    public Card Draw()
    {
    }
}

CardDeck has three methods we'll need to flesh out: CreateDeck, Shuffle, and Draw.

For CreateDeck, we want to create the same number, type, and amount of cards that would be found in a regular real-world Candy Land set. I counted all the cards in my set and found the following numbers:

Let's start with the cards. I copied down the contents of all our cards and came up with the following numbers:

CandyColor Value Count
Red 1 6
Orange 1 6
Yellow 1 6
Green 1 6
Blue 1 6
Purple 1 5
Red 2 4
Orange 2 4
Yellow 2 4
Green 2 3
Blue 2 3
Purple 2 4
Star 1 1
Ice Cream 1 1
Gingerbread 1 1
Lollipop 1 1
Ice Pop 1 1
Chocolate 1 1
Ice Pop 1 1

The total cards we have is 64, which matches what my instruction manual tells me, so we're good to go. Now we can fill in the CreateDeck method that will create a standard, unshuffled deck of cards.

private void CreateDeck()  
{
    Cards = new List<Card>();
    for (int i = 1; i <= 6; i++)
    {
        if (i <= 1) //Specialty Cards
        {
            Cards.Add(new Card() { Color = CandyColor.Cupcake, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.IceCream, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Chocolate, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Lollipop, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.IcePop, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Star, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Gingerbread, Amount = 1 });
        }
        if (i <= 3)
        {
            Cards.Add(new Card() { Color = CandyColor.Green, Amount = 2 });
            Cards.Add(new Card() { Color = CandyColor.Blue, Amount = 2 });
        }
        if (i <= 4)
        {
            Cards.Add(new Card() { Color = CandyColor.Red, Amount = 2 });
            Cards.Add(new Card() { Color = CandyColor.Yellow, Amount = 2 });
            Cards.Add(new Card() { Color = CandyColor.Orange, Amount = 2 });
            Cards.Add(new Card() { Color = CandyColor.Purple, Amount = 2 });
        }
        if (i <= 5)
        {
            Cards.Add(new Card() { Color = CandyColor.Yellow, Amount = 1 });
        }
        if (i <= 6)
        {
            Cards.Add(new Card() { Color = CandyColor.Red, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Orange, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Blue, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Green, Amount = 1 });
            Cards.Add(new Card() { Color = CandyColor.Purple, Amount = 1 });
        }
    }
}

However, that's not going to be much fun, since the cards will always appear in that order. Just like with real-world cards, we want to shuffle these cards, and to do so we can use an algorithm known as the Fisher-Yates shuffle:

public void Shuffle()  
{
    Random rng = new Random();
    int length = Cards.Count;
    while (length > 1) //Cycle through each card in order
    {
        length--;
        int k = rng.Next(length + 1); //Random number 1-64
        Card value = Cards[k]; //Remove the card at that index from the deck
        Cards[k] = Cards[length]; //Take the card from the current index and place it at the randomized index
        Cards[length] = value; //Take the card from the randomized index and place it at the current index.
    }  
}

This results in a good (but not perfect) shuffle, and it'll suit our needs.

We still have one method to work out, and that's Draw(). When a person draws a card in real life, that card is removed from the draw pile. However, we don't want to run into a situation where we have no cards to draw, so we need to account for that as well. Here's the code:

public Card Draw()  
{
    if(!Cards.Any())
    {
        CreateDeck();
        Shuffle();
    }
    var card = Cards.First();
    Cards.RemoveAt(0);
    return card;
}

Ta-da! Now we've got all the functionality we need for the deck of cards!

Red One! Blue One! Red One! Gray One!

Once a player has drawn a card, we need a way to determine to what space on the board s/he should move. There are a few conditions we need to consider.

  • If a player draws a regular color card (with one or two colors) then s/he advances to the next color space.
  • If a player draws a specialty card, s/he moves either forward or backward to the designated space.

I figured the best way to handle this was to build an Extension method for the BoardSpaces list which took the player's current location and the card drawn:

public static class Extensions  
{
    public static BoardSpace GetMatchingSpace(this List<BoardSpace> spaces, int currentIndex, Card card)
    {
        if(card.IsSpecialty()) //The speciality cards can move you forward or backward
        {
            var allMatches = spaces.Where(x => x.Color == card.Color);
            return allMatches.First();
        }
        else //The standard cards can only move you forward
        {
            var index = spaces.FindIndex(currentIndex + 1, x => x.Color == card.Color);
            if(index == -1)
            {
                return new BoardSpace()
                {
                    Color = CandyColor.Rainbow,
                    Location = 133
                };
            }
            return spaces[index];
        }
    }
}

Take Turns, Children

With the Board and Cards created and our GetMatchingSpace extension ready, we now need a way to have a Player conduct their turn. We also need a way to make sure the Players take their turns in order, and we'll get to that in a little bit.

In order for a player to take their turn, they need to know the spaces on the board and the deck of cards. We also want this method to return a description of what happened on that turn, so that we can write the message to the console. Hence our method signature will look like this:

public string TakeTurn(Board board, CardDeck deck)  
{
}

When it is a player's turn, several things can happen.

  • If the player landed on a licorice space on his turn immediately prior, then his turn is skipped.
  • If a player landed on a shortcut space, s/he immediately takes the shortcut.
  • If a player landed on the Rainbow space, s/he wins the game.

We need to account for all five conditions in our TakeTurn method:

public string TakeTurn(Board board, CardDeck deck)  
{
    if(IsSkipped) //Licorice skip
    {
        IsSkipped = false;
        return Name + " was skipped!";
    }
    var card = deck.Draw(); 
    int matchingIndex = CurrentLocation;
    var space = board.Spaces.GetMatchingSpace(CurrentLocation, card);
    CurrentLocation = space.Location; //Move to the space defined by the card drawn.
    string message = Name + " moved to Space " + CurrentLocation.ToString() + " which is a " + space.Color.ToString() + " space.";
    if(space.IsLicorice) //Player is stuck!
    {
        IsSkipped = true;
        message += Name + " is stuck by Licorice!";
    }
    if(space.ShortcutDestination.HasValue) //Player landed on a shortcut!
    {
        CurrentLocation = space.ShortcutDestination.Value;
        message += Name + " took a shortcut to Space " + CurrentLocation.ToString() + "!";
    }
    if(CurrentLocation == 133) //Player won the game!
    {
        IsWinner = true;
        message += Name + " won the game!";
    }
    return message;
}

With that in place, we are ready to write the final piece of our solution: the Game object.

Game On!

The Game object will keep track of the players, the board spaces, and the deck of cards. It will also need to know which player is next, and whether or not the game has started. Finally, we'll create methods to add players, to start the game, and to run the next move.

public class Game  
{
    public Board Board { get; set; }
    public CardDeck Deck { get; set; }
    public List<Player> Players { get; set; }
    public int CurrentPlayer { get; set; }
    public bool IsStarted { get; set; }

    public Game()
    {
        Board = new Board();
        Deck = new CardDeck();
        Players = new List<Player>();
    }

    public void AddPlayer(string name)
    {
    }

    public void StartGame()
    {
    }

    public string NextMove()
    {
    }
}

There's our skeleton, now let's flesh it out.

Start with AddPlayer and StartGame. We can't play a game without at least 2 players, but we can't have more than 4. Also, we can't add players to a game once the game has been started. Therefore, these two method look like this:

public void AddPlayer(string name)  
{
    if(!IsStarted && Players.Count < 4)
    {
        Players.Add(new Player() { Name = name});
    }
}

public void StartGame()  
{
    if(Players.Count >= 2 && Players.Count <= 4)
    {
        IsStarted = true;
    }
}

In the final method, NextMove, we need to tell the current player to take their turn, then process what that turn was. We also need to tell the next player that their turn is coming up. That method looks like this:

public string NextMove()  
{
    if(IsStarted)
    {
        var player = Players[CurrentPlayer];
        var message = player.TakeTurn(Board, Deck);
        CurrentPlayer++;
        if(CurrentPlayer > Players.Count - 1)
        {
            CurrentPlayer = 0;
        }
        return message;
    }
    return "Game has not started yet!";
}

Finally, we've gotten the last piece in place! The Candy Land simulation is complete!

I've written up a command-line program so that you can run this simulation however you like, grab it from GitHub.

I'm hoping to do some more advanced things with this simulation, like maybe calculating the shortest possible path for a single player, or the maximum number of turns a game would take (I'd be willing to bet that given perfectly bad shuffles, the game would never end). If anybody has any ideas, hit me up in the comments or on Twitter.

Happy Coding! Now excuse me while I get some ice cream.