Minesweeper

Dason

Ambassador to the humans
#1
Hey. I got bored so I wrote a minesweeper clone in R. If you run the following script then you can play the game by running
Code:
play.mines.graphic()
play.mines.graphic() is the last line of the script so if you don't want to play right away don't copy the entire thing. If you're truly on a console only system you can even enjoy the fun by running play.mines() - it's not as nice and it's very unpolished but you can still enjoy it.

I considered adding a license but it's really not that complicated and I don't really think anybody would use this in any commercial software. If you really want to copy it and take credit for it I guess you could but that'd be pretty low. Feel free to post it somewhere else if you want but I would appreciate if you give me credit and link to this post.

I'm open to suggestions and feel free to modify it to your liking. Due to limitations of interacting with plots in R you can't place flags. If you click on a mine the game ends. To win you need to clear all locations that are not mines.

I have plans on shading the background of the covered locations to some sort of a gray so they stick out some more. I also wouldn't mind adding some sort of popup after you finish a game asking if you want to replay or stop. I'd also like to add some sort of timer and maybe a high scores list (even if it is just a temporary high scores list during the session) which will be easy to do but I just thought of it and things don't get added instantly. Another thing to add would be difficulty levels. You can essentially mimic those right now by choosing the number of rows, columns, and # of mines correctly but it might be easier for the user to just have a difficulty parameter that accepted something like "easy", "medium", or "hard".

Let me know what you think.

The script:
Code:
## Create a map with mines placed in it
generate.map <- function(n = 8, m = 8, n.mines = 10){
  map <- matrix(F, nrow = n, ncol = m)
  mines <- sample(1:(n*m), n.mines)
  mine.col <- (mines-1) %/% m + 1
  mine.row <- mines %% n + 1
  for(i in 1:n.mines){
    map[mine.col[i], mine.row[i]] <- TRUE
  }
  return(map)
}

## Let Inf represent that location is a mine
get.nums <- function(row, col, map){
  n.row <- dim(map)[1]
  n.col <- dim(map)[2]

  ## Inf represents that location is a mine
  if(map[row, col]){
    return(Inf)
  }
  
  ans <- data.frame(row = c(rep(row-1, 3), rep(row, 2), rep(row+1, 3)),
                    col = c(col-1, col, col+1, col-1, col+1, col-1, col, col+1),
                    check = rep(TRUE, 8),
                    mine = rep(FALSE, 8))
  ## Find the spots outside of the map
  idx <- which(ans$row < 1 | ans$row > n.row | ans$col < 1 | ans$col > n.col)
  ans$check[idx] <- FALSE

  ## Check to see which surrounding areas have mines
  for(i in 1:8){
    if(ans$check[i]){
      ans$mine[i] <- map[ans$row[i], ans$col[i]]
    }
  }

  ## Return the number of mines in surrounding areas
  return(sum(ans$mine))    
}


## I don't think I actually use this
## for anything other than testing...
create.num.table <- function(map){
  n.row <- dim(map)[1]
  n.col <- dim(map)[2]
  table <- matrix(0, n.row, n.col)
  for(i in 1:n.row){
    for(j in 1:n.col){
      table[i, j] <- get.nums(i, j, map)
    }
  }
  return(table)
}


## Return a list with modified player.table
## and a boolean indicating if they lost or not.
guess <- function(row, col, player.table, map){

  ## Assume that the guess doesn't kill the player
  cont <- TRUE

  ## Make the list that we'll return if we don't
  ## need to do anything
  tmplist <- list(player.table = player.table, cont = cont)
  
  ## If the spot isn't in the table then do nothing
  if(!in.table(row, col, player.table)){
    return(tmplist)
  }

  ## If the spot has already been checked then do nothing
  if(!is.na(player.table[row, col])){
    return(tmplist)
  }
  
  ## Check what the value is of the guess
  tmp <- get.nums(row, col, map)
  player.table[row, col] <- tmp
  
  ## If you hit a mine don't continue
  if(tmp == Inf){
    cont <- FALSE
    return(list(player.table = player.table, cont = cont))
  }
  
  ## Clear all cells you could figure out
  ## if the guess had 0 surrounding mines
  ## There might be more efficient ways to do this
  ## but it works.
  if(tmp == 0){
    player.table <- guess(row-1, col-1, player.table, map)$player.table
    player.table <- guess(row-1, col, player.table, map)$player.table
    player.table <- guess(row-1, col+1, player.table, map)$player.table
    player.table <- guess(row, col-1, player.table, map)$player.table
    player.table <- guess(row, col+1, player.table, map)$player.table
    player.table <- guess(row+1, col-1, player.table, map)$player.table
    player.table <- guess(row+1, col, player.table, map)$player.table
    player.table <- guess(row+1, col+1, player.table, map)$player.table
  }
  
  return(list(player.table = player.table, cont = cont))
}

## Checks if a cell is in a given table
in.table <- function(row, col, player.table){
  nrow <- dim(player.table)[1]
  ncol <- dim(player.table)[2]
  ans <- (row >= 1) & (row <= nrow) & (col >= 1) & (col <= ncol)
  return(ans)    
}


## Figure out if the table won
check.win <- function(player.table, map){
  ## You can only win if the number of NAs in
  ## the player table is the same as the number
  ## of mines.
  if(sum(is.na(player.table)) == sum(map)){
    return(TRUE)    
  }
  # Else
  return(FALSE)
}

## A console version where you input your guess
## for the row number and column number...
##
## Currently doesn't check to make sure
## the input is valid.
play.mines <- function(n = 8, m = 8, n.mines = 10){
  player.table <- matrix(NA, n, m)
  map <- generate.map(n, m, n.mines)
  cont <- TRUE
  while(cont){
    win <- check.win(player.table, map)
    if(win){
      print("==============================")
      print("Winner!")
      break;
    }
    print(player.table)
    cat("Row Guess: ")
    row <- readline()
    cat("Col Guess: ")
    col <- readline()
    row <- as.numeric(row)
    col <- as.numeric(col)
    tmp <- guess(row, col, player.table, map)
    player.table <- tmp$player.table
    cont <- tmp$cont
  }
  print(player.table)
  print("==============================")
  invisible(NULL)
}

display.table <- function(player.table, nmines = 10, map){
  nrow <- dim(player.table)[1]
  ncol <- dim(player.table)[2]
  spots <- expand.grid(0:(nrow+1), 0:(ncol+1))
  win <- check.win(player.table, map)
  spotsleft <- sum(is.na(player.table)) - nmines
  messg <- paste("Spots left:", spotsleft, "--- # of Mines: ", nmines)
  if(win){
    messg <- "Winner!"
  }
  
  plot(spots,
       main = messg,
       type = "n",
       xlim = c(0.5, nrow+0.5),
       ylim = c(ncol+0.5, 0.5),
       axes = FALSE,
       xlab = "",
       ylab = "")
  
  ##box()
  ##axis(1, at = 1:ncol, labels = 1:ncol)
  ##axis(2, at = 1:nrow, labels = 1:nrow)
  plot.colors <- rep("blue", nrow*ncol)
  truespots <- expand.grid(1:(nrow), 1:(ncol))
  displaytext <- as.character(t(player.table))
  plot.colors[which(displaytext == "Inf")] <- "red"
  displaytext[which(displaytext == "Inf")] <- "BOOM"
  plot.colors[is.na(displaytext)] <- "black"
  displaytext[is.na(displaytext)] <- "X"
  text(x = truespots[,1], y = truespots[,2], labels = displaytext, col = plot.colors)
  for(i in seq(0.5, ncol+.5, 1)){
    lines(c(i,i), c(0.5, nrow+0.5))    
  }
  for(i in seq(0.5, nrow + .5, 1)){
    lines(c(0.5, ncol+0.5), c(i,i))    
  }
}

play.mines.graphic <- function(n = 8, m = 8, n.mines = 10){
  player.table <- matrix(NA, n, m)
  map <- generate.map(n, m, n.mines)
  cont <- TRUE    
  truespots <- expand.grid(1:(n), 1:(m))
  while(cont){
    win <- check.win(player.table, map)
    if(win){
      ##print("==============================")
      print("Winner!")
      break;
    }
    ## Display the table using the plot
    display.table(player.table, n.mines, map)
    ## Can only click. No flag functionality.
    ## This grabs what spot you wanted to click
    spot <- identify(x = truespots[,1], y = truespots[,2], plot = FALSE, n = 1)

    ## Looks like we're setting the guess
    ## for row and column to be switched but this has to do
    ## with the way display table works.
    row <- truespots[spot,2]
    col <- truespots[spot,1]
    tmp <- guess(row, col, player.table, map)
    player.table <- tmp$player.table
    cont <- tmp$cont
    if(!cont){
      print("Lost")
    }
  }

  display.table(player.table, n.mines, map)
  invisible(NULL)    
}


## To play
play.mines.graphic(n = 8, m = 8, n.mines = 10)
Edit: I've tested on Linux and on my Mac but I haven't gotten around to testing it on windows. Hopefully it works there too? I don't know why it wouldn't unless 'identify' works differently...

Edit 2: I logged onto one of the university servers and it seems to work fine on Windows.
 
Last edited:

TheEcologist

Global Moderator
#3
Edit 2: I logged onto one of the university servers and it seems to work fine on Windows.
Well done, I'm impressed and it works fine on R (64) Windows 7 (in a virtual box - although there is an irritating ping every time you click ;-) ).

I was thinking about making a (2d - 3d) battleship clone in R for a class because you can set up an AI that uses some of the more famous optimization algorithms and it gives you insight into optimized sampling. However, the class is done now and I still haven't found the time :-(
 

Dason

Ambassador to the humans
#4
Hmm. I guess I had my computers muted or it doesn't occur on some of them. Anywho I think adding the following line will get rid of the beep
Code:
options(locatorBell = FALSE)
 

Dason

Ambassador to the humans
#5
I've updated the code a little bit. There were actually a few bugs in the code so if you tried to play with a non-square board things got messed up. I fixed those and added a timer. I also added the gray scaling I was talking about but it adds quite a bit of overhead so only use it if you have a decently powered machine and are playing on a small board. Also added the ability to just specify a difficulty level instead of having to specify the board size and what not. I attached the code in a .txt file. It's just an R script though.

Examples on how to use the new format:
Code:
## Play with low graphics on beginner is default
play.mines.graphic()

## Set a different difficulty level
play.mines.graphic(difficulty = "intermediate")
play.mines.graphic(difficulty = "expert")
play.mines.graphic(difficulty = "custom", n = 13, m = 12, n.mines = 42)

## Play on beginner with 'high' graphics
play.mines.graphic(difficulty = "beginner", graphic = "high")
If I get bored enough some other day I might update this to use Gtk or maybe Qt so that it's even better. I can already think of a few simple extensions I could do using gWidgets just to allow the user to have a restart button, input boxes to choose difficulty and/or size of the board, high score listing... It wouldn't be too bad but I would probably just want to go all out and learn more about RGtk2 and create a full functionality clone with the ability to detect right clicks to set flags and what not.
 
Last edited:

Dason

Ambassador to the humans
#7
Any suggestions for simple things I should implement?

Edit: Note to self - change check.win to this
Code:
## Figure out if the table won
check.win <- function(player.table, map){
  ## You can only win if the number of NAs in
  ## the player table is the same as the number
  ## of mines. Unless it's the last one...
  if((sum(is.na(player.table)) == sum(map)) & (!sum(player.table == Inf, na.rm = TRUE))){
    return(TRUE)    
  }
  # Else
  return(FALSE)
}

## And to increase the speed of graphic = "high" slightly...
## Use rect instead of polygon

## Shades in unknown locations to gray
draw.squares <- function(spots, squares, col = "gray"){
  for(i in which(squares)){
    j <- spots[i,]
    rect(j[1] - 0.5, # xleft
         j[2] - 0.5, # ybottom
         j[1] + 0.5, # xright
         j[2] + 0.5, # ytop
         col = col)
  }
}
 
Last edited:

Dason

Ambassador to the humans
#8
I was thinking about making a (2d - 3d) battleship clone in R for a class because you can set up an AI that uses some of the more famous optimization algorithms and it gives you insight into optimized sampling. However, the class is done now and I still haven't found the time :-(
You should totally implement that. Maybe if we made enough games we could package them together and submit it to CRAN.

Aww heck once I get this minesweeper clone good enough I might just package that together for the heck of it. Are there any packages on CRAN that just provide games?
 

trinker

ggplot2orBust
#9
:tup:It always makes me laugh when someone takes a sophisticated tool and inevitably uses it to play games. I can remember in my youth spending more time making my TI 81 play games then working on my calculus. This is very impressive. I’ve already enjoyed a few rounds.:wave:
 

TheEcologist

Global Moderator
#10
You should totally implement that. Maybe if we made enough games we could package them together and submit it to CRAN.

Aww heck once I get this minesweeper clone good enough I might just package that together for the heck of it. Are there any packages on CRAN that just provide games?
Dason, that is a collaboration I would like to take part in! Lets do it!
 

TheEcologist

Global Moderator
#12
:tup:It always makes me laugh when someone takes a sophisticated tool and inevitably uses it to play games. I can remember in my youth spending more time making my TI 81 play games then working on my calculus. This is very impressive. I’ve already enjoyed a few rounds.:wave:
That is how great things happen! One story I heard is that a few guys ( Ken Thompson, Dennis Ritchie, Brian Kernighan) were bored during night shifts so they sought a way to play some games. This later became "UNIX".
 

Dason

Ambassador to the humans
#14
Any progress on battleship? What kind of an interface were you planning on using?

I recently upgraded the minesweeper game to use gWidgets and the RGtk2 libraries. This turns out to be pretty nice. My main issue right now is getting colors to display in my labels. I was able to get it somewhat working using gtext instead of glabel but that took too long at each click. I might be able to premake all of the 'labels' that I need but I haven't tried that yet and don't know how long that would add to the load up time.

TheEcologist have you ever used git? I'm thinking of possibly making a repository for the collaboration.
 

TheEcologist

Global Moderator
#15
Any progress on battleship? What kind of an interface were you planning on using?

I recently upgraded the minesweeper game to use gWidgets and the RGtk2 libraries. This turns out to be pretty nice. My main issue right now is getting colors to display in my labels. I was able to get it somewhat working using gtext instead of glabel but that took too long at each click. I might be able to premake all of the 'labels' that I need but I haven't tried that yet and don't know how long that would add to the load up time.

TheEcologist have you ever used git? I'm thinking of possibly making a repository for the collaboration.
I started work on it, I liked your graphical interface so I am going to use that however I'm working on an important paper revision now - but I promise to get back to you soon. :)
 

Dason

Ambassador to the humans
#16
No worries. I just worked on the new interface today which made me think of this thread so I was just checking. Have you ever used git though?
 

Dason

Ambassador to the humans
#17
I pretty much have the version using gWidgets (gWidgetsRGtk2 actually...) finished up. It's pretty nice and actually handles a lot better than the previous version using the plotting device (but no surprise there really). At the moment it has color support for the numbers. You can set flags through the use of right clicking. And at the end it tells you how long it took you to complete.

I want to add a few things before releasing any code - mainly a flag count, an automatic clock so you know how long you've taken so far, and a slightly better interface for choosing your difficulty and starting a new game. As it is right now you have to use the command line every time you want to play another game because the mines window closes when you win or lose. I'll probably just add a reset button somewhere on the interface to take care of that and have some file menus to take care of difficulty levels.

Does anybody have/use gWidgets and would be willing to be an alpha tester?
 

bryangoodrich

Probably A Mammal
#18
(1) You have WAY too much time on your hands! :p

(2) Awesome! I just became a winner :D Now I gotta go analyze your code to see the details of this nice little creation of yours.
 

Dason

Ambassador to the humans
#19
(1) You have WAY too much time on your hands! :p

(2) Awesome! I just became a winner :D Now I gotta go analyze your code to see the details of this nice little creation of yours.
I suppose you're playing the old version on the plotting device. The new version with gWidgets is quite a bit nicer ;-)

I just gotta tweak some things. But like I said if you're interested in alpha testing just let me know. I don't have access to a windows machine right now so I'd be interested in how it works there.
 

vinux

Dark Knight
#20
Nice Dason. There should be a button for awesomeness :). I tried a few game stuff in Open GL. that doesn't match in this forum.

Quark may say " There is no b button for awesomeness... or attractiveness" . ( I guess you ppl watched Kungfu panda)