game-tic-tac-toe.Rmd
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.