The Statistics of War (the card game)

Photo by Jack Hamilton on Unsplash.

During Thanksgiving Break I played the card game War with my girlfriend and remembered how wonderfully mindless it is. Because there's no strategy or decision-making, it was feasible to write a program to play the game. Once I could simulate one game, I could simulate several and collect some statistics.

Evidently the rules of War have some gaps, including what to do with the cards you win. Do you place them on the bottom of your deck, in order, or do you shuffle and reuse them once your deck is empty? Why not program both methods and compare? Here is a plot of both approaches after 100,000 trials of each. The x-axis is the number of turns required to complete the game, and the y-axis is the frequency of that outcome across 100,000 trials.

The data proved more interesting than I expected.

  • Shuffling the winnings led to much faster games — a mean of 262, a mode of 84, and a max of 2,702 turns.
  • When the winnings aren't shuffled, the numbers are less clear. Many games were seemingly infinite so I added a cap of 5,000 turns per game. 11,837 (11.8%) of the games hit this limit (and do not appear on the graph). But plenty of games ended after 4,000+ turns, so we could expect many of these long games to be finite as well. If we ignore the capped games, the mode is 104 turns.
  • Both plots show a binary distribution of the results, as you can see in the higher and lower groupings of dots. It turns out that games are more likely to finish after an even number of turns. Each player starts with 26 cards, so if there are no wars and one player wins every hand, the game will last 26 turns. For every turn that player loses, they must win an additional hand to cancel the loss, so the game still requires an even number of turns. Only a war will break this trend because 4 cards change possession instead of 1. A game with an odd number of wars will take an odd number of turns to complete (ignoring wars with fewer than 3 cards, when one player's deck is low).

As for the program, here it is in JavaScript. The highlighted lines show the changes made between the Shuffle and No-Shuffle versions.

(function() {
	var NUM_TRIALS = 100000, // # games to simulate
		MAX_TURNS = 5000; // maximum # of turns to avoid infinite loops

	// From http://stackoverflow.com/a/2450976
	function shuffle(array) {
		var currentIndex = array.length,
			temporaryValue,
			randomIndex;

		// While there remain elements to shuffle...
		while (0 !== currentIndex) {
			// Pick a remaining element...
			randomIndex = Math.floor(Math.random() * currentIndex);
			currentIndex -= 1;
			// And swap it with the current element.
			temporaryValue = array[currentIndex];
			array[currentIndex] = array[randomIndex];
			array[randomIndex] = temporaryValue;
		}
		return array;
	}

	function Card(o) {
		var self = this;
		self.suit = o.suit;
		self.value = o.value;

		// Generate a string representation of the card
		self.toString = function() {
			var displayValue = self.value;
			switch (self.value) {
				case 11:
					displayValue = 'J';
					break;
				case 12:
					displayValue = 'Q';
					break;
				case 13:
					displayValue = 'K';
					break;
				case 14:
					displayValue = 'A';
					break;
			}
			return self.suit + displayValue;
		};

		// Return a negative number if compare is higher than this card.
		// Return a positive number if this card is higher than compare.
		// Return zero if the cards are equal.
		self.compare = function(/*Card*/ compare) {
			return self.value - compare.value;
		};

		return self;
	}

	function Player(o) {
		var self = this,
			hand = [],
			discard = [];

		self.name = o.name || 'Player';

		// Draw one card from the player's hand
		self.draw = function() {
			if (hand.length === 0) {
				// Version 1: The discard pile is shuffled before using				//hand = shuffle(discard).concat(hand);				// Version 2: The discard pile is used in the order that it was created				hand = discard.concat(hand);				discard = [];
			}
			return hand.pop();
		};

		// Add one or more cards to the player's discard pile
		self.discard = function(cards) {
			if (Array.isArray(cards)) {
				discard = cards.concat(discard);
			} else {
				discard.push(cards);
			}
		};

		// Count the total number of cards in this player's possession
		self.numCards = function() {
			return hand.length + discard.length;
		};

		// Does the user have any cards remaining?
		self.hasCards = function() {
			return self.numCards() > 0;
		};

		// Generate a string representation of the player
		self.toString = function() {
			return self.name + ' (' + self.numCards() + ' cards)';
		};

		return self;
	}

	function Game(o) {
		var self = this,
			deck = shuffle(o.deck);

		// The number of turns that have elapsed in this game
		self.numTurns = 0;

		// Simulate a single game
		self.play = function() {
			var player1 = new Player({ name: 'John' }),
				player2 = new Player({ name: 'Jane' });

			// Split the deck between the two players
			var iDeck;
			for (iDeck = 0; iDeck < deck.length; iDeck++) {
				if (iDeck % 2) {
					player1.discard(deck[iDeck]);
				} else {
					player2.discard(deck[iDeck]);
				}
			}

			var player1Card,
				player2Card,
				cardCompare,
				winnings,
				isWar,
				numWarCards,
				iWarCards;

			while (player1.hasCards() && player2.hasCards()) {
				self.numTurns++;

				// End this game if it's taking too long, to avoid potentially infinite loops
				if (self.numTurns > MAX_TURNS) {
					return;
				}

				player1Card = player1.draw();
				player2Card = player2.draw();
				winnings = [player1Card, player2Card];

				cardCompare = player1Card.compare(player2Card);

				if (cardCompare > 0) {
					// Player 1 wins this turn
					player1.discard(winnings);
				} else if (cardCompare < 0) {
					// Player 2 wins this turn
					player2.discard(winnings);
				} else {
					// War!
					do {
						// If a player is completely out of cards now, they lose the game
						if (!player1.hasCards()) {
							// Player 2 wins this game
							isWar = false;
							player2.discard(winnings);
						} else if (!player2.hasCards()) {
							// Player 1 wins this game
							isWar = false;
							player1.discard(winnings);
						} else {
							isWar = true;

							// If either player doesn't have enough cards for war, bet what they have
							numWarCards = Math.min(
								player1.numCards() - 1,
								player2.numCards() - 1,
								3,
							);

							for (iWarCards = 0; iWarCards < numWarCards; iWarCards++) {
								winnings.push(player1.draw());
								winnings.push(player2.draw());
							}

							player1Card = player1.draw();
							player2Card = player2.draw();
							winnings.push(player1Card);
							winnings.push(player2Card);

							cardCompare = player1Card.compare(player2Card);

							if (cardCompare > 0) {
								// Player 1 wins the war and this turn
								isWar = false;
								player1.discard(winnings);
							} else if (cardCompare < 0) {
								// Player 2 wins the war and this turn
								isWar = false;
								player2.discard(winnings);
							} else {
								// The card values are equal again, continue the war
							}
						}
					} while (isWar);
				}
			}

			// Note: this is erroneous if MAX_TURNS was reached
			self.winner = player1.hasCards() ? player1 : player2;
		};

		return self;
	}

	// Generate the master deck of all 52 cards.
	// It will be reused and shuffled in each Game instance.
	var suits = ['&spadesuit;', '&clubsuit;', '&heartsuit;', '&diamondsuit;'],
		deck = [];

	var iSuit, iValue;
	for (iSuit = 0; iSuit < suits.length; iSuit++) {
		// J = 11, Q = 12, K = 13, A = 14
		for (iValue = 2; iValue <= 14; iValue++) {
			deck.push(new Card({ suit: suits[iSuit], value: iValue }));
		}
	}

	// Run the desired number of trials.
	// Populate wins with the number of turns and frequency of that outcome.
	var iTrials,
		thisGame,
		wins = [];
	for (iTrials = 0; iTrials < NUM_TRIALS; iTrials++) {
		thisGame = new Game({ deck: deck });
		thisGame.play();

		if (!wins[thisGame.numTurns]) {
			wins[thisGame.numTurns] = 1;
		} else {
			wins[thisGame.numTurns]++;
		}
	}
})();

To close, enjoy the amazing uniqueness of a well-shuffled deck.

Published under  on .

Drew

Drew

Hi! I'm Drew, the Wimpy Programmer. I'm a software developer and formerly a Windows server administrator. I use this blog to share my mistakes and ideas.

Comments on "The Statistics of War (the card game)"

Avatar for BenjaminBenjamin

I know this is an older post but I just wanted to say thank you! I was assigned the task of creating a program that could simulate a game of war and I designed it to not shuffle. Seeing the statistics laid out like this made it much easier to determine if my game lengths were falling in line with what you may expect from a typical game. The frequency of an endless game being played threw me off and I honestly thought there was something wrong with my code until I took a look at your data. Is the act of shuffling a common rule? Where I live everyone I have ever played with typically adds their winnings to the bottom of their pile as we go along. Thanks again!

Avatar for DrewDrew

Thanks Benjamin! I'm glad this old post could help you.

When I learned to play War, we shuffled. I think this adds excitement to the game. My fiancée learned to play without shuffling, and so perhaps it's no coincidence that she considers War a waste of time. The Wikipedia article seems to confirm that the rules are hazy on this point.

In any event, this simulation was eye-opening for me and I'd definitely recommend shuffling to anyone who wants a quicker game. But then again, I remember as a kid playing War with my brothers during neverending car trips. Stretching out every game by not shuffling might be a better way to pass the time.

Avatar for Don HerlofskyDon Herlofsky

My 8 year old grandson beat me in two hands. We did shuffle after the first round. And also left the jokers in the deck. Can't imagine the odds of anyone doing that! Unbelievable!

Avatar for Apt6NApt6N

Drew - thank you for this post. My roommate and I were having a heated discussion on the game of war. So do you think a game can go on forever without a winner?

Avatar for BladeBlade

My son and I had a game today that went only one round. My son did not win a single time. we had 2 "double wars". We play that on a match card you each lay 3 cards down and flip up on the forth. What are the odds of a single round game???? Never seen that happen before.

Avatar for RayRay

Glad to see other people arriving at the same question. Did you run the program with one side shuffling and the other not?

Avatar for SamSam

This is great! I also made a war simulation without knowing this existed! github.com/sambecker/rustwar I consistently averaged 268 turns with shuffling and 580 turns without shuffling (excluding 6% or so of games that never finish). Would love to compare our approaches, see where differences/edge cases might exist!

Avatar for ClintClint

My wife, son, and I were playing 3-way War last night. We got a 3 way war (very rare), laid down our three face down cards, and then all matched again for a DOUBLE 3-Way War.

I have NEVER seen this in probably thousands of games over my nearly 40 years of playing the game. Is there any way to calculate the odds of such an event? We use a double deck, but the odds still have to be in the thousands, right?

Avatar for MirandaMiranda

I was looking for the odds of having a triple war. My daughter and I just had one and I can never remember ever having a triple war. Miranda

Avatar for WhenYouNeedHelpWhenYouNeedHelp

For the sake of continuing knowledge, I wanted to post about my results with this problem.

Funnily enough, I was also playing a round of war with my girlfriend when I had the idea to simulate the game and find the average number of turns.

I managed to simulate 1 Million games (with random shuffling) and got the following results:

Mode: 86
Average: 260
Minimum: 5
Maximum: 3548

I'm guessing these differences in numbers (Mode and Average) boil down to our handling of some special situations, since anything passed 10,000 simulations tended to have the same average / mode.