How long does a `War’ last?

We all have played the card game called ‘War‘ at least once and know from experience that it can last much too long (often, both players agree to stop before the legal end of the game). But how long actually?

This is the question that came (back) to my mind while playing with my 3 year old son a variant of the game.
So I decided to write some R code and simulate enough games to make up my mind.

I do not need to adopt any convention here (such as a convention on what happens if a player runs out of cards during a war), since I am not interested in the winner, but only in the number of unitary moves; this number will give me a proxy of the total duration of the game. I define a unitary move as the number of cards played by one player during one move. So if there is a war during a move, the number of unitary moves will be at least equal to three.

First, I need to simulate one move. This is the purpose of the function ‘PlayWar1’.

PlayWar1 = function(deck1, deck2, faceUpCards = c(), faceDownCards = c(), iter = 1)
{
# This function simulates one move of 'War'.
# Because of the nature of 'War', this function is recursive.
#
# Arguments:
#   deck1:         numeric vector, the deck of Player 1
#   deck2:         numeric vector, the deck of Player 2
#   faceUpCards:   numeric vector, the cards that are shown by both players
#   faceDownCards: numeric vector, the cards that remain hidden during a war
#   iter:          integer, the number of iterations
#
# Value: a list consisting of the updated values of 'deck1', 'deck2',
#   'faceUpCards', 'faceDownCards', 'iter' at the end of the move.

  n1 = length(deck1)
  n2 = length(deck2)

  if (n1 > 0 & n2 > 0) {

    ## Each player reveals the top card of his/her deck
    faceUpCards = c(deck1[1], deck2[1], faceUpCards)
    deck1 = deck1[-1]
    deck2 = deck2[-1]

    ## Player 1 wins
    if (faceUpCards[1] > faceUpCards[2]) {
      deck1 = c(deck1, sample(c(faceDownCards, faceUpCards)))
      faceUpCards = faceDownCards = c()

    ## Player 2 wins
    } else if (faceUpCards[2] > faceUpCards[1]) {
      deck2 = c(deck2, sample(c(faceDownCards, faceUpCards)))
      faceUpCards = faceDownCards = c()

    ## 'War'
    } else {

      n1 = length(deck1)
      n2 = length(deck2)

      if (n1 > 0 & n2 > 0) {
        faceDownCards = c(deck1[1], deck2[1], faceDownCards)
        deck1 = deck1[-1]
        deck2 = deck2[-1]

        r = Recall(deck1, deck2, faceUpCards, faceDownCards, iter = iter + 2)
        deck1 = r[[1]]
        deck2 = r[[2]]
        faceUpCards = r[[3]]
        faceDownCards = r[[4]]
        iter = r[[5]]
      }
    }
  }

  return(list(deck1, deck2, faceUpCards, faceDownCards, iter))
}

Then I can define a game as a succession of moves with the function ‘PlayWar’. This function has two arguments, which characterize the deck in terms of height and width.

PlayWar = function(height = 13, width = 4)
{
# This function simulates one game of 'War'.
#
# Arguments:
#   height:   integer, the number of cards per colour
#   width: integer, the number of colours of the card game (usually fours,
#     namely spades, hearts, diamonds, and clubs)
#
# Value: the number of unitary moves, understood as a proxy of the duration
#   of the game.

  ## Create the deck
  game = rep(1:height, width)

  ## Shuffle cards
  game = sample(game)

  ## Give cards to each player
  deck1 = game[1:(length(game)/2)]
  deck2 = game[(length(game)/2+1):length(game)]

  ## Counter
  i = 0

  ## While someone has cards, the game goes on
  while(length(deck1) > 0 & length(deck2) > 0) {
    p = PlayWar1(deck1, deck2, c(), c())
    deck1 = p[[1]]
    deck2 = p[[2]]
    i = i + p[[5]]
  }

  ## Proxy of the duration of the game
  return(i)
}

It now remains to try this out:

set.seed(17)
N = 10000
d1 = sapply(1:N, FUN = function(i) PlayWar(13,4))
range(d1) # min = 38, max = 4102

In this simulation I obtain a maximum of 4102 unitary moves! In the variant played with my son, the deck is made of six ‘colours’, with six cards of each colour.

d2 = sapply(1:N, FUN = function(i) PlayWar(6,6))
range(d2) # min = 18, max = 1220

I can draw the densities of the duration in the 13-4 and 6-6 situations with the following code (that uses package ‘ggplot2’):

data = data.frame(duration = c(d1,d2), type = c(rep("13-4", N), rep("6-6",N)))
ggplot2::ggplot(data, aes(duration, fill = type)) + geom_density(alpha = 0.2)

Density

Next step: fit a probability distribution to these durations!

ADDED LATER: See this post on MathOverflow for a thorough discussion on the same topic.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s