R/gameFunctions.R

Defines functions msweepeR is.msweepeR draw.msweepeR play.msweepeR

Documented in draw.msweepeR is.msweepeR msweepeR play.msweepeR

#' Create a new msweepeR session
#'
#' This function creates a new msweepeR game (session). As in the classic
#' Microsoft Minesweeper included in various Windows standard installs, you can
#' choose between three levels of difficulty: beginner, intermediate, and expert.
#' However, unlike the MS version, you have to manually define the height and width
#' of the board. By default, height and width are set to 10 tiles. \strong{Please keep in
#' mind that the board must be narrower than the console width}, since a larger
#' game board could not be printed in the console.
#'
#' Once you created a new msweepeR session (by assigning it to a new object \code{x <- msweepeR()} ),
#' you can start playing by invoking the \code{\link{play.msweepeR}} method: \code{play.msweepeR(x)}. A full
#' explanation on how to play is in this vignette:
#'
#' \code{vignette("How to play msweepeR", package = "msweepeR")}
#'
#' @param w Width of the game board. Must be an integer > 3. Please keep in
#' mind that the board must be narrower than the console width.
#' @param h Height of the game board. Must be a positive integer > 3.
#' @param level Difficulty level of the game. Must be an integer between 1 and 3 (beginner,
#' intermediate, and expert). If \code{level} is set to \code{NULL}, you can manually set the number
#' of mines in the gameboard (see \code{mines}).
#' @param mines Number of mines in the game board. Must be a positive integer. You can
#' only manually define the number of mines if you set \code{level} to \code{NULL}. Please keep in mind that
#' the number of mines can't be higher than half of the total number of tiles.
#' @return This function returns an R object of class \code{msweepeR}. This object contains basic information
#' about the msweepeR session, like the start time and whether the game has already started. It also contains
#' a \code{\link{msBoard}} class object, containing all the game board attributes.
#' @examples
#' \dontrun{
#' x <- msweepeR()
#' x <- play(x)
#'
#' x <- msweepeR()
#' x <- play.msweepeR(x)
#' }
#' @seealso \code{\link{is.msweepeR}}, \code{\link{draw.msweepeR}}, \code{\link{play.msweepeR}}
#' @export msweepeR
msweepeR <- function(w = 10, h = 10, level = 1, mines = NULL) {
  #Obtener ancho de la pantalla
  console_width <- options("width")

  #Verificamos parámetros
  if (w < 3) stop("Board must be at least 3 tiles wide")
  if (h < 3) stop("Board must be at least 3 tiles tall")
  if ( (w * 3 + nchar(h) + 1 ) >= console_width) {
    stop("Board is wider than the console output")
  }
  if (!is.null(mines) & !is.null(level)) stop("You can't define a difficulty level while defining the number of mines in the board")
  if (!is.null(level)) {
    #Definir número de minas
    if (level == 1) {
      mines <- round(w*h*0.10)
    } else if (level == 2) {
      mines <- round(w*h*0.15)
    } else if (level == 3) {
      mines <- round(w*h*0.20)
    } else if (!is.null(mines) & is.null(level)) {
      if (!is.integer(mines) | mines < 1 ) stop("Mines number must be a positive integer!")
      if (mines > (w*h*0.25)) warning("You're confident on your minesweeping levels")
      if (mines > (w*h*0.5)) stop("More than a half of the board tiles are mines!")
    } else {
      stop("Level parameter must be an integer between 1 and 3")
    }
  }

  #Crear tablero
  mines <- as.integer(mines)
  board <- msBoard(w = w, h = h, m = mines)

  #Definir clase del objeto
  return_obj <- list(board = board, start_time = Sys.time(), first = TRUE)
  return_obj <- structure(return_obj, class = "msweepeR")

  #Regresar objeto
  return(return_obj)
}

#' Check if an R object is a msweepeR session
#'
#' Tests whether an object is a msweepeR session.
#' @param x an R object.
#' @examples
#' x <- msweepeR()
#' is.msweepeR(x)
#' @seealso \code{\link{msweepeR}}
#' @export
is.msweepeR <- function(x) {
  inherits(x, "msweepeR")
}

#' Print msweepeR session info and board
#'
#' This method will print basic info about the game session, then
#' it will draw the msweepeR game board into the output console. When drawing
#' the board game into the console, all tiles will have a white background.
#' \strong{Unopened tiles will have a black dot, while empty ones will be represented by one
#' blank space or will contain a number indicating the total number of mines in the 8 neighbouring tiles. }
#' @param x a msweepeR session.
#' @examples
#' x <- msweepeR()
#' draw.msweepeR(x);
#' @seealso \code{\link{msweepeR}}
#' @import crayon
#' @export
draw.msweepeR <- function(x) {
  #Verificar tipo de objeto
  if ( !is.msweepeR(x) ) stop("This method is only available for msweepeR objects")

  #Obtener tiempo transcurrido
  score <- Sys.time() - x$start_time
  score <- as.numeric(score, units = "secs")

  #Imprimir info de la sesión
  cat(bold("Moves: "), x$board$turn, "\n", sep = "")
  cat(bold("Elapsed time: "), score, "\n", sep = "")
  cat(bold("Remaining mines: "), x$board$mines - x$board$player_mines, "\n", sep = "")

  #Imprimir tablero
  draw.msBoard(x$board)

}


#' Starts or resumes a msweepeR game session
#'
#' Once you started a msweepeR game session, by assigning it to a new object \code{x <- msweepeR()},
#' you can start playing by using this method (\code{play.msweepeR}). See details for more info on how
#' to play msweepeR.
#'
#' Once you started a msweepeR session, by assigning it to a new object \code{x <- msweepeR()},
#' you can start playing by using this method (\code{play.msweepeR(x)}). This method will first print basic info about
#' the game session and the game board (contained in the \code{\link{msweepeR}} object), and then it will draw the game board in
#' the output console. After that, you'll be asked to make a move. As in any other minsweeper game, you can either open
#' a tile to reveal its content, or you can put a flag on top of a tile.
#'
#' To make a move you have to use a special msweepeR command syntax:
#'
#' \code{action @ row_num, col_num}.
#'
#' Where \code{action} is either \emph{'open'} or \emph{'flag'}, and \code{row_num, col_num} correspond to the
#' coordinates of the desired tile in the game board. Imagine that the game board is a matrix,
#' and each tile is an element within such matrix; therefore, each tile has an row, col coordinate (like any other matrix
#' in R). You'll see the row and column numbers printed in the first column and first row of the board respectively.
#'
#' For example, if you want to open tile [1,1], you'll have to type: \code{open @@ 1,1}
#'
#' If you want to put a flag on tile [3,7], you'll have to type: \code{flag @@ 3,7}
#'
#' Once you introduced a valid command, it will be parsed and evaluated. If you opened an empty tile, or
#' you placed a flag, the board will be printed again in the console and you'll be asked again what's your desired move.
#' If you have flaged all mined tiles, you'll win. However, if you open a mined tile, you'll loose.
#'
#' If you type an invalid command, a warning will be printed, and you'll be asked again to type a valid command.
#'
#' @param x A msweepeR session (an R object of class \code{msweepeR}).
#' @examples
#' \dontrun{
#' x <- msweepeR(); play.msweepeR(x)
#' }
#' @seealso \code{\link{is.msweepeR}}, \code{\link{draw.msweepeR}}, \code{\link{msweepeR}}
#' @export
play.msweepeR <- function(x) {

  #Verificar tipo de objeto
  if ( !is.msweepeR(x) ) stop("This method is only available for msweepeR objects")

  #Definir hora de inicio si es la primera vez
  #También si es la primera vez, guardar nombre del obj
  if (x$first) {
    x$start_time <- Sys.time()
    x$first <- FALSE
  }

  #Verificar que el jugador siga vivo
  if (x$board$alive == 0) {
    stop("You already lost this game!")
  }

  #Imprimir sesión
  draw.msweepeR(x)

  #Capturar comando del usuario
  user_cmd <- readline("What would you like to do? \u001b[0m")

  #Parsear y evaluar comando del usuario
  result <- eval_msweepeR_cmd(parse_msweepeR_cmd(user_cmd))

  #Si hubo error, sólo repetir
  if (result[1] == "error") {
    readline("Press enter to continue...\n")
    return(play.msweepeR(x))
  #Si el usuario pide salir
  } else if (result[1] == "end") {
    #Guardar .Rhistory
    update_history(user_cmd)
    cat("Goodbye!\n")
    readline("Press enter to continue...\n")
    return(x)
  #Si el usuario introdujo una acción válida
  } else if (result[1] == "flag" | result[1] == "open") {
    #Guardar .Rhistory
    update_history(user_cmd)
    #Si quiere poner una bandera y ya no hay advertir
    if (result[1] == "flag" & (x$board$mines - x$board$player_mines) < 0 ) {
      cat("Well... There are more flags than mines in the board\n")
    }
    #Actualizar tablero
    x$board <- change.msBoard(x = x$board, action = result[1],
                                    row = as.integer(result[2]), col = as.integer(result[3]),
                                    warnings = F)
    #Verificar que el jugador no ha ganado
    if ((x$board$mines - x$board$player_mines) == 0) {
      #Hacer copias de las matrices
      x_real <- x$board$board
      x_mask <- x$board$player_board
      #Sustituir los valores != 2 con cero y =2 con 1
      x_mask[x_mask != 2] <- 0
      x_mask[x_mask == 2] <- 1
      #Verificar que las matrices sean iguales
      if (all(x_real == x_mask)) {
        cat("YOU WON!!\n")
        return(x)
      }
    }
    #Si sigue vivo, volver a repetir función
    if (x$board$alive == 1) {
      return(play.msweepeR(x))
    #Si no, el jugador ya perdió
    } else {
      cat("Ooops! You've opened a mined tile x.x\n")
      readline("Press enter to continue...\n")
      #Regresar tablero tablero
      return(x)
    }
  } else {
    readline("Error: Well... something went wrong while evaluating the command\n")
    readline("Press enter to continue...\n")
    #Regresar tablero tablero
    return(x)
  }

}
pablorm296/msweepeR documentation built on Nov. 4, 2019, 11:16 p.m.