A. Introduction
Chess is an ancient board game that is still played in modern time. It is a strategy game that attracts many great brains in the history. In chess, there is a move named ‘castle’ which the rook and king move past each other. This move is a special move because it helps people develop their pieces very fast and put the king to safe place. In this assignment, I am trying to investigate to learn which moves or combinations of moves will lead to the ‘castle’ move. In addition, in chess, there are long castle ‘O-O-O’ and short castle ‘O-O’, in the scope of this assignment, I investigate only the short castle in the perspective of black and white. The purpose of this investigation is to help players prepare their repertoire before play the games. They can sense when their opponents are likely castle after seeing certain moves or combinations of moves.
B. Dataset
The dataset I use for this assignment is taken from Kaggle. It includes more than 20,000 games played online on lichess.org.
df <- read.csv2("G:/My Drive/UL/Assignment 3/chess_games.csv", sep =",")
C. Pre-proceed the dataset
In the dataframe, I am interested with only 1 variables which is ‘moves’. It contains all moves of the each game. The problem is that it is not separate and it is not well split into black moves and white moves. So, for the first step, I am splitting the ‘moves’ column into 2, one for black, one for white. For white, the moves are: 1, 3, 5, 7,… in the vector. For black, they are: 2, 4, 6, 8, … in the ‘moves’ vector.
#split the string in column 'moves'
df$moves <- strsplit(df$moves, " ")
#create a new column that contains the number of moves happening in each game
df$length<- lapply(df$moves, length)
#filter to get the games that have 2 moves, at least 1 move for black and 1 move for white
df <- df[df$length >= 2,]
#I write two functions in order to create the vector of moves for black and vector of moves for white
#The goal is to use the vector of each color to decode the moves in 'moves' variables
#wmv is white move vector, bmv is black move vector
wmv <- function(x) {
result <- seq(1,x,2)
}
bmv <- function(x) {
result <- seq(2,x,2)
}
df$wmv <- lapply(df$length, wmv)
df$bmv <- lapply(df$length,bmv)
After I have 2 vectors, 1 for each color, I will use it to decode. So white moves are moves in ‘moves’ that stand in position c(1, 3, 5, 7,…) and black moves are moves in ‘moves’ that stand in position c(2, 4, 6, 8,…)
decode <- function(x,y) {
result <- x[y]
return(result)
}
df$wm <- mapply(decode,df$moves,df$wmv)
df$bm <- mapply(decode,df$moves,df$bmv)
Now I have 2 columns, wm is white moves, bm is black moves. Next, I am going to find where the move castle (O-O) happens.I am interested in only the moves that happen before the O-O, therefore, I will clean all moves of black and white that happen after O-O. For the games that have no O-O, I put it 0 and later I will filter them out.
#Here is the function to find the index of move O-O in the vector. Games have no O-O will have 0 as index.
find_OO <- function(x) {
OO <- which(x == 'O-O')
if (length(OO) == 0) {
result <- 0
}
else {
result <- OO
}
return (result)
}
#After that, there are 2 columns wOO and bOO, they contains the index of O-O in white and black
df$wOO <- sapply(df$wm,FUN = find_OO)
df$bOO <- sapply(df$bm,FUN = find_OO)
#Filter out the games that have no O-O in both Black and White
main <- df[(df$wOO != 0 | df$bOO != 0),]
#Create the vector of moves until O-O happens
main$wmvOO <- lapply(main$wOO,seq)
main$bmvOO <- lapply(main$bOO,seq)
#Decode to have the expected moves for further analysis, i.e. moves until O-O happens for black and white
main$wmOO <- mapply(decode,main$wm,main$wmvOO)
main$bmOO <- mapply(decode,main$bm,main$bmvOO)
D. Association rule analysis
Here I create list of transactions for black and white.
#Because not all games have O-O in both Black and White, I still need to filter again the game where O-O did not happen.
white <- as(main[main$wOO > 0,"wmOO"],"transactions")
## Warning in asMethod(object): removing duplicated items in transactions
black <- as(main[main$bOO > 0,"bmOO"],"transactions")
## Warning in asMethod(object): removing duplicated items in transactions
inspect(head(white))
## items
## [1] {Be2,
## Bf4,
## d4,
## e3,
## Nc3,
## Nf3,
## O-O}
## [2] {a3,
## b4,
## b5,
## Bd3,
## Bg5,
## Bxf6,
## d4,
## d5,
## e4,
## Nc3,
## Nf3,
## O-O}
## [3] {Bb5,
## Bd3,
## d4,
## e4,
## f3,
## Nc3,
## Nxf3,
## O-O}
## [4] {a3,
## Bb3,
## Bc4,
## Be3,
## d3,
## e4,
## h3,
## Nc3,
## Nf3,
## O-O,
## Qf3,
## Qxe3,
## Qxf3}
## [5] {Bd2,
## Bd3,
## d4,
## dxc5,
## e3,
## Nc3,
## Nf3,
## O-O,
## Qe2}
## [6] {Bg2,
## d4,
## e4,
## exd5,
## g4,
## Nc3,
## Nf3,
## O-O,
## Qe2+}
inspect(head(black))
## items
## [1] {Be7, d6, e5, Na6, Nb4, Nc6, Nf6, O-O}
## [2] {Bc5, e5, Nc6, Nd4, Nf6, Nxf3+, O-O}
## [3] {b6, Bc6, Bd7, Be7, c5, c6, d6, dxc5, e6, Nf6, O-O}
## [4] {Be7, d5, e6, exd5, Nf6, O-O}
## [5] {Bc5, e5, Nc6, Nf6, O-O}
## [6] {Bc5, e5, h6, Nc6, Nf6, Nxe4, O-O, Qd8, Qe7, Qh4}
#Check the top 15 moves for black and white
stab_white <- crossTable ( white , measure = "support" , sort = TRUE )
View(stab_white[1:10,1:10])
stab_black <- crossTable ( black , measure = "support" , sort = TRUE )
View(stab_black[1:10,1:10])
# visualization
itemFrequencyPlot ( white , topN = 10 , type = "absolute" , main = "Item Frequency" )
itemFrequencyPlot ( black , topN = 10 , type = "absolute" , main = "Item Frequency" )
Through the tables and charts, for White, Nf3, e3, d4, Nc3 are the most popular 4 moves. For black, Nf6, Nc6, d5, e6 happen most frequently. It is true according to common sense, because ‘1. e4 e5’ and ‘1. d4 d5’ are the most common openings in chess, and developing the knights before other pieces is a also a principle.
After that, I start to generate rules for apriori algorithm. Because the goal is to have O-O in right hand side, and all games have O-O, therefore, the confidence is always 1. In chess, in order to do castle, there should be no piece between the rook and king. In other words, the bishop, knight have to move out of their starting position. And to let the bishop move, one pawn must move before. Therefore, white/black has to make at least 3 moves before doing castle. Therefore, I set the minimum length of transaction to 4 (3 mandatory moves + O-O). For support, I have some experiments with it. The purpose for this assignment is to help players notice when their opponents are going to castle, therefore, I assume 30 rules are enough for people to memorize. I decided to set the support as 0.11 so that it return around 30 rules for each color.
# generating rules.
rules.OO_white <- apriori ( data = white , minlen = 4, parameter = list ( supp = 0.11 , conf = 1 ), appearance = list ( default = "lhs" , rhs = "O-O" ), control = list ( verbose = F ))
rules.OO_white.bysup <- sort ( rules.OO_white , by = "support" , decreasing = TRUE )
inspect ( rules.OO_white.bysup )
## lhs rhs support confidence coverage lift count
## [1] {d4, e4, Nf3} => {O-O} 0.3743944 1 0.3743944 1 4559
## [2] {e4, Nc3, Nf3} => {O-O} 0.3587090 1 0.3587090 1 4368
## [3] {d4, Nc3, Nf3} => {O-O} 0.3242178 1 0.3242178 1 3948
## [4] {Bc4, e4, Nf3} => {O-O} 0.2643508 1 0.2643508 1 3219
## [5] {d4, e4, Nc3} => {O-O} 0.2407818 1 0.2407818 1 2932
## [6] {d4, e4, Nc3, Nf3} => {O-O} 0.2211546 1 0.2211546 1 2693
## [7] {c4, d4, Nf3} => {O-O} 0.1852673 1 0.1852673 1 2256
## [8] {Bd3, d4, Nf3} => {O-O} 0.1791903 1 0.1791903 1 2182
## [9] {d4, e3, Nf3} => {O-O} 0.1700747 1 0.1700747 1 2071
## [10] {c4, Nc3, Nf3} => {O-O} 0.1651474 1 0.1651474 1 2011
## [11] {c4, d4, Nc3} => {O-O} 0.1566888 1 0.1566888 1 1908
## [12] {d3, e4, Nf3} => {O-O} 0.1446169 1 0.1446169 1 1761
## [13] {Bc4, e4, Nc3} => {O-O} 0.1401002 1 0.1401002 1 1706
## [14] {c4, d4, Nc3, Nf3} => {O-O} 0.1401002 1 0.1401002 1 1706
## [15] {Be2, d4, Nf3} => {O-O} 0.1336947 1 0.1336947 1 1628
## [16] {Bc4, Nc3, Nf3} => {O-O} 0.1325450 1 0.1325450 1 1614
## [17] {Bb5, e4, Nf3} => {O-O} 0.1304919 1 0.1304919 1 1589
## [18] {Bc4, e4, Nc3, Nf3} => {O-O} 0.1299171 1 0.1299171 1 1582
## [19] {Be2, e4, Nf3} => {O-O} 0.1286031 1 0.1286031 1 1566
## [20] {c3, e4, Nf3} => {O-O} 0.1255646 1 0.1255646 1 1529
## [21] {c3, d4, Nf3} => {O-O} 0.1226082 1 0.1226082 1 1493
## [22] {Bd3, e4, Nf3} => {O-O} 0.1194876 1 0.1194876 1 1455
## [23] {Bc4, d4, e4} => {O-O} 0.1189948 1 0.1189948 1 1449
## [24] {Bd3, d4, e4} => {O-O} 0.1188306 1 0.1188306 1 1447
## [25] {e4, exd5, Nf3} => {O-O} 0.1180094 1 0.1180094 1 1437
## [26] {Be2, Nc3, Nf3} => {O-O} 0.1171881 1 0.1171881 1 1427
## [27] {Bd3, d4, Nc3} => {O-O} 0.1157099 1 0.1157099 1 1409
## [28] {c4, d4, e3} => {O-O} 0.1152172 1 0.1152172 1 1403
## [29] {Bc4, d4, Nf3} => {O-O} 0.1139854 1 0.1139854 1 1388
## [30] {Bd3, Nc3, Nf3} => {O-O} 0.1125893 1 0.1125893 1 1371
## [31] {c4, e3, Nf3} => {O-O} 0.1107826 1 0.1107826 1 1349
rules.OO_black <- apriori ( data = black , minlen = 4, parameter = list ( supp = 0.11 , conf = 1 ), appearance = list ( default = "lhs" , rhs = "O-O" ), control = list ( verbose = F ))
rules.OO_black.bysup <- sort ( rules.OO_black , by = "support" , decreasing = TRUE )
inspect ( rules.OO_black.bysup )
## lhs rhs support confidence coverage lift count
## [1] {e5, Nc6, Nf6} => {O-O} 0.2749178 1 0.2749178 1 3092
## [2] {d5, e6, Nf6} => {O-O} 0.2544679 1 0.2544679 1 2862
## [3] {d5, Nc6, Nf6} => {O-O} 0.1909843 1 0.1909843 1 2148
## [4] {Bg7, g6, Nf6} => {O-O} 0.1643994 1 0.1643994 1 1849
## [5] {c5, Nc6, Nf6} => {O-O} 0.1643105 1 0.1643105 1 1848
## [6] {d6, Nc6, Nf6} => {O-O} 0.1613764 1 0.1613764 1 1815
## [7] {Be7, Nc6, Nf6} => {O-O} 0.1603983 1 0.1603983 1 1804
## [8] {e6, Nc6, Nf6} => {O-O} 0.1602205 1 0.1602205 1 1802
## [9] {Be7, e6, Nf6} => {O-O} 0.1586201 1 0.1586201 1 1784
## [10] {d6, e5, Nf6} => {O-O} 0.1571975 1 0.1571975 1 1768
## [11] {d5, e6, Nc6} => {O-O} 0.1467058 1 0.1467058 1 1650
## [12] {c5, e6, Nf6} => {O-O} 0.1419045 1 0.1419045 1 1596
## [13] {Be7, e5, Nf6} => {O-O} 0.1303459 1 0.1303459 1 1466
## [14] {c5, e6, Nc6} => {O-O} 0.1290122 1 0.1290122 1 1451
## [15] {Be7, d5, Nf6} => {O-O} 0.1278563 1 0.1278563 1 1438
## [16] {Be7, d6, Nf6} => {O-O} 0.1274118 1 0.1274118 1 1433
## [17] {d6, e5, Nc6} => {O-O} 0.1251889 1 0.1251889 1 1408
## [18] {Be7, d5, e6} => {O-O} 0.1170979 1 0.1170979 1 1317
## [19] {Bc5, e5, Nf6} => {O-O} 0.1158531 1 0.1158531 1 1303
## [20] {c5, d6, Nf6} => {O-O} 0.1151418 1 0.1151418 1 1295
## [21] {c6, d5, Nf6} => {O-O} 0.1128301 1 0.1128301 1 1269
## [22] {d5, e5, Nf6} => {O-O} 0.1128301 1 0.1128301 1 1269
## [23] {a6, Nc6, Nf6} => {O-O} 0.1127412 1 0.1127412 1 1268
## [24] {c5, d5, e6} => {O-O} 0.1124744 1 0.1124744 1 1265
## [25] {Be7, c5, Nf6} => {O-O} 0.1116742 1 0.1116742 1 1256
## [26] {d5, e6, Nc6, Nf6} => {O-O} 0.1101627 1 0.1101627 1 1239
E. Visulization, intepretation
# Visualization rules
plot ( rules.OO_white.bysup , method = "paracoord" , control = list ( reorder = TRUE ), main = "White")
plot ( rules.OO_black.bysup , method = "paracoord" , control = list ( reorder = TRUE ), main = "Black")
Thanks to the output and the graphs, we can visualize the variation in a chessboard. Above you can see the top 5 variations that can lead to short castle. The move Nf3 is always there. For the light square bishop, it needs to be developed too. However, in the top 5 variant, it is seen to be at square c4. It means that apart from Bc4, the other bishop moves are less popular. In addition, we see that e4 and d4 appear separately and together in the top 5 variations, which mean that O-O is very common for both type of opening: e4 and d4.
For black, the same principle appears in variation 1 and 3 (pic 1 and pic 3) where the pawn e and d are developed, so are the two knights. Players with white can expect O-O when seeing those. In addition, the 2nd and 4th picutres, we can see something interesting. Those are the 2 main lines that black uses to deal with the Queen Gambit opening. Thus, whenever white see black plays main line (pic 2) or fianchetto variation (pic 4), they can expect the short castle to happen. For the 5th picture, it can either be part of Queen gambit or English opening.
F. Conclusion
Generally, the association rule helps provide some helpful insights for players in beginner level. This practice can be replicated to predict the precedent moves of long castle (O-O-O) or to investigate the following moves after h4 which is a trendy move suggested by Alpha Zero these days.