Skip to content

Instantly share code, notes, and snippets.

@coolbutuseless
Created November 5, 2020 20:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save coolbutuseless/341e0564fad8e939d26e7493c1e391e3 to your computer and use it in GitHub Desktop.
Save coolbutuseless/341e0564fad8e939d26e7493c1e391e3 to your computer and use it in GitHub Desktop.
Chess game in R using the stockfish engine
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' 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