Card Game War

NOTE: This is Part 2 of a three-part series demonstrating how we might model the card game War as a C# program. Part 1 is over here. You might want to use the sample project over on GitHub to follow along with this post. Also, check out my other posts in the Modeling Practice series!

  1. Card Game War Rules
  2. Card Game War For Kids

In the game of War, a war is a means to break a tie. When two cards of the same rank are played, you break the tie by playing new cards in addition to those already on the table. The player with the highest-ranking new card wins the tie breaker and all the played cards. Full Playlist: more How to Play Card Games videos: http://www.howcast.com/vid.

Now that we've got our Objects, observations, and other rules in place, it's time to start building them!

The Card

Let's start with the simplest object in this model: the card itself.

In our model, each Card has three properties:

  1. A Suit (Hearts, Clubs, Diamonds, or Spades)
  2. A Value
  3. A Display Name (e.g. '10D' for 10 of diamonds, 'AC' for Ace of Clubs, etc.)

I ended up using a C# enum to represent the Suit of the cards, so here's that enum plus the Card object:

Player

Now let's start with a very simple definition for the Player object. A player, in our model, has the following properties:

  1. A name
  2. A collection of cards (this is the player's deck).

So, our player object looks like this:

Now before you go yelling at me, let me explain why there is no Deck object...

Deck of Cards

Here's the first secret in our model: there actually isn't an object called Deck. Rather, we implement a deck of cards using the built-in Queue<> object in C#. That collection has almost all the functionality we need to implement a deck of cards, so why not use it?

Key word: Almost. We need one other method to make our code simpler: a method which can 'enqueue', or place into the collection, a collection of Cards rather than just a single one. Here's that extension method:

The Deck of Cards

Even though there is no Deck object, we will still need to create a standard 52-card playing card deck and shuffle it.

A standard deck has 13 cards for each of the four suits: an Ace, cards numbered 2-10, a Jack, a Queen, and a King.

Here's a static class DeckCreator which will give us a standard deck of cards:

Note the unusual for declaration. We are going to make 13 cards per suit, but each card will have a value from 2 to 14. This is because Aces are high in this game (higher than Kings), and we want to compare values easily.

Also, note the Shuffle() method that we haven't implemented yet. I previously wrote a post on the Fisher-Yates card shuffling algorithm and that's what we're going to use to shuffle the cards.

Finally, we need to handle the GetShortName() method. We don't want to be displaying 'Ace of Spades' every time that card appears, rather we want to show the short name 'AS' (or '5C' for 5 of Clubs, or 'KD' for King of Diamonds, and so on). So we need a method to assign this short name to each card, like so:

With the card, player, and 'deck' in place, we can finally create the last and most complex object in our model: the game itself.

The Game

In this modeling practice, like several of the others, the game itself is represented by an object. The properties of this object are the things required to play a game. In real life, what we would need to play a game is simple: two players, and a deck of cards.

Card Game War

However, we also need to end games if they become infinite, so we need to keep track of the number of turns elapsed.

Remember that our model makes the deck a property of the Player object, and so it will not be declared in the Game object.

Now we need to think about the steps involved in playing a turn in War. Here's what I came up with.

  1. The first step is to create the players, shuffle the cards, and pass a deck to each player.
  2. The players play turns until one player is left without any cards.
  3. There is an 'end of game' check which sees if either player is out of cards.

The first step is relatively easy, so let's code that up:

The end-of-game step is also relatively easy, so that's next. In the real world, end-of-game happens whenever a player is out of cards.

Decision Point: In Part 1, we discussed the possibility of infinite games of War. In our model, we want to avoid said infinite games, and so we'll forcibly end the game after 1000 turns have elapsed (the reasoning for this particular number will be in Part 3 of this series).

Here's that end-of-game check method in the Game object:

Now we have to deal with the most difficult of the three steps: playing a turn.

Playing a Turn

Playing a turn in War would be simple, if it weren't for the whole War mechanic. Flip two cards, higher card gets both. This sounds simple, but the War mechanic makes this more difficult than it sounds.

Before we show the code for this part, let's walk through the logic involved.

  1. Each player flips a card. If one card is bigger than the other, that player gets both cards.
  2. If the two cards are the same value, we start a War. Each player lays down three cards, and flips the fourth one. If one of the flipped cards is bigger than the other, that player gets all the cards on the table.
  3. If both flipped cards are the same, repeat the process (place 3, flip fourth) until one player has a bigger card than the other
  4. If a player runs out of cards during this process, they automatically lose.

Decision Point: In Part 1, I mentioned that the 'official' rules of War do not say what happens if a player runs out of cards during a War. In this model, I am making that scenario an immediate end-of-game, as it simplifies the system as a whole.

War

There's one trick we're going to employ: for the face-down cards during a War, we're going to put them in a common pool. At that point, it doesn't matter who placed them, it just matters that they go to the winner of the War.

Card

With all this in mind, here's the code:

Decision Point: In Part 1, we discussed that War in real-life is not deterministic, meaning that the outcome cannot be known after the cards are shuffled because they will be added to players decks in random order. Our model, almost by accident, has made War deterministic; cards are always added to player decks in a known order. Therefore, if we wanted to, we could 'know' after dealing which player will win. It's up to you, dear readers, to decide what to do with this information.

Card Game War Rules

Guess what? We now have a simple C# application that can play games of War! But we're not done yet. In the final part of this series, we'll run this system thousands of times, to see whether or not we accidentally biased it and how we might improve.

Don't forget to check out the sample project on GitHub!. As always, constructive criticism and tips to improve this project are welcome. Share your thoughts in the comments!

Card Game War For Kids

Happy Coding!