# R- implementation of the minesweeper game that was a standard # game in the old windows versions. # # The game uses a graphical device and is played using mouse. # # The starting display of the game is a rectangular grid, with ncol columns and nrow rows. # A certain proportion of the cells (specified by parameter p) # will include a mine. # The aim is to open all cells except the ones that include a mine. # The player opens the cells using mouse. # If an opened cell is a mine, then the game is over. # If not, the cell includes a number indicating how many neighboring cells include a mine. # If none of the neighboring cells has a mine, # the program opens all neighboring cells automatically and continues this # recursively for the neighbours. # The player can either # - open a cell # - mark a cell as a mine # - open all non-marked cells around a cell if the mine count matches with the number of marked # neighboring cells. # Instructions on how to use the mouse for these operations are shown on the screen # either in English or in Finnish. # # Author: Lauri Mehtätalo 30.12.2016 # Homepage: http://cs.uef.fi/~lamehtat/ # You may distribute and develop this further freely. # # I made this game originally for my kids, therefore it was originally in Finnish. # Thereafter I implemented the english version to make it accessible to my non-Finnish friends. # # Parameters # ncol - number of columns in the grid # nrow - number of rows in the grod # p - proportion of mines # English - language of instructions. TRUE->English, FALSE-> Finnish. # # Note: the recursive opening of cells may cause an error because of too deep recurison # if nrow and ncol are large and p is small. Values higher than 30 for nrow and # ncol are not suggested. You can adjust the limit for too deep recursion by parameter # expressions, see ?options() # # Usage # source("minesweeper.R") # windows() or quarz() # minesweepeR() # Have fun! ############################################################################### options(expressions=100000) # a function for marking a mine mark<-function(row,col,mineguess) { mineguess[row,col]<-!mineguess[row,col] mineguess } openfun<-function(row,col,open,howmany,nrow,ncol) { if (!sum(open$row==row&open$col==col)) { open<-rbind(open,c(row,col)) if (howmany[row,col]==0) { openalso<-data.frame(row=c(row-1,row,row+1,row-1,row+1,row-1,row,row+1), col=c(rep(col-1,3),rep(col,2),rep(col+1,3))) openalso<-openalso[openalso$row>0&openalso$row<=nrow&openalso$col>0&openalso$col<=ncol,] for (i in 1:dim(openalso)[1]) { open<-openfun(openalso$row[i],openalso$col[i],open,howmany,nrow,ncol) } } } open<-open[open$row>0&open$row<=nrow&open$col>0&open$col<=ncol,] unique(open) } # open neighbours open.nb<-function(row,col,open,howmany,nrow,ncol,mineguess,mines) { nb<-data.frame(row=row+rep(c(-1,0,1),each=3),col=col+rep(c(-1,0,1),3)) nb<-nb[nb$row>0&nb$row<=nrow&nb$col>0&nb$col<=ncol,] notguess<-!mineguess[as.matrix(nb)] if (howmany[row,col]==sum(mineguess[as.matrix(nb)])) { nb<-nb[notguess,] for (i in 1:dim(nb)[1]) { open<-openfun(nb$row[i],nb$col[i],open,howmany,nrow,ncol) } list(open=open,pum=as.logical(sum(mines[as.matrix(nb)]))) } else { list(open=open,pum=FALSE) } } draw<-function(howmany,open,mineguess,mineloc=NA,finish=FALSE) { ncol<-ncol(howmany) nrow<-nrow(howmany) plot(1,1,type="n",xlim=c(0.5,ncol+0.5),ylim=c(0.5,nrow+0.5)) sapply(0:nrow+0.5, function(x) lines(c(0.5,ncol+0.5),rep(x,2))) sapply(0:ncol+0.5, function(x) lines(rep(x,2),c(0.5,nrow+0.5))) if (sum(mineguess)) { row<-rep(1:nrow,ncol) col<-rep(1:ncol,each=nrow) points(col[mineguess],row[mineguess],pch=16) } if (open[1,1] !=0) { for (i in 1:dim(open)[1]) { x<-open[i,2] y<-open[i,1] polygon(x+c(-0.5,-0.5,0.5,0.5),y+c(-0.5,0.5,0.5,-0.5),col="gray") } hmvec<-howmany[as.matrix(open)] text(open$col,open$row,hmvec, col=c("green","blue","red","brown","brown", "brown","brown","brown","brown")[hmvec+1]) } if (finish) points(mineloc[,2],mineloc[,1],cex=2,pch=16,col="red") } ## The main function minesweepeR<-function(ncol=10,nrow=10,p=0.15,English=TRUE) { #ncol<-10 # sarakkeiden maara #nrow<-10 # rivien maara #p<-0.1 # miinojen osuus nmines<-max(2,floor(ncol*nrow*p)) # number of mines (integer) coords<-cbind(row=rep(1:nrow,each=ncol),col=rep(1:ncol,nrow)) # cell coordinates # A binary nrow*ncol matrix of mine locations mineloc<-coords[sample(nrow(coords),nmines,replace=FALSE),] mines<-matrix(0,ncol=ncol,nrow=nrow) mines[mineloc]<-1 # A binary matrix telling which cells have been opened open<-data.frame(row=0,col=0) # A matrix telling how many neighbours have a mine howmany<-matrix(0,ncol=ncol,nrow=nrow) for (i in 1:nrow(mineloc)) { nbrow<-max(1,(mineloc[i,1]-1)):min(nrow,(mineloc[i,1]+1)) nbcol<-max(1,(mineloc[i,2]-1)):min(ncol,(mineloc[i,2]+1)) nbhood<-cbind(row=rep(nbrow,each=length(nbcol)),col=rep(nbcol,length(nbrow))) howmany[nbhood]<-howmany[nbhood]+1 } # for (i in 1:nrow(mineloc)) howmany[as.logical(mines)]<-9 mineguess<-matrix(FALSE,ncol=ncol,nrow=nrow) noPum<-TRUE noFinish<-TRUE while (noPum&noFinish) { draw(howmany,open,mineguess) illegalInput<-TRUE while (illegalInput) { # tyyppi<-readline("Mark (m) or open (other than m): ") loc<-locator(n=1) col<-round(loc$x) row<-round(loc$y) if(!(col<=0|row<=0|col>ncol|row>nrow)) { illegalInput<-FALSE } ## (col<=0|row<=0|col>ncol|row>nrow) } ## while illegal Input lines(col+c(-0.5,0.5,0.5,-0.5,-0.5),row+c(-0.5,-0.5,0.5,0.5,-0.5),col="red",lwd=2) if (sum(open$col==col&open$row==row)) { # if already open if (English) { mtext(paste("Coordinates (",col,", ",row, ") selected. Reclick the same cell to open around."),line=2) } else { mtext(paste("Koordinaatit (",col,", ",row, ") valittu. Avaa ymparilta painamalla uudestaan."),line=2) } } else { if (English) { mtext(paste("Coordinates (",col,", ",row, ") selected. Reclick the same cell to open."),line=2) } else { mtext(paste("Koordinaatit (",col,", ",row, ") valittu. Avaa ruutu painamalla uudestaan."),line=2) } } # If already open if (English) { mtext(paste("Mark as mine/non-mine by clicking another cell"),line=1) } else { mtext(paste("Merkkaa miinaksi painamalla alapuolella tai sivulla olevaa ruutua"),line=1) } loc2<-locator(n=1) col2<-round(loc2$x) row2<-round(loc2$y) if (col==col2&row==row2) { # if the same cell if (sum(open$col==col&open$row==row)) { ## if already open open<-open.nb(row,col,open,howmany,nrow,ncol,mineguess,mines) if (open$pum) noPum<-FALSE open<-open$open } else { open<-openfun(row,col,open,howmany,nrow,ncol) if (mines[row,col]) noPum<-FALSE } ## (sum(open$col==col&open$row==row)), if already open } else { if (!(sum(open$col==col&open$row==row))) {## if not open mineguess<-mark(row,col,mineguess) } else { mtext("Cell already open",line=1,col="red") } } ## if (col==col2&row==row2) (if the same cell) if (dim(open)[1]==ncol*nrow-nmines) noFinish<-FALSE } ## while draw(howmany,open,mineguess,mineloc=mineloc,finish=TRUE) if (noPum) { if (English) { text(ncol/2,nrow/2,"FINISH",col="green",cex=5) } else { text(ncol/2,nrow/2,"LOPPU",col="green",cex=5) } } else { text(ncol/2,nrow/2,"PUM",col="red",cex=5) } } # minesweepeR()