Minesweeper, Zelda Style

Views and opinions expressed are solely my own.

The analogue to the popular game Minesweeper in Zelda’s Skyward Sword HD is known as Thrill Digger. I wrote R code to help assist me in playing Thrill Digger.

Background

Solely focusing on the Beginner mode, Thrill Digger is like Minesweeper except:

  • The main character, Link, has to pay 30 to play the game.
  • There are four bombs laid out in four spots of a 4-by-5 grid.
  • When a spot is chosen, if it is a bomb, the game ends. If it is not a bomb:
    • if the spot contains a green rupee (worth 1), there are no bombs adjacent to that spot.
    • if the spot contains a blue rupee (worth 5), there are 1 or 2 bombs adjacent to that spot.
    • if the spot contains a red rupee (worth 20), there are 3 or 4 bombs adjacent to that spot.

The goals for this project were as follows:

  • Have a program give one the ability to input each step of Thrill Digger as they choose spots (namely, the x-coordinate, the y-coordinate, and the number of rupees at each spot).
  • Compute the probability that each square of the grid does not have a bomb.
  • Give suggestions for what squares should be chosen next.

Planning The Code

The basic plan I had for the code was:

  • Generate a list of all possible 4-by-5 grids with four bombs.
  • Insert the rupee amounts in each grid based on the bomb placements.
  • As input is provided as someone plays Thrill Digger, filter the original list for all grids that contain the values that have values matching the inputs provided to that point.
  • Use the filtered list to compute the probabilities that each cell does not have a bomb.

The Code

Below is the code I used for this problem. I appreciate the assistance I had from Stackoverflow as well as a Discord user who wishes to remain anonymous.

########################## Set variables
row_n <- 4
col_n <- 5
bomb_count <- 4
bomb_cost <- -30
rupee_vector <- c(1, 5, 20, 100, 200)
rupoor_count <- 0 # 0 for beginner, 4 for advanced, 8 for intermediate

########################## Define functions
insert_indices <- function(nrow, ncol, indices, cost) {
  x <- matrix(0, nrow, ncol)
  x[indices] <- bomb_cost
  return(x)
}

generate_matrices <- function(nrow, ncol, bomb_count, rupoor_count, bomb_cost) {
  out <- combn(seq_len(nrow * ncol), 
               m = bomb_count + rupoor_count, 
               FUN = function(x){ insert_indices(nrow, ncol, x) },
               simplify = FALSE)
  return(out)
}

# Input rupee amounts
insert_rupees <- function(x, bomb_cost) {
  # For each cell
  for (i in 1:nrow(x)) {
    for (j in 1:ncol(x)) {
      # without a bomb or rupoor
      if (x[i,j] != bomb_cost & x[i,j] != -10) {
        # gather submatrix around the cell
        submatrix <- x[ifelse(i-1 >= 1,
                              i-1,
                              i):ifelse(i+1 <= nrow(x),
                                        i+1,
                                        i),
                       ifelse(j-1 >= 1,
                              j-1,
                              j):ifelse(j+1 <= ncol(x),
                                        j+1,
                                        j)]
        # convert to vector
        submatrix <- c(submatrix)
        
        # Compute bomb and rupoor counts
        bad_counts <- sum(submatrix %in% c(bomb_cost, -10))
        
        # If bad_counts is 0, set reward to 1. 
        # If 1 or 2, then 5.
        # If 3 or 4, then 20. 
        # If 5 or 6, then 100.
        # If 7 or 8, then 200.
        x[i,j] <- ifelse(
          bad_counts == 0, 1, 
          ifelse(bad_counts %in% 1:2, 5, 
                 ifelse(bad_counts %in% 3:4, 20, 
                        ifelse(bad_counts %in% 5:6, 100,
                               ifelse(bad_counts %in% 7:8, 200, NA))))
        )
        rm(bad_counts, submatrix)
      }
      
    }
  }
  rm(i, j)
  return(x)
}

p_bomb <- function(i, j, rupee, lst, rupee_vals = rupee_vector) {
  # Subset of matrices corresponding to filter
  L_subset <- Filter(function(x) x[i,j] == rupee, lst)
  
  # Dimension of the matrices
  d <- dim(lst[[1]])
  
  # List of i, j pairwise
  df_ij <- expand.grid(1:d[1], 1:d[2])
  list_ij <- split(as.matrix(df_ij), 1:prod(d))
  
  # Iterate over each i, j
  p <- Map(function(ij) {
    i = ij[1]
    j = ij[2]
    mean(unlist(Map(function(x) ifelse(x[i,j] %in% rupee_vals, 1, 0), L_subset)))
  }, ij = list_ij)
  
  out <- matrix(0, nrow = d[1], ncol = d[2])
  out[as.matrix(df_ij)] <- unlist(p)
  return(list(probs = out, new_list = L_subset))
}

l <- generate_matrices(row_n, col_n, bomb_count, rupoor_count, bomb_cost)
l <- lapply(X = l, FUN = insert_rupees, bomb_cost)
rm(generate_matrices, insert_rupees, insert_indices)

########################## Begin game simulation
i <- 0
j <- 0
rupee <- 0
temp_list <- l

# History of positions
positions_df <- data.frame(
  i = integer(),
  j = integer(),
  rupee = numeric()
)

# Begin game
while(rupee != bomb_cost) {
  i <- as.integer(readline("Indicate the row of the cell (top = 1): "))
  j <- as.integer(readline("Indicate the column of the cell (left = 1): "))
  rupee <- as.numeric(
    readline(paste0("Indicate the rupee value of the cell (", 
                    paste0(rupee_vector, collapse = ", "),
                    "); use -30 for bombs or rupoors): "))
  )
  positions_df[nrow(positions_df) + 1,] <- list(i, j, rupee)
  if (i %in% 1:row_n & j %in% 1:col_n & rupee %in% c(rupee_vector, bomb_cost)) {
    out <- p_bomb(i, j, rupee, temp_list)
    temp_list <- out$new_list
    print(out$probs)
    # Matrix for finding the most likely spot without a bomb
    max_matrix <- out$probs 
    
    # NA out all spots we have already chosen
    for (r in 1:nrow(positions_df)) {
      max_matrix[positions_df$i[r],positions_df$j[r]] <- NA
    }
    print(which(out$probs == max(max_matrix, na.rm = TRUE), arr.ind = TRUE))
  } else {
    stop("Invalid input.")
  }
}

An Actual Simulation

Below is an excerpt of one test I performed on this code. I ended up losing at the last step from choosing [1, 1] which contained a bomb. There was not an optimal choice for the last step, as each remaining cell was safe with probability 0.25.

Indicate the row of the cell (top = 1): 4
Indicate the column of the cell (left = 1): 3
Indicate the rupee value of the cell (1, 5, 20, 100, 200); use -30 for bombs or rupoors): 1
          [,1]      [,2]      [,3]      [,4]      [,5]
[1,] 0.7142857 0.7142857 0.7142857 0.7142857 0.7142857
[2,] 0.7142857 0.7142857 0.7142857 0.7142857 0.7142857
[3,] 0.7142857 1.0000000 1.0000000 1.0000000 0.7142857
[4,] 0.7142857 1.0000000 1.0000000 1.0000000 0.7142857
     row col
[1,]   3   2
[2,]   4   2
[3,]   3   3
[4,]   4   3
[5,]   3   4
[6,]   4   4
...
Indicate the row of the cell (top = 1): 4
Indicate the column of the cell (left = 1): 4
Indicate the rupee value of the cell (1, 5, 20, 100, 200); use -30 for bombs or rupoors): 5
          [,1]      [,2]      [,3]      [,4]      [,5]
[1,] 0.3636364 0.3181818 0.6818182 0.5909091 0.6363636
[2,] 1.0000000 1.0000000 1.0000000 1.0000000 0.6818182
[3,] 1.0000000 1.0000000 1.0000000 1.0000000 0.5909091
[4,] 1.0000000 1.0000000 1.0000000 1.0000000 0.1363636
     row col
[1,]   1   3
[2,]   2   5
Indicate the row of the cell (top = 1): 1
Indicate the column of the cell (left = 1): 3
Indicate the rupee value of the cell (1, 5, 20, 100, 200); use -30 for bombs or rupoors): 5
          [,1] [,2] [,3]      [,4]      [,5]
[1,] 0.3333333  0.2    1 0.4666667 0.6000000
[2,] 1.0000000  1.0    1 1.0000000 0.6666667
[3,] 1.0000000  1.0    1 1.0000000 0.5333333
[4,] 1.0000000  1.0    1 1.0000000 0.2000000
     row col
[1,]   2   5
Indicate the row of the cell (top = 1): 2
Indicate the column of the cell (left = 1): 5
Indicate the rupee value of the cell (1, 5, 20, 100, 200); use -30 for bombs or rupoors): 5
     [,1] [,2] [,3] [,4] [,5]
[1,]  0.3  0.2    1  0.4  0.5
[2,]  1.0  1.0    1  1.0  1.0
[3,]  1.0  1.0    1  1.0  0.4
[4,]  1.0  1.0    1  1.0  0.2
     row col
[1,]   1   5
Indicate the row of the cell (top = 1): 1
Indicate the column of the cell (left = 1): 5
Indicate the rupee value of the cell (1, 5, 20, 100, 200); use -30 for bombs or rupoors): 5
     [,1] [,2] [,3] [,4] [,5]
[1,] 0.25 0.25    1    0 1.00
[2,] 1.00 1.00    1    1 1.00
[3,] 1.00 1.00    1    1 0.25
[4,] 1.00 1.00    1    1 0.25
     row col
[1,]   1   1
[2,]   1   2
[3,]   3   5
[4,]   4   5
Indicate the row of the cell (top = 1): 

Conclusions

This was a fun simulation to do, another example of complexities in games. It may be worth pursuing reinforcement learning for this problem in the future, but I’m not sure, since the total number of possibilities is relatively small (i.e., \(\binom{20}{4} = 4845\) possibilities).

It would be worth figuring out how to extend this simulation for the intermediate and advanced cases in Thrill Digger, but I haven’t gotten to them.

Yeng Miller-Chang
Yeng Miller-Chang

I am a Senior Data Scientist - Global Knowledge Solutions with General Mills, Inc. Views and opinions expressed are my own.

Related