Can You Solve Dead Drop in the First Round?

Dead Drop is a fantastic card game by Jason Kotarski with a relatively simple premise. There is a deck of 13 cards. One card, known as the “drop,” is placed face down in front of the players and one card per player is placed face up on the table and referred to collectively as “the stash.” The rest of the cards are distributed between the players, who are then tasked with keeping their hands a secret. The goal: Be the first player to correctly guess the number on the “drop” card. If your guess is wrong, you’re out of the game.

The game deck consists of four 0s, three 1s, two 2s, two 3s, one 4, and one 5. Or:

0 1 2 3 4 5
Number of Cards 4 3 2 2 1 1

In a three-person game, for example, each player receives three cards, three cards are placed in the stash, and the drop is placed face down. That means at the very beginning of the game, you, player one, know the “identities” of six cards in the deck. As the game progresses, you obtain intel on the cards the other players hold by taking one of three actions during your turn:

  • Share Info: Trade one card from your hand with one card from any other player.
  • Swap the Stash: Trade one card from your hand with one card in the stash.
  • Sell Secrets: Reveal two cards in your hand to another player, who must respond whether or not he/she is carrying a card equal to the sum of your two cars. Example, I show a 1 and 2 to player two. If player two has a 3, he/she is forced to hand that card to me in exchange for one of the cards I revealed.

The game is purely about deduction based on information gathering. What I wanted to explore is by knowing almost half the cards in the deck at the beginning of the first round, could you accurately guess the drop at the beginning of the game?

To do that, I wrote a script in R that simulates a three-person game of Dead Drop (skip if you are more interested in the results). It consists of two functions. One, simulate.game(), creates a deck and distributes cards to each player, the stash, and the drop. The second function, remove.values(), removes the cards from the deck after each hand is distributed. Then I use set.seed() to make sure my outcomes are the same each time I run the script. Finally, I run the simulation and store the results in the object game1.

# function to remove draws from the deck
remove.values <- function(x,y) {
  indices <- c()
    for(i in 1:length(x)) {
      index <- grep(x[i],y)
      if(index[1] %in% indices) {
        if(index[2] %in% indices) {
          if(index[3] %in% indices){
            if(index[4] %in% indices){
              indices <- c(index[5],indices)
            } else { indices <- c(index[4],indices) }
          } else { indices <- c(index[3],indices) }
        } else { indices <- c(index[2],indices) }
      } else { indices <- c(index[1],indices) }
    } 
  print(indices)
}
# function to simulate the game
simulate.game <- function() {

  deck <- c(rep(0,4),rep(1,3),rep(2,2),rep(3,2),4,5)
  # player1's hand
  player1 <- sample(deck,3,replace = F)
  deck <- deck[-remove.values(player1,deck)]
  # the stash
  stash <- sample(deck,3,replace = F)
  deck <- deck[-remove.values(stash,deck)]
  # player2's hand
  player2 <- sample(deck,3,replace = F)
  deck <- deck[-remove.values(player2,deck)]
  # player 3's hand
  player3 <- sample(deck,3,replace = F)
  deck <- deck[-remove.values(player3,deck)]
  # function outcome is a list of all results
  list(player1 = player1, player2 = player2, player3 = player3,
       stash = stash, drop = deck)
}
set.seed(13)
game1 <- simulate.game()

Let’s walk through the game simulated above. You are dealt 3, 0, 1. The stash is 0, 4, 5. Since 4 and 5 are on display, you can immediately rule those out as the drop. Seems like a 0, 1 or 2 would be pretty likely and a 3 would be less likely, right?

There are two ways to find out: The complicated way and the easy way.

The Complicated Way: Bayes’ Theorem

Bayes’ Theorem is a famous formula in probability and statistics that allows you to calculate the probability of an event based on certain conditions, also known as conditional probability. The formula relies on three variables.

\[P(A|B)=\frac{P(A)P(B|A)}{P(B)}\]

  • \(P(A)\): The probability of A occurring.
  • \(P(B|A)\): The probability of BÂ occurring if AÂ is true
  • \(P(B)\): The probability of B occurring.

Putting those into the formula gives us the probability of A occurring if B has occurred. Already confused? How about an example: What is the probability of the drop being a zero if we are dealt two zeroes? This is displayed as \(P(A|B)\) where A is the drop being zero and B is being dealt two zeros.

The formula above tells us we need to know the \(P(A)\) or probability of the drop being zero. Ignoring any other information, the probability would be \(\frac{4}{13}\). There are four 0s in the deck and 13 cards total.

Next we need to know the probability of being dealt two zeroes if the drop were zero. This is slightly complicated if you’re not familiar with combinations. In this scenario, the drop is accounted for, which leaves us 12 cards in the deck. We only get to see 6 of these cards. We use this formula the figure all the possible combinations.

\[{12 \choose 6} = \frac{12!}{6!(12-6)!}\]

There are \({12 \choose 6}\) or 924 ways to choose 6 cards out of 12 and \({3 \choose 2}\) or 3 ways to choose two zeroes from the remaining number in the deck. That’s \({3 \choose 2}/{12 \choose 6}\). So \(P(B|A)\) is 0.0032468.

Finally, we need the probability of being dealt two zeroes, \(P(B)\). There are \({13 \choose 6}\) or 1716 ways to choose 6 cards out of 13 and \({4 \choose 2}\) or 6 ways to choose two zeroes. So \(P(B)\) is \({4 \choose 2}/{13 \choose 6}\), or 0.0034965.

Let’s calculate the probability of the drop being a zero based on our information.

pA <- 4/13
pBA <- choose(3,2)/choose(12,6)
pB <- choose(4,2)/choose(13,6)

paste(round(((pA*pBA)/pB)*100,2),"%",sep="")
## [1] "28.57%"

The method above is a nice way of understanding probability and an introduction to Bayes’ Theorem, however, it is totally unnecessary. Because:

The Easy Way

You can figure out \(P(A|B)\) without Bayes’ Theorem. At the beginning of the game, the identities of six cards are available to you. You’re merely looking for the probability that the drop is one of the remaining zeroes in the seven cards you don’t know anything about. That’s \(\frac{2}{7}\) or 28.57%.

Why did I give such a detailed description of the complicated method above? First, it’s always good to know your options. Second, the wonderful thing about mathematics (and programming, really) is there are always multiple ways to figure out a problem. Bayes’ Theorem really comes in handy in many situations, so you should become familiar with it if you already are not.

Now that we’ve established the probability of the drop being a zero, you might also have noticed (using the easy method) that the probabilities of the drop being 0, 1, or 2 are all equally likely. How do I know that?

In the R script below, I wrote a function to analyze the game outcomes and spit out the probabilities for each number. I then stored the results in a data frame.

calculate.probs <- function(l,x) {
  # information available to the player
  available <- c(l$player1, l$stash)
  # the number we want to run prob on 
  number <- table(grepl(x,available))[2]
  if(is.na(number)) { number <- 0 }
  # how many are normally in the deck
  normal <- table(full.deck)[x+1]
  pA <- normal/13
  pBA <- choose(normal-1,number)/choose(12,6)
  pB <- choose(normal,number)/choose(13,6)
  bayes.theorem(pA,pBA,pB)
}

bayes.theorem <- function(x,y,z) { round(((x*y)/z)*100,2) }

game.results <- data.frame(prob0=calculate.probs(game1,0),
                           prob1=calculate.probs(game1,1),
                           prob2=calculate.probs(game1,2),
                           prob3=calculate.probs(game1,3),
                           prob4=calculate.probs(game1,4),
                           prob5=calculate.probs(game1,5),
                           deaddrop=game1$drop)

It looks a little like this.

Probabilities of Cards Being the Drop
0 1 2 3 4 5
Probability 28.57% 28.57% 28.57% 14.29% 0 0

SPOILER ALERT: The drop in this game is 1. So while probability helped us narrow down the potential candidates, it didn’t point a big, flashing arrow at the card we were looking for. What if I simulated 99 more games? Perhaps we’ll get an idea of how accurate this method is over a larger sample size.

# set.seed for reproducing my simulations
set.seed(50)
# this runs the simulate game function 100 times and stores the results in an object
simulations <- replicate(99,simulate.game())

# now I'm going to loop through the results and bind them to my data frame
x <- 1
while(x <= 99){
  game.results <- rbind(game.results,
                        c(calculate.probs(simulations[,x],0),
                          calculate.probs(simulations[,x],1),
                          calculate.probs(simulations[,x],2),
                          calculate.probs(simulations[,x],3),
                          calculate.probs(simulations[,x],4),
                          calculate.probs(simulations[,x],5),
                          simulations[,x]$drop))
  x <- x + 1
}

Now I want to answer three questions with these simulated games.

  • What is the probability of success using the methods described above to approximate the drop?
  • What is the probability of success using the methods above to successfully guess the drop?
  • What is the probability of success using the method of picking a random number to successfully guess the drop?

Sparing you the gory programming details (although you can find how I did it in my GitHub repository), the answers are surprising.

What is the probability of success using the methods described above to approximate the drop?

Out of 100 games, using the methods above only correctly pointed towards potential candidates for the drop 57 times. That means only 57% of the time you’ll be able to narrow down potential candidates for the drop card in the first round.

What is the probability of success using the methods above to successfully guess the drop?

This means the card with the highest probability of being the drop is actually the drop. Out of 100 games, using the methods above only successfully guessed the drop 20 times. That means only 20% of the time you’ll be able to guess the drop card in the first round.

Finally, what is the probability of success using the method of picking a random number to successfully guess the drop?

This is the shocker. Out of 100 games, using the methods above only successfully guessed the drop 21 times. That means in these simulated games successfully guessing the drop in the first round by using the methods I’ve detailed in this post and by picking a random number were almost equally likely!

Wait … Don’t Walk Away!

That doesn’t mean you should abandon the methods I’ve described and start picking random numbers. The whole point of the game is learning more about the cards on the table and updating your assumptions. Most people, of course, do this in their heads and don’t write long blog posts with scripts detailing simulated games.

Say in the first round of the very first game I simulated, player two trades a card with you. You now know of an additional card, a 2. If you recall, 0, 1, and 2 were equally likely to be the drop. Not anymore!

Updated Probabilities of Cards Being the Drop
0 1 2 3 4 5
Probability 33% 33% 17% 17% 0 0

If you recall, the drop is a 1. If an additional 0 is revealed, you can make a solid guess about the drop. (Omnipotent game master here happens to know that player three is holding both of the remaining zeroes, but he/she may be hesitant to reveal that in order to keep everyone else guessing.)

So now that you are armed with the knowledge of how Dead Drop works, go forth and have fun. What’s fascinating about this game is that most of us run these calculations in our heads while we’re playing and we don’t even think about it. Of course, if you want to bring a pen and paper or calculator to the table, I won’t judge you.

NOTE: While working on a follow-up post that tests some assumptions here, I rewrote the remove.values() function to better handle indices and the simulate.game() function to take number of players as an argument. The remove.values() function has been updated here.