ComputerChess PDF
ComputerChess PDF
Ulysse Carion,
Junior at La Jolla High School
February 2, 2013
Abstract
The goal of this project is to create a computer program that plays
a relatively strong game of chess using programming techniques used by
the top engines in use today. The result of this project is Godot, a Java
program that plays a redoubtable game of chess.
Godot uses bitboards (64-bit numbers representing a chessboard) to
implement board representation. When searching for moves, the Godot
uses the most common methods of the day, including alpha-beta pruning,
principal variation searching, history heuristics, iterative deepening, qui-
escent searching, static exchange evaluation, and null move pruning. The
program evaluates positions by taking into account many factors that are
typically an indication of a strong position. Godot can evaluate tens of
thousands of positions per second.
Godot also has an opening book based on a large database of thousands
of very high-quality games. At the time of this writing, Godot’s opening
database has a little over 252,000 positions pre-programmed into it.
The program is based on other chess engines, especially open-source
ones such as Stockfish, Carballo, and Winglet. Despite being based on
other programs, Godot has a distinctive “style” of play that has been
repeatedly described as appearing “creative”.
Godot has achieved an Elo ranking online of above 2100 at 1-minute
chess. It has also defeated multiple FIDE1 -titled players. Though cer-
tainly not nearly as strong as commercial chess engines, Godot certainly
plays a very respectable game of chess.
i
Contents
1 Acknowledgements 1
2 Introduction 2
3 Statement of Purpose 3
4 Review of Literature 4
4.1 Board Representation . . . . . . . . . . . . . . . . . . . . . . . . 4
4.1.1 Square-Centric Methods . . . . . . . . . . . . . . . . . . . 4
4.1.2 Piece-Centric Methods . . . . . . . . . . . . . . . . . . . . 4
4.1.3 Hybrid Methods . . . . . . . . . . . . . . . . . . . . . . . 5
4.2 Bitboards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
4.2.1 Binary Operators . . . . . . . . . . . . . . . . . . . . . . . 5
4.2.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . 6
4.2.3 Zobrist Hashing . . . . . . . . . . . . . . . . . . . . . . . . 9
4.3 Searching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.3.1 Claude Shannon’s Types A & B . . . . . . . . . . . . . . 10
4.3.2 Minimax . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
4.3.3 Alpha-Beta Pruning . . . . . . . . . . . . . . . . . . . . . 12
4.3.4 Quiescent Searching . . . . . . . . . . . . . . . . . . . . . 13
4.3.5 Iterative Deepening, Principal Variation, and History Heuris-
tic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4.3.6 Null Move Pruning . . . . . . . . . . . . . . . . . . . . . . 16
4.4 Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
4.4.1 Material . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.4.2 Pawn Structure . . . . . . . . . . . . . . . . . . . . . . . . 18
4.4.3 Piece-Square Tables . . . . . . . . . . . . . . . . . . . . . 19
4.4.4 King Safety . . . . . . . . . . . . . . . . . . . . . . . . . . 19
5 Development 21
5.1 Version 0.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
5.2 Version 0.2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.3 Version 0.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
5.4 Version 0.4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.5 Version 0.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.6 GodotBot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
6 Results 36
7 Recommendations 37
ii
1 Acknowledgements
I’d like to thank Mr. Greg Volger for helping me write my notebook, Dr. David
Groce for his support in the creation of this project’s presentation, and Mr.
Martin Teachworth for guiding me and acting as my mentor.
In addition, I’d like to thank the chess programming community for helping
me with more technical issues I faced.
1
2 Introduction
“We stand at the brief corona of an eclipse - the eclipse of certain
human mastery by machines that humans have created.”
—Stephen Levy
2
3 Statement of Purpose
Chess has always been at the heart of computer science and artificial intelligence.
The goal of this project is to research and develop a chess-playing program that
is unique and plays a strong game.
Creating a program that can compete at the level of play of current top-level
programs is almost impossible and not the objective of this project—the goal is
to create an original program that uses a carefully-selected combination of the
hundreds of ways to assault the challenge of teaching a computer to play chess.
3
4 Review of Literature
Since the beginning of chess-playing programs in 1956, computer scientists and
avid programmers have created hundreds of chess engines of differing strengths.
Despite the large amount of different chess programs, most programmers choose
to use the same time-tested techniques in almost all of their creations. Vasik
Rajlich, creator of Rybka, one the strongest chess programs ever, remarked that
“when two modern top chess programs play against each other maybe 95% of the
programs are algorithmically the same. What is classing is the other 5%” (Riis,
2012).
There exist three major elements to a chess program that must be carefully
chosen and experimented with: representation, searching, and evaluation. A
strong combination of these three elements is what Rajlich calls the “classing”
elements of a program.
4
centric methods are optimized for situations where we must find out where a
given piece is, but are far from optimal if we must find out what, if anything,
stands on a given square. The very first chess programs used such a method
called “piece-lists”, wherein a list storing where each piece stands on the board
is maintained. These piece-lists were optimal in the early days of computing,
when memory was very valuable, because they required very little space on the
computer. (Kotok, 1962). The modern variant of piece-centric representation,
bitboards, will be explained later in section 4.2 (Hyatt, 2004).
4.2 Bitboards
Bitboards are a piece-centric board representation that remains popular to this
day. Because most of the strongest engines of this day, including Houdini,
Critter, and Stockfish (respectively 1st, 2nd, and 3rd in world ranking at the
time of this writing), use bitboards, it is worthwhile to understand how they
work.
Bitboards are optimized for computers because they work directly with bit-
wise operations. Bitwise operations are simple processes that a computer pro-
cessor uses to work with binary numbers. The five major bitwise operations are
the bitwise AND, bitwise OR, bitwise XOR, bitwise left shift, and bitwise right
shift.
10110100
& 10011000
10010000
Notice how in the bitwise operator AND, we set the nth bit to “1” if the nth
bit of the first number is “1” and the nth bit of the second number is “1” as
well. Similar logic works for the bitwise OR operator, which sets the nth bit to
“1” if the nth bit of the first number or the second number is “1”. Thus, taking
the same example as last time,
5
10110100
| 10011000
10111100
Finally, the XOR operator, or the “exclusive or” operator, sets the nth bit
to “1” if the nth bit of the first or second (but not both) is “1”. With the same
example as before3
10110100
⊕ 10011000
00101100
The left-shift and right-shift operators are much simpler than the AND, OR,
and XOR operators. These operators simply move the bits of a number left or
right (as they appear when written out). Thus, 00100 left-shifted by 2 gives
10000, and right-shifted by 2 gives 00001. Note that the left-shift and right-
shift operators do not “replace” numbers if they are shifted out; new bits being
inserted at either end of a number being shifted are unconditionally 0. If we
work with a number that has strictly eight bits,
10001010
left-shift by 1 = 00010100
(Note how the 1 at the left-end of the number was not “replaced” (“carried”)
back into the result—a zero will unconditionally be placed at the end of the
number.)
11001010
right-shift by 1 = 01100101
(Note how 0 was inserted where there was an “opening” as the number was
shifted right.)
All of these operators have symbols in programming languages. In C (and
related languages, such as C++ or Java) the operations AND, OR, and XOR
are represented as “&”, “|”, and “ˆ”, respectively. In Java, left-shift is <<, and
right-shift is >>> 4 .
4.2.2 Implementation
Bitboards are considered the best way to represent a chessboard because they
are optimized for usage with bitwise operations. The idea is to represent the
64 squares on a chessboard with the 64 bits in a 64-bit number. In Java, these
numbers are called “longs” (in C, these are called “unsigned long longs”). We
3 The example below denotes XOR with the symbol ⊕, which is also commonly used to
represent the logical XOR operator; however, this symbol is not used in any programming
languages.
4 Many languages represent right-shifting with >>, but in Java this symbol is used for
a variant of bit-shifting that preserves the state of the leftmost bit, an operation called an
arithmetic shift.
6
can represent the bottom-left square of a chess board as the last (rightmost) bit
of a long, and assign the top-right square to the first (leftmost) bit, as shown in
Figure 1.
57 58 59 60 61 62 63 64
49 50 51 52 53 54 55 56
41 42 43 44 45 46 47 48
33 34 35 36 37 38 39 40
25 26 27 28 29 30 31 32
17 18 19 20 21 22 23 24
9 10 11 12 13 14 15 16
1 2 3 4 5 6 7 8
Figure 1: The position of each bit; board shown from White’s perspective.
We can represent where any type of piece is using a single number thanks
to bitboards. For instance, if we wanted to represent the position of the white
bishops in the initial setup of the board, we would use the number “0000 [...]
000100100”, which would correspond to the board shown in Figure 2.
8
0Z0Z0Z0Z
7
Z0Z0Z0Z0
6
0Z0Z0Z0Z
5
Z0Z0Z0Z0
4
0Z0Z0Z0Z
3
Z0Z0Z0Z0
2
0Z0Z0Z0Z
1
Z0A0ZBZ0
a b c d e f g h
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 1 0 0 1 0 0
7
In the previous example, we saw how we could use a single bitboard to repre-
sent the white bishops. In the more general case, we have to represent 6 types of
pieces for 2 different colors, resulting in 12 total bitboards minimum. However,
most programs also use three extra bitboards, representing white pieces, black
pieces, and all pieces. Thus, we use 15 bitboards to represent a chess position.
There exist other factors as well (such as whose turn it is to move), but these
are trivial to implement.
Representing a board as a number is especially useful when we want to
evaluate a position. If we want to find all white pawns that are on black’s side
of the board, we can do this using one quick bitwise operation. Let’s consider
the pawn position shown in Figure 3.
8
0Z0Z0Z0Z
7
Z0Z0Z0Z0
6
0Z0O0Z0Z
5
Z0Z0ZPZ0
4
0Z0Z0Z0Z
3
O0O0Z0O0
2
0O0Z0Z0Z
1
Z0Z0Z0Z0
a b c d e f g h
If we wanted to find all pawns that are on black’s half the board, we would
simply construct a bitboard representing black’s side and AND this number
8
with our white-pawns bitboard. The result is a bitboard of all white pawns on
black’s side, as shown in Figure 5.
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 1 0 0
& =
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
In one fell swoop, we have can find all white pawns on the opponent’s side
of the board. Bitboard programs are very fast because they use many of these
bitboards to quickly find pieces. This is only a brief summary of a few of the
tricks we can enjoy with bitboards—there exist countless methods people have
found to optimize their programs thanks to fancy operations with this useful
method.
Z = Z ⊕ P awnAt[B4]
Z = Z ⊕ P awnAt[C5]
To undo this change, we could repeat the same process. Because XOR is com-
mutative (A ⊕ B ⇔ B ⊕ A) and associative (A ⊕ (B ⊕ C) ⇔ (A ⊕ B) ⊕ C), it
doesn’t matter what order XOR things “in” or “out”, and we can always undo
our actions by simply repeating the XOR we have executed.
9
By using a Zobrist hash to represent our board position, to check for a
repeated position we simply need to maintain an array of previous Zobrist hashes
of our position and check to see if the same number appears three times. This
approach makes our process much faster and easier to implement, if at the price
of a (very rare) case of a hash collision5 (Zobrist, 1970).
4.3 Searching
Board representation is only the foundations for the actual point of a chess
program: searching. Searching is the process a chess program undergoes to look
for and find the best move available in its position.
4.3.2 Minimax
The basis of chess artificial intelligence was explored far before computers ex-
isted; in 1928 John von Neumann described an algorithm nowadays known as
minimax that could help computers make decisions in games including chess (von
Neumann, 1928).
Minimax works by considering two players, aptly named “min” and “max”.
Min’s goal is to try minimize the amount of points Max gets. Max’s goal,
obviously, is to try to maximize his score. Max’s technique is to attribute a
5 The likelihood of a collision in a Zobrist hash is remarkably unlikely—so unlikely that
Robert Hyatt (creator of Crafty) and Anthony Cozzie (creator of Zappa) remark that the
danger of collisions “isn’t a problem that should worry anyone”
10
score to every move, and then choose the one with the highest score. Min’s
technique is precisely the same thing, except he chooses the move the lowest
score. Max finds out how good a score is by asking Min what he thinks of it,
and Min finds out how good a score is by asking Max the very same question.
Of course, there is a problem here—if Max and Min just ask each other what
the score is all day, we will never find out what Max’s move is. To fix this, we
simply say that when we reach a certain search depth, we stop searching and
just give an estimation of how good a position is using an evaluation function.
In psuedocode, minimax is shown in Figure 6.
11
Figure 7: Minimax as a Tree-Searching Algorithm
12
function minimax(depth)
if endOfGame() ∨ depth = 0 then
return estimation()
end if
max ← −∞
for all moves do
makeTheMove()
score ← −minimax(depth − 1)
unmakeTheMove()
if score > max then
max ← score
end if
end for
return max
end function
13
function alphabeta(α, β, depth)
if endOfGame() ∨ depth = 0 then
return estimation()
end if
for all moves do
makeTheMove()
score ← alphabeta(−β, −α, depth − 1)
unmakeTheMove()
if score ≥ β then
return β
end if
if score > α then
α ← score
end if
end for
return α
end function
8
0Z0Z0ZkZ
7
Z0Z0ZpZ0
6
pm0o0m0Z
5
M0ZpZ0o0
4
PO0OpZ0l
3
ZBsbO0LP
2
0Z0Z0ZKZ
1
Z0Z0S0Z0
a b c d e f g h
estimation function (a “stand pat” score) in hopes of faster performance by failing hard or by
14
function qsearch(α, β)
standP at ← estimation()
if standP at ≥ β then
return β
end if
if standP at > α then
α ← standP at
end if
for all capturing moves do
makeTheMove()
score ← −qsearch(−β, −α)
unmakeTheMove()
if score ≥ β then
return β
end if
if score > α then
α ← score
end if
end for
return α
end function
improving alpha.
15
because alpha-beta works fastest if the best moves are found first (if good moves
are found early on, more moves will be eliminated due to a narrower “window”
between alpha and beta), it is oftentimes worthwhile to spend a little extra
effort to identify moves that appear to have a lot of potential.
Principal Variation Searching (PVS) identifies the “best move” at the current
depth (the move that raises alpha) and stores it in order for it to be found again
at the next search depth. Oftentimes (in fact, most of the time), this best move
(the prinipal variation) remains the best move even as we increase our depth-
search, so after we find the principal variation at a low depth (which takes little
time), we quickly find the best move at a high depth because we begin our
search with the principal variation (Marsland and Campbell, 1982).
History heuristic is the technique of maintaining two 64x64 arrays (one for
each side) that represents “from” and “to” squares for moves that cause a value
to be returned (be it due to beta cutoff or alpha improvement). When we
progress to the next search depth, we will prioritize moves with high values
in the history heuristic arrays because these are more likely to cause a beta-
cutoff or alpha improvement (which, either way, makes the process of searching
faster). History heuristic is a controversial method–though simple to implement,
top-level programs avoid it because it ceases to be useful at very high search
depths (Schaeffer, 1989).
that a null move is detrimental (this is called the “null move observation”) to their advantage.
16
8
0ZkZ0Z0Z
7
Z0O0Z0Z0
6
0Z0J0Z0Z
5
Z0Z0Z0Z0
4
0Z0Z0Z0Z
3
Z0Z0Z0Z0
2
0Z0Z0Z0Z
1
Z0Z0Z0Z0
a b c d e f g h
we attempt a null move; zugzwang positions where there exists even a minor
piece are extraordinarily rare and so many programs simply assume that a
position with a piece on the board cannot have zugwzwang, though high-level
programs may spend more time identifying zugzwang positions.
4.4 Evaluation
Position evaluation is the third and final element to a chess engine. Search
optimizations can help a program go faster, but only a strong evaluation function
can make it “stronger” (in terms of its knowledge of chess). Deep Blue, the
famous IBM computer that defeated world champion Garry Kasparov in a 6-
game match in 1997, had an extremely complex position evaluator that had
been developed for years by multiple grandmasters.
All searching algorithms eventually reach rock bottom and stop searching
any further; instead of going to deeper search depths, they call an evaluator
that “estimates” how good a position is for whoever’s turn it is to move. If
a position is advantageous for the side to move, the evaluator should return a
positive value; if the position is not good for the side to move, the result should
be negative. A dead draw should return 0.
Most chess players are familiar with the idea of certain pieces being “worth
more” than others. For example, it is relatively common knowledge that a rook
is more or less worth five pawns. However, some other elements to playing, such
as having pieces in good places, are rarely worth as much as a pawn is, even in
the most contrived of scenarios. So how do we represent things that are worth
less than a pawn? The key is to come up with an imaginary unit, the centipawn,
and use this value when evaluating. The advantage to using centipawns is that
we can easily handle things worth less than a pawn (for instance, something
17
worth a quarter of a pawn is worth 25 centipawns), and it allows us to work
only with integers, which are faster to work with than floating-point decimals
are.
A strong evaluation function can take many forms, but always works by
assigning a bonus for some element of a position that brings about an advantage
to the person whose turn it is to move, and assigning penalties if the opponent
has good elements to his/her position. Most evaluators take many factors into
consideration when evaluating a position.
4.4.1 Material
By far the most important aspect of position evaluation is material—having
more pieces on the board. However, specifically determining what we should
assign as values remains a topic of debate. A very popular essay on the matter
comes from GM Larry Kaufman, who assigns the values Pawn = 1, Knight =
31⁄2, Bishop = 31⁄2, Rook = 5, and Queen = 93⁄4. Kaufman also specifically
encourages an additional “bonus” of 1⁄2 for having both bishops (the “bishop
pair”) (Kaufman, 1999).
Some programmers may chose to assign variable values for pieces—for ex-
ample, it may be desirable to make knights be worth more when a position is
“closed” (where there is little open space on the board) and make bishops more
valuable in “open” positions (where the center of the board is open).
18
4.4.3 Piece-Square Tables
An easy way to encourage a chess program to put its pieces in good squares is
to use Piece-Square Tables. These tables are in fact arrays that give a given
bonus (or penalty) for a piece on a specific square. For example, a piece-square
table for white pawns would likely give bonuses for pawns in the center and
for those about to promote. A piece-square table for knights would probably
penalize having the knight along the edge of the board (“a knight on the rim is
grim”).
An example of a piece-square table for white pawns could look like the
array shown in Figure 14. This particular piece-square table encourages rapid
development of the center pawns and gives bonuses for pawns that have moved
forward, especially if they are closer to the central files. Such a piece-square
table will likely encourage good piece positioning.
0 0 0 0 0 0 0 0
5 10 15 20 20 15 10 5
4 8 12 16 16 12 8 4
3 6 9 12 12 9 6 3
2 4 6 8 8 6 4 2
1 2 3 −10 −10 3 2 1
0 0 0 −40 −40 0 0 0
0 0 0 0 0 0 0 0
19
as the current reigning champions, it will certainly represent its own unique
take at the challenge. The decisions I make when developing my program will
determine what techniques will be useful to me further down the line. Each
chess program has its own defining characteristics in representation, searching,
and evaluation that distinguish it from others and perhaps adds a very human
aspect to a deterministic process.
20
5 Development
5.1 Version 0.1
My first version of my chess engine is finished by early November. This basic
program uses basic searching techniques: alpha-beta searching with bitboards,
as well as basic evaluation (based on Tomasz Michniewski’s simplified evaluation
function). During this time I reached preliminary results and very high speeds
when searching (especially when compared to my very first attempt, using an
8x8 array with only minimax).
Due to the fact that I spent much of my time waiting for my program to
play when testing old versions, I decided to name my engine Godot (in reference
to Samuel Beckett’s play Waiting for Godot).
One particularly interesting test to assure that a move generation works
correctly is perft. This is simply a test case where the program is given a position
and is asked to generate all possible legal moves at progressively increasing
depths. For instance, if we begin from the starting position, the number of legal
moves we have is:
Depth Moves
1 20
2 400
3 8902
4 197281
5 4865609
6 119060324
21
8
rmblka0s
7
opo0Z0op
6
0Z0Z0o0Z
5
Z0Z0Z0Z0
4
0ZNZ0OnZ
3
Z0Z0Z0Z0
2
POPZ0ZPO
1
SNAQJBZR
a b c d e f g h
22
Depth Time (s) Nodes Rate (node/s)
1 .042 43 1023.810
2 .114 157 1377.193
3 .146 1198 8205.479
4 .218 3829 17564.220
5 .764 29777 38975.130
6 7.319 340529 46526.711
program working more effectively with respect to time, at the price of having
to do more work for each node. Despite the improvements, Godot still cannot
play even semi-decently. More work must be done.
23
Ulysse Carion vs. Godot
5 minute game
The game begins in a rather usual way, though Godot’s lack of an opening reper-
toire is obvious from move 1.
1 e4 Nc6 2 Nf3 Nf6 3 Nc3 e6 4 d4 Bb4 5 e5 Ng4 6 Bd2
8
rZblkZ0s
7
opopZpop
6
0ZnZpZ0Z
5
Z0Z0O0Z0
4
0a0O0ZnZ
3
Z0M0ZNZ0
2
POPA0OPO
1
S0ZQJBZR
a b c d e f g h
At this point Godot makes a flat-out blunder with the startlingly dumb move
6. . . NXd4??
I happily accept the sacrifice.
7 NXd4 Qh4
Unfortunately for me I was even more short-sighted than Godot was here;
8 Bb5?? QXf2m
8
rZbZkZ0s
7
opopZpop
6
0Z0ZpZ0Z
5
ZBZ0O0Z0
4
0a0M0ZnZ
3
Z0M0Z0Z0
2
POPA0lPO
1
S0ZQJ0ZR
a b c d e f g h
24
Godot 0.2 vs. Godot 0.3
1 minute game
8
rZ0ZkZ0s
7
opo0Zpop
6
nZ0Z0m0Z
5
ZNZpZpZ0
4
0a0O0Z0Z
3
Z0O0ONZ0
2
PO0Z0OPO
1
S0A0J0ZR
a b c d e f g h
Godot 0.3 here plays a clever . . . c6!, which forces Godot 0.2 to juggle too
many balls at once ...
10. . . c6! 11 cXb4 cXb5 12 O-O NXb4
8
rZ0ZkZ0s
7
opZ0Zpop
6
0Z0Z0m0Z
5
ZpZpZpZ0
4
0m0O0Z0Z
3
Z0Z0ONZ0
2
PO0Z0OPO
1
S0A0ZRJ0
a b c d e f g h
... and Black is now up a pawn and has a bishop for a knight. The game then
drifts into an uncomfortable endgame for white.
13 Bd2 Nd3 14 a4 bXa4 15 Ne5 NXe5 16 dXe5 Ne4 17 Be1 Nc5 18
Rd1 O-O-O 19 Bb4 Nb3 20 Rd3 f6 21 Rc3+ Kb8 22 eXf6 gXf6 23 Be7
Rc8 24 BXf6 Rhe8 25 Rd1 Rc6 26 RXc6 bXc6 27 Kf1 Kc7 28 Bc3 Kd6
29 Ke2 f4 30 g3 f Xe3 31 f4 Kc5 32 Bf6 Nd2 33 Bc3 Nb3 34 Bf6 Nd2 35
Bc3 Nc4 36 Bd4+ Kd6
25
8
0Z0ZrZ0Z
7
o0Z0Z0Zp
6
0Zpj0Z0Z
5
Z0ZpZ0Z0
4
pZnA0O0Z
3
Z0Z0o0O0
2
0O0ZKZ0O
1
Z0ZRZ0Z0
a b c d e f g h
In the style of Cronus with Uranus and Zeus with Cronus, Godot 0.3 defeats
its predecessor with great gusto.
37 b4 aXb3 38 Bc3 c5 39 Ra1 d4 40 Be1 b2 41 Rb1 Rb8 42 Ba5 Na3
43 Rd1 Kd5 44 Bc3 b1Q 45 RXb1 RXb1 46 BXd4 cXd4 47 Kf3 Rf1+ 48
Ke2 Rf2+ 49 Kd1 Nc2 50 f5 d3 51 Kc1 e2 52 Kb2 e1Q 53 g4 Qa1+ 54
Kb3 Qa3m
8
0Z0Z0Z0Z
7
o0Z0Z0Zp
6
0Z0Z0Z0Z
5
Z0ZkZPZ0
4
0Z0Z0ZPZ
3
lKZpZ0Z0
2
0ZnZ0s0O
1
Z0Z0Z0Z0
a b c d e f g h
Godot 0.3 Victorious
26
1 e4 Nf6 2 e5 Nd5 3 d4 Nc6 4 c4 Nb6 5 d5 NXe5 6 b3 e6 7 Bb2 d6 8 dXe6
BXe6 9 f4 Ng6 10 g3 d5 11 c5 BXc5 12 Qc2 Bb4+ 13 Nd2 O-O 14 f5 BXd2+
15 KXd2 Qg5+ 16 Kd1 QXf5 17 Bd3 Qg4+ 18 Ne2 Qf3 19 Kd2 Rac8 20 Rhf1
Qh5 21 h4 Nd7 22 Nd4 Nge5 23 Be2 Qh6+ 24 Ke1 Qe3 25 Nf5 BXf5 26 QXf5
QXg3+ 27 Qf2 QXf2+ 28 RXf2 Rce8 29 Kf1 Re6 30 Rc1 Rc8 31 Kg1 Rg6+ 32
Kh1 Rd6 33 h5 d4 34 Rg2 d3 35 Bg4 d2 36 Rd1 NXg4 37 RXg4 f6 38 h6 g5
39 Rg2 Kf7 40 RdXd2 RXd2 41 RXd2 Ne5 42 Rf2 Ng4 43 Rg2 NXh6 44 Rf2
Ng4 45 Rf3 Ke6 46 Bc3 Ne5 47 Re3 Kd5 48 Re2 Re8 49 Be1 f5 50 Rd2+ Ke4
51 Bf2 a6 52 Re2+ Kd5 53 Rc2 Re7 54 Bg1 f4 55 Rd2+ Ke4 56 Re2+ Kd5 57
Rh2 g4 58 Rd2+ Ke4 59 Rh2 g3 60 Rh6 f3 61 Rh4+ Kf5 62 Rd4 f2
8
0Z0Z0Z0Z
7
Zpo0s0Zp
6
pZ0Z0Z0Z
5
Z0Z0mkZ0
4
0Z0S0Z0Z
3
ZPZ0Z0o0
2
PZ0Z0o0Z
1
Z0Z0Z0AK
a b c d e f g h
8
0Z0Z0Z0Z
7
Z0Z0ZkZ0
6
0Z0o0Z0Z
5
Z0Z0Z0J0
4
0Z0ZpZ0Z
3
s0Z0Z0Z0
2
0Z0Z0o0Z
1
Z0Z0Z0Z0
a b c d e f g h
27
• Godot v. GM Evan Ju (2353) (Loss against chess.com’s then-#1 player.)
1 Nc3 g6 2 Nf3 Bg7 3 d4 d6 4 e4 Nf6 5 Bf4 O-O 6 e5 Nfd7 7 Bc4 Nb6 8 Qe2
NXc4 9 QXc4 c6 10 eXd6 eXd6 11 Bg5 Re8+ 12 Kf1 Qb6 13 Na4 Qa5 14 Bd2
Qh5 15 Re1 RXe1+ 16 KXe1 b5 17 Qb4 bXa4 18 QXd6 Nd7 19 QXc6 Rb8 20
Kf1 Nf6 21 Kg1 Bg4 22 Qd6 RXb2 23 Qd8+ Bf8 24 QXf6 BXf3 25 gXf3 Qh3
26 Qf4 Rb1+ 27 Bc1 Bh6 28 QXh6 QXh6 29 Kg2 RXc1 30 RXc1 QXc1 31 a3
QXc2 32 d5 Qb3 33 f4 QXd5+ 34 Kg3 Qb3+ 35 Kg2 QXa3 36 h3 Qb3 37 h4
a3 38 f5 a2 39 f Xg6 a1Q 40 gXh7+ KXh7 41 h5 Qd5+ 42 f3 Qad4 43 h6 Qg5+
44 Kf1 Qd1+ 45 Kf2 Qdd2+ 46 Kf1 Qgg2m
8
0Z0Z0Z0Z
7
o0Z0ZpZk
6
0Z0Z0Z0O
5
Z0Z0Z0Z0
4
0Z0Z0Z0Z
3
Z0Z0ZPZ0
2
0Z0l0ZqZ
1
Z0Z0ZKZ0
a b c d e f g h
The results of online play indicated two things–first, that Godot was def-
initely playing at an extremely high level (to the extent that I was no longer
capable of judging how well it plays purely by looking at its moves), and second,
that Godot could still be improved. 1-minute chess is highly tactical, and that
humans could defeat it meant that it was not searching deeply enough. I still
had many optimizations I could explore at this point. Also, it was becoming
quite evident that my evaluation was not very good–by analysing my games us-
ing a stronger program, it became evident that Godot was not putting enough
emphasis on things such as pawn structure or king safety.
28
Depth Time (s) Nodes Rate (node/s)
1 .138 58 420.290
2 .173 483 2791.908
3 .249 1336 32610.281
4 .425 6271 33968.309
5 .52 9424 5365.462
6 3.928 103167 18123.077
7 13.785 377588 27391.222
Passed Pawns Godot offers a 20 cp bonus for a pawn that is passed (that is,
no pawn ahead of it is in an adjacent file).
Rooks Behind Passed Pawns Godot offers an additional 20 cp bonus for a
rook that is behind and on the same file as a passed pawn.
Pawn Shield Godot gives 9 cp for a pawn immediately ahead and adjacent to
the king, and also gives 4 cp for a pawn two ranks ahead of the king.
Nearby Threats Godot penalizes for having any opponent’s piece too close
to the king.
Piece Placement Godot also uses piece-square tables, but unlike before they
have a relatively small impact on evaluation.
This new evaluator does not cause any measurable slowdown in searching,
but certainly does cause the computer to play better.
I also implemented delta pruning, a technique that attempts to speed up
searching in quiescent searching by returning alpha if one side is so far above
alpha that there is no hope of reaching an equalizing position. To my surprise,
this optimization slightly slowed down my program–the extra work necessary
to test the “potential value change” in a position, as well as the extra logic
involved made the optimization not worthwhile in my program.
At this point, the new program plays considerably better than I do, mak-
ing testing the program in specific scenarios very difficult. Thankfully, a very
strong chess player, Varun Krishnan (a USCF11 Life Master), volunteered to
play against my program. The game went as:
10 cp = Centipawns (see Section 4.4)
11 United States Chess Federation
29
1 e4 Nc6 2 d4 d5 3 e5 e6 4 Nf3 Bb4+ 5 c3 Ba5 6 Bd3 Nge7 7 O-O
O-O 8 BXh7+ KXh7 9 Ng5+ Kg8 10 Qh5 Re8 11 QXf7+ Kh8 12 f4
Nb8 13 Rf3 Nf5 14 Rh3+ Nh6 15 RXh6+ gXh6 16 Qh7m
Krishnan remarked that my program mostly lacked was an opening book—
the moves 1 e4 Nc6 2 d4 d5 3 e5 e6 lead to a position that strongly favors white.
Shredder’s opening database, which has nearly 3,000 positions in the position
following 1 e4 e6 2 d4 d5 3 e5 (the French Defence, Advance Variation), only
finds 2 games where the move . . . Nc6 was made. It was apparent that it was
time to create an opening book.
Although many opening databases exist online and in other programs, I
decided to create my own opening database. To do this, I needed a large sample
size of high-quality games. I found a large database of games from the Dutch
Chess Championship, ranging 25 years from 1981 to 2006. The database had a
total of 104,020 games.
To parse the database, the first step was to convert the PGN-formatted
database into something my program could use. A PGN game takes the format
of:
[Event ""]
[Site ""]
[Date "1994.??.??"]
[Round "?"]
[White "Broll, Egon"]
[Black "Fischer, Max Dr(33)"]
[Result "1-0"]
[WhiteElo "1625"]
[ECO "A10"]
1.c4 b6 2.g3 Bb7 3.Nf3 e6 4.Bg2 d6 5.O-O g6 6.d4 Bg7 7.Nc3 Nf6
8.Bg5 O-O 9.Qd2 Qd7 10.Bh6 Re8 11.Bxg7 Kxg7 12.Rae1 c5 13.e4
cxd4 14.Qxd4 Nc6 15.Qd2 e5 16.Nb5 Red8 17.a3 a6 18.Nc3 Na5
19.Qd3 Rac8 20.Nd5 Nxc4 21.b3 Bxd5 22. exd5 Nxa3 23.Qxa6 Nc2
24.Rd1 b5 25.Ng5 Nb4 26.Qa5 Nc2 27.Bh3 Ng4 28.f3 Nce3 29.Bxg4
Nxg4 30.fxg4 Rf8 31.Qb4 h6 32.Nf3 Rce8 33.Rc1 e4 34.Nd4 e3
35.Qxb5 Qa7 36.Ne2 Rb8 37.Qd3 Rfe8 38.Rf4 Qa3 39.Rcf1 Re7 40.Qc3+
Re5 41.Rxf7+ Kg8 42.Qc7 1-0
Using a RegEx (Regular Expression) (a type of string that can find and re-
place character sequences), we can parse the entire database into a long sequence
of lines, each of them representing a game. I removed any character sequence
matching the RegEx:
[\\{.+?\\} | \\d+\\. | \\s\\s+]
Which removes all comments (which are enclosed between {. . .}), numbers
(which take the form of digits followed by a period), and extra whitespace.
I also completely ignore any line that begins with ”[”. The aforementioned
PGN now becomes the line:
30
c4 b6 g3 Bb7 Nf3 e6 Bg2 d6 O-O g6 d4 Bg7 Nc3 Nf6 Bg5 O-O [...]
At this point, all we have is a long list of moves in what is called algebraic
notation 12 . My goal at this point was to create a large database of positions and
the most common move in that position. Though it may be tempting to simply
create a large table of lines of algebraic notation and replies in that position,
this would be inadequate. Such an implementation would not consider the move
sequences 1 e4 e5 2 Nc3 and 1 Nc3 e5 2 e4 to be equivalent, despite the fact
that they are13 .
For my program to work correctly with transpositions, it must enter the
moves into the actual chess-playing logic, and ask the chess program itself if two
lines are equivalent. This means I must convert SAN into computer-formatted
moves. Algebraic notation is not optimal for computers, who must try to convert
a move represented in legible format into a move usable by the program. This is
due to the fact that algebraic notation accounts for the “ambiguity” of a move.
Normally, a move representing a rook moving to f3 would be represented as Rf3
in algebraic notation, but if there are two rooks that can move there, then we
disambiguate by specifying the file or rank (preferably the former) of the piece
moving. This is demonstrated in Figure 21.
8 0Z0Z0Z0Z 8 0Z0Z0Z0Z
7 Z0Z0Z0Z0 7 Z0Z0Z0Z0
6 0Z0Z0Z0Z 6 0Z0Z0s0Z
5 Z0Z0Z0Z0 5 Z0Z0Z0Z0
4 0Z0Z0Z0Z 4 0Z0Z0Z0Z
3 ZrZ0Z0Z0 3 ZrZ0Z0Z0
2 0Z0Z0Z0Z 2 0Z0Z0Z0Z
1 Z0Z0Z0Z0 1 Z0Z0Z0Z0
a b c d e f g h a b c d e f g h
Figure 21: With SAN, Rb3→f3 is Rf3 in the first position, but Rff3 in the latter.
31
My final implementation has a very large mapping of positions with moves,
giving Godot the capacity to respond to 252,360 different positions without ever
having to think. Effectively, this means that Godot will likely be able to play
without searching for the first 7 moves of the game.
Krishnan against volunteered to play against Godot, this time in the format
of a 1-minute game.
32
Nb1??
8
0sbZ0skZ
7
o0Z0opap
6
0o0l0mpZ
5
ZBoPo0Z0
4
PZ0ZPZ0Z
3
Z0Z0A0ZP
2
0OPZ0OPZ
1
SNZQZRJ0
a b c d e f g h
8
0sbZ0skZ
7
o0Z0Zpap
6
0o0Z0mpZ
5
OBoqo0Z0
4
0Z0Z0Z0Z
3
Z0Z0APZP
2
0O0Z0ZPZ
1
SNZQZRJ0
a b c d e f g h
With only a few seconds on the clock and Godot with still over two thirds
of his time remaining, white makes another final blunder.
18 Nc3 Qe6 19 Ba4 c4 20 aXb6 aXb6 21 Qe2 Nh5 22 Bb5??
33
Ng3!
8
0sbZ0skZ
7
Z0Z0Zpap
6
0o0ZqZpZ
5
ZBZ0o0Z0
4
0ZpZ0Z0Z
3
Z0M0APmP
2
0O0ZQZPZ
1
S0Z0ZRJ0
a b c d e f g h
White loses on time.
Other games demonstrate the same pattern—Godot wins most of its games
by playing relatively solid moves consistently, winning by taking advantage of
its opponent’s blunders. Against a very careful opponent, Godot struggles to
outperform its rivals, but in rapid chess, when perfect opening play and fast
thinking have a powerful psychological effect, the computer can incite mistakes
in the humans that face it.
5.6 GodotBot
GodotBot is the nickname gave to the new, final online version of Godot. Playing
on chess.com, GodotBot is how I evaluate exactly how good Godot really is by
putting it up against chess players online from around the world.
34
The main challenge with GodotBot is that it must communicate over the
internet. I chose to implement this by using Selenium, a tool for simulating
a web browser. Specifically, I used Selenium WebDriver with ChromeDriver,
which allows me to automate a Chrome browser in Java. The algorithm I used
is shown in Figure 22.
Set up
Wait for
game to
begin
Make move
Game
yes over? no
35
6 Results
The final result of the project is Godot, a chess program that is surprisingly
strong. In sifting through the many options available to me, I made Godot
into a bitboard-based program relying on a combination of null-move pruning,
iterative deepening, quiescent searching, static exchange evaluation, alpha-beta,
PVS, and history heuristics.
For the vast majority of chess players, Godot would be very challenging to
play against, especially in short time controls. However, using Godot to set up
a position and find the best move available takes only two lines of code. The
Godot source code is very user-friendly.
Though Godot is strong, it will not be able to defeat current world-class pro-
grams. Godot is not Deep Blue, and will almost certainly lose against Stockfish,
Rybka, Critter, or other top-level engines.
The program is
• Fast — Godot can generate in excess of 2.4 million nodes per second, and
can evaluate over 30 thousand positions per second.
• Fine-Tuned — Godot’s position evaluation takes into account material,
pawn structure, king safety, rook-pawn cooperation, and piece placement
to determine how good a position is.
• Unique — No single program is the basis for the project’s result; Godot
is a combination of techniques inspired from world-class programs and is
original.
36
7 Recommendations
Though Godot is certainly a strong program, there exist countless optimizations
and improvements to explore. If I could extend my program, I would look into
re-writing Godot in a faster language than Java—a C++ version of my program
would likely run considerably faster. In addition, I would also like to look into
other popular optimizations, such as transposition tables, aspiration windows,
razoring, and others.
37
References
G. M. Adel’son-Vel’skii, V. L. Arlazarov, A. R. Bitman, A. A. Zhivotovskii, and
A. V. Uskov. Programming a computer to play chess. Russian Mathematical
Surveys, 25(2):221, 1970.
Georgy Adelson-Velsky, Vladimir Arlazarov, and Mikhail Donskoy. Some meth-
ods of controlling the tree search in chess programs. Artificial Intelligence, 6
(4):361–371, 1975.
D. J. Edwards and T. F. Hart. The alpha-beta heuristic, December 1961.
Frederic Friedel. A short history of computer chess. Chessbase, 2002.
Robert Hyatt. Chess program board representations, 2004.
Alan Kotok. A chess playing program. Technical report, MIT, December 1962.
David Levy. Bilbao: The humans strike back. Chessbase, November 2005.
T. A. Marsland and M. Campbell. Parallel search of strongly ordered game trees.
Technical report, Department of Computing Science, University of Alberta,
Edmonton, 1982.
Søren Riis. A gross miscarriage of justice in computer chess. Chessbase, February
2012.
Jonathan Schaeffer. The history heuristic and alpha-beta search enhancements
in practice. IEEE Transactions on Pattern Analysis and Machine Intelligence,
11:1203–1212, 1989.
Claude E. Shannon. Programming a computer for playing chess. Philosophical
Magazine, 41(314), March 1950.
John von Neumann. Zur theorie der gesellschaftsspiele. Mathematische Annalen,
100(1):295–320, 1928.
Mark Allen Weiss. Data Structures & Problem Solving Using Java. Addison
Wesley, 2 edition, 2002.
Albert L. Zobrist. A new hashing method with application for game playing.
Technical Report 88, Computer Sciences Department, University of Wiscon-
sin, April 1970.
38