Click here to Skip to main content
Click here to Skip to main content
Go to top

War Card Game Simulation in C#

, 22 Jun 2009
Rate this:
Please Sign up or sign in to vote.
Windows Forms Application using LINQ expressions and Dictionary objects to recreate a classic card game
WarGameScreenSmall2.png

Introduction

(Note, in this article, the capitalized word ‘War’ refers to the game, while the lowercase word ‘war’ refers to a battle in which opponent’s card values are equal.)

Recently, I needed a method to generate data for a statistical analysis course I was taking. While playing the card game War with my young niece, it dawned on me; War was the perfect game simulation from which to generate all types of statistical data. What is the average number of battles it takes to complete the game? What are the odds a battle will result in war (a tie)? How about the odds of a double or triple war (a series of ties)? Are there ways to predict the outcome? How are the results influenced by varying the way rules of the game?

Background

Doing some research on the web, it was clear I was not the first to undertake this endeavor. Due to the simplicity of War, there are an infinite number of computer game simulations available. In addition, there is a profusion of articles written about programming principals that can be applied to the construction of a game simulation such as War, and statistical analysis that can be done based on the game’s outcomes. Before creating the simulation, I limited my research to the rules of the card game, not the architecture of any existing simulations. I wanted the challenge of creating a working simulation.

War is what is known as a simple accumulating-type card game. The objective of the game is to acquire (win from the opponent) all the cards in a standard 52-card Anglo-American playing card deck. In War, each card generally has one of thirteen possible predetermined values – Two (deuce) through Ace. In war, the card’s suite — hearts, diamonds, spades, or clubs, have no impact on the cards value.  

There are many variations of War. For this simulation, I choose the most basic 2-player variation of the game. Here are the rules:

  1. Starting a standard 52-card deck, the deck is shuffled and the cards are dealt to each player. Both players receive 26 cards, face down.
  2. The game consists of a series of ‘battles’ in which each player draws a single off the top of their deck and places it face up on the table. If both cards have different values, the player with the highest value card wins the battle. The winner of the battle takes all cards from the table and places them onto the bottom of their deck, in no particular order.
  3. A battle in which both players lay down a card of equal value is referred to as ‘war’ (thus the name of the game). In this event, each player places two additional cards face-down on the table, followed by a third card face-up. The winner is then determined as before by higher-valued card. If perchance these cards are also identical (a ‘double war’), the process repeats itself until a winner is determined.
  4. The player that eventually accumulates all 52 cards, leaving the opponent with no cards, is the winner. In the event of war, if a player doesn't have enough cards left for the war to commence (two cards down and one up), she/he looses the battle and therefore the game.

Using the Code

This simulation is constructed as a simple Windows Forms Application, built in C# 3.0 using Visual Studio 2008. The UI consists of a single Windows Form, which displays the game’s state. In addition, there are a series of classes that contains the games logic. My primary focus was on the accuracy of the simulation, construction of the code, and extraction of data, not creating a visually rich GUI. Many enhancements to the user experience could certainly be made to increase the game’s entertainment factor.

The simulation allows the game to be played in two different modes, manual and automatic. You can choose to play the game manually, conducting each battle one at a time and seeing the results. Alternately, you can choose to have the program conduct all battles until a winner is determined.

Classes

The abstract base class, CardGame, contains generic fields, properties, and methods, which could be reused to create many types of card games. CardGame class contains methods for creating a new deck of 52 cards, shuffling a set of cards (see code below) and dealing cards to each player. Fields and properties in the base class include Dictionary<> objects to store the player’s cards, and player, battle, and game stats.

protected Dictionary ShuffleCards(Dictionary cardsToShuffle)
{
    protected Dictionary ShuffleCards(Dictionary cardsToShuffle)
    {
        Dictionary ShuffledCards = new Dictionary(cardsToShuffle);
        Random RandomNumber = new Random();
        ShuffledCards = ShuffledCards.Select(cards => cards)
            .OrderBy(cards => RandomNumber.Next())
            .ToDictionary(item => item.Key, item => item.Value);
        return ShuffledCards;
    }
}

The WarCardGame class inherits the CardGame class, and adds additional fields, properties, and methods, specific to the game of War. WarCardGame class adds methods for starting a game, executing a battle (see code below), and determining the type of battles – single, double, etc. The WarCardGame class’s fields store values such as war tallies and number of cards to be placed face down in war. One might argue that more of the WarCardGame class’s fields, properties, and methods could be placed in the base class and overridden if necessary.

public bool Battle()
{
    //Fill temp decks with players cards
    tempPlayer1Deck = Player1Deck.Select(cards => cards).ToDictionary(item => 
					item.Key, item => item.Value);
    tempPlayer2Deck = Player2Deck.Select(cards => cards).ToDictionary(item => 
					item.Key, item => item.Value);

    if (Player1Deck.Count() > 0) { Player1CardKey = Player1Deck.ElementAt(0).Key; }
    if (Player2Deck.Count() > 0) { Player2CardKey = Player2Deck.ElementAt(0).Key; }

    if (tempPlayer1Deck.Count() == 0)
    {
        StatusMessage = "Player 2 wins the game!";
        OutcomeCode = 3;
        return false;
    }
    else if (tempPlayer2Deck.Count() == 0)
    {
        StatusMessage = "Player 1 wins the game!";
        OutcomeCode = 1;
        return false;
    }
    else if (tempPlayer1Deck.Count() < (2 + cardsPlacedFaceDown) && 
	tempPlayer1Deck.ElementAt(0).Value == tempPlayer2Deck.ElementAt(0).Value)
    {
        // Game ended in war (tied in battle)
        // Player 1 doesn't have enough cards for a war
        Player2TricksWon++;
        TotalTricksWon++;
        UpdateWarTotals(tempCardsOnTheTable.Count());
        StatusMessage = "Player 2 wins the game! Game ended in a war.";
        OutcomeCode = 4;
        return false;
    }
    else if (tempPlayer2Deck.Count() < (2 + cardsPlacedFaceDown) && 
	tempPlayer1Deck.ElementAt(0).Value == tempPlayer2Deck.ElementAt(0).Value)
    {
        Player1TricksWon++;
        TotalTricksWon++;
        UpdateWarTotals(tempCardsOnTheTable.Count());
        StatusMessage = "Player 1 wins the game! Game ended in a war.";
        OutcomeCode = 2;
        return false;
    }
    else // Game hasn't ended so continue
    {
        if (CardsOnTheTable != null) // If last battle ended in war, 
				// collect cards left on table
        {
            tempCardsOnTheTable = CardsOnTheTable.Select(cards => 
		cards).ToDictionary(item => item.Key, item => item.Value);
        }

        Player1CardValue = tempPlayer1Deck.ElementAt(0).Value;
        Player2CardValue = tempPlayer2Deck.ElementAt(0).Value;

        // Begin battle, each player lays down face-up card
        tempCardsOnTheTable.Add(tempPlayer1Deck.ElementAt(0).Key, 
				tempPlayer1Deck.ElementAt(0).Value);
        tempPlayer1Deck.Remove(tempPlayer1Deck.ElementAt(0).Key);
        tempCardsOnTheTable.Add(tempPlayer2Deck.ElementAt(0).Key, 
				tempPlayer2Deck.ElementAt(0).Value);
        tempPlayer2Deck.Remove(tempPlayer2Deck.ElementAt(0).Key);
        
        // Randomizes order of card on table before they are placed 
        // back onto the bottom of the winning player's deck
        tempCardsOnTheTable = ShuffleCards(tempCardsOnTheTable);

        if (Player1CardValue > Player2CardValue)
        {
            StatusMessage = "Player 1 wins the battle.";
            // Add cards on table to winning player's hand
            var playerDeck1LINQ = tempPlayer1Deck.Select(cards => 
		cards).Concat(tempCardsOnTheTable.Select(cards => cards));
            tempPlayer1Deck = playerDeck1LINQ.ToDictionary(item => 
					item.Key, item => item.Value);
            Player1TricksWon++;
            TotalTricksWon++;
            UpdateWarTotals(tempCardsOnTheTable.Count());
            tempCardsOnTheTable.Clear(); 	// There is a winner so clear 
					// the table of cards
        }
        else if (Player1CardValue < Player2CardValue)
        {
            StatusMessage = "Player 2 wins the battle.";
            // Add cards on table to winning player's hand
            var playerDeck2LINQ = tempPlayer2Deck.Select(cards => 
		cards).Concat(tempCardsOnTheTable.Select(cards => cards));
            tempPlayer2Deck = playerDeck2LINQ.ToDictionary(item => 
					item.Key, item => item.Value);
            Player2TricksWon++;
            TotalTricksWon++;
            UpdateWarTotals(tempCardsOnTheTable.Count());
            tempCardsOnTheTable.Clear();
        }
        else if (Player1CardValue == Player2CardValue) // Players cards are 
						// of equal value - war
        {
            StatusMessage = "It's a tie. Time for war.";
            // Lay card(s) face-down on table for war
            for (int counter = 0; counter < cardsPlacedFaceDown; counter++)
            {
                tempCardsOnTheTable.Add(tempPlayer1Deck.ElementAt(0).Key, 
					tempPlayer1Deck.ElementAt(0).Value);
                tempPlayer1Deck.Remove(tempPlayer1Deck.ElementAt(0).Key);
                tempCardsOnTheTable.Add(tempPlayer2Deck.ElementAt(0).Key, 
					tempPlayer2Deck.ElementAt(0).Value);
                tempPlayer2Deck.Remove(tempPlayer2Deck.ElementAt(0).Key);
            }
        }
        // Battle over, reassign cards in correct locations
        Player1Deck = tempPlayer1Deck.Select(cards => 
		cards).ToDictionary(item => item.Key, item => item.Value);
        Player2Deck = tempPlayer2Deck.Select(cards => 
		cards).ToDictionary(item => item.Key, item => item.Value);
        CardsOnTheTable = tempCardsOnTheTable.Select(cards => 
		cards).ToDictionary(item => item.Key, item => item.Value);
        return true; //Game is not over
    }
}

The instance of the WarCardGame object manages three sets of card during game play: Player One’s cards, Player Two’s cards, and the ‘cards on the table’ (the players have laid down during the battle). The card sets are stored in Dictionary<string, int> object fields. The Dictionary’s KeyValuePair elements represent individual cards. The Key represents the card as a string, for example the ‘9 ?’. The Value represents the value of the card as an integer, for example ‘9’.

During a battle, card sets are copied to one of three temporary Dictionaries using LINQ expressions (System.LINQ class). During the battle, cards are moved between the three temporary Dictionaries. After the battle, LINQ is used again to copy the cards back to the appropriate Dictionary fields. LINQ is also used to reverse the order of the cards and to concatenate the contents of Dictionaries. The temporary Dictionaries represent the state of the decks during the battle, while the original Dictionary fields represent the state of the decks prior to and proceeding a battle. I felt using this approach helped maintain the ‘state’ of the game, easier.

After each battle, and at the end of the game, the fields and properties that track the game’s status are updated to reflect the current game state — number on tricks, player wins, number of wars, etc. Statistics are returned to the UI from DisplayResults(). Contents of each deck are displayed as a comma-delimited string using DisplayDeck(Dictionary< string, int >). The optional DataToClipboard() can be called to return the game’s statistics as a tab-delimited string for easy export to the clipboard and onto Excel for testing and statistic analysis.

Points of Interest

Deck Weight

After completing the simulation, I read the PREDICTABILITY IN THE GAME OF WAR, an article by Jacob Haqq-Misra, which appeared online in Science Creative Quarterly. Reading Jocob’s article, I decided to add the deck weighting method he described to the simulation. The weight range for a player’s initial 26-card deck will be between +84 and -84. A deck with an ideal maximum weighting of +84 would have to contain only the highest values cards possible: 4 Aces, 4 Kings, 4 Queens, etc. down to and including 2 Eights. The weighting theory suggests that the higher a player’s deck weight, the higher the probability that player will win the game.

Interestingly (and logically), the opposite player’s deck (1/2 the original deck) will always have the inverse weight of the opposite player, since the weight of the entire 52-card deck is always 0 (zero). If Player One has a weight +25, Player Two’s deck will always have a weight of -25. This inverse rule is true even though the cards were shuffled, making each player’s decks totally random.

In the simulation, the DeckWeight(Dictionary<string, int>) method accepts a set of cards, to which it assigns individual cards, weights between -8 and +8, depending on the card’s face value (see code below). The DeckWeight(Dictionary<string, int>) method returns an integer representing the deck's weight — the sum of all the individual card’s weight.

public int CalculateDeckWeight(Dictionary deckToWeigh)
{
    int theDecksWeight = 0;
    foreach (KeyValuePair kvp in deckToWeigh)
    {
        theDecksWeight += kvp.Value - 8;
    }
    return theDecksWeight;
}

On a side note, in testing several thousand games, I was unable to achieve a player deck weight greater then +48. I theorize that the 52-card deck in the simulation is ‘shuffled’ in a manner that creates a greater randomized ordering of the cards than shuffling real cards by hand would achieve. Shuffling by hand is not as efficient in ensuring complete randomization the cards. The probability of a significantly higher- or lower-valued deck with hand-shuffling seems more likely. 

Statistics

The next update of this program will contain the statistical results of 5,000 games played. Generating the data is quick and easy using the GenerateGameData(int) and DataToClipboard() methods included with the source code. It can be pasted in Excel and analyzed.

History

  • June 20, 2009 - version 1.0
    • Initial version
  • June 22, 2009 - version 1.1
    • Fixed minor error in UpdateWarTotals(int) method
    • Improved the way properties are reset after a game is complete. Effected StartGame() method
    • Rewrote ShuffleCards(Dictionary<string, int>) and DealCards(Dictionary<string, int>, int) methods to take better advantage of LINQ

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gary Stafford
Software Developer (Senior) Paychex
United States United States
I am a senior software developer, architect, and project manager, specializing in .NET, JavaScript, Java, and database development, and build automation. I am currently a Lead Developer (.NET) / Developer IV for Paychex Enterprise Business Solutions. Paychex (PAYX) provides payroll, human resources, and benefits outsourcing and web-based solutions to business.
 
Prior to Paychex, I served as Lead Software Engineer, Operations Manager, and Technical Product Manager at Bio-Optronics. Bio-Optronics develops, deploys and operates information technology solutions to help healthcare professionals manage and optimize workflow to enhance quality, productivity, and patient and staff satisfaction and safety. Previously, I held positions of President, COO, Chief Technology Officer (CTO), and SVP of Technology for Lazer Incorporated. Lazer is a successful, digital imaging and Internet-based content management services provider.
Follow on   Twitter

Comments and Discussions

 
QuestionI mentioned this article in my recent writing and thought that you probably like to know Pinmemberw582811-Feb-12 12:29 
GeneralRe: I mentioned this article in my recent writing and thought that you probably like to know PinmemberGary Stafford24-Mar-12 1:41 
GeneralInteresting PinmemberMember 46812978-Dec-09 10:55 
GeneralMy vote of 1 PinmemberC#20101-Jul-09 6:35 
It, seems not useful to me.
AnswerRe: My vote of 1 PinmemberC#20101-Jul-09 6:38 
GeneralRe: My vote of 1 [modified] PinmemberGary Stafford1-Jul-09 7:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 22 Jun 2009
Article Copyright 2009 by Gary Stafford
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid