Created
November 5, 2020 20:33
-
-
Save coolbutuseless/341e0564fad8e939d26e7493c1e391e3 to your computer and use it in GitHub Desktop.
Chess game in R using the stockfish engine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
#' Convert a FEN string to a char matrix representing the chess board. | |
#' | |
#' @param fen fen string | |
#' | |
#' @return 8x8 character matrix representing the chess board | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
fen_to_matrix <- function(fen) { | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Currently only interested in 1st field in fen i.e. piece placement | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
pieces <- strsplit(fen, ' ')[[1]][1] | |
rows <- strsplit(pieces, "/")[[1]] | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Replaces digits with the number of spaces they represent | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
for (i in 1:8) { | |
rows <- gsub(i, paste(rep(' ', i), collapse=''), rows) | |
} | |
matrix(unlist(strsplit(rows, '')), 8, 8, byrow = TRUE, list(c(8:1), letters[1:8])) | |
} | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
#' Create a plot of the state of a chess board from a FEN string | |
#' | |
#' @param fen FEN string | |
#' | |
#' @return ggplot2 object | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
plot_board <- function(mat) { | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Unicode chars for chess pieces | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
unicode <- c( | |
` ` = '', | |
P='\u2659', R='\u2656', N='\u2658', B='\u2657', Q='\u2655', K='\u2654', # White | |
p='\u265F', r='\u265C', n='\u265E', b='\u265D', q='\u265B', k='\u265A' # Black | |
) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Convert the matrix chess board to a data.frame | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
board <- expand.grid(x=1:8, y=1:8) %>% | |
mutate( | |
tilecol = (x+y)%%2 == 1, | |
piece = as.vector(t(mat[8:1,])), | |
colour = piece %in% letters, | |
unicode = unicode[piece] | |
) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Create plot | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
p <- ggplot(board, aes(x, y)) + | |
geom_tile(aes(fill = tilecol), colour = 'black', show.legend = FALSE) + | |
geom_text(aes(label = unicode), family="Arial Unicode MS", size = 8) + | |
coord_equal() + | |
scale_fill_manual(values = c('grey50', 'grey95')) + | |
scale_y_continuous(breaks = 1:8) + | |
scale_x_continuous(breaks = 1:8, labels = letters[1:8]) + | |
theme_bw() + | |
theme( | |
axis.title = element_blank(), | |
plot.background = element_blank(), | |
panel.grid = element_blank(), | |
panel.border = element_blank(), | |
axis.ticks = element_blank() | |
) | |
plot(p) | |
invisible() | |
} | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Super basic Long Annoation to matrix coordinates | |
# | |
# @param lan e.g. e2e5 | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
lan_to_coords <- function(lan) { | |
stopifnot(nchar(lan) == 4) | |
lan <- tolower(lan) | |
bits <- strsplit(lan, '')[[1]] | |
bits[1] <- match(bits[1], letters[1:8]) | |
bits[3] <- match(bits[3], letters[1:8]) | |
bits <- as.integer(bits) | |
list( | |
start = matrix(c(9 - bits[2], bits[1]), nrow = 1), | |
end = matrix(c(9 - bits[4], bits[3]), nrow = 1) | |
) | |
} | |
library(ggplot2) | |
library(processx) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Start the Stockfish proces | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
sf <- process$new('stockfish', stdin = '|', stdout = '|', stderr = '|') | |
sf$write_input('uci\n') | |
jnk <- sf$read_output_lines() | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Initialise board (i.e. matrix) to opening position | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
fen <- 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1' | |
board <- fen_to_matrix(fen) | |
plot_board(board) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Keep track of all the moves played. This list of moves is fed to Stockfish | |
# every time, and then it decides what the next move should be | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
all_moves <- c() | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# | |
# Game Loop | |
# | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
while(TRUE) { | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Ask user for a move and add it to the list of all moves | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
move_lan <- readline("Your move? : ") | |
if (move_lan %in% c('', 'q', 'Q', 'quit')) { | |
message("Done") | |
break | |
} | |
all_moves <- c(all_moves, move_lan) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Validate move | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Not done yet | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Move the piece on the local board | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
move_coords <- lan_to_coords(move_lan) | |
if (board[move_coords$start] %in% c(NA, ' ', '')) { | |
stop("Bad move given current board state: ", best_move) | |
} | |
board[move_coords$end ] <- board[move_coords$start] | |
board[move_coords$start] <- ' ' | |
plot_board(board) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Tell stockfish about the move | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
moves <- paste(all_moves, collapse = " ") | |
cmd <- paste('position startpos moves', moves, '\n') | |
# print(cmd) | |
sf$write_input(cmd) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Ask stockfish to consider the next move for 0.1 seconds | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
sf$write_input('go movetime 100\n') | |
Sys.sleep(0.2) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Extract the best move from output from Stockfish | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
sf_response <- sf$read_output_lines() | |
best_move_line <- tail(sf_response, 1) | |
best_move <- strsplit(best_move_line, " ")[[1]][2] | |
cat("Computer move:", best_move, "\n") | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Add the computers move | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
all_moves <- c(all_moves, best_move) | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
# Move the piece on the local board | |
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | |
move_coords <- lan_to_coords(best_move) | |
if (board[move_coords$start] %in% c(NA, ' ', '')) { | |
stop("Bad move given current board state: ", best_move) | |
} | |
board[move_coords$end ] <- board[move_coords$start] | |
board[move_coords$start] <- ' ' | |
plot_board(board) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment