candy-land

Simulating Candy Land in .NET Part 2: Programming the Game

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.

Simulating Candy Land in .NET

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

Anybody remember the game Candy Land? You know, the simple colors and sweets game that little children play to learn how to play board games? The one that requires absolutely zero skill or reading comprehension to play?

A child plays Candy Land Candy Land by amboo who?, used under license

Lemme break it down for those of you who haven't seen this game before. Players draw cards which have simple colors (red, orange, yellow, blue, green, purple) on them, and then move to the next matching space with that color. There are also seven "specialty" spaces with pictures of sweet items on them, and if you draw the card with that item you go directly to that space (which also means you could move backwards by drawing a specialty card). The first player to reach the end of the path wins.

Only two complications arise on the board: a couple spaces have black licorice on them, and if you land on one of those space your next turn is skipped (which doesn't make any sense to me, seeing as black licorice is delicious); and there are two shortcuts on the board that can be accessed by landing on their starting spaces.

Simple enough, right? Here's the interesting part: the game is deterministic. Once the cards are shuffled, a person who can see the deck of cards will know who will win the game.

That means that we can write a program to simulate playing the game, and it wouldn't be too hard. Guess what we're gonna do? Let's build a command line Candy Land simulator in C# and .NET!

Basically Awesome

Before we can do anything else, we need to model the components of Candy Land as C# classes. In any game of Candy Land, there are three basic components:

  1. The players playing the game (between 2 and 4)
  2. The cards the players can draw (64 in my edition)
  3. The spaces the players can land on (134 in my edition)

However, there are also three higher-level objects, which are comprised of the basic objects.

  1. The deck of cards that can be drawn from.
  2. The board, which contains all of the spaces.
  3. The game itself, which comprises the board, the deck, and the players.

We'll need all six of these objects to play a game; the latter three we will deal with in Part 2 of this series. For now, let's start with the players.

Don't Hate the Players

We'll need some basic information about the players in order to simulate the game. Technically, the only thing we really "need" is their turn order, but to make the simulation a little nicer we'll also gather the players' names.

public class Player  
{
    public string Name { get; set; }
    public int CurrentLocation { get; set; }
    public int Order { get; set; }
    public bool IsSkipped { get; set; }
    public bool IsWinner { get; set; }
}

Note the property CurrentLocation. We need a way to store what space the player currently resides on, and IMO the best place to put that is in the Player class itself. We'll explain the IsSkipped property in the BoardSpace section.

The property IsWinner is a flag that the Game object will look at after each turn; as soon as one Player has IsWinner set to true, the game is over.

Now that we've got Player defined, we need to take a look at something that is common to the BoardSpace and Card classes: CandyColor.

Have Some Candy!

Here's the board for my game:

The game board for Candy Land, showing all spaces

Note that there are six "regular" colors for the spaces: red, orange, yellow, green, blue, and purple. These six colors also appear on the cards. Further, note that there are seven "special" spaces that can appear on both the board and the cards: star, ice cream, gingerbread, chocolate, cupcake, lollipop, and ice pop.

Since these thirteen "values" are available to both cards and spaces, we're going to make an Enumeration to represent them (because I like enums), and we'll call that Enum CandyColor:

public enum CandyColor  
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Purple,
    Cupcake,
    Star,
    IceCream,
    Gingerbread,
    Chocolate,
    Lollipop,
    IcePop,
    Rainbow
}

Wait, you say, what is "rainbow? Note that board's final space has all the colors, signifying that you can draw any color to land on it and win the game. As we'll see in later parts of this series, it's not strictly necessary to represent this space, but I'm going to do so for posterity.

With the common values defined, we can start building the Card and BoardSpace classes.

Is THIS Your Card?

Cards in Candy Land show a CandyColor, but can also show one or two of the "regular" colors. My class for these Cards looks like this:

public class Card  
{
    public CandyColor Color { get; set; }
    public int Amount { get; set; }
}

Woohoo easy part!

Gimme Some Space!

The next piece to this puzzle is the class that represents the spaces on the board. In addition to the CandyColors represented, there are two other "special" kinds of spaces:

  • Licorice spaces which cause any player that lands on one to have their next turn skipped (this is why we have the Player.IsSkipped property).
  • Shortcut spaces which send the player forward to a different space.

Our BoardSpace class will look like this:

public class BoardSpace  
{
    public CandyColor Color { get; set; }
    public int Position { get; set; }
    public bool IsLicorice { get; set; }
    public int? ShortcutDestination { get; set; }
}

Now that we've got all of our Game Components defined, we can start writing code to set up the game itself. We'll do that in Part 2 of this series, where we discuss Programming the Game.