R/msBoard.R

Defines functions msBoard is.msBoard change.msBoard draw.msBoard

Documented in change.msBoard draw.msBoard is.msBoard msBoard

#' Creates a new msweepeR board
#'
#' This function creates a new msweepeR board of width \code{w} and height \code{h},
#' with the specified number of mines \code{m}.
#'
#' This function is called with the constructor \code{\link{msweepeR}}, and it's recommended that
#' it's only called through such class constructor.
#'
#' @param w Width of the game board. Must be a positive integer > 3.
#' @param h Height of the game board. Must be a positive integer > 3.
#' @param m Number of mines in the board. Must be a positive integer. 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{msBoard}. It contains a
#' matrix representing the mines positions in the game board, a matrix representing
#' the mine count in the 8 neighbouring tiles, and a matrix representing the player's
#' uncovered tiles and flags. The \code{msBoard} object also includes some basic info
#' about the game, like the number of turns (moves) playes, whether the player has already
#' opened a mined tile, the total number of flags, the total number of mines, and the board size.
#' @examples
#' x <- msBoard()
#' @seealso \code{\link{msweepeR}}
#' @export
msBoard <- function(w = 10, h = 10, m = as.integer(5)) {
  #Obtener ancho de la pantalla
  console_width <- options("width")
  #Verificar 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.integer(m) | m < 1 ) stop("Mines number must be a positive integer!")
  if (m > (w*h*0.25)) warning("You're confident on your minesweeping levels")
  if (m > (w*h*0.5)) stop("More than a half of the board tiles are mines!")
  #Definir tamaño del tablero
  board_size <- w * h
  #Generar el tablero como una matriz
  board <- matrix(nrow = h, ncol = w, data = 0)
  #Definir aleatoreamente la posición de las minas
  mine_location <- sample(1:board_size, m, replace = F)
  #Colocar minas (-1)
  board[mine_location] <- 1

  #Calcular números en el tablero
  numbers_board <- matrix(nrow = h, ncol = w, data = 0)
  #Por cada columna
  for (col in 1:ncol(board)) {
    #Por cada fila
    for (row in 1:nrow(board)) {
      #Inicializar número de minas alrededor de la baldosa
      n_mines <- 0
      #Determinar si hay una mina
      if (board[row, col] == 1) {
        #Colocar -1 y pasar a siguiente iteración
        numbers_board[row, col] <- -1
        next()
      }
      #Definir coordenadas de las 8 baldosas al rededor
      tile_no <- c(row - 1, col - 1)
      tile_n <- c(row - 1, col)
      tile_ne <- c(row - 1, col + 1)
      tile_o <- c(row, col - 1)
      tile_e <- c(row, col + 1)
      tile_so <- c(row + 1, col - 1)
      tile_s <- c(row + 1, col)
      tile_se <- c(row + 1, col + 1)
      #Ponerlas en una lista
      tiles_coordinates <- list(tile_no, tile_n, tile_ne,
                                tile_o, tile_e,
                                tile_so, tile_s, tile_se)
      #Iterar por cada una de las 8 baldosas al rededor
      for (tile in tiles_coordinates) {
        #Baldosa inválida
        if (tile[1] < 1 | tile[1] > h) {
          next()
        }
        #Baldosa inválida
        if (tile[2] < 1 | tile[2] > w) {
          next()
        }
        #Si hay mina, contarla
        if (board[tile[1], tile[2]] == 1) {
          n_mines <- n_mines + 1
        }
      }
      numbers_board[row, col] <- n_mines
    }

  }

  player_board <- matrix(nrow = h, ncol = w, data = 0)

  return_obj <- list(board = board, size = c(w, h), mines = m,
                     turn = 0, player_mines = 0, player_board = player_board,
                     alive = 1, numbers_board = numbers_board)
  return_obj <- structure(return_obj, class = "msBoard")
  return(return_obj)

}

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

#' Changes the state of a tile in a msweepeR board
#'
#' This method updates a msweepeR board by opening a tile or by placing a flag on it. If the opened
#' tile contains a mine, the game it's over.
#'
#' This method is called with the S3 method \code{\link{play.msweepeR}}, and it's recommended that
#' it's only called through such method.
#'
#' @param x A msweepeR board (an R object of class \code{msBoard}).
#' @param action A character vector of length 1. Valid options are: \code{"open"} and \code{"flag"}.
#' @param row Row number of the desired tile to interact with. Must be a positive integer.
#' Each tile in the game board has a [row, col] coordinate, ike any other matrix like object in R.
#' @param col Column number of the desired tile to interact with. Must be a positive integer.
#' Each tile in the game board has a [row, col] coordinate, like any other matrix like object in R.
#' @param warnings Logical vector of length 1. Indicates whether warnings are enabled.
#'
#' @return This method returns the updated msweepeR board (an R object of class \code{msBoard}).
#' @examples
#' x <- msBoard()
#' x <- change.msBoard(x, "open", 1, 1, FALSE)
#' @seealso \code{\link{msweepeR}}, \code{\link{msBoard}}
#' @export
change.msBoard <- function(x, action, row, col, warnings = TRUE) {
  #Verificar objeto
  if (!is.msBoard(x)) stop("This method is only available for msBoard objects")

  #Obtener altura y longitud del tablero
  w <- x$size[1]
  h <- x$size[2]
  #Obtener tablero de minas
  board <- x$board
  #Obtener tablero de jugador
  player_board <- x$player_board
  #Verificar parámetros
  if (row > h | col > w) stop("Invalid tile coordinates")
  if (row < 1 | col < 1) stop("Invalid tile coordinates")

  #Hay dos acciones, revelar y bandera
  #El usuario quiere revelar la casilla
  if (action == "open") {
    #Si hay mina, game over
    if (board[row, col] == 1) {
      if (warnings) warning("Ooops! You opened a mined tile x.x")
      x$alive <- 0
    } else {
      player_board <- revealTile(board, player_board, row, col)
    }
  #El usuario quiere colocar una bandera
  } else if (action == "flag") {
    #Si ya está abierta, no hacer nada
    if (player_board[row, col] != 1) {
      player_board[row, col] <- 2
      x$player_mines <- x$player_mines + 1
    }
  #Aacción desconocida
  } else {
    stop("Unknown action parameter. You can only update a board by revealing a
         tile (action = 'reveal' or by putting a flag on a tile (action = 'flag')")
  }
  x$turn <- x$turn + 1
  x$player_board <- player_board
  return(x)
}

#' Draw a msweepeR board
#'
#' This method draws a msweepeR 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 opened ones will be represented by one
#' blank space if they're empty or they will contain a number indicating the total number of mines in the 8 neighbouring tiles.}
#'
#' This method is called with the S3 method \code{\link{play.msweepeR}}, and it's recommended that
#' it's only called through such method.
#'
#' @param x A msweepeR board (an R object of class \code{msBoard}).
#' @examples
#' \dontrun{
#' x <- msBoard(); draw.msBoard(x)
#' }
#' @seealso \code{\link{msweepeR}}, \code{\link{msBoard}}
#' @export
draw.msBoard <- function(x) {
  #Verificar objeto
  if (!is.msBoard(x)) stop("This method is only available for msBoard objects")

  #Obtener tablero del jugador y de numeros
  player_board <- x$player_board
  numbers_board <- x$numbers_board
  #Obtener altura y longitud del tablero
  w <- x$size[1]
  h <- x$size[2]

  #Imprimir número de fila
  cat("\u001b[47m\u001b[30m")
  cat(rep(" ", nchar(h) + 1 ), sep = "" )
  cat(1:w, sep = " " )
  cat("\n")

  #Por cada fila
  for (row in 1:nrow(player_board)) {
    #Por cada columna
    for (col in 1:(ncol(player_board) + 1) ) {
      if (col == (ncol(player_board) + 1) ) {
        cat("\n")
        next()
      }
      cat("\u001b[47m\u001b[30m")
      if (col == 1) {
        cat(row, rep(" ", nchar(h) - nchar(row) ), sep = "" )
      }
      tile <- player_board[row, col]
      under_tile <- numbers_board[row, col]
      if (tile == 0) {
        cat(" ",".", rep(" ", nchar(col) - 1), sep = "")
      } else if (tile == 1) {
        if (under_tile == 0) {
          cat("\u001b[1m", " "," ", rep(" ", nchar(col) - 1), "\u001b[0m", sep = "")
        } else {
          cat("\u001b[1m", " ", msweepeR_colors[under_tile], rep(" ", nchar(col) - 1), "\u001b[0m", sep = "")
        }
      } else if (tile == 2) {
        cat("\u001b[1m", " ", "F", rep(" ", nchar(col) - 1), "\u001b[0m", sep = "")
      }
    }
    cat("\u001b[0m")
  }
  cat("\u001b[0m")
}
pablorm296/msweepeR documentation built on Nov. 4, 2019, 11:16 p.m.