Tic Tac Toe

Play tic-tac-toe against the computer. The computer is very dump and just picks a move at random.

Anyone want to write an optimal “next move” engine for the computer?

library(eventloop)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Game Board 3x3 integer matrix
#  0 means not taken
#  1 for user
# -1 for computer
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
init  <- FALSE
board <- matrix(0L, 3, 3)
done  <- FALSE


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Draw the game board
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
draw_board <- function(board) {
  grid.rect(gp = gpar(fill='white', col = NA))
  
  grid.polyline(
    x = c(1/3, 1/3,  2/3, 2/3,     0,   1,     0,   1),
    y = c(  0,   1,    0,   1,   1/3, 1/3,   2/3, 2/3),
    id = c(1, 1,  2, 2,  3, 3,  4, 4),
    default.units = 'snpc'
  )
  
  for (row in 1:3) {
    y <- c(1/6, 1/2, 5/6)[row]
    for (col in 1:3) {
      x <- c(1/6, 1/2, 5/6)[col]
      if (board[row, col] == -1) {
        grid.polyline(
          x = c(x - 1/6, x + 1/6, x - 1/6, x + 1/6),
          y = c(y - 1/6, y + 1/6, y + 1/6, y - 1/6),
          gp = gpar(fill=NA, col = 'red'), default.units = 'snpc'
        )
      } else if (board[row, col] == 1) {
        grid.circle(x, y, r = 1/6, gp = gpar(fill=NA, col = 'blue'), default.units = 'snpc')
      }
    }
  }
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# tests to see if game is over
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
game_is_over <- function(board) {
  user_is_winner(board) ||
    computer_is_winner(board) ||
    sum(board == 0) == 0 # board is full without a winner
}

user_is_winner <- function(board) {
  any(c(rowSums(board), colSums(board), sum(diag(board)), sum(board[3,1] + board[2,2] + board[1,3])) == 3)
}

computer_is_winner <- function(board) {
  any(c(rowSums(board), colSums(board), sum(diag(board)), sum(board[3,1] + board[2,2] + board[1,3])) == -3)
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Computer takes a random move
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
computer_takes_a_move <- function(board) {
  loc <- which(board == 0)
  if (length(loc) > 1) {
    loc <- sample(loc, 1)
  }
  board[loc] <- -1L
  board
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Reset the board to play again
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
reset <- function() {
  init  <<- FALSE
  board <<- matrix(0L, 3, 3)
  done  <<- FALSE
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' This is a verbose callback that simply prints information to the console
#' about what events are happening
#' 
#' Press ESC to quit.
#' 
#' @param event The event from the graphics device. Is NULL when no event
#'        occurred.  Otherwise has `type` element set to:
#'        `event$type = 'mouse_down'` 
#'               - an event in which a mouse button was pressed
#'               - `event$button` gives the index of the button
#'        `event$type = 'mouse_up'`   
#'               - a mouse button was released
#'        `event$type = 'mouse_move'`   
#'               - mouse was moved 
#'        `event$type = 'key_press'`  
#'               - a key was pressed
#'               - `event$str` String describing which key was pressed. 
#'                  See \code{grDevices::setGraphicsEventHandlers} for more information.
#' @param mouse_x,mouse_y current location of mouse within window in normalised 
#'        coordinates in the range [0, 1]. If mouse is 
#'        not within window, this will be set to the last available coordinates
#' @param frame_num Current frame number (integer)
#' @param fps_actual,fps_target the curent framerate and the framerate specified
#'        by the user
#' @param dev_width,dev_height the width and height of the output device. Note:
#'        this does not cope well if you resize the window
#' @param ... any extra arguments ignored
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
tictactoe <- function(event, mouse_x, mouse_y, frame_num, fps_actual,
                      fps_target, dev_width, dev_height, ...) {
  
  if (!init) {
    draw_board(board)
    init <<- TRUE
  }
  
  # Did an event happen at all in this window?
  if (!done &&!is.null(event) && event$type == 'mouse_down') {
    
    row <- ceiling(mouse_y * 3)
    col <- ceiling(mouse_x * 3)
    
    current <- board[row, col]
    
    if (current == 0) {
      # this is an allowable move as the square is empty
      board[row, col] <<- 1L
      draw_board(board)
      
      if (!game_is_over(board)) {
        board <<- computer_takes_a_move(board)
        draw_board(board)
      }
      
      if (game_is_over(board)) {
        done <<- TRUE
        if (user_is_winner(board)) {
          grid.text("You win!", gp = gpar(cex = 3, col = 'green'))
        } else if (computer_is_winner(board)) {
          grid.text("you lost!", gp = gpar(cex = 3, col = 'red'))
        } else {
          grid.text("It's a tie!", gp = gpar(cex = 3, col = 'hotpink'))
        }
        grid.text(y = 1, vjust = 1, hjust = 0.5, "Press any key to restart", gp = gpar(cex = 2, col = 'blue'))
      }
    }
  }
  
  if (done && identical(event$type, 'key_press')) {
    reset()
  }
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Start the event loop. Press ESC to quit.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eventloop::run_loop(tictactoe, fps_target = 10, show_fps = TRUE, double_buffer = FALSE)

Since an interactive window cannot be captured in a vignette, a video screen capture has been taken of the window and included below.