Louis Johnson - Dissertation Tools

Louis Johnson
A Game Playing Program: Gomoku
Supervisor: Dr. Andrea Schalk
Abstract
This report the details of the development of a computer program capable of
playing the board game, Gomoku.
I begin by describing the rules of Gomoku I then move on to explaining how
game playing programs function and how I have chosen to implement specific
elements of my program. I elaborate on some of the problems I encountered
during implementation and detail some of my solutions to these problems. The
report then describes how I tested the success of the project and concludes
with an evaluation of the project as a whole.
Page 2
Contents
Chapter 1 – Introduction
5
Chapter 2 – Background Theory
6
Chapter 3 – Design and Implementation
16
Chapter 4 – Challenges Faced
25
Chapter 5 – Testing
29
Chapter 6– Conclusion
32
References
34
Appendix A – Project Poster
36
Appendix B – Seminar Slides
37
1.1 - Project Overview
1.2 - Aims and Objectives
2.1 – The Rules of Gomoku
2.2 – Game Theory Fundamentals
2.3 – Minimax Algorithm
2.4 – Alpha Beta Pruning
2.5 – Evaluation Functions
2.6 – Chapter Summary
3.1 – Board Implementation
3.2 – Generating Moves
3.3 – Minimax and Alpha Beta Implementation
3.4 – Evaluation Function Implementation
3.5 - Graphical User Interface
3.6 – Chapter Summary
4.1 – Space Complexity
4.2 – Time Complexity
4.3 – Position Hashing
4.4 – Chapter Summary
5.1 – Manual Testing
5.2 – Self Testing
5.3 – Comparison to Existing Applications
5.4 – Player Testing
5.5 – Chapter Summary
6.1 – Project Summary and Evaluation
6.2 – Summary of Additional Work
5
5
6
8
10
12
14
15
17
19
19
20
22
24
25
27
28
28
29
30
30
31
31
32
33
Page 3
Figure List
Chapter 1 – Introduction
5
Chapter 2 – Background Theory
6
Figure 2.1.1 – Example Gomoku Position
Figure 2.1.2 – Example Threats
Figure 2.2.1 – Example Game Tree
Figure 2.3.1 – Minimax Algorithm Pseudocode
Figure 2.3.2 – Minimax Algorithm Example
Figure 2.3.3 – Minimax Nodes Visited
Figure 2.4.1 – Alpha-Beta Pruning Pseudocode
Figure 2.4.2 – Alpha Beta Algorithm Example
Chapter 3 – Design and Implementation
Figure 3.1.1 – Initial Idea for Board Implementation
Figure 3.1.2 – Bitboard Implementation
Figure 3.4.1 – Direction Vectors
Figure 3.5.1 – Gomoku Graphical User Interface
Figure 3.5.2 – Button Grid Code
6
7
9
10
11
12
13
13
16
17
18
21
22
23
Chapter 4 – Challenges Faced
25
Chapter 5 – Testing
29
Figure 4.1.1 – Memory Usage Graph
Figure 4.2.1 – Initial Gomoku Profile
Figure 4.2.2 – Profile after Optimization
Figure 5.1.1 – Endgame board in self-test
25
26
26
30
Chapter 6 – Conclusion
References
Appendix A
Page 4
Chapter 1
Introduction
1.1 - Project Overview
The aim of the game playing program project is to create a program capable of
playing a strategic board game, such as Chess or Checkers, against a human player.
I was attracted to this project because from previous study, I found many of the
concepts involved in the mathematics of game theory absorbing. I also found the
idea of creating a standalone application appealing. Having a program that anyone
can interact with and enjoy seemed an engaging prospect.
Having chosen the title for my project. The next step was to select a specific game
to create my program for. I chose Gomoku as it is a game that can be played by
anyone but is sufficiently complex. I also found the fact that Gomoku can be played
with many additional rules added scope to the project.
1.2 – Aims and Objectives
The core aim for my project was to create a program able to play Gomoku using the
standard set of rules, at a level that is capable of beating most casual Gomoku
players. The program should also be fast at processing move choices and should
display the game in a simple, “pick up and play” style graphical user interface.
Additional objectives for my project include:
•
the ability to play the game at different difficulty levels
•
the ability to play the computer against itself
•
the ability to load and save the game in a portable format
Page 5
Chapter 2
Background Theory
In this chapter, I summarise the background theory required to understand many
of the technical details of my project. I begin by covering the basic rules of Gomoku
and give an indication of how the strength of a Gomoku board can be estimated by
using threats. I move on to describing some game theory fundamentals that form
the basis of my project.
2.1 – The Rules of Gomoku[1]
Gomoku is a word originating from the Japanese language, the word “Go” meaning
five, and the word “Moku” meaning pieces. The game is traditionally played on a
19 by 19 board, with a set of black and white stones identical to those used in Go.
Starting with black, players alternate in placing a stone on an unoccupied
intersection on the board. The winner of the game is the first player to form an
unbroken series of five stones in their colour. This series can be either horizontally,
vertically or diagonally. Figure 2.1.1 illustrates an example position in Gomoku.
Figure 2.1.1 – Example Gomoku Position
Page 6
There are a number of variations to Gomoku, with additional rules. Generally with
the basis being in reducing the advantage that black has by making the first move.
Terminology will be explained in the following section:
•
No Over-lines. Only series' of five stones count as a win. Lines of more than
five stones do not constitute a winning series.
•
The Rule of Three and Three[2]. Bans a move that forms two threats of open
threes. Such as a move placing a stone in the centre of a formation in the
shape of a plus sign.
•
The Rule of Four and Four[2] is similar to this rule but forbids the
simultaneous formation of two rows of four. These can be open or not.
•
15 by 15 board. Gomoku is commonly played on a smaller, 15 by 15 board.
I have chosen to use this variation.
Threats
A Game of Gomoku is won by forming a sequence of threats that force a win.
Understanding these threats is fundamental to Gomoku. These threats are
illustrated in Figure 2.1.2 and explained below.
Figure 2.1.2 – Example Threats
Page 7
A) Five; the player forming the five has won.
B) Open Four; The player has forced a threat that cannot be countered. This
player wins the game if the other player does not as a result of their next
move.
C) Closed Four; The opponent must counter this threat the next turn. This
threat occurs commonly and is often used to form a winning sequence of
threats.
D) Open Three. A series of three stones that threatens to become an open 4. so
must be countered.
E) Closed Three.
Three stones blocked at one end. This threat does not
require immediate action but may become a stronger threat as part of a
threat sequence
F) Split Three. Three stones in a row, with a space between two of the stones.
This arrangement threatens to create an open four. It therefore has to be
dealt with immediately. This can be done by placing a stone either in the
space. Or at either end. Placing a stone in the space is often preferable as it
prevents the opponent forming a closed four.
Gomoku is generally won as a player repeatedly creates a threat that requires
immediate attention from the opponent.
Until ultimately, 2 threats arise
simultaneously that cannot both be countered.
2.2 – Game Theory Fundamentals
Game theory is the science of studying the behaviour of agents in a competitive
environment. In particular, how behaviour guides towards an objective outcome.
A game is defined as the interaction of players according to a predefined set of
rules.
We assume that at a given time, one player is permitted to make a move
permitted in the predefined rule set[3].
This allows a game (or sub-game) to be
represented as a Game Tree; a directed graph showing the decision process
followed when playing the game. The figure below is a game tree of a sub-game of
Noughts and Crosses.
Page 8
X's move
O's move
X's move
Figure 2.2.1 - Example Game Tree
In this figure, each node in the tree represents the position at that stage in the
game, whereas each edge represents a move made to a new position. The game
tree of Gomoku is an extrapolation of the game tree of noughts and crosses. The
game begins with a node representing an empty board. This node has one branch
for each of the nodes on the board. The initial branching factor is the number of
intersections on the board and decreases by one every time a stone in placed (not
that the game tree can be reduced by using symmetry). The game tree terminates
when one player achieves a win or when the board is full. It is clear that the full
game tree for Gomoku is very large!
Strategies
A strategy in game theory is defined as a set of rules that show which move should
be made in any situation that may occur. Formally, a strategy for Player A is
defined as a sub-tree of the game tree with properties[4]:
•
The root belongs to the sub-tree.
•
whenever it is Player A's turn, exactly one of the moves available is chosen.
•
whenever it is not Player A's turn, all available nodes are added to the the
sub-tree.
In large games, such as Gomoku, however the entire game tree cannot be
generated. Therefore strategies must be generated to deal with new positions that
occur. As a strategy cannot be predefined for a large game. An alternative
approach must be taken to generate a strategy whenever a new situation occurs.
One effective way of doing this Is using the Minimax Algorithm.
Page 9
2.3 - Minimax Algorithm
The minimax algorithm is a recursive algorithm for choosing the next move in a
zero-sum game. The algorithm is based on Von Neumann's Minimax Theorem
which states[5]:
For every two-person, zero-sum game with finite strategies, there exists a
value V and a mixed strategy for each player, such that (a) Given player 2's
strategy, the best pay-off possible for player 1 is V, and (b) Given player 1's
strategy, the best pay-off possible for player 2 is -V.
This can be explained more simply as Player 1 is guaranteed a pay-off of V.
whichever strategy Player 2 chooses to adopt, and vice-versa. The name Minimax
comes from the fact that each player is minimizing the maximum pay-off for their
opponent whilst maximising his own minimum pay-off. Due to the zero-sum
nature of the game.
The pay-off of a game is an indication of how well the player has done. At the leaf
of a game tree. The game is either won, lost or drawn. This can be represented as
the values infinity for a win, zero for a draw. And negative infinity for a loss. Using
this representation of strength. An estimated enumeration of a node in the game
tree, varying between infinity for a win and negative infinity for a loss, can be
generated by using an evaluation function, which will be explained in section 2.5.
This enumeration of a position can be used by the minimax algorithm to estimate
the best possible move to make.
The Minimax algorithm is best described using pseudocode:
Minimax (node, depth)
if node is a leaf o r depth is 0
return evaluation of node
els e if node is player 1's turn
let value = -∞
fo r each child
let value = max(value, minimax(child, depth – 1)
return value
els e if node is player 2's turn
let value = +∞
fo r each child
let value = min(value, minimax(child, depth – 1)
return value
Figure 2.3.1 - Minimax Pseudocode
Page 10
The function of the minimax algorithm is illustrated below, in figure 2.3.1[6]
Figure 2.3.2 – Route of the Minimax Algorithm
On the left branch from the root node, I have illustrated the path that the algorithm
takes. Minimax recurses to a leaf of the game tree, or to a set depth. Here a value
is taken dependent on the value of this node. This value is returned to the parent
node to this leaf. The algorithm repeats this for each child of that node and returns
the maximum or minimum value, depending on which player the node represents.
This process is repeated up the the tree, the branch from the node with the most
desirable value is taken as the preferred move. The performance of the minimax
algorithm is dependent on the branching factor of the tree and the depth that the
algorithm is set to search to. Note that the amount of nodes explored increases
exponentially with the depth limit. An approximation of the number of nodes
explored is:
Nodes Explored ≈ Average Branching Factor
Depth
If the average game of Gomoku is taken at a length of 30 turns. The size of the
game tree becomes apparent:
Nodes Explored ≈ Average Branching Factor Depth
Nodes Explored≈
225225−30 30

2
Nodes Explored ≈21030
Nodes Explored ≈4.64×1069
It is clear from this calculation that this is not a feasible way of determining the
next move in a game of Gomoku. Therefore steps must be taken to reduce the
Page 11
number of nodes which are visited. The number of nodes explored is
proportional to the average branching factor of the game tree, and exponentially
proportional to the depth that the algorithm must search. It is therefore clear that
to reduce the number of nodes explored by the algorithm, the first step is to reduce
these numbers!
The first step is to set a depth limit on the minimax algorithm and to use an
evaluation function on nodes that occur at the given depth. A higher depth search
gives a more accurate result, but with the exponential complexity, this accuracy
comes at a cost. An illustration of this is show in Figure 2.3.3 As shown in figure
2.3.2
Depth Limit
Approximation of Nodes Visited
3
9.26×10
6
5
4.08×10
11
7
18×10
9
7.94×10
15
20
Figure 2.3.3 - Nodes visited in Minimax
Although these numbers are significantly smaller than exploring the entire game
tree, the complexity of the minimax algorithm is still very high. One way of
reducing this is to consider less moves at each stage. Another is to apply Alpha
Beta pruning to the algorithm.
2.4 – Alpha-Beta Pruning
Alpha-beta pruning is a search algorithm which reduces the nodes explored by the
minimax algorithm. The algorithm works by using the values calculated for
previous sub-games to evaluate whether or not it is worthwhile evaluating other
sub-games[8]. This algorithm keeps track of a relevant range of values and stops
evaluating a move when it finds that another move is superior.
Two values are maintained by the algorithm, Alpha represents the minimum score
that the maximum player is guaranteed, whereas Beta represents the maximum
score that the minimum player is assured of. These values start at negative infinity
Page 12
and positive infinity respectively and tend towards each other as the algorithm
progresses; The window of relevant values becomes smaller. If a value is found
outside this relevant range, it can be disregarded as it indicates that the current
position is not the result of best play. At this point, the children of this branch do
not have to be evaluated, part of the tree evaluated by the search algorithm has
been pruned. Pseudocode for this algorithm is shown in figure 2.4.1.
AlphaBeta (node, depth, A, B)
if node is a leaf o r depth is 0
return evaluation of node
els e if node is player 1's turn
let value = A
fo r each child
let A = max(A, minimax(child, depth – 1, A, B)
if (B≤A) b reak;
return value
els e if node is player 2's turn
let value = B
fo r each child
let B = min(B, minimax(child, depth – 1, A, B)
if (A≥B) b reak;
return value
Initially, A = ∞, B= -∞
Figure 2.4.1 - Alpha-Beta Pruning pseudocode
The lines highlighted in the pseudo code indicate where pruning occurs. When a
value falls outside the range (A ≤ value ≤ B) the algorithm stops looping on each
child and returns the current value. An illustration of how this effects the minimax
algorithm is given in the following figure[6]:
Figure 2.4.2 - Alpha Beta algorithm illustration
Page 13
In the example, it is clear that alpha-beta pruning can significantly reduce the
complexity of the minimax algorithm.
If the complexity of Minimax is taken as O Average Branching Factor Depth  . It
can be shown that, under optimal circumstances discussed later, Alpha Beta
pruning reduces this complexity to O Average Branching Factor
Depth

2
 . This is
equivalent to searching to double the depth of minimax while maintaining the
same amount of computation required[9].
In order to achieve the reduction in complexity shown above, the children of each
node must be in optimal order; the more appealing strategies should be evaluated
first. Optimal order is near impossible to achieve in practice, but it is important to
order the children nodes according to prior information in order to maximise the
depth that the algorithm can achieve in a given time-span.
In conclusion. To maximise the search depth:
•
Reduce the branching factor of the tree. Only consider relevant moves.
•
Implement Alpha-beta pruning.
•
Sort moves to near optimal order.
2.5 – Evaluation Function
An Evaluation function is a function for estimating the value of a position in a
game-tree. Generally, these algorithms are designed to be efficient and fast, at the
cost of complete accuracy. A common way of forming an evaluation function is by
summing a number of weighted components that attribute to the strength of a
position[10]. An example of this is shown.
Evaluation=c 1×Tempoc 2 ×Spacec 3×Threats
Tempo, Space and Threats indicate attributes of the position to be evaluated
whereas c 1... n indicate the respective strengths given to these attributes. These
strengths can be varied either by hand or using a hill climbing algorithm (this is
often ineffective)
Page 14
In Gomoku, many of the attributes taken are specific threats that occur, such as the
closed 4 and the split three. The strength of these positions varies on how easy
they are to counter, a five might have a strength of ∞ whereas the strength of a closed
three might be just 50!
2.6 – Chapter Summary
In this chapter, I have outlined the theory required to create a game playing
program. Many of the points mentioned here will be expanded upon in context in
the following chapter; Design and Implementation.
Page 15
Chapter 3
Design and Implementation
In this chapter, I record details of the design decisions I took throughout my
implementation of Gomoku. I also describe the choices I made in engineering the
project.
I chose to write the code for my project in Java. I based this choice on the fact that I
have more experience programming in Java than I do with C, C++ or any alternative
language. I also find the collections interface used by Java to be easy to use, and
found this useful in implementing parts of Gomoku.
I developed Gomoku using an agile software development model[11]. As I had done
no programming in the domain of game theory previous to the project. And have
little experience in coding larger projects. I decided that It would not be possible to
plan in as much detail as would be possible with more experience. The agile
development model gave me more freedom to work in the way that felt right. I also
believed the agile development process would allow me to respond to problems
more efficiently than if I was using a more plan-driven development model.
In order to keep a record of development. I chose to commit iterations of my
classes to an online subversion repository[12]. This allowed me to easily revert
classes when problems arose, compare different implementation of classes and to
maintain the ability to access a current version of my work from in university and
at home without having to transfer the code manually.
The implementation of my program falls into 4 main areas:
•
Board Representation
•
Minimax Algorithm with Alpha Beta Pruning
•
Evaluation Function
•
Graphical User Interface
Page 16
3.1 – Board Design
Due to the breadth of the game tree of Gomoku, it is imperative to have each
instance of the game board stored efficiently. In searching for the best move. Alpha
Beta will need to explore millions of these objects. Initially, I planned on coding
each position as an array of “Piece” objects, set as Black, White or Null ,with a
number of additional variables such as whether it was Black's turn to place a stone
(see Figure 3.1.1). It quickly become apparent that this was an insufficient way of
representing the board. An implementation such as this would require an amount
of memory and computation that can be avoided by clever implementation of the
game board.
A bitboard is an alternative way of
representing a position[13]. As opposed to
an array of piece objects, a bitboard is a
presentation of where to find each piece
(figure 3.1.2). Thus the position can be
stored as 225 bits for black, and 255 bits
for white. This is much more efficient
than filling the heap with “piece” objects!
Using bitboards also allows us to use fast
bit-wise boolean operations.
Figure 3.1.1 - Initial Idea for Board Implementation
In my implementation of Gomoku. A position is represented by 4 such bitboards
and one boolean, Indicating whether or not it is Black's turn. The 4 bitboards used
to represent a position are:
•
Black Bitboard – Indicates whether a black piece is present.
•
White Bitboard – Indicates whether a white piece is present.
•
Null Bitboard – Computed as the bitwise NOR of Black and White
•
Active Bitboard – A Bitboard computed from the Null Board to represent
where there is an empty space which is within a certain distance from a
stone. This bitboard is used to calculate the possible moves from this
position.
Page 17
Black Bitboard
White Bitboard
Null Bitboard
Active Bitboard(proximity of 1)
BlacksTurn = true
Figure 3.1.2 – Board Implementation
Figure 3.1.2 shows an illustration of how I have used bitboards to store a sample
position.
In addition to the bitboards and boolean described. I added a number of basic
methods to the class used to represent a board:
•
findChildren() -Generates a list of moves possible from this position
•
toString() - Returns an ASCII representation of the board. This was used in
testing and in the command line interface.
Page 18
•
Clean() - Removes references to other position objects. This method is
invoked in minimax to reduce the amount of memory used by the heap.
•
hashCode() - returns a unique integer representing the position. Details of
this are given in section 4.3.
3.2 – Generating Moves
From the board design detailed, It proved easy to extract a list of possible moves. A
simple check over the Active Bitboard for a given position results in a list of
potential moves. In implementing the active bitboard. I chose a proximity of 2 as
opposed to the proximity of 1 shown in the example. This allows the game to select
moves that are not directly adjacent to an existing piece; this is common strategy in
Gomoku. These moves are added to an ArrayList[14]. This allows access to the
moves in linear time and allows the use of an external sorting method should it be
needed.
3.3 – Minimax and Alpha Beta implementation
Minimax with AlphaBeta is implemented as described in section 2.4. In addition,
the algorithm conditionally invokes the findChildren() method of the position
being evaluated to generate the legal moves from the position. Invoking the
method in this way ensures that child nodes are not generated for nodes that are
pruned from the game tree. This also ensures that children are not found for nodes
at the depth limit for that recursion of the algorithm. Minimax also invokes the
clean method (described in section 3.1) before returning the value of a node to its
parent. This prevents a node from knowing about it's grandchildren. This saves
memory.
Page 19
3.4 – Evaluation Function Implementation
The first generation of my evaluation function works relatively simply.
The
function contains a method for identifying threats in a position. The basis of the
evaluation function is a method which takes a direction vector, given as delta X and
delta Y (illustrated in figure 3.4.1). as the direction to look for sequences of stone. It
also takes a boolean value indicating whether sequences of stones for black are
being located. This method loops through the bitboard corresponding to the
colour that is being evaluated. When the method finds a 1 in the bitboard, it
searches in the direction indicated by the direction vector. If this is also a 1, the
process repeats. When a sequence of 3 consecutive bits is located in this way. The
threat can be classified in one of the following ways, depending on the values of the
next 2 positions given by following the direction vector:
•
Both bits are 1. a sequence of five has been located
•
The first bit is 1, the second is 0. a sequence of four has been located. From
here. The algorithm rates the value of the threat depending on the bit
corresponding to the second bit on the opposite bitboard. As well as the bit
corresponding to the space at the opposite end of the four.
•
•
If both of these bits are 0. and open four has been found.
•
if one of these bits is 0, a closed four has been found.
•
if neither of these bits are 0, the threat is null.
The first bit is 0. a sequence of three is located. An extension of the
reasoning explained above is applied to categorise the threat as: an open
three, a split four, a closed three or a null threat.
A set value is assigned to each type of threat. The total value of the search is
accumulated as the loop continues. In total. The evaluation function follows this
process 8 times. One time in each direction, for each colour. The value assigned to
the position is returned as the total value of white's threats subtracted from the
total value of black's.
Page 20
Delta X
Delta Y
1
0
0
1
1
1
1
-1
Direction
Figure 3.4.1 Direction Vectors
When testing this evaluation function in the game, I noticed that, given a number of
equal positions. It would always favour the position closest to the bottom left of the
board. Due to this, I added a small random variable to the evaluation function.
This variable is not significant enough to make a weaker position appear stronger
than one with a higher value. But it adds fluidity and variation, especially during
the opening moves of a game.
Potential Improvements to the Evaluation Function
I have identified a number of potential improvements that could be made to the
first generation of my evaluation function. Primarily, the function should reward
the creation of threat chains more than it does currently. The value of a board
should possibly be based more on the amount of threats on the board than the
nature of the threats present.
The evaluation function should also be able to identify threats before they reach
the point of being three consecutive stones. The current function does not
recognise the formation of a split three, which is a very common construction in
Gomoku.
If I had more time in testing, I also would have tested alternative evaluation
functions against each other in the game. I could then have applied optimization
techniques such as a hill-climbing algorithm to the parameters in the evaluation
function. Unfortunately, due to the problems discussed in chapter 4. .
Page 21
3.5 – Graphical User Interface
The design of the user interface for Gomoku is a relatively simple one. Gomoku as
a game is simple to represent without any ambiguity. A screenshot of the interface
is shown in figure 3.5.1
Figure 3.5.1 – User Interface
One of the significant reasons for my decision to code Gomoku in Java was the
ability to use familiar packages in development. One of these packages is Swing[15].
Swing is a set of components for building user interfaces in Java. Swing is also fully
portable, meaning my Interface should have no problems running on any operating
system with a Java Virtual Machine. I designed the GUI to be as intuitive as
possible. When it is the player's turn, one simple click on a location on the grid
places a stone in that position. I implemented the representation of the game
board as a grid of Jbuttons. This allows easy interaction with action listeners
associated with the button and allows the game board to be extended without
extensive recoding. (figure 3.5.2)
Page 22
JPanel grid = new JPanel();
grid.setLayout(new GridLayout(boardsize, boardsize);
board = new JButton[boardsize][boardsize];
for(int y=boardsize – 1, y>= 0; y--)
{
for(int x=0; x<=boardsize -1; x++)
{
board[x][y] = new JButton(emptyIcon);
board[x][y].setPreferredSize(buttonSize);
board[x][y].addActionListener(this);
board[x][y].setToolTipText(x + "," + y);
grid.add(board[x][y]);
} //y
} //x
super.add(grid, BorderLayout.CENTER);
Figure 3.5.2 - Button Grid Code
This is written in such a way that the User Interface is not affected if the size of the
board is altered. When interaction with the grid occurs, Swing calls the
“actionPerformed” method, this method searches the grid for the source of the
interaction. It then updates the current position of the game in the underlying
model and the graphic corresponding to the grid square to be updated.
This representation of the game board is also customisable. The graphics for an
empty space, a black piece and a white piece are stored externally from the
executable file and hence can be modified if, for example, a high contrast version is
needed by someone who's vision is impaired. This design choice is unfortunately
detrimental to the portability of the program. It is not possible to transfer just a
single file for Gomoku. As the images must be in the same file as the Gomoku
executable.
The user interface also includes a JMenuBar at the top. The Game menu is used to
start a new game, exit the game, allows the facility to load and save games1. The
Options menu allows the user to undo a move. It also allows the user to alter the
settings of the game. Whether each player is human or computer controlled. As
well as the level that each player is set to. The Help menu contains a brief “about”
box but could be extended to link an HTML document listing the rules of Gomoku.
Page 23
The Status box at the bottom of the interface indicates when user interaction is
expected. It also indicates where the last piece was placed by the computer and
shows when the game is over.
3.6 – Chapter Summary
Having implemented the main components for Gomoku. I faced a problem,
Although the game worked as intended, The time taken to make a move was often a
matter of minutes, even at a low depth search. The following chapter describes the
problem and elaborates on some of steps taken to increase the speed of the game.
Page 24
Chapter 4
Challenges Faced
During Implementation, It became clear that my program was running too slowly.
Even at the depth level of four, my algorithm was often taking a number of minutes
to obtain it's solution. In this chapter I document the steps I have taken to increase
the efficiency of my game.
4.1 – Space Complexity
The first step I took in dealing with this problem the amount of heap data stored
after making each move. I calculated the heap memory using methods from the
Java.Runtime[16] class.
Depth Level
Pieces on Board
Memory Used (kb)
4
1
3065
4
3
4370
16000
4
5
7200
14000
4
7
5349
12000
4
9
6650
10000
4
11
10447
8000
4
13
8644
6000
4
15
14368
4000
4
17
12778
2000
4
19
17497
0
4
19
18590
20000
18000
1
3
5
7
9
11 13 15 17 19 21
Figure 4.1.1 - Memory Usage Graph
The memory usage of Gomoku is not especially high. Figure 4.1.1 illustrates that
the memory usage is roughly linear with respect to the amount of pieces on the
board.
This led me into investigating where my program was spending the
majority of its time.
Page 25
4.2 – Time Complexity
In order to determine where my program was spending the majority of its time, I
invoked methods from the System[17]library to profile my application. I generated
the following data from a sample run of my application at a depth limit of 4. Note
that the time for “Move Generation” to occur involves creating all the new positions
associated with the moves that are generated. Also that the time taken for Alpha
Beta Search to complete involves the creation of a large number of these Move
Generations. This set of data is taken from obtaining the computer's first move.
Component
Mean Time Taken (ns) Times Invoked Total Time Taken
Position Construction 23,000
20,577
473,271,000
Move Generation
1,071,400
1,297
1,389,605,800
Alpha Beta Search
1,890,712,000
1
1,890,712,000
Evaluation Function
7,500
13,029
97,717,500
Figure 4.2.1 - Initial Gomoku Profile
This data indicates that a large amount of time is taken in Generating Moves and
creating positions. The first step taken to try and decrease the time spent in
creating positions is to alter the way which Positions are stored. Instead of storing
the activeBoard and nullBoard. These are created on demand. Running the same
tests as before yielded the following data.
Component
Mean Time Taken (ns) Times Invoked Total Time Taken
Position Construction 8,500
20,577
174,904,500
Move Generation
876,602
1,297
1,136,952,794
Alpha Beta Search
1,148,627,000
1
1,148,627,000
Evaluation Function
7,500
13,036
97,717,500
Figure 4.2.2 - Optimizated Profile
Although this is a large improvement over the initial results. As the game
progresses moves can still take up to a minute to compute. Profiling of the
methods in this way did reveal any additional modifications to increase the
efficiency of the code. Other than completely redesigning the data structure and
move generation system. At this point, I chose to try implementing new
components for the system in an attempt to decrease the number of nodes visited.
Page 26
4.3 – Position Hashing
Position Hashing is a way of storing positions which have previously been
explored, so as to eliminate redundancy. I opted to implement Position Hashing to
work alongside the minimax algorithm as a way of reducing the time taken to
generate the optimal move. Position hashing works by statically storing a map
containing the hash of each position encountered with a value for that position.
When a position is encountered, its hash is calculated and the algorithm checks if
there is a value for the position at that location in the map. If a value is present.
This value is used by the minimax algorithm. Otherwise the position is added to the
hash map once a value for it has been calculated.
The first step in implementing position hashing is to write a hash code function for
the Position class. In order to do this I have also written a hash code method for
the BitBoard class, this returns the hash of the boolean array used to store the
BitBoard. A common way of implementing a hash code method for an object
containing a small number of simple, other objects is to use the following
convention[18]:  x , y . hashCode=x.hashCode y.hashCode . I applied this convention to
the hash code method of Position. The hash of a position is the hash of the Black
bitboard to the power of the white bitboard.
I Chose to create the hashmap in the GUI class, using the following line of code:
public static HashMap<Integer, Integer> map = new HashMap<Integer, Integer>(1500, 0.75f);
This indicates that the collection maps an integer value (the hash of the position)
to another integer value (the value of the position). It also indicates the hash map
starts with a capacity of 1500 and increases in size when it reaches 75% full. I then
modified the minimax algorithm to search the hash map and add new positions to
the map when they are evaluated.
My implementation of Position Hashing reduced the amount of nodes explored by
the minimax algorithm significantly. This, however, came at a cost, The
computation taken to maintain and update the hash map actually caused the
program to run more slowly than it had without the position hashing code. I have
therefore removed the code for position hashing from Gomoku.
Page 27
4.3 – Further Work on Improving Speed
The following modifications to my code are potential additions to the program. I
have not implemented anything described in this section.
One potential modification that may increase the speed of the game is to combine
the black and white BitBoards to one 15 by 30 boolean array. This reduces the
number of objects in the heap and reduces the amount of calls the program has to
make to BitBoard functions for copying and altering boards.
Alpha Beta pruning in my implementation is crippled by the fact that moves are not
sorted. Another possible modification would be to apply a sort to the list of moves.
The challenge would be to judge the strength of a position without running a
complex evaluation function on it. One simple modification that could be made
would be to first consider moves adjacent to existing pieces. Followed by ones
further away. The improvements offered by this simplified approach are likely to
be minimal.
The method used to generate potential moves in Gomoku takes a large amount of
time. This can be altered by creating an list of coordinates corresponding to the
move to be made, as opposed to the position when that move has been made. In
this representation. Pieces could be simply placed and removed from the board as
the minimax algorithm recurses as opposed to creating each individual node as an
object. Representation in this way would require me to recode almost the entire
project, but has the potential to increase the speed dramatically. If I was to start
the project again, this is the representation I would choose.
4.4 – Chapter Summary
Even having profiled my Gomoku line by line, and having attempted to increase its
efficiency by hashing. The time taken to choose a move is still much larger than it
should be. I believe this to be down to the design fault in how the program
represents moves and positions, as mentioned in the previous paragraph.
Unfortunately, the slow speed of Gomoku even at a low depth, has had impacts on
how I have been able to test the game.
Page 28
Chapter 5
Testing
The testing procedures applied to my program has been relatively limited. The
amount of time required for a game to complete reaches an hour for a longer game.
I have however, been able to test the program in the following ways:
•
Manual Testing – running the program myself, observing how the program
plays in certain situations
•
Self-Testing – running the program against itself. The most time consuming
process. Due to the inability to vary the depth of alpha beta, these tests are
more inclined to test whether the evaluation function favours one colour
over another.
•
Comparison with other programs – running the program against other
Gomoku engines
•
Player Testing – distributing the application to others to see how they fare.
5.1 – Manual Testing
I tested the Gomoku manually throughout project development and made
alterations to code depending on how the game was performing. One major
observation of the final game is that the evaluation function favours defensive play.
Often the program chooses to place a stone in a position to block a threat of three
as opposed to placing a stone to create a stronger threat for itself. I have found this
property of the evaluation function makes the program quite hard to beat. It also
often means that the program often appears to have little chance of winning until
much later in the game. I believe this property to make the game more enjoyable
for the Gomoku beginner, One cannot beat the program without chaining threats
effectively. In playing the final version of Gomoku, I was beaten by the game
slightly over half the time. I have taken the conclusion from this testing that the
game is suitable as a beginner Gomoku program, but lacks refinement in the
Page 29
evaluation function, as well as the ability to search to high depths to challenge a
more experienced player.
5.2 – Self Testing
Due to the speed of the game and its tendency to play defensively, self-testing of
Gomoku was a lengthy process. I completed three complete games of Gomoku with
the computer choosing moves for both black and white. Two of these games
resulted in a draw, the remaining game was a win for white (see figure 5.2.1). I
concluded from this that the evaluation function is not unbalanced towards either
player.
Figure 5.2.1 - Final Game Board in Self Test
5.3 – Comparison to Existing Applications
I tested Gomoku against Super[19] , A strong Gomoku and Renju program also
written in Java. In five attempts, my program was unable to defeat Super when set
to its “Low” difficulty. I put this down to the refinement in the evaluation function
for Super, the program was able to chain threats more effectively than my program.
I do not find it surprising that Gomoku was unable to defeat Super. I assume that
even on the “Low” difficulty, Super searches to a depth of at least the 4 achieved by
Page 30
my program. Super has placed well in tournaments for Renju and Gomoku playing
programs and includes a library of opening moves, a feature my program does not
possess. It is therefore clear that Super is more tuned than my attempt.
5.4 – Player Testing
I distributed by program to five people possessing little experience with games
such as Gomoku. Each of these players played the game 3 times then gave me
informal feedback. The program defeated each player on their first attempt. 1 of
the players was able to defeat Gomoku on their second attempt and an additional
player defeated Gomoku on their third attempt. Unfortunately, due to the amount
of time taken to complete a game, none of these players chose to continue playing
full games after their 3rd. They did however give feedback on other parts of the
program.
When asked about the program's GUI, each player responded positively; indicating
that they found the interface attractive and intuitive. It was suggested, however
that I include a clear description of the rules of Gomoku in the help menu.
Two players commented on the lack of options available with regards to the
difficulty and play style of the computer. Indicating they would like to be able to
customise the game further. Each player commented on the time taken for the
computer to make a move impacted on their enjoyment of the game. This was
unanimously selected as the main drawback of the program. It was, however,
mentioned that the game functioned well as an activity to partake in whilst doing
other things!
5.5 – Chapter Summary
Overall, testing indicates that the program I have created does not function well as
a standalone Gomoku game, the inability to search to a high depth means that it
functions only as a game for those new to Gomoku. As a beginner's game, the
program functions as intended but lacks the a clear set of rules for the player. The
final chapter analyses the aims and objectives stated in chapter 1. It also includes a
summary of improvements that would be made if more time was available.
Page 31
Chapter 6
Conclusion and Evaluation
This chapter compares my implementation of Gomoku with the aims and
objectives detailed in section 1.2. It also contains an evaluation of what I have
learnt from the project and concludes with a summary of further work that could
be done to improve the project
6.1 – Project Summary and Evaluation
This section reiterates the aims and objectives from the first chapter. It then
discusses how the outcomes of my implementation meet the objectives.
The core aim for my project was to create a program able to play Gomoku using the
standard set of rules. My program satisfies this objective, my program is able to
play Gomoku sensibly and is capable of beating human players. More experienced
players are likely to have little trouble in beating my program, however, this is due
to the small search depth of the minimax algorithm. My aims also state that the
program should also be fast at processing move choices. My implementation fails
to meet this objective. I believe the reason for this is the early design decision on
how to internally represent moves and the board (see section 4.3). Another Aim
was for the program to display the game on an attractive and intuitive graphical
user interface. I believe my implementation satisfies this aim completely as
feedback relating to the user interface is universally positive.
The additional objectives detailed in the first chapter are:
•
the ability to play the game at different difficulty levels
•
the ability to play the computer against itself
•
the ability to load and save the game in a portable format
The first of these objectives is technically feasible, but practically not. The
limitation that speed places on my program's ability to play the game means that
the option to increase the depth level above 4 results in a game that is too slow to
Page 32
be played. The second of these objectives, the ability to play the game against itself
has been achieved, However the third objective was not implemented due to time
constraints.
Overall, the quality of every aspect of my project has suffered due to the time taken
solving the problems detailed in section 4. The evaluation function is not as
refined as I would have liked it to be, and many basic user features, such as the
ability to save a game, have not been implemented due to the amount of time spent
trying to increase the speed of my project. I think some of the design decisions
taken before implementation reflect my lack of experience in programming
systems such as this. I also believe my choice to work in a bottom-up fashion
affected my ability to react to problems that arose in developement. Had I opted to
work in a top-down way, many of these issues could have been resolved more
easily. The choice to work to the agile software development model potentially had
an adverse effect on my final implementation. Had a stricter development model
been applied, the problem may have been uncovered earlier in coding. The final
section summarises how I would adapt my program if I choose to continue the
project.
6.2 – Summary of Additional Work
•
Complete rework of data structures and legal move generation. Detailed in
section 4.3.
•
Implementation of an improved version of position hashing. With more
efficient hash methods.
•
Addition of an opening move library in order to strengthen play.
•
The ability to load and save games.
•
Refinement of the Evaluation function by tweaking the values for each
threat and encouraging multiple consecutive threats
•
The ability to play the game with different versions of the evaluation
function. This could characterise different play styles. One evaluation
function could encourage more aggressive play than another
•
the ability to play Gomoku between multiple computers. Not strictly
relevant to the aims of the project
•
hint function to allow the computer to pick a move for you
Page 33
References
[1] Gomoku Rules
http://www.yourturnmyturn.com/rules/go-moku.php
[2] Gomoku Wikipedia Article
http://en.wikipedia.org/wiki/Go-moku
[3] So What's a Game?
Schalk, Andrea, “COMP30191 Theory of Games and Game Models”,
University of Manchester, School of Computer Science, lecture notes 2008
Section 1.1.
[4] Strategies
Schalk, Andrea, “COMP30191 Theory of Games and Game Models”,
University of Manchester, School of Computer Science, lecture notes 2008
Section 1.2.
[5] Von Neumann, J: Zur Theorie der Gesellschaftsspiele Math. Annalen. 100 (1928)
295-320
[6] Game Visualisation
http://wolfey.110mb.com/GameVisual/launch.php
[7] Average Length of GoMoku 15x15 Freestyle
Victor Allis (1994). Searching for Solutions in Games and Artificial
Intelligence. Ph.D. Thesis, University of Limburg, Maastricht, The
Netherlands. ISBN 9090074880.
http://fragrieu.free.fr/SearchingForSolutions.pdf.
[8] Alpha-Beta Pruning
Schalk, Andrea, “COMP30191 Theory of Games and Game Models”,
University of Manchester, School of Computer Science, lecture notes 2008
Section 5.4.
[9] Complexity of Alpha-Beta Pruning
Russell, Stuart J.; Norvig, Peter (2003), Artificial Intelligence: A Modern
Approach (2nd ed.), Upper Saddle River, NJ: Prentice Hall,
ISBN 0-13-790395-2
Page 34
[10] Evaluation Functions
Schalk, Andrea, “COMP30191 Theory of Games and Game Models”,
University of Manchester, School of Computer Science, lecture notes 2008
Section 6.3.
[11] Manifesto for Agile Software Development
http://www.agilemanifesto.org/
[12] Assembla Online Workspaces
http://www.assembla.com/
[13] Representing Positions and Moves
Schalk, Andrea, “COMP30191 Theory of Games and Game Models”,
University of Manchester, School of Computer Science, lecture notes 2008
Section 6.2
[14] ArrayList
http://java.sun.com/j2se/1.4.2/docs/api/java/util/ArrayList.html
[15] Java Project Swing
http://java.sun.com/j2se/1.5.0/docs/guide/swing/index.html/
[16] Java Runtime Class
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Runtime.html
[17] Java Timing Methods
http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html#nanoTime()
[18] Guide to writing hash functions
http://www.javamex.com/tutorials/collections/hash_function_guidelines.shtml
[19] Super GoMoku
http://www.5stone.net/en/
Page 35
Appendix A
Project Poster
Page 36
Appendix B
Seminar Slides
Page 37
Page 38
Page 39
Page 40