# The Queen’s Gambit - Post 4

Inspired by The Queen’s Gambit on Netflix, I’m doing a few posts on Chess in R.

This screenshot from the show explains everything:

## Forsyth-Edwards Notation (FEN)

Forsyth-Edwards Notation (FEN) is a short ascii description of the state of play of a game of chess.

See the article on wikipedia for a more complete description.

## Examples of FEN

Initial state of a chess game

rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1

This is an actual board state when opening with the queen’s gambit.

rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2

## FEN format

A FEN record contains six fields. The separator between fields is a space. The fields are:[5]

1. Piece placement (from White’s perspective). Each rank is described, starting with rank 8 and ending with rank 1; within each rank, the contents of each square are described from file “a” through file “h”. Following the Standard Algebraic Notation (SAN), each piece is identified by a single letter taken from the standard English names (pawn = “P”, knight = “N”, bishop = “B”, rook = “R”, queen = “Q” and king = “K”). White pieces are designated using upper-case letters (“PNBRQK”) while black pieces use lowercase (“pnbrqk”). Empty squares are noted using digits 1 through 8 (the number of empty squares), and “/” separates ranks.
2. Active color. “w” means White moves next, “b” means Black moves next.
3. Castling availability. If neither side can castle, this is “-”. Otherwise, this has one or more letters: “K” (White can castle kingside), “Q” (White can castle queenside), “k” (Black can castle kingside), and/or “q” (Black can castle queenside). A move that temporarily prevents castling does not negate this notation.
4. En passant target square in algebraic notation. If there’s no en passant target square, this is “-”. If a pawn has just made a two-square move, this is the position “behind” the pawn. This is recorded regardless of whether there is a pawn in position to make an en passant capture.[6]
5. Halfmove clock: This is the number of halfmoves since the last capture or pawn advance. The reason for this field is that the value is used in the fifty-move rule.[7]
6. Fullmove number: The number of the full move. It starts at 1, and is incremented after Black’s move.

## Parsing Piece Placement from a FEN record

The following code is only determining the placement of pieces on the board. All other information is ignored.

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' 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]))
}

fen <- 'rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2'
fen_to_matrix(fen)
##   a   b   c   d   e   f   g   h
## 8 "r" "n" "b" "q" "k" "b" "n" "r"
## 7 "p" "p" "p" " " "p" "p" "p" "p"
## 6 " " " " " " " " " " " " " " " "
## 5 " " " " " " "p" " " " " " " " "
## 4 " " " " "P" "P" " " " " " " " "
## 3 " " " " " " " " " " " " " " " "
## 2 "P" "P" " " " " "P" "P" "P" "P"
## 1 "R" "N" "B" "Q" "K" "B" "N" "R"

## Tighter matrix output

mat <- fen_to_matrix(fen)
mat[mat ==' '] <- '.'
cat(apply(mat, 1, paste, collapse=''), sep="\n")
## rnbqkbnr
## ppp.pppp
## ........
## ...p....
## ..PP....
## ........
## PP..PPPP
## RNBQKBNR

## Add a splash of colour with {emphatic}

# remotes::install_github('coolbutuseless/emphatic')
library(emphatic)
library(dplyr)
hl_opt_global(dark_mode = FALSE)

mat <- fen_to_matrix(fen)
mat[] <- paste0(mat, ' ')

mat %>%
hl_matrix('grey90') %>%
hl_matrix('grey10', selection = (row(.x) + col(.x)) %% 2 == 1)
     a  b  c  d  e  f  g  h
8   r  n  b  q  k  b  n  r
7   p  p  p     p  p  p  p
6
5            p
4         P  P
3
2   P  P        P  P  P  P
1   R  N  B  Q  K  B  N  R


## ggplot of the board state from a FEN

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Create a plot of the state of a chess board from a FEN string
#'
#' @param fen FEN string
#'
#' @return ggplot2 object
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_fen_plot <- function(fen) {

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Convert fen to matrix
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mat <- fen_to_matrix(fen)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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() +
theme_void() +
scale_fill_manual(values = c('grey50', 'grey95'))  +
labs(
title = "Forsyth-Edwards Notation of the Queen's Gambit in #RStats",
subtitle = fen
)

}

create_fen_plot(fen)

## Convert a matrix of chess locations into FEN

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Matrix to Forsyth-Edwards Notation notation
#'
#' This function only considers piece positions. All other information is
#' filled in with dummy values e.g. next move is always set to white (0)
#'
#' @param mat 8x8 character matrix of chess locations.
#'
#' @return Single character string containing board status in Forsyth-Edwards Notation
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
matrix_to_fen <- function(mat) {

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Sanity check board
# Empty board spaces must be a single blank character
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
stopifnot(is.character(mat))
stopifnot(nrow(mat) == 8, ncol(mat) == 8)
mat[is.na(mat) | mat == ''] <- ' '

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Collapse each row
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
rows <- apply(mat, 1, paste, collapse='')

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Replace runs of spaces with the integer count of spaces
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in 8:1) {
rows <- gsub(paste(rep(' ', i), collapse=''), i, rows)
}

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# piece position is rows concatentated with "/" as dividing character
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pieces <- paste(rows, collapse="/")

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Remaining information filled out with dummy values
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
move      <- 'w'
castling  <- 'KQkq'
enpassant <- '-'
halfmove  <- 0
fullmove  <- 1

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Concatenate all the information together - separated by spaces
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
paste(pieces, move, castling, enpassant, halfmove, fullmove)
}

fen
## [1] "rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR b KQkq c3 0 2"
mat <- fen_to_matrix(fen)
mat
##   a   b   c   d   e   f   g   h
## 8 "r" "n" "b" "q" "k" "b" "n" "r"
## 7 "p" "p" "p" " " "p" "p" "p" "p"
## 6 " " " " " " " " " " " " " " " "
## 5 " " " " " " "p" " " " " " " " "
## 4 " " " " "P" "P" " " " " " " " "
## 3 " " " " " " " " " " " " " " " "
## 2 "P" "P" " " " " "P" "P" "P" "P"
## 1 "R" "N" "B" "Q" "K" "B" "N" "R"
matrix_to_fen(mat)
## [1] "rnbqkbnr/ppp1pppp/8/3p4/2PP4/8/PP2PPPP/RNBQKBNR w KQkq - 0 1"