The Game

In this simple guessing game, using a standard deck of 52 playing cards, the goal is to correctly guess, or at least guess as close to the correct card as possible. The player first makes a preliminary guess, then is told whether the actual card is higher or lower than their guess, and makes a final guess. The player’s error is the absolute distance of their final guess’s card value and the actual card value. For instance, if a player’s final guess is 5 and the actual card is 8, the player’s error is 3.

Optimization

The player’s goals are two-fold: guess the correct card, and minimize error. The following simulations will test several strategies to determine the optimal strategy for each goal. It is not necessarily the case that one stragtegy will optimize both goals.

Method 1:

Always Guess the Median

  • For the preliminary guess, the median of the cards in the deck is chosen.
  • For the secondary guess, the median of the remaining subset is chosen.
  • If it is the case that the median of either guess falls between two integers, then the lower or upper value is randomly selected. For example, if the remaining cards are 2,3,4,5, and the median is 3.5, 3 or 4 would be randomly selected.

Method 2a:

Guess the Median, Then Guess the Highest Probability (Random Tiebreaker)

  • For the preliminary guess, the median of the cards in the deck is chosen (with random selection between lower and upper if median is not a whole number).
  • For the secondary guess, the card with the highest probability is chosen.

Note: Actual probabilities are not calculated; since the probability of each remaining value equals the count of each card (variable) divided by total number of remaining cards (constant), computation time can be saved by simply using the raw counts instead of probability.

  • Tiebreaker: If more than one card have the same probability (count), the tie is broken by randomly selecting one of the cards with the highest count.

Method 2b:

Guess the Median, Then Guess the Highest Probability (Closest to Median Tiebreaker)

  • For the preliminary guess, the median of the cards in the deck is chosen (with random selection between lower and upper if median is not a whole number).
  • For the secondary guess, the card with the highest probability is chosen.

  • Tiebreaker: If more than one card have the same probability (count), the tie is broken by selecting the card whose value is closest to the median of the remaining cards. If two cards are equidistant from the median, one is selected at random.

Method 3a:

Guess the Highest Probability (Random Tiebreaker)

  • The preliminary guess and secondary guess follow the same logic: Guess the card with the highest probability, and if there are ties, randomly select from that subset of cards.

Method 3b:

Guess the Highest Probability (Closest to Median Tiebreaker)

  • The preliminary guess and secondary guess follow the same logic: Guess the card with the highest probability, and if there are ties, choose the card closest to the median of the remaining subset.

Method 4:

Random Guessing

  • With complete disregard for probability or any notion of optimization, what happens if the only constraint on guessing is to choose a remaining card at random? This is the control group.

Simulations

Each method was designed into an algorithm that systematically progresses through an entire simulated deck of 52 cards, recording the guess error (distance from actual card) and whether turn resulted in a correct guess on either the first or second try. Each method was tested 5,000 times, and the average error and correct number of guesses were calculated for each iteration.

Note: Code for all simulations can be found in the appendix

Results

Summary Statistics

In the Method description in the table below, Median, Prob, and Random refer to the type of guess and type of tiebreaker when outside and inside parentheses, respectively.

Method Average_Error SD_Error Average_Correct SD_Correct CE_Ratio
Method 1: Median, Median 1.168 0.102 18.110 2.588 15.505
Method 2a: Median, Prob (Random) 1.424 0.196 20.794 2.599 14.603
Method 2b: Median, Prob (Med) 1.253 0.149 20.856 2.663 16.645
Method 3a: Prob, Prob (Random) 1.997 0.408 20.200 2.855 10.115
Method 3b: Prob, Prob (Med) 1.397 0.278 21.352 2.723 15.284
Method 4: Random, Random 2.158 0.327 17.458 3.065 8.090


Method Performance as Game Progresses


Appendix:

Method 1 Code:

# Generate 'error' and 'correct' dataframe template
set.seed(622)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m1_error_results <- data.frame(t(1:52))
names(m1_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m1_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m1_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        med1 <- median(deck)
        guess_1 <- sample(c(floor(med1), ceiling(med1)), 1)
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1] # Remove 'actual' card from deck
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1] # Subset deck into possible choices
            med2 <- median(choices)
            # If the median is not an integer, need to randomly choose between lower and upper value
            guess_2 <- sample(c(floor(med2), ceiling(med2)), 1)
            this_error[j] <- abs(guess_2 - actual) # Get absloute difference in guessing error
            this_correct[j] <- guess_2 == actual   # TRUE if guess_2 is correct, FALSE otherwise
            deck <- deck[-1]                       # Remove top card from deck
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            med2 <- median(choices)
            guess_2 <- sample(c(floor(med2), ceiling(med2)), 1)
            this_error[j] <- abs(guess_2 - actual)
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            
        }
    }
    m1_error_results <- rbind(m1_error_results, this_error)
    m1_correct_results <- rbind(m1_correct_results, this_correct)
}

m1_error_results <- m1_error_results[-1, ]
m1_error_results$mean_error <- rowMeans(m1_error_results)

m1_correct_results <- m1_correct_results[-1, ]
m1_correct_results$num_correct <- rowSums(m1_correct_results)

if(!file.exists("m1_error_results")) { saveRDS(m1_error_results, "m1_error_results.rds") }
if(!file.exists("m1_correct_results")) { saveRDS(m1_correct_results, "m1_correct_results.rds") }

Method 2a Code:

# Generate 'error' and 'correct' dataframe template
set.seed(892)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m2a_error_results <- data.frame(t(1:52))
names(m2a_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m2a_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m2a_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        med1 <- median(deck)
        # Choose the median, or randomly select between lower and upper bound of median
        guess_1 <- sample(c(floor(med1), ceiling(med1)), 1)
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1] # Remove 'actual' card from deck
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1] # Subset deck into possible choices
            df <- as.data.frame(table(choices)) # Create frequency table of possible choices
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))] # Find which cards have the greatest frequency
            # If there is only one option, choose it for guess_2, otherwise randomly select from options
            guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            this_error[j] <- abs(guess_2 - actual) # Get absloute difference in guessing error
            this_correct[j] <- guess_2 == actual   # TRUE if guess_2 is correct, FALSE otherwise
            deck <- deck[-1]
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            df <- as.data.frame(table(choices))
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))]
            guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            this_error[j] <- abs(guess_2 - actual)
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            
        }
    }
    m2a_error_results <- rbind(m2a_error_results, this_error)
    m2a_correct_results <- rbind(m2a_correct_results, this_correct)
}

m2a_error_results <- m2a_error_results[-1, ]
m2a_error_results$mean_error <- rowMeans(m2a_error_results)

m2a_correct_results <- m2a_correct_results[-1, ]
m2a_correct_results$num_correct <- rowSums(m2a_correct_results)

if(!file.exists("m2a_error_results")) { saveRDS(m2a_error_results, "m2a_error_results.rds") }
if(!file.exists("m2a_correct_results")) { saveRDS(m2a_correct_results, "m2a_correct_results.rds") }

Method 2b Code:

# Generate 'error' and 'correct' dataframe template
set.seed(845)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m2b_error_results <- data.frame(t(1:52))
names(m2b_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m2b_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m2b_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        med1 <- median(deck)
        # Choose the median, or randomly select between lower and upper bound of median
        guess_1 <- sample(c(floor(med1), ceiling(med1)), 1) 
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1] # Remove 'actual' card from deck
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1] # Subset deck into possible choices
            df <- as.data.frame(table(choices)) # Create frequency table of possible choices
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))] # Find which cards have the greatest frequency
            
            if ( length(options) == 1 ) { # If there is only one option, choose it for guess_2
                guess_2 <- options
            } else {
                med2 <- median(choices) 
                # Find absolute distance of possible choices from the median of possible choices
                df$dist <- abs(df$choices-med2)
                # Subset choices by cards that have the greatest frequency
                df <- df[which(df$Freq==max(df$Freq)),]
                # Subset by cards that have minimum distance from median
                options <- df$choices[which(df$dist==min(df$dist))]
                # If only one option, choose it. Otherwise randomly choose between two options.
                guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            }
            
            this_error[j] <- abs(guess_2 - actual) # Get absloute difference in guessing error
            this_correct[j] <- guess_2 == actual   # TRUE if guess_2 is correct, FALSE otherwise
            deck <- deck[-1]
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            df <- as.data.frame(table(choices))
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))]
            
            if ( length(options) == 1 ) {
                guess_2 <- options
            } else {
                med2 <- median(choices)
                df$dist <- abs(df$choices-med2)
                df <- df[which(df$Freq==max(df$Freq)),]
                options <- df$choices[which(df$dist==min(df$dist))]
                guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            }
            
            this_error[j] <- abs(guess_2 - actual) # Get absloute difference in guessing error
            this_correct[j] <- guess_2 == actual   # TRUE if guess_2 is correct, FALSE otherwise
            deck <- deck[-1]
            
        }
    }
    m2b_error_results <- rbind(m2b_error_results, this_error)
    m2b_correct_results <- rbind(m2b_correct_results, this_correct)
}

m2b_error_results <- m2b_error_results[-1, ]
m2b_error_results$mean_error <- rowMeans(m2b_error_results)

m2b_correct_results <- m2b_correct_results[-1, ]
m2b_correct_results$num_correct <- rowSums(m2b_correct_results)

if(!file.exists("m2b_error_results")) { saveRDS(m2b_error_results, "m2b_error_results.rds") }
if(!file.exists("m2b_correct_results")) { saveRDS(m2b_correct_results, "m2b_correct_results.rds") }

Method 3a Code:

# Generate 'error' and 'correct' dataframe template
set.seed(735)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m3a_error_results <- data.frame(t(1:52))
names(m3a_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m3a_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m3a_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    #####
    # guess_1 and guess_2 follow the same logic and structure as guess_2 in Method 2a
    #####
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        df1 <- as.data.frame(table(deck))
        df1$deck <- as.numeric(levels(df1$deck))
        options <- df1$deck[which(df1$Freq==max(df1$Freq))]
        guess_1 <- ifelse(length(options)==1, options, sample(options, 1))
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1]
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1] 
            df <- as.data.frame(table(choices)) 
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))]
            guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            this_error[j] <- abs(guess_2 - actual) 
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            df <- as.data.frame(table(choices))
            df$choices <- as.numeric(levels(df$choices))
            options <- df$choices[which(df$Freq==max(df$Freq))]
            guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            this_error[j] <- abs(guess_2 - actual)
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            
        }
    }
    m3a_error_results <- rbind(m3a_error_results, this_error)
    m3a_correct_results <- rbind(m3a_correct_results, this_correct)
}

m3a_error_results <- m3a_error_results[-1, ]
m3a_error_results$mean_error <- rowMeans(m3a_error_results)

m3a_correct_results <- m3a_correct_results[-1, ]
m3a_correct_results$num_correct <- rowSums(m3a_correct_results)

if(!file.exists("m3a_error_results")) { saveRDS(m3a_error_results, "m3a_error_results.rds") }
if(!file.exists("m3a_correct_results")) { saveRDS(m3a_correct_results, "m3a_correct_results.rds") }

Method 3b Code:

# Generate 'error' and 'correct' dataframe template
set.seed(470)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m3b_error_results <- data.frame(t(1:52))
names(m3b_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m3b_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m3b_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    #####
    # guess_1 and guess_2 follow the same logic and structure as guess_2 in Method 2b
    #####
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        df1 <- as.data.frame(table(deck))
        df1$deck <- as.numeric(levels(df1$deck))
        med1 <- median(deck)
        df1$dist <- abs(df1$deck-med1)
        df1 <- df1[which(df1$Freq==max(df1$Freq)),]
        options <- df1$deck[which(df1$dist==min(df1$dist))]
        guess_1 <- ifelse(length(options)==1, options, sample(options, 1))
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1] 
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1]
            df2 <- as.data.frame(table(choices))
            df2$choices <- as.numeric(levels(df2$choices))
            options <- df2$choices[which(df2$Freq==max(df2$Freq))]
            
            if ( length(options) == 1 ) {
                guess_2 <- options
            } else {
                med2 <- median(choices)
                df2$dist <- abs(df2$choices-med2)
                df2 <- df2[which(df2$Freq==max(df2$Freq)),]
                options <- df2$choices[which(df2$dist==min(df2$dist))]
                guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            }
            
            this_error[j] <- abs(guess_2 - actual) 
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            df2 <- as.data.frame(table(choices))
            df2$choices <- as.numeric(levels(df2$choices))
            options <- df2$choices[which(df2$Freq==max(df2$Freq))]
            
            if ( length(options) == 1 ) {
                guess_2 <- options
            } else {
                med2 <- median(choices)
                df2$dist <- abs(df2$choices-med2)
                df2 <- df2[which(df2$Freq==max(df2$Freq)),]
                options <- df2$choices[which(df2$dist==min(df2$dist))]
                guess_2 <- ifelse(length(options)==1, options, sample(options, 1))
            }
            
            this_error[j] <- abs(guess_2 - actual) 
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            
        }
    }
    m3b_error_results <- rbind(m3b_error_results, this_error)
    m3b_correct_results <- rbind(m3b_correct_results, this_correct)
}

m3b_error_results <- m3b_error_results[-1, ]
m3b_error_results$mean_error <- rowMeans(m3b_error_results)

m3b_correct_results <- m3b_correct_results[-1, ]
m3b_correct_results$num_correct <- rowSums(m3b_correct_results)

if(!file.exists("m3b_error_results")) { saveRDS(m3b_error_results, "m3b_error_results.rds") }
if(!file.exists("m3b_correct_results")) { saveRDS(m3b_correct_results, "m3b_correct_results.rds") }

Method 4 Code:

# Generate 'error' and 'correct' dataframe template
set.seed(624)
rn <- rep("error", times=52)
rn <- paste0(rn, 1:52)
m4_error_results <- data.frame(t(1:52))
names(m4_error_results) <- rn

rn <- rep("correct", times=52)
rn <- paste0(rn, 1:52)
m4_correct_results <- data.frame(t(rep(FALSE, times=52)))
names(m4_correct_results) <- rn
rm(rn)

# Begin simulation
for ( i in 1:5000 ) { # Simulation iteration (1,000 trials)
    deck <- rep(1:13, each=4)   # create card deck
    deck <- sample(deck)        # randomly shuffle the deck
    this_error <- integer()
    this_correct <- logical()
    
    for ( j in 1:52 ) { # Turn iteration for each game
        actual <- deck[1]
        # Random guess from remaining cards in deck
        guess_1 <- ifelse(length(deck)==1, deck, sample(deck, 1)) 
        
        if ( guess_1 == actual ) { # If first guess is correct
            this_error[j] <- 0
            this_correct[j] <- TRUE
            deck <- deck[-1]
            next
            
        } else if ( actual > guess_1 ) { # If the actual card is greater than guess_1
            choices <- deck[deck > guess_1]
            # Random guess from remaining choices
            guess_2 <- ifelse(length(choices)==1, choices, sample(choices, 1))
            this_error[j] <- abs(guess_2 - actual) 
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            next
            
        } else if ( actual < guess_1 ) { # If the actual card is less than guess_1, same logic applies
            choices <- deck[deck < guess_1]
            guess_2 <- ifelse(length(choices)==1, choices, sample(choices, 1))
            this_error[j] <- abs(guess_2 - actual)
            this_correct[j] <- guess_2 == actual
            deck <- deck[-1]
            
        }
    }
    m4_error_results <- rbind(m4_error_results, this_error)
    m4_correct_results <- rbind(m4_correct_results, this_correct)
}

m4_error_results <- m4_error_results[-1, ]
m4_error_results$mean_error <- rowMeans(m4_error_results)

m4_correct_results <- m4_correct_results[-1, ]
m4_correct_results$num_correct <- rowSums(m4_correct_results)

if(!file.exists("m4_error_results")) { saveRDS(m4_error_results, "m4_error_results.rds") }
if(!file.exists("m4_correct_results")) { saveRDS(m4_correct_results, "m4_correct_results.rds") }

Results Tables and Figures:

methods <- c("Method 1", "Method 2a", "Method 2b", "Method 3a", "Method 3b", "Method 4")

er <- list(m1_error_results, m2a_error_results, m2b_error_results, m3a_error_results,
           m3b_error_results, m4_error_results)

cr <- list(m1_correct_results, m2a_correct_results, m2b_correct_results, m3a_correct_results,
           m3b_correct_results, m4_correct_results)

results <- data.frame(Method = paste0(methods, ": ", 
                                      c("Median, Median", "Median, Prob (Random)", 
                                        "Median, Prob (Med)", "Prob, Prob (Random)", 
                                        "Prob, Prob (Med)", "Random, Random")),
                      Average_Error = sapply(er, function(x) round(mean(x$mean_error), 3)),
                      SD_Error = sapply(er, function(x) round(sd(x$mean_error), 3)),
                      Average_Correct = sapply(cr, function(x) round(mean(x$num_correct), 3)),
                      SD_Correct = sapply(cr, function(x) round(sd(x$num_correct), 3))
                      )
results$CE_Ratio <- round(results$Average_Correct / results$Average_Error, 3)
saveRDS(results, "results_5000.rds")
kable(results)
par(mfrow=c(1,2), mar=c(4,6,2,1), las=1)

boxplot(er[[6]]$mean_error, er[[5]]$mean_error, er[[4]]$mean_error,
        er[[3]]$mean_error, er[[2]]$mean_error, er[[1]]$mean_error,
        names = rev(methods), horizontal = TRUE, 
        main="Average Error", sub="(Figure 1)")
mtext("(Figure 1a)", 1, 3)

boxplot(cr[[6]]$num_correct, cr[[5]]$num_correct, cr[[4]]$num_correct,
        cr[[3]]$num_correct, cr[[2]]$num_correct, cr[[1]]$num_correct,
        names = rev(methods), horizontal = TRUE, 
        main="Average Correct Guesses", sub="(Figure 2)")
mtext("(Figure 1b)", 1, 3)
par(mfrow=c(1,2), mar=c(6,4,2,1), cex.lab=1.2)

# Plot of average change in error by turn
plot(1:52, (1:52)/(52/3), ylim = c(0,3), type = 'n', frame.plot = FALSE,
     main = "Change in Average Error as Game Progresses",
     ylab = "Average Error", xlab = "Number of Turns")
mtext("(Figure 2a)", 1, 5)
pchs <- c(1, 0, 15, 2, 17, 19)

for( i in 1:6 ) {
    z <- colMeans(er[[i]][,1:52])
    points(z, pch=pchs[i], cex=0.8)
    lines(z)
}

legend(2, 1, methods, lty = rep(1, 6), pch = pchs, bty = "n")

# Plot of average number of correct guesses by turn
plot(1:52, (1:52)/52, ylim = c(0.2,1), type = 'n', frame.plot = FALSE,
     main = "Varying Rates of Correct Guess Percentage by Turn",
     xlab = "Number of Turns", ylab = "Percent Correct")
mtext("(Figure 2b)", 1, 5)
pchs <- c(1, 0, 15, 2, 17, 19)
for( i in 1:6 ) {
    z <- colMeans(cr[[i]][,1:52])
    points(z, pch=pchs[i], cex=0.8)
}
abline(h=seq(0.2, 1, 0.1), lty=2, col="gray40")
legend(2, 0.95, methods, pch = pchs, box.col = "white", bg="white")
par(mfrow=c(1,1), mar=c(6,4,4,2))
plot(1:51, (1:51)/(51/1.3), type = 'n', frame.plot = FALSE,
     main = "Difference in Correct/Error Ratio by Turn",
     xlab = "Number of Turns", ylab = "Correct/Error Ratio")
mtext("(Figure 2c)", 1, 5, cex = 0.8)
for(i in 1:6){
    cc <- colMeans(cr[[i]])
    ee <- colMeans(er[[i]])
    ccrr <- cc/ee
    points(ccrr, pch=pchs[i], cex=0.7)
}
legend(2, 1.1, c("Method 1", "Method 2a", "Method 2b", "Method 3a", "Method 3b", "Method 4"),
       pch = pchs, bty = "n")