2 - The Game of Oware

advertisement
Decision Making System
for the
Game Oware
By: Carlos Noguero Galilea
Home University: Facultad de Informática de Madrid
Date: 23/03/2004
Institut für Algorithmen und Kognitive Systeme (IAKS)
Fakultät für Informatik - Universität Karlsruhe
Index:
Index: .................................................................................................................. 1
1 - Introduction: ................................................................................................... 3
2 - The Game of Oware: ......................................................................................... 8
2.1 - Rules:........................................................................................................ 8
2.2 - Strategies: ................................................................................................15
2.2.1 - Defensive: ...........................................................................................15
2.2.2 - Attack: ................................................................................................16
2.2.3 - Kroo Building: ......................................................................................17
2.2.4 - Overloading: ........................................................................................17
2.2.5 - Pressure: ............................................................................................18
2.2.6 - Counter attack: ....................................................................................19
2.3 – Game Phases: ...........................................................................................20
2.3.1 - Openings: ...........................................................................................20
2.3.2 - Middle Game: ......................................................................................22
2.3.3 - End Game: ..........................................................................................23
3 – Modelling a utility function: ..............................................................................25
3.1 - Searching trees for Oware: .........................................................................25
3.2 - Player’s Evolution: .....................................................................................29
3.3 - Decision Making and Utility Functions: ..........................................................31
3.4 - Logic Representation: .................................................................................33
3.4.1 - Unification: ..........................................................................................33
3.4.2 - Inference: ...........................................................................................34
3.5 - Production Systems: ..................................................................................35
3.5.1 - Backward Chaining: ..............................................................................36
3.5.2 - Production System for Oware: ...............................................................37
4 – The Program: .................................................................................................41
4.1 – Prolog: .....................................................................................................41
5 – Conclusions: ...................................................................................................44
6 - References: ....................................................................................................47
Appendix 1: .........................................................................................................49
Appendix 2: .........................................................................................................57
2
1 - Introduction:
This work comes from the study of a paper about multi-agent systems and utility
functions [13]. After researching on these topics and other theories of AI a specific
domain was selected. A game was chosen to apply these techniques and theories. Games
and AI have a strong relationship, from a simple program that can play the game, to a
complex system that tries to play as a human. Beneath all this there are multiple
techniques and combinations that can be applied in game domains. Games provide some
advantages over other problems for applying different techniques and measuring the
results.
One of the most important things is that games have a clearly defined set of rules.
These rules limit the possibilities and outcomes of a game and therefore of the problem
we are trying to study; as opposed to real world problems, where uncertainty is always
present. Games have a clearly defined goal or objective, so that the result calculated by
the system can be rated. They also have several degrees of complexity and they present
different problems for which some techniques work better than others.
All of these features make games an excellent domain in which to test and
develop new or old AI theories. This is the domain we have chosen and in it we take a
look at certain techniques, some of them used scarcely in games. Before any attempt to
solve a game we must choose a representation for it. There are many representations
used in AI, one widely used and studied is logic representation. With logic we can
describe nearly any system, it is best used in systems that have little information so that
the representation does not become complicated. With logic we can also use several
methods of reasoning. Using rules is the simplest way to express this reasoning. We call
one of these methods of reasoning a 'decision making system'.
Decision making systems use logical representations of rules and facts to obtain
new results using inference . This systems can work with a simple set of rules and, by
means of a complex process, produce the desired conclusions. This is something that is
common in games where the information is limited, mainly by the rules. In these cases
we have a complex inference mechanism that uses this information to get an answer
from the system. Another way to produce a decision making system is with the use of a
utility function. In the following chapters we will explain how this can be done. There are
many techniques and studies for all kind of games, probably the classic ones are the
ones that attract most attention; chess, checkers, go, etc. We will use a classic but less
well known, or at least less popular, game.
3
The game used for the study is ancient and has its origins in Africa. Nowadays it is
played all around the world, mostly in Africa and Asia. Several names have been given to
this game, the most common ones are: Oware, Wari and Mancala. We will refer to the
game from now on as Oware. Also, the game is played with different rules all over the
world, we will use a standard set of rules used in some tournaments. These rules will be
explained in more detail in the following chapter.
The selection of Oware as an example for decision making systems and other
theory topics covered in the study came from playing the game [10]. It is a game with
mathematical fundamentals, in which numeric calculations are constantly made. But
these calculations on their own are not enough to master the game. Underneath the rules
and the "limitation" of possible moves lies a very complicated strategy. Moreover this
strategy cannot be easily described in easy terms like mathematic calculation. There are,
what in other games has been called, patterns which master players can detect and use
to their advantage.
Altogether these features make Oware a good specific problem in which to test
playing techniques with computers and AI, computer analysis of this game is not
something new. Other studies have been done and different techniques for playing have
been used. The reason behind the interest that the AI community has in this game
comes mainly from its distinguishable features. Some examples are that it is a two player
game, it has and uses perfect information, there are a limited number of moves and
these rules are simple. A few other features need to be explained in more detail.
So we have a game with certain characteristics that are good for AI solving
techniques. Furthermore we have a game of medium complexity. It is more complex
than previously solved and studied games like Tic-Tac-Toe or Connect-4, but also less
complex than Chess, one of the most studied games.
In this study of the game we focus on the playing strategy. By doing a thorough
analysis of how the game is played we define some strategies that give a better chance
of winning. Looking at several sources there are several aspects of the game to take into
account; mainly from expert writings about the game, sample games, other studies and
from self experience. Oware is a simple game to play but very difficult to master. Even
the best players and people who studied the game cannot define the best strategy to
play. There are guides, advices, game situations...
4
After having read and learned what achievements have been reached with the use
of Oware in the AI community we have to ask ourselves new questions. Is a game with
perfect information too limited? Can we innovate in a game that has been studied so
much? Is it possible to extrapolate the results obtained with this game to other games or
even to other problems?
All of these questions and more arose after accumulating knowledge in various
topics like:
agent
systems, decision
making
systems, neural
networks, genetic
algorithms, etc... Having also played the game and read about it, other questions came
into consideration. There are no easy answers, most techniques applied to play Oware
have been used before with good results. In nearly all of them a search tree is used to
evaluate possible moves. The use of Search trees is an easy procedure and, in a sense, a
way in which the use of computers can be advantageous. We could divide games in two
big groups, the ones than can be solved by brute force (explore every move) and those
that cannot be solved that way. Here we must raise a point: as computers become more
powerful, an increasing number of games can be solved by this technique. Still, there are
some in which no good results using this technique can be expected in the near future.
Therefore we have tried to explore possibilities that eradicate the use of a search
tree, even if this is only a partial tree. Our aim is to study the strategy behind the game
and try to get the computer to make use of it. Our system does not rely on a computer's
processing power and would probably play poorly against more advanced computerised
players. It only takes the static information of the board in a given position and has to
decide which is the best move in that position. There are techniques that the system
does not use and some of them that could improve its performance greatly. Due to the
interest of focusing on the strategy of the game we left behind some good AI techniques
such as: Learning, consideration of similar moves, similar positions, long term strategy,
etc. If we bear all of these AI techniques in mind we can give an outline of our goal.

First of all we want to extract all the information about the the game. Not only the
general strategy but also all the information that the board has in a given
position. This takes into account a lot of variables that will be developed in the
following chapters.

Secondly we want to develop a system that infers the best move with the static
information of the board. Without the use of search trees and without looking at
past moves. At this point we are defining the utility function for our game. This
function takes the strategy within the game into account, but the strategies are
not explicitly defined.
5

Lastly, we want our system to reason the choice for each move. As the selection
of moves comes from the given strategy the explanation can be achieved by
reversing the process after a move has been selected.
With all the previous studies of the game, AI theory and keeping our goals in mind
we start to study the game. Oware is an amazingly simple concept; although ancient, it
contains a lot possibilities. The best example of the finesse of the game is the fact that
with perfect play from both players the result is a draw. This is, that there is no
advantage from been the first or the second player. We take advantage of this fact and
study moves for only one of the players (south player). This has been done to simplify
the system, although a very simple change is needed to consider both player's moves.
We commence by explaining the strategy of the game in detail. From beginner's
techniques to advanced tactical moves. This information comes from several sources, but
mainly from expert players and their writings [5] [6]. Next, we examine the three phases
that take place during a game. These are: Openings, middle game and end game
positions. For each of them we give examples of situations, the best strategies and
specific best moves in some cases. As in any other game, openings and end game
positions have been studied in depth, in part because the limit in the number of moves
makes it easier. In the case of Oware this was true until recently [3], now as every
possible move is known, middle game positions are no longer a mystery.
In order to represent the system, several methods have been used. A production
system has been defined to express the rules of the game and all the valid or legal
moves that can be made during a game. This system allows both players to make moves
in turns until a certain end condition is met, ending the game with a result. This result
can be a draw or a declaration of a win for one of the players.
After representing the set of rules we define a system with all the knowledge of
the strategy obtained from the study of the game. This system also uses a logical
representation and has been implemented in Prolog. The program is able to infer the best
move given a static position of the board. The information given by a specific position is
the following: the number of stones in each pit and the number of stones captured by
each player. With this information the program is able to obtain more details needed to
perform the inference, such as the distribution of the stones around the board, the
number of stones on each side of the board, the difference in the number of captured
stones by each player, the number of pits with a specific amount of stones, etc...
6
Due to a certain limitation of the Prolog language in itself, and also a lack of
expertise in using it, the system does not perform at an optimal level. What is more,
there are certain things that could not be done in the way they where intended to be
done. This is left for a future implementation probably using a different language and
also for a fully interactive version of the game.
7
2 - The Game of Oware:
In this chapter we study the game, from the basic rules to the complex strategies
and game positions.
2.1 - Rules:
The game consists of a board with two rows of six pits each (also called homes,
squares, holes...). At the start of the game there are four stones (also called seeds,
beans, counters...) in each pit. The game finishes when one player cannot move or when
one player has captured more than half of the 48 stones. In the case in which a player
cannot move (no counters in his pits) the other player captures the rest of the stones
that are in the board. Another rule for movement is that a player has to make a move, if
he cans, that leaves at least one stone in the opponent's side. This is the same as
making a move that will allow the opponent to move. The game is played by turns, in
each turn the player chooses one of the six pits on his side. He picks up all the stones in
that pit and then he starts dropping them one by one from the next pit in anticlockwise
direction putting one stone in each pit. If he reaches the pit where he started from he
skips it and resumes the operation in the following pits.
Capturing of stones is made when the last stone is dropped on the opponent's
side. We then count the number of stone in it. If it is 2 or 3 then those stones are
captured by the player who did the move. We do the same with the previous pit
(clockwise direction) until the number of stones is not 2 or 3 or we finish with the pits on
the opponent's side.
The rules here described are used in some parts of Ghana, surely they are used in
other places and they are very similar to other variants of the game.
We can give the step by step instructions for playing the game:
1) On your turn, select a non-empty pit on your side of the board. "Sow" the stones in
that pit around the board, dropping one at a time stone-clockwise into each pit.
2) If you choose a pit with enough stones to go completely around the board (12 or
more), the original pit is skipped and left empty.
8
3) If the last stone is dropped into a pit on your opponent's side, leaving that pit with 2
or 3 stones, you capture all the stones in that pit. The capture continues with consecutive
previous pits on that side which also contain 2 or 3 stones.
4) If all the opponent's stones are captured, it is called a "Grand Slam", and ends the
game (see Rule 5).
* Variation 4a: Grand Slam captures are not legal moves.
* Variation 4b: Such a move is legal, but no capture results.
* Variation 4c: Such a move is legal, but the last pit is not captured.
* Variation 4d: Such a move is legal, but only the first pit is captured.
* Variation 4e: Grand Slam captures are allowed, however all remaining stones on the
board are awarded to the opponent.
5) If all your opponent's pits are empty, you must make a move that will give him a
move. If no such move can be made, you capture all the remaining stones on the board,
ending the game.
6) The game is over when one player has captured 25 or more stones, or both players
have taken 24 stones each (draw), or a position is repeated, in which case each player
captures the stones on their side of the board.
For the rest of the analysis of the game we use the variation 4e for Grand Slam captures.
Here are some basic situations to explain better the rules of the game and the
results of each move:
The notation used is the following:
The board consists of two sides which we call North and South. In each side the
pits are give letters. For South from left to right (a,b,c,d,e,f). For North from right to left
(A,B,C,D,E,F). This may seem strange at first glance but it gives some advantages when
trying to see where a move will end.
The moves are written as a number followed by a letter. The number represents
the number of stones in the pit and the letter the pit selected. The information is
redundant and use for confirmation. Captures are noted beside the move inside
9
parenthesis with the number of stones captured. For example: 4c indicates a move of the
4 stones by South player from pit c (the third from the left on his side).
Initial board:
Initially the board has 4 stones in each pit (total of 48 stones). One of the players starts
to move, in our games it will always be South player.
F
E
D
C
B
A
N
4
4
4
4
4
4
0
4
4
4
4
4
4
0
a
B
c
d
e
f
S
Initial State
Basic Moves:
From the initial state a basic move would be for example 4d. Picking all stones in
the pit and leaving one in e, f, A and the last one in B. Note that the stones are placed in
anticlockwise direction going from f in South's side to A in North's side and from F in
North's side to a in South's side. This move does not capture any stones as there are not
2 or 3 stones in the last pit B. So it is North's turn to move.
F
E
D
C
B
A
N
4
4
4
4
4
4
0
4
4
4
4
4
4
0
a
B
c
d
e
f
S
F
E
D
C
B
A
N
4
4
4
4
5
5
0
4
4
4
0
5
5
0
a
b
c
d
e
f
S
Basic Move 4d
After 4d
Round Moves:
In this case the pit has 18 stones. Moving 18d drops one stone in each pit except d and
another one in e,f,A,B,C,D and E. Notice that the number of pits covered in the second
round is 7, that is 18-11. As we know there are 12 pits and leaving the first one we need
11 stones to do a a complete pass back to the first pit.
10
Note: The game is in progress and to show it each player has captured in total some
stones. North has 4 and South has 3, notice that the sum of captured stones and
remaining stones in board must be 48.
F
E
D
C
B
A
N
0
3
4
0
8
2
4
2
3
0
18 0
1
3
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
5
6
2
10 4
4
3
4
1
0
2
3
3
a
b
c
d
e
f
S
Round Move 18d
After 18d
Simple Captures:
In this case one of the movements produces a capture. The move 2e will end in A and it
will have three stones in it. As it meets the capture condition (2 or 3 stones in opponent's
side) the three stones are captured by South player. As A is the first pit of North player
we do not need to check other pits. In case it was not the first one we would check the
previous to see if it meets the condition stated before. We will see this in the next
example (Multiple Captures).
F
E
D
C
B
A
N
0
3
4
0
8
2
4
1
3
0
18 2
0
3
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
3
4
0
8
0
4
1
3
0
18 0
1
6
a
b
c
d
f
S
e
Simple Capture 2e
After 2e (3)
Multiple Captures:
11
Multiple captures as explained above happen when the last stone ends up in a pit which
is not the first one. The move 5e as shown in the diagram ends up in D before capturing.
We check D for capture condition, after that we check C, then B and A. Until one of them
does not meet the capture condition. In this case all of them meet it (2 or 3 stones) so
all the stones are captured by South player, ten stones in total.
F
E
D
C
B
A
N
0
10 1
2
1
2
5
1
3
0
18 5
0
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
10 2
3
2
3
5
1
3
0
18 0
1
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
10 0
0
0
0
5
1
3
0
18 0
1
10
a
b
c
d
f
S
e
Multiple Captures 5e
Before capture
After 5e (10)
Round Move Captures:
Round move captures occur when there are enough stones in one pit to go around the
board and end in the opponent's side. In this case pit d has eighteen stones and will end
the move in D. After the move and before the capture we can see that some pits have
two more stones than before and some have only one. This is because of the complete
cycle achieved by the move. From pit e to E there are two more stones than before.
Now we check the capture condition as usual, E meets it, as D and C does. B does not
meet the condition therefore we stop with a capture total of eight stones by South
player.
F
E
D
C
B
A
N
0
1
0
1
8
4
10
2
3
0
18 0
1
0
Round Move Captures 18d
12
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
3
2
3
10 6
10
3
4
1
0
2
3
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
0
0
0
10 6
10
3
4
1
0
2
3
8
a
b
c
d
e
f
S
Before capture
After 18d (8)
Forced Moves:
As one of the rules states. 5) If all your opponent's pits are empty, you must make a
move that will give him a move. If no such move can be made, you capture all the
remaining stones on the board, ending the game. In this case South cannot play 3b or 1e
as they do not comply with the rule. Therefore South has to play 5d giving North a
chance to move in his next turn.
F
E
D
C
B
A
N
Forced Moves 5d
0
0
0
0
0
0
20
South has to move 5d.
0
3
0
5
1
0
20
Only move that gives North the chance to move in his turn
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
0
0
1
1
1
20
0
3
0
0
2
1
20
a
b
c
d
e
f
S
Grand Slams:
This type of move is subjected to different rules. We will apply the one stated above.
Grand Slam captures are allowed, however all remaining stones on the board are
awarded to the opponent. With 5d South captures 6 stones and performs a Grand Slam,
North cannot move so remaining stones are captured by him. In this case the result of
the game is a draw but it depends on the situation of the board.
13
F
E
D
C
B
A
N
Grand Slam Move 5d
0
0
0
1
1
1
18
0
3
0
5
1
0
18
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
0
0
0
0
0
18
0
3
0
0
2
1
24
a
b
c
d
e
f
S
F
E
D
C
B
A
N
Remaining stones are for opponent (6)
0
0
0
0
0
0
24
In this case the game is a draw
0
0
0
0
0
0
24
a
b
c
d
e
f
S
After 5d (6)
From the rules and experience we can give more details on counting moves:
1) If there are 6 stones in one pit the "Sow" will end in the diagonal opposite that has the
same letter assigned to it. For example a move from c6 will end in C.
2) A pit with 11 stones will end the move in the previous pit after leaving one stone in
each pit except the first one (empty).
3) Pits with more than 11 stones have a great advantage in the game, but are only
useful with a specific amount of stones. For South player (North player would be the
same), having from 12 to 17 stones in pit f will end the move in the opponent's side.
Adding one stone for each previous pit in his side we can get the amount of stones
needed in each pit. That is, from 13 to 18 in pit e, from 14 to 19 in pit d and so on...
Until pit a which must have between 17 and 22 stones to end the move in opponent's
side.
4) Pits with no stones are only protected against direct attacks but not from moves that
go all around the board (see point 3). This is because in the first pass one stone is put in
the empty pit so in the next pass another one is dropped and so both stones are
captured. Notice that pits with two stones are protected against this type of move,
14
because in the first pass the pit will have three stones so that when another bead is
dropped there is no capture.
5) Obviously pits that have no stones cannot be chosen for a move, thus limiting the
number of possible moves for a player. This makes end games simpler as there are fewer
stones and also fewer moves to choose from.
2.2 - Strategies:
There are different strategies and variations of them. We list the most used ones
with commented examples.
2.2.1 - Defensive:
Trying to protect all pits with 3 or more stones. Note that a pit without stones is
still vulnerable to moves by opponent with 12 or more stones. Also every move leaves at
least one empty pit so the defensive strategy is limited in this sense.
F
E
D
C
B
A
N
Defensive
0
0
5
4
5
0
6
South has all pits with less than 3 stones.
2
2
2
2
1
2
7
He must move a thus protecting a, b and c.
a
b
c
d
e
F
S
2a
F
E
D
C
B
A
N
After 2a
0
0
5
4
5
0
6
0
3
3
2
1
2
7
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 4C
1
1
6
0
5
0
6
Now North can capture a or d
1
3
3
2
1
2
7
a
b
c
d
e
f
S
South moves 3c protecting d and f
F
E
D
C
B
A
N
After 3c
1
1
6
0
5
0
6
1
3
0
3
2
3
7
15
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
1
6
0
5
0
8
0
3
0
3
2
3
7
a
b
c
d
e
f
S
After 1F
2.2.2 - Attack:
Trying to leave the opponent with pits vulnerable for capturing. That is more than
one pit with 1 or 2 stones. So that he has to leave at least one pit vulnerable. This can be
achieved in several ways and it depends greatly on the disposition of the stones.
F
E
D
C
B
A
N
Offensive
3
0
0
0
0
0
17
South has four moves which leave vulnerable pits
0
0
8
6
3
1
10
1f leaves 1, 3e leaves 2, 6d leaves4, 8c leaves 5
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 8c
3
1
1
1
1
1
17
North has to move 1E to avoid 10 stones captured by South
0
0
0
7
4
2
10
a
b
c
d
e
f
S
F
E
D
C
B
A
N
4
0
1
1
1
1
17
0
0
0
7
4
2
10
a
b
c
d
e
f
S
F
E
D
C
B
A
N
4
0
1
0
0
0
17
0
0
0
7
0
3
16
a
b
c
d
e
f
S
After 1E
After 4e (6)
This shows also that the player that has more stones in his side has an advantage.
We will see this in more detail after.
16
2.2.3 - Kroo Building:
Kroo building [8] is one of the terms used for having enough stones to go around
all the board reaching the pit where the move started. This strategy has the advantage
that the opponent has always to leave a pit empty and therefore vulnerable to a Kroo
move. Kroo building is more easily done in the end pits of each side. You need less
stones (12 to 17 in the last one) and you can add stones more easily with your moves.
F
E
D
C
B
A
N
Kroo Building
1
3
5
3
5
0
5
South wants to accumulate stones in f
0
3
4
1
3
10
5
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
3
5
3
6
0
5
0
3
0
0
5
12
5
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 1F, 5e, 3E
1
0
6
4
7
1
5
North cannot move 7B because it is menaced by Kroo in 13f
2
4
0
0
0
13
5
a
b
c
d
e
f
S
After 4c, 1A, 2d
2.2.4 - Overloading:
When an opponent has build a kroo one way to neutralize it is to add more stones
to that pit. Remember that a kroo is only useful if it has an exact number of stones, for
example for the end pit between 12 and 17 stones.
F
E
D
C
B
A
N
Overloading
1
3
17 3
5
0
5
North has a kroo in D which is menacing d
0
1
0
1
3
4
5
South cannot protect d by moving 1d
a
b
c
d
e
f
S
Only way is to supply more stones to D with 4a
F
E
D
C
B
A
N
After 4a
1
3
18 4
6
1
5
17
0
1
0
1
3
0
5
North has a kroo but move 18D will end in e with 5 stones
a
b
c
d
e
f
S
South has prevented the use of the kroo by overloading it
2.2.5 - Pressure:
Force the opponent who has build a kroo to move it. By not supplying the other
side
with
stones
so
that
the
only
valid
move
is
to
pick
the
kroo.
F
E
D
C
B
A
N
Force
1
1
17 0
0
0
8
South could move 8d thus capturing 4 stones but then...
0
1
0
8
1
1
8
North could move 17D capturing 9 stones
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 1a
1
1
17 0
0
0
8
South is delaying the move
0
2
1
8
1
1
8
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
1
17 0
0
0
8
1
2
1
8
1
1
8
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
1
17 0
0
0
8
1
0
2
9
1
1
8
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
0
17 0
0
0
8
1
1
2
9
1
1
8
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 1e
0
0
17 0
0
0
8
South forces North player to move the kroo
1
1
2
9
0
2
8
a
b
c
d
e
f
S
After 1F
After 2b
After 1E, 1a, 1F
18
F
E
D
C
B
A
N
After 17D
2
2
0
1
1
1
8
North has loose the kroo and South can capture some stones
3
3
4
11 1
3
8
a
b
c
d
e
f
S
F
E
D
C
B
A
N
2
2
0
0
0
0
8
3
3
4
11 1
0
14
a
b
c
d
f
S
e
After 3f (6)
2.2.6 - Counter attack:
This move is possibly one of the most difficult to prepare. The idea is to let the
opponent use his kroo to capture some stones but giving the chance to capture other
(some or even more) stones in the next move.
F
E
D
C
B
A
N
Counter Attack
13
0
0
1
1
1
10
South cannot avoid North playing his kroo
0
1
6
0
1
1
13
He plays 1b so that c will counter attack after N plays 13F
a
b
c
d
e
f
S
F
E
D
C
B
A
N
13
0
0
1
1
1
10
0
0
7
0
1
1
13
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
1
1
2
2
2
14
0
0
8
1
2
2
13
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
0
0
0
0
0
14
0
0
0
2
3
3
26
a
b
c
d
e
f
S
After 1b
After 13F (4)
After 8c (13) Grand Slam
South wins the game
19
All these tactics or strategies can be used throughout the game. Some of them
are general strategies and others come from response to opponent’s moves or board
situations.
2.3 – Game Phases:
During the game we can establish three different phases. The first one is in the
beginning, or taking the term from chess the opening. The second one is when there are
no or few captured stones and most of them are still on the board. The last one is the
end game phase, when there are few stones in the board and also when it is possible for
one player to win the game by capturing a small amount of stones (less than 8).
2.3.1 - Openings:
From the work done by [reference] we know that the best opening move is the
last pit, for South that would be 4f. This was also said by [reference] but deduced in
other way. One of the indications of the status of the board is what he calls, MiH (Moves
in Hand) or OMiH (Onside Moves in Hand). [references].
MiH or OMiH is the number of moves a player can do without giving any stones to
the opponent's side, not taking into account the moves done by the opponent.
F
E
D
C
B
A
N
MiH
0
0
5
4
5
0
8
0
0
4
2
1
2
7
North has 0 MiH
a
b
c
d
e
f
S
South has 3 MiH (1e, 2d, 1e).
This is a static measure of the board, but it gives an idea of which player has a
better position. The best way to have more MiH is to move first the end pits, in the case
of South to move from right to left, or in general from the last pit to the first. This makes
sense with opening with the last pit f so that MiH are maximized.
Openings then follow this rule also so beginning with pit f for South player is the
best choice. From that move most of the moves should be done in response to
opponent’s choices. In the first steps of the game accumulating stones in one pit and
20
having enough moves to choose from are the two most important factors to take into
account.
Imitating Moves:
F
E
D
C
B
A
N
Opening game
4
4
5
5
5
5
0
4
4
4
4
4
0
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
4
5
5
5
5
0
5
5
5
5
4
0
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
0
4
5
6
6
6
0
5
5
5
0
5
1
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
5
0
6
6
6
0
6
6
6
0
5
1
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
1
5
0
7
7
7
0
6
6
0
1
6
2
0
a
b
c
d
e
f
S
F
E
D
C
B
A
N
After 6c
2
6
1
0
7
7
0
Note that North is making the same moves as South
7
7
1
2
6
2
0
Here he has move 7C (The pit has one more stone)
a
b
c
d
e
f
S
The result is that South now has 2 more stones in his side
After 4f
After 4F
After 5d
After 5D
In this simple example we see how imitating an opening is not good. The player
who moves first will get more stones in his side which gives an advantage. There are
several fixed openings that could give a little advantage to a player, but any of them
which leaves enough stones in one side is good. Capturing can also be part of the
21
opening phase. Usually very few stones are captured and therefore the advantage it
gives is also very limited.
2.3.2 - Middle Game:
During the middle of the game several things happen (or may happen):
Note: The numbers only give an approximation to see how the game develops.
Players have captured a certain amount of stones (from 5 to 15 each, that is a
total of 10 to 30 or from 25 to 75 in percentage representation)
In the board there are then fewer stones remaining (from 18 to 38)
Each player can then have in his side of the board between 9 and 19 stones
Some pits would be empty, remember that each move leaves an empty pit. So at least
one empty pit, probably more. There can be Kroo’s. With the above values probably only
one for each player with 12 to 17 stones.
In this situation it is difficult to see a clear winner. The number of captured stones
is not representative unless there is a very big difference. The remaining stones are also
more significant when there is few of them (less than 18). The number of stones that
each player has on his side has more importance, but also when the difference is bigger.
Empty pits also have a lot of importance because they limit player movements and are
vulnerable to attacks from opponent’s kroos. Finally we come to Kroos which could be the
most important factor during middle game, the ability to build, maintain and use a Kroo
are fundamental to win the game.
Therefore we have sorted the important factors for middle game playing by order
of importance. Listing first the most important:
Kroo building and use.
Empty pits.
Stones in each side.
Stones captured by each player.
We can add to this list the number of MiH for each player. Although as we will see
this is even more important for the last phase of the game. We can give a general rule
when trying to decide a move, in case of doubt between two or more moves that seem
22
equally good, always move the stones in the last pit. This is done to maximize MiH for
each player.
2.3.3 - End Game:
End game situations have been completely studied as they have less moves and
positions. Having more MiH than the opponent usually gives an advantage. Even for a
minimum difference between the MiH for each player. Therefore maximizing MiH or, what
is the same, moving last pits first is the best choice.
We also have to take into account moves that capture stones and the number of
captures of each player. So in this phase the important factors are the following:
Stones captured by each player.
Stones in each side.
Number of MiH of each player.
With all the information from the given phases we can try to obtain a function that
gives us the state of the board. There are factors that we can take into account for each
move:
Number of stones captured by each player (having number of stones remaining in board)
Number of stones in each side of the board
Number of stones that can be captured with a move
Number of stones that can be lost in next move when choosing a particular move
Number of vulnerable pits (1 or 2 stones) in each side
Number of 2 pass vulnerable pits (0 or 1 stone) in each side
Number of MiH (Moves in Hand) for each player
Number of real MiH including protection of menaced pits or possible captures
Number of pits with enough stones to go round the board. (12-22)
We can give more information for some of these factors:
1) Number of stones captured by each player (having then stones still in the board)
23
This factor has a lot of importance because it determines the winner of the game. Once
somebody has captured more than 24 stones he is declared the winner. Therefore it is
even more important near the end of the game. We can choose to divide this factor into
three:
Number of stones captured by player
Number of stones captured by opponent
Difference between number of captures
The last one is calculated from the first two ones.
2) Number of stones in each side of the board
This is the number of stones each player has on his side of the board, the more stones
the better. This is also more important towards the end of the game and it is directly
related to the term MiH explained before.
3) Number of MiH (Moves in Hand) for each player
This is how many moves a player can make before giving any stones to the opponent.
What we want to know is which player has more MiH, so we want to calculate the
difference between the two values.
4) Number of pits with enough stones to go round the board. (12-22)
This is the most complex factor, not only the building of a kroo is important, but also its
use. Its use depends greatly on the situation of the stones in the opponents side. As well
as picking the right moment to use it before it becomes useless.
All this information and more is used to build a utility function, in it is contained all
the knowledge of the strategies of the game. Defining or more specifically modelling a
utility function is not an easy task, we will discuss more about this and other methods of
resolution in the next chapter.
24
3 – Modelling a utility function:
3.1 - Searching trees for Oware:
Building a search tree for all possible Oware moves is a complicated task. In any
move each player has a maximum of 6 pit choices. As the game progresses this number
decreases, as empty pits cannot be selected for valid moves. This gives us a tree of a
width of 6 branches in each move (maximum). The duration of an Oware game can vary
greatly from 30 to 80 or more moves. Taking for example 50 as a middle value we could
have a depth of 50 levels for our search tree.
So the number of total positions and therefore every possible game position that
could be achieve can be easily calculated.
Number of positions: N=6*6*6*6…..*6 (50 times) = 650 = 8.082812774 +38
The figure below shows how a tree would be built. Starting from the top position,
the start game position, selecting in each branch a different move for each player. In
some levels the number of branches would be less than 6.
F
4
4
a
F
4
0
a
E
4
5
b
D
4
5
c
C
4
5
d
B
4
5
e
A
4
4
f
N
0
0
S
4b
E
4
4
b
4c
D
4
4
c
C
4
4
d
...
B
4
4
e
A
4
4
f
N
0
0
S
4d
4e
F
4
4
a
E
4
4
b
D
5
4
c
C
5
4
d
B
5
4
e
A
5
0
f
N
0
0
S
...................................................
25
This is of course not accurate, but it shows that the number of game positions is
very high. Actually the exact number of game positions has been calculated and is:
889.063.398.406 game positions.
The number was calculated using a different system to build the tree. Starting
from every end game position and going back to the start of the game. The technique
used was retrograde analysis and was the first one to do it with the whole number of
stones, 48. [3]
This study has lead to interesting facts. As, for example, that with perfect play
from both sides the final result of the game is a draw. But analysing the database we can
extract more useful conclusions.
The figure on the next page shows how the positions are divided over the scores.
The length of each bar reflects the number of positions that have a particular score. The
colours distinguish the numbers of stones in the positions. For example, there are about
100 billion positions that have score 0, of which about 33 billion have 48 stones.
26
27
Most of the games end in a draw and also with a score of +2 for the player who
moves first. Usually the moves that make a capture are the best ones, this is true in 78%
of the cases in which there was to select between a capturing and a non-capturing move.
This is even more accused in the beginning of the game when captures are less
important than having a good position in the board.
Another important fact is that the best opening move and the only one that is sure
to give the best outcome is moving the last pit (f). As we said before when we discussed
the strategies this first move is the strongest move. From this move the alternatives
depend on each of the players moves, but a strong follow for the (f) pit move is the (d)
pit.
As the database with all possible board positions has been calculated it is
impossible to make a system that can win every game. The best outcome is to get a
draw in every game, but building a system that can achieve this without relying on the
database can be a futile task. There are two objectives remaining, a system that can play
without the use of a search tree and a system that is capable of learning from each game
it takes part on.
28
3.2 - Player’s Evolution:
Another approach to build a strong system that can play Oware and improve in
each game has been done [4]. In this case this work shows how a player can be evolved
using a co-evolutionary approach where computer players play against one another, with
the strongest players surviving and being mutated using an evolutionary strategy (ES).
The players are represented using a simple evaluation function, representing the current
state of the game, with each term of the function having a weight which is evolved using
the ES.
The function we present to the evolving player is as follows
f = w1a2 + w2a3 + w3ß2 + w4ß3 + w5as + w6ßs (1)
where :
w1-w6 The weightings for each term of f
a2 Number of opponents pits vulnerable to a 2 stones capture on the next move
a3 Number of opponents pits vulnerable to a 3 stones capture on the next move
ß2 Number of players pits vulnerable to a 2 stones capture on the next move
ß3 Number of players pits vulnerable to a 3 stones capture on the next move
as The current score of the opponent
ßs The current score of the evolving player
The idea is to start with a player that uses a function with random weight, playing
a very weak game. And by playing games, adjusting the weights, can achieve to play at
a good level with the new function. This is done with the following choices:
-
A population, P, of 20 players is created. Each member of the population, pn
(n=1..20), contains six real numbers which correspond to the weights, w1..w6.
The weights are randomly initialised with values –1..+1.
-
Each pn plays every other pn member twice, but they do not play themselves.
They play once as north and once as south.
-
For each move by the evolving player, a search tree is constructed. The depth of
the search tree is determined by the available search time. The search depth
chosen was 7. The evaluation function (1) assigns a value to each of the
terminal nodes and these values are propagated up to the root of the search
tree using the mini-max algorithm.
29
-
At the end of all the games the top m (the choice made was m=5) players were
retained and the rest were discarded.
-
Each retained player produces an equal number of children. If this would exceed
the population size (as n=20 and m=5, this was not relevant to us) then the
production is biased towards the fittest individuals. The production of a new
player is produced by pn(wi) = pn(wi) + N(0,1) (2)
We note however that this system still relies in a search tree, although hidden
behind all the process to select the best player. This search tree is in the end the one
responsible for the improvement of the player. All the children produced by the player,
the weight adjustment and the selection are minor elements that contribute to the
evolved player.
A seven level search tree for the game Oware is good. The number of positions
given by this tree can be calculated. Considering the maximum of six possible moves for
each player, mostly in the beginning of games we have the number of positions:
N=6*6*6*6*6*6*6=67=279.936 positions
That is nearly three hundred thousand positions per move. It is stated that each
move take up to one minute for the system to calculate it, building the tree and selecting
the best move. In a typical end game where we have less stones we can have around
three moves to choose from, as most pits are empty. This gives us N=3 7=2.187 positions
with a seven level tree during the final moves of the game.
The evolved player is able to play at a good level, even considering only capturing
moves and current player scores for the evaluation function. We can assume that a great
part of the power of the player comes from the search tree that builds while developing
the final evaluation function. One of the next objectives for this player is to find its own
evaluation function and finding at the same time which factors are important for the
strategy of the game.
30
3.3 - Decision Making and Utility Functions:
In our decision making system we are going to evaluate moves from the
information given by the static position of the board. With all the data from the moves
and the selection of different positions we can define a utility function that represents the
system we design. The previous method uses an evaluation function to calculate the best
move. This function is also called a utility function, a term that comes from economics.
Utility is used to describe how good is a state given, therefore the goal is to maximize the
values of the utility function. For multiple states we can find the values given by the
utility function and choose the highest one. This is what the previous method does,
although relying still in a search tree to find the ultimate utility function.
It is finding the utility function the most complicated task. Not only it is difficult to
find one, but sometimes one cannot assure if it is a good function or even if exists for a
particular given problem. Utility functions are used in systems under uncertainty where
they introduce the term expected utility. This expected utility is then calculated based on
probabilities for the outcome of each action. In our game we do not need to take into
account probabilities because it has no hidden information. A utility function for the game
considers that status of the board and the possible outcomes of each move. All the
information is available for the system to evaluate the utility function.
A decision making system can rely in a large variety of structures to work.
Decision making systems based on utility functions are common, the way to choose an
action is to see the value that the function gives. [9]
In the game Oware finding a suitable utility function can be achieved in several
ways. In the previous study we have examined the function was defined based on the
knowledge of the game. The function is limited in two ways: It has been arbitrarily
defined and the weights for the final function are based on a limited search tree. Even
with these two restrictions the function does a good job performing at a decent level
against another computer system use for testing.
These two factors that limit the effectiveness of the utility function can be
eliminated by using a different strategy to obtain our decision making system. First of all
we avoid the use of an explicit utility function. By using a set of rules that take into
account the important factors that affect the game we can get similar results. These rules
could be converted to a explicit utility function because they consist of the information
that is important for the game. Not only do the rules have this information, they also
have the strategy for playing the game. In this sense we are substituting the second
31
factor that limits the effectiveness of the function. Instead of using tests, in this case
building a limited search tree, we decide which is the best move based on the strategy
proposed.
This way of presenting the decision making system has also disadvantages. One of
them is that the important information regarding the game and the strategy for playing it
are mixed up in the set of rules. Another aspect is that the information to define the rules
come from experience with the game and sometimes this cannot be specified. In this
sense Oware is a game that has an intricate strategy, there is agreement in which things
are important for the game but not in a very specific way.
Before presenting the decision making system we explain in the next chapter the
basics of logic representation. This would be use to describe the rules and facts that
conceive our strategy.
32
3.4 - Logic Representation:
One of the ways to represent knowledge is the use of predicate logic. It is based
on the idea that sentences do really represent relations between objects, as well as
qualities and attributes of those objects. These relations, qualities or attributes are called
predicates and the objects are the arguments of the predicates. The predicates have a
truth value that depends on its arguments, it can be true for some arguments and false
for others. For example the predicate:
colour (sky, blue) is true
but colour (sky, pink) is false
The truth of these predicates depends on the relation with the system we are
trying to represent. This is done so that it seems more natural but it is not a strict
requirement. The only important aspect is that reasoning is done correctly with the use
of inference rules. Based on true predicates the result of applying the inference rules will
provide only true consequences.
The predicates that are establish as been true are called facts. These facts are
always true in our system without considering information from the real world. For
example we could establish that the sky is red by writing the fact:
colour (sky, red)
This fact is true in our system and would be use during the reasoning with the
rules. In the predicate knowledge systems we can use connectors and operators to
represent complex rules. We can also use variables that have no value and get the value
during the process of evaluation of the rules.
3.4.1 - Unification:
The unification process tries to match the components in the predicates with the
facts that are in the knowledge database. This process gets more complex when there
are variables involved. The process can be described by the following steps:
1- All predicates without variables must have a fact that match it for the predicate to be
true.
33
2- If there are variables they have to be associated with a fixed value. The predicate is
compared to all facts not taking into account the variable. The variable is associated with
the value in the fact that is in the same position. If there is more than one fact that
meets the condition all values are taken are treated separately.
3- The process of identification continues considering that the value of the variable is
used in the rest of the predicates.
3.4.2 - Inference:
Inference is a process in which a conclusion is obtained from a set of facts. There
are certain rules that form the basic principles of inference.
Modus ponens is the most important one in knowledge based systems. This rule
can be explained in the following way:
If p and p->q are known to be true then q is also true.
Modus tolens can be also described in a similar way:
If p->q is true and q is false then p is false.
Resolution is a technique that uses contradiction to validate sentences. The way to
do it is to negate the original sentence apply the technique until a contradiction is
reached therefore validating the original sentence. This is a very powerful technique to
demonstrate theorems in logic and is the basic inference system that PROLOG uses.
The resolution rule can be represented by the following rules:
If A->B is true and B->C is true then A->C is true
These techniques and a few others that PROLOG uses can be treated as reasoning
mechanism. This mechanism is based on inference and in a certain way is a step ahead.
In predicate logic there are three basic methods of reasoning: deduction, abduction and
induction.
Deduction can obtain a logical conclusion from a given premise. By doing logic
inferences that are correct we can guarantee that the conclusions would also be valid if
the premises where true.
34
This is the most simple to understand method and also accepted to be valid. It
can be expressed in predicate logic as follows:
Given A,B,C and Greater(A,B) and Greater(B,C) we can deduce that Greater(A,C)
Abduction is used to generate explanations and cannot guarantee that the
conclusion would be valid. Therefore it is not a fundamental method for inference.
Beginning with the conclusion the method tries to obtain the conditions that make that
conclusion true. Trying to find an explanation or a cause for the given conclusion.
If A->B is true and B is true then A could be true
Induction develops from individual situations and tries to reach a general
conclusion. This is the basics of scientific research and investigation. It is also a not a
solid method for inference.
For example given P(A),P(B)....P(N) we can give P(X) for every X as a conclusion.
Deduction is a monotonic way of reasoning that produces conclusion that are still
valid. This type of reasoning is not affected by the inclusion of new facts. Another aspect
of this reasoning is that the order in which the inference is applied, to rules and facts,
does not limit the inferences that can be made.
3.5 - Production Systems:
Production systems [Elaine Rich] can represent a system with the use of predicate
logic. These systems are defined by three components:
a) A set of rules: This set of rules can be represented using predicate logic, with
the form (condition -> action). As in predicate logic in both sides we can have
different elements and connectors. In Prolog the representation is not the
same, we will see this in more detail in the next section.
b) One or more databases: Typically these systems have an initial database and a
working database. The first one consist of the initial facts that are used for the
system to start working and does not change. The second one is modified by
including new elements that come from the application of the rules. This
database increases as the system applies new rules. Joining the two databases
we have the full knowledge of the system.
c) A control strategy: This part of the system decides which rule is going to be
applied in each step of the process. There are a lot of methods for control
35
strategies but usually each problem requires a personalized control strategy to
get a good result and also for optimisation. This part of the system also is in
charge of resolving possible conflicts that can arise from the application of the
rules.
The way this system resolves problems is very simple. Given that the three parts
are well defined and coherent we can describe the process of solving as a cycle:
a) Detect all the rules that can be applied with the facts in the knowledge of the
system. This is the same as trying to match the left hand side elements of the
rules with the facts that we have.
b) Select and apply one of the rules. This is done by the defined control strategy.
c) Update the knowledge database with the new elements infered from the rule
applied. This is the same as adding the elements on the right hand side of the
rule that has been applied.
This process is done until we achieve the objective which should be in the
knowledge database or when there are no more rules to apply. We can also reach a point
where we have a contradiction or conflict, the control strategy should be able to detect
and adapt to these situations.
There are two basic strategies for resolution, forward and backward chaining. The
first one starts with an initial state and applies rules to give solutions. Backward
reasoning starts with an objective or hypothesis and tries to find a solution that matches
that hypothesis. Prolog uses backward chaining so we would describe it in more detail.
3.5.1 - Backward Chaining:
This type of strategy allows rather a more focused style of reasoning. While
forward reasoning can lead to a lot of information added to the database that is not
relevant, with backward resolution we concentrate in the objective given. To prove the
goal we apply two simple steps:
a) If the goal is in the database it is proven.
b) If not, we find a rule that has the goal in the right hand side and we try to
prove each of the conditions of the selected rule.
36
This leads to new problems like for example when we have two rules with the goal
on the right side. In this case we have to test both rules independently, luckily this is
done by Prolog.
3.5.2 - Production System for Oware:
This production system is a simplified version of our system. It has the necessary
information to play the game but without any kind of strategy. All the rules are
represented as well as the results of each move, so that every move that is made is
valid. The missing part is the control strategy that selects which rule to apply. In this
case the possible set of rules to apply are the valid moves for a player. So finding a
control strategy that selects the best move each time is the next goal of this essay.
Below there is a description of the system, the elements used in the rules are
described first. Some of them can only have a true or false value. These elements can
only have one value at a time, take for example the rule A->¬A. This means that if we
have A in the knowledge database after applying the rule we will have only ¬A in the
database, not both elements. This has been done for a clear reading of the set of rules,
in Prolog the way of doing it is significantly different.
37
Terminology:
COU[SQ]=X
Number of stones for pit (SQ = 1..12)
TURN=PLY
Turn of player (PLY = 1/2)
MOV[INI,ACT,RES]
Movement INI initial pit, ACT actual pit, RES remaining stones
CAP[SQ]
Capture of stones in pit (SQ = 1..12)
TOTAL[PLY]=X
Number of total stones each player has captured (PLY = 1/2)
ENDTURN
If the actual turn has ended
¬
Eliminate element from knowledge
++
Increment value by one
--
Decrement value by one
+=
Increment original value by another value
<
Logical Operator (Less Than)
>
Logical Operator (More Than)
=
Logical Operator (Equal)
<>
Logical Operator (Not Equal)
&
Logical AND
NOTE: The SQ values go in a round chain, that means that after 12 goes number 1...
(12++)=1
Initial State:
COU[1..12]=4
Every pit has 4 stones in it
TURN=1
The first player starts
TOTAL[1,2]=0
Initial stones captured is 0 for both players
ENDTURN
To begin the game
Rules:
Selecting:
If TURN=1 & ENDTURN & COU[1..6]<>0 -> MOV[1..6,1..6,COU[1..6]] & COU[1..6]=0 &
¬ENDTURN
If
TURN=2
&
ENDTURN
&
COU[7..12]<>0
->
MOV[7..12,7..12,COU[7..12]]
&
COU[7..12]=0 & ¬ENDTURN
Note: His is a reduced representation of 12 rules.
38
If TURN=1 & ENDTURN & COU[1]<>0 -> MOV[1,1,COU[1]] & COU[1]=0 & ¬ENDTURN
If TURN=1 & ENDTURN & COU[2]<>0 -> MOV[2,2,COU[2]] & COU[2]=0 & ¬ENDTURN
If TURN=1 & ENDTURN & COU[3]<>0 -> MOV[3,3,COU[3]] & COU[3]=0 & ¬ENDTURN
If TURN=1 & ENDTURN & COU[4]<>0 -> MOV[4,4,COU[4]] & COU[4]=0 & ¬ENDTURN
If TURN=1 & ENDTURN & COU[5]<>0 -> MOV[5,5,COU[5]] & COU[5]=0 & ¬ENDTURN
If TURN=1 & ENDTURN & COU[6]<>0 -> MOV[6,6,COU[6]] & COU[6]=0 & ¬ENDTURN
If TURN=2 & ENDTURN & COU[7]<>0 -> MOV[7,7,COU[7]] & COU[7]=0 & ¬ENDTURN
If TURN=2 & ENDTURN & COU[8]<>0 -> MOV[8,8,COU[8]] & COU[8]=0 & ¬ENDTURN
If TURN=2 & ENDTURN & COU[9]<>0 -> MOV[9,9,COU[9]] & COU[9]=0 & ¬ENDTURN
If TURN=2 & ENDTURN & COU[10]<>0 -> MOV[10,10,COU[10]] & COU[10]=0 &
¬ENDTURN
If TURN=2 & ENDTURN & COU[11]<>0 -> MOV[11,11,COU[11]] & COU[11]=0 &
¬ENDTURN
If TURN=2 & ENDTURN & COU[12]<>0 -> MOV[12,12,COU[12]] & COU[12]=0 &
¬ENDTURN
Moving:
If MOV[INI,ACT,stoneS] & stoneS>0 & INI<>ACT ->
COU[ACT]++ & ¬MOV[INI,ACT,stoneS] & MOV[INI,ACT++,stoneS--]
If MOV[INI,ACT,stoneS] & stoneS>0 & INI=ACT ->
¬MOV[INI,ACT,stoneS] & MOV[INI,ACT++,stoneS]
If MOV[INI,ACT,stoneS] & stoneS=0 ->
¬MOV[INI,ACT,stoneS] & CAP[ACT]
Capturing:
If TURN=1 & CAP[SQ] & SQ>6 & 0<COU[SQ]<4 ->
TOTAL[1]+=COU[SQ] & COU[SQ]=0 & CAP[SQ--]
If TURN=1 & CAP[SQ] & SQ>6 & 0=COU[SQ]>3 -> TURN=2 & ¬CAP[SQ] & ENDTURN
If TURN=1 & CAP[SQ] & SQ<7 -> TURN=2 & ¬CAP[SQ] & ENDTURN
39
If TURN=2 & CAP[SQ] & SQ<7 0<COU[SQ]<4 ->
TOTAL[2]+=COU[SQ] & COU[SQ]=0 & CAP[SQ--]
If TURN=2 & CAP[SQ] & SQ<7 & 0=COU[SQ]>3 -> TURN=1 & ¬CAP[SQ] & ENDTURN
If TURN=2 & CAP[SQ] & SQ>6 -> TURN=1 & ¬CAP[SQ] & ENDTURN
Ending:
If TOTAL[1]>24 -> WIN[1]
If TOTAL[2]>24 -> WIN[2]
Total 24 RULES
Control Strategy:
Priority
Always check ending rules (after capturing)
Basic order is (Selecting, Moving, Capturing, Ending)
The strategy should be applied for the selecting rules.
40
4 – The Program:
When trying to decide which kind of system was going to be implemented several
options came up. Representation of the game was the most important issue, how to
represent the game in a simple and clear way; while keeping in mind all the strategy that
is supposed to be used for playing the game.
A full implementation of an interactive game was not carried out due to lack of
time and mostly because it was not the aim of the study. Therefore the focus came into
the better way to implement the strategy defined. At the same time we had a good logic
representation of the game that we did not want to loose.
The final decision was to use Prolog, not without a discussion, as this was the first
time it was used in a complex problem by me. Also the game involved a lot of arithmetic
calculation for which Prolog is not well prepared. After solving these difficulties with the
language and some workarounds for the representation of the board the system was fully
defined.
4.1 – Prolog:
Prolog deals with knowledge, mainly with what we call rules and facts. With these
facts and set of rules Prolog can use reasoning to obtain new results. In our case we will
try to define this set of rules and facts with the game so that our system can inference
the best move for a given position.
The facts will be the static information given by the board. We have the initial
state of the board before the move and the results of each possible move. We present
this information in several values and two lists. Each fact has the following form:
board(i0,f0, [4,4,4,4,4,0], [5,5,5,5,4,4], 0,0).
For each line we have identifier of the initial state and the move, i(n) (initial state,
number of board). Followed by two lists that represent the pits in each side of the board
for each player. Finally, the last values, are the stones captured by South and North.
With this information defined as facts we can obtain all the details explained
before to inference which is the best move. This reasoning comes from the set of rules
41
that we have defined accordingly to the strategy chosen. The rules follow three mayor
steps to do the inference:
a) Check if the move is a winning move. In this case the number of stones
captured with the move will exceed 24 thus winning the game. Such a move
has priority over all other moves. This is important because in other systems
sometimes this move was not chosen in favour of another move that would
give a mayor benefit after more moves. When using search trees these sort of
things may happen. The search tree gives the best values for each move, but
do not take into account other factors of the game.
b) Find in what phase the game is in. The strategy depends greatly on the phase
of the game, so we have to see if we are in the opening, the middle game or
the ending. This is done mostly calculating the number of stones remaining
and the number of stones captured by each player. Another way to do it would
be to keep a counter of the number of moves done, but this introduces a
temporary element that we wish to avoid; at least in this first version.
c) We try to match the conditions of the move to the ones that are in the given
strategy. With this we can tell if a specific move is good, in what degree and
the reasons why it is a good move. Here Prolog is not very helpful as the
measure of how good is a move has to be done independently and we come
closer to the use of a utility function that is or objective. Also the reasoning
about why it is a good move cannot be easily followed using the Prolog
interpreter (in this case SWI-Prolog). Another way to keep track of this
reasoning should be use to make the process more clear to the user.
For example a rule of our system consists of:
testMove(Ini,Move) :- winMove(Ini,Move) ; valueMove(Ini,Move).
The left hand side of the rule is the conclusion, while the right hand side are the
conditions that must be met. In common logic representation the rule would be as
follows:
winMove(Ini,Move) OR valueMove(Ini,Move) -> testMove(Ini,Move)
Notice the change of symbols and that the elements are reversed. These are the
mayor notation changes that Prolog uses compared to traditional logic representation.
42
Even though all the strategy and the facts are in the knowledge that Prolog uses,
there are several ways in which this can be done. Here there is freedom for the
programmer or designer to decide in which way does he want the system to behave. In
our case the aim was to make a simple system, that was fast in calculating the best
move (almost instantaneous) and that could be easily modified to add new knowledge or
even update the existing one. While doing this some things have been left behind and
they add to the list of possible next steps to take for studying Oware.
The full commented code can be found in the appendix. The code was tested only
in SWI-Prolog for Windows. It can be found on the internet in http://www.swi-prolog.org/
43
5 – Conclusions:
The goal of this study was to obtain a utility function from the system that was
implemented in Prolog. Due to the limitations of the Prolog program this could not be
done. The program is too simple and can only infer good moves with static positions one
by one. It needs a faster and automatic way to introduce positions and also to store the
best move for each position. Therefore no utility function could be produced with this
system and this is left for further research and work. During the development of the
system in Prolog several difficulties were encountered. A great part of the programming
could had been done more easily in traditional languages like C, although Prolog was
helpful for other things. The way the strategy of the game was defined and the nature of
the game in itself were responsible for the need of arithmetic calculations.
In the case of Oware this calculations are constantly made, for each move that a
player makes for example. We used two lists to represent the pits of each player, but a
circular list would have been better. Mainly to simplify some calculations of moves as the
moves go round the board from one players side to the other. Even with these limitations
the system is able to infer from the static information of the board the best move. This
was one of the main goals of the study and it was achieved with a satisfactory level.
There are a couple of improvements and other paths that could have been
exploited. Not looking at other AI techniques and concentrating in the way we wanted to
build our decision making system we still can find lot of variations:

Using a different structure to represent the board. Something like a circular list so
that the moves could be simulated easily.

Developing a program which could backtrack the steps Prolog used to infer the
result. Presenting this steps in a clearly and specific way for the user to read.

Give the option to change weights for some factors that have an influence in the
strategy. For example giving more important to some variables than others.

Implement a full interactive game that uses the decision making system to make
the moves. This could lead to tests to see the performance of the system against
human players and other computerised opponents.
44
The backtracking of the steps that Prolog uses to produce the result can be
obtained but in no easy way. Using one of Prolog commands, “trace”, we can see step by
step how it does the reasoning. This is an internal command of Prolog used mainly for
debugging as it is not user friendly.
Apart from this goals that had to deal with the developing of the system a detail
strategy for Oware was developed. This strategy is by no means complete, but
considering the type of system we wanted to built it takes into account all the variables
that are important for playing the game. Trying to obtain a set of rules with only the
static information of the board was not an easy task. There is a lot of information that
can potentially improve the performance of the system, as other techniques previously
studied, use of search trees, looking at previous moves…
Given this limitations, the system built uses a simple but clear strategy. A set of
rules that anyone can understand; represented in a similar way as how a human player
thinks each move. This was the main goal of the study, making a system that behaves,
or in this case plays, as a human.
Even though this is an utopia, we can find certain similarities between the way the
system behaves and an average human player. Our system can choose a move without
having to calculate in advance all the possibilities and branches that the move can
produce. It can reason which move is good in certain positions and game phases; been
able to do several things at the same time, like protecting his pits, capturing stones,
getting a better position, menacing opponents pits…
At the same time the system lacks some abilities that human possess. Obviously
all learning related abilities are not in this system. Other features which human players
use that are not present are the following:

The possibility to plan ahead. This is not to calculate the next moves and all the
ramifications but to use a long term strategy.

Adaptation to the opponents style of playing. The system does not take into
account the kind of opponent it is playing with and it assumes it is a common
player.

Change the strategy while playing. This could be done for several reasons, but the
system is not capable of doing so.
45
Some of this features could be included in new development of the system but
most of them are truly complicated. They are topics or themes that are been studied
thoroughly by the AI community and represent major goals to the world of computer
games. The ability to emulate a human in games has always been interesting, as this
sort of behaviour could be taken to other systems achieving a major breakthrough in AI.
Oware is a game with lots of possibilities, simpler than chess, and has
demonstrated that it is very useful to the development and study of different AI
techniques. As one master Oware player said, even though all the game positions have
been calculated this does not mean that people are going to stop playing the game. It is
an opportunity to study the game for a better understanding and to adapt the way of
playing. All the work done with a game has benefits that could be applied to the study of
other games or other problems.
This work was only a feeble attempt to study a game using AI techniques. Most of
the time was spent studying the game in itself. This also happens when trying to solve
any kind of problem, before starting to work on the solution we must study the problem
in detail. Nowadays the AI is becoming more complex and new solutions or techniques
are been discovered. I hope this trend continues and that games are kept as good
domains to test all this new discoveries.
46
6 - References:
[1] H S Nwana, L Lee and N R Jennings: Co-ordination in Software Agent Systems
BT Technol J Vol 14 No 4 (October 1996).
[2] Rosenschein J S: Rational Interaction: Cooperation Among Intelligent Agents
PhD Thesis, Stanford University (1985).
[3] John W. Romein and Henri E. Bal: Solving the Game of Awari using Parallel
Retrograde Analysis Vrije Universiteit, Faculty of Sciences, Amsterdam, The
Netherlands (July 2002).
[4] Davis J. E. and Kendall G.: An Investigation, using Co-Evolution, to Evolve an Awari
Player Accepted for 2002 Congress on Evolutionary Computation (CEC2002),
Honolulu, Hawaii (May 2002).
[5] Sapient Software: Oware! (1995)
[6] David Chamberlin: Playing Warri
http://www.hut.fi/~vkorkiak/mancala/doc/html_rules/chapter1.html
[7] Fogel, David B.: Evolutionary computation : Toward a new philosophy of machine
intelligence
[8] Steffan O'Sullivan: Awale (2002)
http://www.panix.com/~sos/bc/awale.html
[9] Zimmermann, Hans-Jürgen: Fuzzy sets, decision making and expert systems (1991)
[10] George Leitmann: Multicriteria decision making and differential games (1971)
[11] Games of No Chance : Combinatorial Games (1994 at MSRI)
[12] Johan Bos, Kristina Striegnitz, Patrick Blackburn: Learn Prolog Now! (2001)
[13] Jacques Calmet, Anusch Daemi, Regine Endsuleit and Thilo Mie:
A Liberal Approach to Openness in Societies of Agents
47
[14] Joseph Y. Halpern: A Computer Scientist Looks at Game Theory (2002)
[15] Kemal Enver: Intelligent Multi-Agent Systems in Games (2003)
[16] David B. Fogel: Evolutionary Entertainment with Intelligent Agents
[17] Wynn C. Stirling: Satisfying Games and Decision Making,
Brigham Young University (2003)
[18] Jonathan Schaeffer: A Gamut of Games,
American Association for Artificial Intelligence (2001)
[19] H. S. Nwana, L. Lee and N. R. Jennings: Co-ordination in software agent systems
[20] H.H.L.M. Donkers, H.J. van den Herik, J.W.H.M. Uiterwijk:
Opponent-Model
Search
In
Bao:
Conditions
for
a
Successful
Application,
Universiteit Maastricht.
[21] Conlon, Tom: Start problem-solving with Prolog, Addison-Wesley (1985)
48
Appendix 1 – Prolog Code:
Prolog code with comments:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% PROLOG PROGRAM - DECSION MAKING SYSTEMS FOR OWARE
%
% CARLOS NOGUERO - FACULTAD DE INFORMATICA DE MADRID
%
% ERASMUS AT KARLSRUHE UNIVERSITY
%
% FROM 1-10-2003 TO 31-03-2004
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%Initial Knowledge
%With each board we give the following informacion:
%The initial state of the board before the move and the results of each possible move.
%The information in each line is the identifier of the move, in (initial state, number of board).
%Then the stones in South pits and North pits, the last values are stones captured by South and North.
% Opening
board(i0,f0, [4,4,4,4,4,0], [5,5,5,5,4,4], 0,0).
board(i0,e0, [4,4,4,4,0,5], [5,5,5,4,4,4], 0,0).
board(i0,d0, [4,4,4,0,5,5], [5,5,4,4,4,4], 0,0).
board(i0,c0, [4,4,0,5,5,5], [5,4,4,4,4,4], 0,0).
board(i0,b0, [4,0,5,5,5,5], [4,4,4,4,4,4], 0,0).
board(i0,a0, [0,5,5,5,5,4], [4,4,4,4,4,4], 0,0).
board(i0,i0, [4,4,4,4,4,4], [4,4,4,4,4,4], 0,0).
% Middle Game
board(i1,f1, [4,1,2,3,0,0], [1,15,1,1,3,4], 5,8).
board(i1,d1, [3,0,2,0,1,9], [1,14,0,0,2,3], 5,8).
board(i1,c1, [3,0,0,4,1,8], [0,14,0,0,2,3], 5,8).
board(i1,a1, [0,1,3,4,0,8], [0,14,0,0,2,3], 5,8).
board(i1,i1, [3,0,2,3,0,8], [0,14,0,0,2,3], 5,8).
% End Game
board(i2,f2, [1,0,2,0,0,0], [2,2,2,0,1,1], 22,15).
board(i2,c2, [1,0,0,1,1,3], [2,2,2,1,0,0], 20,15).
board(i2,a2, [0,1,2,0,0,3], [2,2,2,1,0,0], 20,15).
board(i2,i2, [1,0,2,0,0,3], [2,2,2,1,0,0], 20,15).
% Oppening with Kroo
49
board(i3,f3, [0,17,3,0,0,0], [2,10,2,0,5,1], 4,4).
board(i3,c3, [0,17,0,1,1,3], [2,10,2,0,4,0], 4,4).
board(i3,b3, [1,0,5,2,2,4], [3,11,3,1,6,2], 4,4).
board(i3,i3, [0,17,3,0,0,2], [2,10,2,0,4,0], 4,4).
% Middle Game with Capture
board(i4,f4, [3,3,2,4,0,0], [2,8,0,0,0,0], 18,10).
board(i4,d4, [3,3,2,0,1,5], [2,8,1,1,0,0], 12,10).
board(i4,c4, [3,3,0,5,1,4], [2,8,1,1,2,1], 7,10).
board(i4,b4, [3,0,3,5,1,4], [2,8,1,1,2,1], 7,10).
board(i4,a4, [0,4,3,5,0,4], [2,8,1,1,2,1], 7,10).
board(i4,i4, [3,3,2,4,0,4], [2,8,1,1,2,1], 7,10).
% Middle Game
board(i5,e5, [3,1,1,5,0,2], [3,5,1,2,0,0], 13,12).
board(i5,d5, [2,0,0,0,15,1], [2,4,0,1,1,2], 8,12).
board(i5,a5, [0,1,1,4,14,0], [2,4,0,1,0,1], 8,12).
board(i5,i5, [2,0,0,4,14,0], [2,4,0,1,0,1], 8,12).
% NOTE: This are only some examples of game positions.
%------------------------------------------------------------------------------------------%Checks if a Move is good
%use: goodMove(index of initial state ("i"+number), index of move or VAR (letter+number)
%------------------------------------------------------------------------------------------goodMove(Ini,Move) :- testMove(Ini,Move).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks first to see if the Move is a winning move (ends)
%called by: goodMove(Ini,Move)
%------------------------------------------------------------------------------------------testMove(Ini,Move) :- winMove(Ini,Move) ; valueMove(Ini,Move).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks if it is a winning move, more than 24 stones captured
%called by: testMove(Ini,Move)
%------------------------------------------------------------------------------------------winMove(Ini,Move) :board(Ini,Move, [],[], CapS,_),
CapS>24.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Counts the number of stones that remain on the board
%use: countTotal(initial state ("i"+number), move (letter+number), RESULT)
%------------------------------------------------------------------------------------------countTotal(Ini,Move,Total) :-
50
board(Ini,Move, LS, LN, _,_),
cntTotal(LS,LN,Total).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks move for starting games
%called by: testMove(Ini,Move)
%------------------------------------------------------------------------------------------valueMove(Ini,Move) :- openMove(Ini,Move), countTotal(Ini,Move,Total), Total>37.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks move for middle games
%called by: testMove(Ini,Move)
%------------------------------------------------------------------------------------------valueMove(Ini,Move) :- middleMove(Ini,Move), countTotal(Ini,Move,Total), Total>18, Total<38.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks move for END games
%called by: testMove(Ini,Move)
%------------------------------------------------------------------------------------------valueMove(Ini,Move) :- endMove(Ini,Move), countTotal(Ini,Move,Total), Total<19.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks conditions for good moves in OPENING phase
%called by: valueMove(Ini,Move)
%------------------------------------------------------------------------------------------openMove(Ini,Move) :isCaptureMove(Ini,Move);
isStonesSide(Ini,Move).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks conditions for good moves in MIDDLE game
%called by: valueMove(Ini,Move)
%------------------------------------------------------------------------------------------middleMove(Ini,Move) :isEmptyPits(Ini,Move);
isStonesSide(Ini,Move);
isCaptureMove(Ini,Move);
isProtectPits(Ini,Move).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks conditions for good moves in END games
%called by: valueMove(Ini,Move)
%-------------------------------------------------------------------------------------------
51
endMove(Ini,Move) :isCaptureMove(Ini,Move);
isStonesSide(Ini,Move);
isProtectPits(Ini,Move);
isMIHMove(Ini,Move).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks if the move is a good move that leaves few empty pits
%use: isEmptyPits(initial state ("i"+number), move (letter+number))
%------------------------------------------------------------------------------------------isEmptyPits(Ini,Move) :numberEmpty(Ini,Move,BefS,AftS,BefN,AftN),
BefS>AftS, BefN<AftN.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks if the move is a good capture move depending on several factors
%use: isCaptureMove(initial state ("i"+number), move (letter+number))
%------------------------------------------------------------------------------------------isCaptureMove(Ini,Move) :captureMove(Ini,Move,NumCap),
captureNum(Ini,CapS,CapN),
CapN>CapS, NumCap>1.
isCaptureMove(Ini,Move) :captureMove(Ini,Move,NumCap),
captureNum(Ini,CapS,CapN),
CapS=<CapN, NumCap>3.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks if the move is a good move to get more stones in South side or less in North side
%use: isStonesSide(initial state ("i"+number), move (letter+number))
%------------------------------------------------------------------------------------------isStonesSide(Ini,Move) :stonesSide(Ini,Move,SideSB,_,SideSA,_),
captureNum(Ini,CapS,CapN),
CapN>CapS, (SideSB-SideSA)=0.
isStonesSide(Ini,Move) :stonesSide(Ini,Move,SideSB,_,SideSA,_),
captureNum(Ini,CapS,CapN),
CapS=<CapN, (SideSB-SideSA)<2.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Checks if the move is a good move to protect pits in South side and no protection in North side
%use: isProtectPits(initial state ("i"+number), move (letter+number))
52
%------------------------------------------------------------------------------------------isProtectPits(Ini,Move) :numberPits(Ini,Move,BefS1,AftS1,_,_,1),
numberPits(Ini,Move,BefS2,AftS2,_,_,2),
BefS1>AftS1,
BefS2>AftS2.
isProtectPits(Ini,Move) :numberPits(Ini,Move,_,_,BefN1,AftN1,1),
numberPits(Ini,Move,_,_,BefN2,AftN2,2),
BefN1<AftN1,
BefN2<AftN2.
%------------------------------------------------------------------------------------------isMIHMove(_,_).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% RECURSIVE FUNCTIONS USED
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%------------------------------------------------------------------------------------------%Given a list counts the number of elements that are null
%use: cntZero(input list, RESULT)
%------------------------------------------------------------------------------------------cntZero([],0).
cntZero([H|T], Cnt) :H =\= 0,
cntZero(T, Cnt).
cntZero([H|T], Cnt) :H =:= 0,
cntZero(T, Cnt2),
Cnt is Cnt2 + 1.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Given a list counts the number of elements that are the same as a given value
%use: cntNum(input list, RESULT, condition value)
%------------------------------------------------------------------------------------------cntNum([],0,_).
cntNum([H|T],Cnt,Cond) :H =\= Cond,
cntNum(T,Cnt,Cond).
53
cntNum([H|T],Cnt,Cond) :H =:= Cond,
cntNum(T,Cnt2,Cond),
Cnt is Cnt2 + 1.
%-------------------------------------------------------------------------------------------
%------------------------------------------------------------------------------------------%Given a list gets the value of the N-th element
%use: getNElem(input list, N-th element, RESULT)
%------------------------------------------------------------------------------------------getNElem([X|_],1,X).
getNElem([_|T],N,R) :Val is N-1,
getNElem(T,Val,R).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Given two lists gets the value of all the elements added
%use: cntTotal(input list1, input list2, RESULT)
%------------------------------------------------------------------------------------------cntTotal([],[],0).
cntTotal([HS|TS],[HN|TN], Cnt) :cntTotal(TS,TN,Cnt2),
Cnt is Cnt2+HS+HN.
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Given two lists gets the value of all the elements added from each list separately
%use: cntTotal(input list1, input list2, RESULT1, RESULT2)
%------------------------------------------------------------------------------------------cntSide([],[],0,0).
cntSide([HS|TS],[HN|TN], CntS, CntN) :cntSide(TS,TN,CntS2,CntN2),
CntS is CntS2+HS,
CntN is CntN2+HN.
%-------------------------------------------------------------------------------------------
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% INTERMEDIATE PREDICATES USED
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%numberMIH(Ini,Move) :-
54
%------------------------------------------------------------------------------------------%Gives the number of captured stones by each player in an initial state
%use: captureNum(initial state ("i"+number), RESULTSOUTH, RESULTNORTH)
%------------------------------------------------------------------------------------------captureNum(Ini,CapS,CapN) :board(Ini,Ini ,_,_,CapS,CapN).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Gives the number of captured stones in a particular move by South player
%use: captureMove(initial state ("i"+number), move (letter+number), RESULT)
%------------------------------------------------------------------------------------------captureMove(Ini,Move,NumCap) :board(Ini,Ini ,_,_,CapSBef,_),
board(Ini,Move,_,_,CapSAft,_),
NumCap is (CapSAft-CapSBef).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Gives the total number of empty pits for each player before and after a move
%use: numberEmpty(initial state ("i"+number), move (letter+number), BEFS, AFTS, BEFN, AFTN)
%------------------------------------------------------------------------------------------numberEmpty(Ini,Move,BefS,AftS,BefN,AftN) :board(Ini,Ini,LSIni,LNIni,_,_),
board(Ini,Move,LSMove,LNMove, _,_),
cntZero(LSIni,BefS),
cntZero(LNIni,BefN),
cntZero(LSMove,AftS),
cntZero(LNMove,AftN).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Gives the total number of pits with N Stones for each player before and after a move
%use: numberPits(initial state ("i"+number), move (letter+number), BEFS, AFTS, BEFN, AFTN, stones)
%------------------------------------------------------------------------------------------numberPits(Ini,Move,BefS,AftS,BefN,AftN,Stones) :board(Ini,Ini,LSIni,LNIni,_,_),
board(Ini,Move,LSMove,LNMove, _,_),
cntNum(LSIni,BefS,Stones),
cntNum(LNIni,BefN,Stones),
cntNum(LSMove,AftS,Stones),
cntNum(LNMove,AftN,Stones).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Gives the total number of Stones in each side for each player before and after a move
%use: stonesSide(initial state ("i"+number), move (letter+number), BEFS, AFTS, BEFN, AFTN)
%-------------------------------------------------------------------------------------------
55
stonesSide(Ini,Move,SideSB,SideNB,SideSA,SideNA) :board(Ini,Ini, LSIni, LNIni, _,_),
board(Ini,Move, LSMove, LNMove, _,_),
cntSide(LSIni,LNIni,SideSB,SideNB),
cntSide(LSMove,LNMove,SideSA,SideNA).
%------------------------------------------------------------------------------------------%------------------------------------------------------------------------------------------%Gives the number of Stones in the Pit N for each player before and after a move
%use: numberStones(initial state ("i"+number), move (letter+number), BEFS, AFTS, BEFN, AFTN, pitnumber)
%------------------------------------------------------------------------------------------numberStones(Ini,Move,BefS,AftS,BefN,AftN,NElem) :board(Ini,Ini,LSIni,LNIni,_,_),
board(Ini,Move,LSMove,LNMove, _,_),
getNElem(LSIni,NElem,BefS),
getNElem(LNIni,NElem,BefN),
getNElem(LSMove,NElem,AftS),
getNElem(LNMove,NElem,AftN).
%-------------------------------------------------------------------------------------------
isKrooSB(Ini,Pit) :board(Ini,Ini,LSIni,_,_,_),
getNElem(LSIni,Pit,Tot),
Tot>17-Pit,
Tot<24-Pit.
isKrooSA(Ini,Move,Pit) :board(Ini,Move,LSAft,_,_,_),
getNElem(LSAft,Pit,Tot),
Tot>17-Pit,
Tot<24-Pit.
isKrooNB(Ini,Pit) :board(Ini,Ini,_,LNIni,_,_),
getNElem(LNIni,Pit,Tot),
Tot>17-Pit,
Tot<24-Pit.
isKrooNA(Ini,Move,Pit) :board(Ini,Move,_,LNAft,_,_),
getNElem(LNAft,Pit,Tot),
Tot>17-Pit,
Tot<24-Pit.
%useKroo(Ini,Move) :vul1Pits(Ini,Move,BefS,AftS,BefN,AftN) :numberPits(Ini,Move,BefS,AftS,BefN,AftN,1).
56
vul2Pits(Ini,Move,BefS,AftS,BefN,AftN) :numberPits(Ini,Move,BefS,AftS,BefN,AftN,2).
Appendix 2 – Sample Traces:
Here are some sample traces from Prolog with a specific situation of the board.
The trace comes from the command of Prolog itself and it is complicated to follow. An
ideal way to do it would be using natural language to explain the inference process.
Below there is the initial knowledge that Prolog uses to do the inference. The last
row has the initial state of the board before the move. The other rows represent the
result of each of the possible moves (in this case a,d,e).
board(i5,e5, [3,1,1,5,0,2], [3,5,1,2,0,0], 13,12).
board(i5,d5, [2,0,0,0,15,1], [2,4,0,1,1,2], 8,12).
board(i5,a5, [0,1,1,4,14,0], [2,4,0,1,0,1], 8,12).
board(i5,i5, [2,0,0,4,14,0], [2,4,0,1,0,1], 8,12).
We make a query in Prolog, to keep the trace short we choose only for one move.
For example the trace for the query “isCaptureMove(i5,e5).” would be:
[trace] 1 ?- isCaptureMove(i5,e5).
Call: (7) isCaptureMove(i5, e5) ? creep
Call: (8) captureMove(i5, e5, _L182) ? creep
Call: (9) board(i5, i5, _L222, _L223, _L203, _L225) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) board(i5, e5, _L222, _L223, _L204, _L225) ? creep
Exit: (9) board(i5, e5, [3, 1, 1, 5, 0, 2], [3, 5, 1, 2, 0, 0], 13, 12) ? creep
^ Call: (9) _L182 is 13-8 ? creep
^ Exit: (9) 5 is 13-8 ? creep
Exit: (8) captureMove(i5, e5, 5) ? creep
Call: (8) captureNum(i5, _L183, _L184) ? creep
Call: (9) board(i5, i5, _L261, _L262, _L183, _L184) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Exit: (8) captureNum(i5, 8, 12) ? creep
^ Call: (8) 12>8 ? creep
^ Exit: (8) 12>8 ? creep
^ Call: (8) 5>1 ? creep
^ Exit: (8) 5>1 ? creep
Exit: (7) isCaptureMove(i5, e5) ? creep
57
Yes
Another sample trace for “isEmptyPits(i5,d5).”:
[trace] 1 ?- isEmptyPits(i5,d5).
Call: (7) isEmptyPits(i5, d5) ? creep
Call: (8) numberEmpty(i5, d5, _L182, _L183, _L184, _L185) ? creep
Call: (9) board(i5, i5, _L207, _L208, _L230, _L231) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) board(i5, d5, _L209, _L210, _L230, _L231) ? creep
Exit: (9) board(i5, d5, [2, 0, 0, 0, 15, 1], [2, 4, 0, 1, 1, 2], 8, 12) ? creep
Call: (9) cntZero([2, 0, 0, 4, 14, 0], _L182) ? creep
^ Call: (10) 2=\=0 ? creep
^ Exit: (10) 2=\=0 ? creep
Call: (10) cntZero([0, 0, 4, 14, 0], _L182) ? creep
^ Call: (11) 0=\=0 ? creep
^ Fail: (11) 0=\=0 ? creep
Redo: (10) cntZero([0, 0, 4, 14, 0], _L182) ? creep
^ Call: (11) 0=:=0 ? creep
^ Exit: (11) 0=:=0 ? creep
Call: (11) cntZero([0, 4, 14, 0], _L270) ? creep
^ Call: (12) 0=\=0 ? creep
^ Fail: (12) 0=\=0 ? creep
Redo: (11) cntZero([0, 4, 14, 0], _L270) ? creep
^ Call: (12) 0=:=0 ? creep
^ Exit: (12) 0=:=0 ? creep
Call: (12) cntZero([4, 14, 0], _L283) ? creep
^ Call: (13) 4=\=0 ? creep
^ Exit: (13) 4=\=0 ? creep
Call: (13) cntZero([14, 0], _L283) ? creep
^ Call: (14) 14=\=0 ? creep
^ Exit: (14) 14=\=0 ? creep
Call: (14) cntZero([0], _L283) ? creep
^ Call: (15) 0=\=0 ? creep
^ Fail: (15) 0=\=0 ? creep
Redo: (14) cntZero([0], _L283) ? creep
^ Call: (15) 0=:=0 ? creep
58
^ Exit: (15) 0=:=0 ? creep
Call: (15) cntZero([], _L334) ? creep
Exit: (15) cntZero([], 0) ? creep
^ Call: (15) _L283 is 0+1 ? creep
^ Exit: (15) 1 is 0+1 ? creep
Exit: (14) cntZero([0], 1) ? creep
Exit: (13) cntZero([14, 0], 1) ? creep
Exit: (12) cntZero([4, 14, 0], 1) ? creep
^ Call: (12) _L270 is 1+1 ? creep
^ Exit: (12) 2 is 1+1 ? creep
Exit: (11) cntZero([0, 4, 14, 0], 2) ? creep
^ Call: (11) _L182 is 2+1 ? creep
^ Exit: (11) 3 is 2+1 ? creep
Exit: (10) cntZero([0, 0, 4, 14, 0], 3) ? creep
Exit: (9) cntZero([2, 0, 0, 4, 14, 0], 3) ? creep
Call: (9) cntZero([2, 4, 0, 1, 0, 1], _L184) ? creep
^ Call: (10) 2=\=0 ? creep
^ Exit: (10) 2=\=0 ? creep
Call: (10) cntZero([4, 0, 1, 0, 1], _L184)
? creep
^ Call: (11) 4=\=0 ? creep
^ Exit: (11) 4=\=0 ? creep
Call: (11) cntZero([0, 1, 0, 1], _L184) ? creep
^ Call: (12) 0=\=0 ? creep
^ Fail: (12) 0=\=0 ? creep
Redo: (11) cntZero([0, 1, 0, 1], _L184) ? creep
^ Call: (12) 0=:=0 ? creep
^ Exit: (12) 0=:=0 ? creep
Call: (12) cntZero([1, 0, 1], _L372) ? creep
^ Call: (13) 1=\=0 ? creep
^ Exit: (13) 1=\=0 ? creep
Call: (13) cntZero([0, 1], _L372) ? creep
^ Call: (14) 0=\=0 ? creep
^ Fail: (14) 0=\=0 ? creep
Redo: (13) cntZero([0, 1], _L372) ? creep
^ Call: (14) 0=:=0 ? creep
^ Exit: (14) 0=:=0 ? creep
Call: (14) cntZero([1], _L404) ? creep
^ Call: (15) 1=\=0 ? creep
59
^ Exit: (15) 1=\=0 ? creep
Call: (15) cntZero([], _L404) ? creep
Exit: (15) cntZero([], 0) ? creep
Exit: (14) cntZero([1], 0) ? creep
^ Call: (14) _L372 is 0+1 ? creep
^ Exit: (14) 1 is 0+1 ? creep
Exit: (13) cntZero([0, 1], 1) ? creep
Exit: (12) cntZero([1, 0, 1], 1) ? creep
^ Call: (12) _L184 is 1+1 ? creep
^ Exit: (12) 2 is 1+1 ? creep
Exit: (11) cntZero([0, 1, 0, 1], 2) ? creep
Exit: (10) cntZero([4, 0, 1, 0, 1], 2) ? creep
Exit: (9) cntZero([2, 4, 0, 1, 0, 1], 2) ? creep
Call: (9) cntZero([2, 0, 0, 0, 15, 1], _L183) ? creep
^ Call: (10) 2=\=0 ? creep
^ Exit: (10) 2=\=0 ? creep
Call: (10) cntZero([0, 0, 0, 15, 1], _L183) ? creep
^ Call: (11) 0=\=0 ? creep
^ Fail: (11) 0=\=0 ? creep
Redo: (10) cntZero([0, 0, 0, 15, 1], _L183) ? creep
^ Call: (11) 0=:=0 ? creep
^ Exit: (11) 0=:=0 ? creep
Call: (11) cntZero([0, 0, 15, 1], _L455) ? creep
^ Call: (12) 0=\=0 ? creep
^ Fail: (12) 0=\=0 ? creep
Redo: (11) cntZero([0, 0, 15, 1], _L455) ? creep
^ Call: (12) 0=:=0 ? creep
^ Exit: (12) 0=:=0 ? creep
Call: (12) cntZero([0, 15, 1], _L468) ? creep
^ Call: (13) 0=\=0 ? creep
^ Fail: (13) 0=\=0 ? creep
Redo: (12) cntZero([0, 15, 1], _L468) ? creep
^ Call: (13) 0=:=0 ? creep
^ Exit: (13) 0=:=0 ? creep
Call: (13) cntZero([15, 1], _L481)
? creep
^ Call: (14) 15=\=0 ? creep
60
^ Exit: (14) 15=\=0 ? creep
Call: (14) cntZero([1], _L481) ? creep
^ Call: (15) 1=\=0 ? creep
^ Exit: (15) 1=\=0 ? creep
Call: (15) cntZero([], _L481) ? creep
Exit: (15) cntZero([], 0) ? creep
Exit: (14) cntZero([1], 0) ? creep
Exit: (13) cntZero([15, 1], 0) ? creep
^ Call: (13) _L468 is 0+1 ? creep
^ Exit: (13) 1 is 0+1 ? creep
Exit: (12) cntZero([0, 15, 1], 1) ? creep
^ Call: (12) _L455 is 1+1 ? creep
^ Exit: (12) 2 is 1+1 ? creep
Exit: (11) cntZero([0, 0, 15, 1], 2) ? creep
^ Call: (11) _L183 is 2+1 ? creep
^ Exit: (11) 3 is 2+1 ? creep
Exit: (10) cntZero([0, 0, 0, 15, 1], 3) ? creep
Exit: (9) cntZero([2, 0, 0, 0, 15, 1], 3) ? creep
Call: (9) cntZero([2, 4, 0, 1, 1, 2], _L185) ? creep
^ Call: (10) 2=\=0 ? creep
^ Exit: (10) 2=\=0 ? creep
Call: (10) cntZero([4, 0, 1, 1, 2], _L185) ? creep
^ Call: (11) 4=\=0 ? creep
^ Exit: (11) 4=\=0 ? creep
Call: (11) cntZero([0, 1, 1, 2], _L185) ? creep
^ Call: (12) 0=\=0 ? creep
^ Fail: (12) 0=\=0 ? creep
Redo: (11) cntZero([0, 1, 1, 2], _L185) ? creep
^ Call: (12) 0=:=0 ? creep
^ Exit: (12) 0=:=0 ? creep
Call: (12) cntZero([1, 1, 2], _L570) ? creep
^ Call: (13) 1=\=0 ? creep
^ Exit: (13) 1=\=0 ? creep
Call: (13) cntZero([1, 2], _L570) ? creep
^ Call: (14) 1=\=0 ? creep
^ Exit: (14) 1=\=0 ? creep
Call: (14) cntZero([2], _L570) ? creep
^ Call: (15) 2=\=0 ? creep
^ Exit: (15) 2=\=0 ? creep
61
Call: (15) cntZero([], _L570) ? creep
Exit: (15) cntZero([], 0) ? creep
Exit: (14) cntZero([2], 0) ? creep
Exit: (13) cntZero([1, 2], 0) ? creep
Exit: (12) cntZero([1, 1, 2], 0) ?
^ Call: (12) _L185 is 0+1 ? creep
^ Exit: (12) 1 is 0+1 ? creep
Exit: (11) cntZero([0, 1, 1, 2], 1) ? creep
Exit: (10) cntZero([4, 0, 1, 1, 2], 1) ? creep
Exit: (9) cntZero([2, 4, 0, 1, 1, 2], 1) ? creep
Exit: (8) numberEmpty(i5, d5, 3, 3, 2, 1) ? creep
^ Call: (8) 3>3 ? creep
^ Fail: (8) 3>3 ? creep
Redo: (14) cntZero([2], _L570) ? creep
^ Call: (15) 2=:=0 ? creep
^ Fail: (15) 2=:=0 ? creep
Redo: (13) cntZero([1, 2], _L570) ? creep
^ Call: (14) 1=:=0 ? creep
^ Fail: (14) 1=:=0 ? creep
Redo: (12) cntZero([1, 1, 2], _L570) ? creep
^ Call: (13) 1=:=0 ? creep
^ Fail: (13) 1=:=0 ? creep
Redo: (10) cntZero([4, 0, 1, 1, 2], _L185) ? creep
^ Call: (11) 4=:=0 ? creep
^ Fail: (11) 4=:=0 ? creep
Redo: (9) cntZero([2, 4, 0, 1, 1, 2], _L185) ? creep
^ Call: (10) 2=:=0 ? creep
^ Fail: (10) 2=:=0 ? creep
Redo: (14) cntZero([1], _L481) ? creep
^ Call: (15) 1=:=0 ? creep
^ Fail: (15) 1=:=0 ? creep
Redo: (13) cntZero([15, 1], _L481) ? creep
^ Call: (14) 15=:=0 ? creep
^ Fail: (14) 15=:=0 ? creep
Redo: (9) cntZero([2, 0, 0, 0, 15, 1], _L183) ? creep
^ Call: (10) 2=:=0 ?
^ Fail: (10) 2=:=0 ? creep
Redo: (14) cntZero([1], _L404) ? creep
^ Call: (15) 1=:=0 ? creep
62
^ Fail: (15) 1=:=0 ? creep
Redo: (12) cntZero([1, 0, 1], _L372) ? creep
^ Call: (13) 1=:=0 ? creep
^ Fail: (13) 1=:=0 ? creep
Redo: (10) cntZero([4, 0, 1, 0, 1], _L184) ? creep
^ Call: (11) 4=:=0 ? creep
^ Fail: (11) 4=:=0 ? creep
Redo: (9) cntZero([2, 4, 0, 1, 0, 1], _L184) ? creep
^ Call: (10) 2=:=0 ? creep
^ Fail: (10) 2=:=0 ? creep
Redo: (13) cntZero([14, 0], _L283) ? creep
^ Call: (14) 14=:=0 ? creep
^ Fail: (14) 14=:=0 ? creep
Redo: (12) cntZero([4, 14, 0], _L283) ? creep
^ Call: (13) 4=:=0 ? creep
^ Fail: (13) 4=:=0 ? creep
Redo: (9) cntZero([2, 0, 0, 4, 14, 0], _L182) ? creep
^ Call: (10) 2=:=0 ? creep
^ Fail: (10) 2=:=0 ? creep
Redo: (9) board(i5, d5, _L209, _L210, _L230, _L231) ? creep
Fail: (9) board(i5, d5, _L209, _L210, _L230, _L231) ? creep
Fail: (8) numberEmpty(i5, d5, _L182, _L183, _L184, _L185) ? creep
Fail: (7) isEmptyPits(i5, d5) ? creep
No
And yet one more sample trace for “isStonesSide(i5,a5).”:
[trace] 2 ?- isStonesSide(i5,a5).
Call: (7) isStonesSide(i5, a5) ? creep
Call: (8) stonesSide(i5, a5, _L182, _L204, _L183, _L206) ? creep
Call: (9) board(i5, i5, _L207, _L208, _L230, _L231) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) board(i5, a5, _L209, _L210, _L230, _L231) ? creep
Exit: (9) board(i5, a5, [0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) cntSide([2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], _L182, _L204) ? creep
Call: (10) cntSide([0, 0, 4, 14, 0], [4, 0, 1, 0, 1], _L255, _L256) ? creep
Call: (11) cntSide([0, 4, 14, 0], [0, 1, 0, 1], _L280, _L281) ? creep
63
Call: (12) cntSide([4, 14, 0], [1, 0, 1], _L305, _L306) ? creep
Call: (13) cntSide([14, 0], [0, 1], _L330, _L331) ? creep
Call: (14) cntSide([0], [1], _L355, _L356) ? creep
Call: (15) cntSide([], [], _L380, _L381) ? creep
Exit: (15) cntSide([], [], 0, 0) ? creep
^ Call: (15) _L355 is 0+0 ? creep
^ Exit: (15) 0 is 0+0 ? creep
^ Call: (15) _L356 is 0+1 ? creep
^ Exit: (15) 1 is 0+1 ? creep
Exit: (14) cntSide([0], [1], 0, 1) ? creep
^ Call: (14) _L330 is 0+14 ? creep
^ Exit: (14) 14 is 0+14 ? creep
^ Call: (14) _L331 is 1+0 ? creep
^ Exit: (14) 1 is 1+0 ? creep
Exit: (13) cntSide([14, 0], [0, 1], 14, 1) ? creep
^ Call: (13) _L305 is 14+4 ? creep
^ Exit: (13) 18 is 14+4 ?
creep
^ Call: (13) _L306 is 1+1 ? creep
^ Exit: (13) 2 is 1+1 ? creep
Exit: (12) cntSide([4, 14, 0], [1, 0, 1], 18, 2) ? creep
^ Call: (12) _L280 is 18+0 ? creep
^ Exit: (12) 18 is 18+0 ? creep
^ Call: (12) _L281 is 2+0 ? creep
^ Exit: (12) 2 is 2+0 ? creep
Exit: (11) cntSide([0, 4, 14, 0], [0, 1, 0, 1], 18, 2) ? creep
^ Call: (11) _L255 is 18+0 ? creep
^ Exit: (11) 18 is 18+0 ? creep
^ Call: (11) _L256 is 2+4 ? creep
^ Exit: (11) 6 is 2+4 ? creep
Exit: (10) cntSide([0, 0, 4, 14, 0], [4, 0, 1, 0, 1], 18, 6) ? creep
^ Call: (10) _L182 is 18+2 ? creep
^ Exit: (10) 20 is 18+2 ? creep
^ Call: (10) _L204 is 6+2 ? creep
^ Exit: (10) 8 is 6+2 ? creep
Exit: (9) cntSide([2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 20, 8) ? creep
Call: (9) cntSide([0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], _L183, _L206) ? creep
Call: (10) cntSide([1, 1, 4, 14, 0], [4, 0, 1, 0, 1], _L255, _L256) ? creep
Call: (11) cntSide([1, 4, 14, 0], [0, 1, 0, 1], _L280, _L281) ? creep
64
Call: (12) cntSide([4, 14, 0], [1, 0, 1], _L305, _L306) ? creep
Call: (13) cntSide([14, 0], [0, 1], _L330, _L331) ? creep
Call: (14) cntSide([0], [1], _L355, _L356) ? creep
Call: (15) cntSide([], [], _L380, _L381) ? creep
Exit: (15) cntSide([], [], 0, 0) ? creep
^ Call: (15) _L355 is 0+0 ? creep
^ Exit: (15) 0 is 0+0 ? creep
^ Call: (15) _L356 is 0+1 ? creep
^ Exit: (15) 1 is 0+1 ? creep
Exit: (14) cntSide([0], [1], 0, 1) ? creep
^ Call: (14) _L330 is 0+14 ? creep
^ Exit: (14) 14 is 0+14 ? creep
^ Call: (14) _L331 is 1+0 ? creep
^ Exit: (14) 1 is 1+0 ? creep
Exit: (13) cntSide([14, 0], [0, 1], 14, 1) ? creep
^ Call: (13) _L305 is 14+4 ? creep
^ Exit: (13) 18 is 14+4 ? creep
^ Call: (13) _L306 is 1+1 ? creep
^ Exit: (13) 2 is 1+1 ? creep
Exit: (12) cntSide([4, 14, 0], [1, 0, 1], 18, 2) ? creep
^ Call: (12) _L280 is 18+1 ? creep
^ Exit: (12) 19 is 18+1 ? creep
^ Call: (12) _L281 is 2+0 ? creep
^ Exit: (12) 2 is 2+0 ? creep
Exit: (11) cntSide([1, 4, 14, 0], [0, 1, 0, 1], 19, 2) ? creep
^ Call: (11) _L255 is 19+1 ? creep
^ Exit: (11) 20 is 19+1 ? creep
^ Call: (11) _L256 is 2+4 ? creep
^ Exit: (11) 6 is 2+4 ? creep
Exit: (10) cntSide([1, 1, 4, 14, 0], [4, 0, 1, 0, 1], 20, 6) ? creep
^ Call: (10) _L183 is 20+0 ? creep
^ Exit: (10) 20 is 20+0 ? creep
^ Call: (10) _L206 is 6+2 ? creep
^ Exit: (10) 8 is 6+2 ? creep
Exit: (9) cntSide([0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], 20, 8) ? creep
Exit: (8) stonesSide(i5, a5, 20, 8, 20, 8) ? creep
Call: (8) captureNum(i5, _L184, _L185) ? creep
Call: (9) board(i5, i5, _L267, _L268, _L184, _L185) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
65
Exit: (8) captureNum(i5, 8, 12) ? creep
^ Call: (8) 12>8 ? creep
^ Exit: (8) 12>8 ? creep
Call: (8) 20-20=0 ? creep
Fail: (8) 20-20=0 ? creep
Redo: (9) board(i5, a5, _L209, _L210, _L230, _L231) ? creep
Fail: (8) stonesSide(i5, a5, _L182, _L204, _L183, _L206) ? creep
Redo: (7) isStonesSide(i5, a5) ? creep
Call: (8) stonesSide(i5, a5, _L182, _L197, _L183, _L199) ? creep
Call: (9) board(i5, i5, _L200, _L201, _L223, _L224) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) board(i5, a5, _L202, _L203, _L223, _L224) ?
creep
Exit: (9) board(i5, a5, [0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Call: (9) cntSide([2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], _L182, _L197) ? creep
Call: (10) cntSide([0, 0, 4, 14, 0], [4, 0, 1, 0, 1], _L248, _L249) ? creep
Call: (11) cntSide([0, 4, 14, 0], [0, 1, 0, 1], _L273, _L274) ? creep
Call: (12) cntSide([4, 14, 0], [1, 0, 1], _L298, _L299) ? creep
Call: (13) cntSide([14, 0], [0, 1], _L323, _L324) ? creep
Call: (14) cntSide([0], [1], _L348, _L349) ? creep
Call: (15) cntSide([], [], _L373, _L374) ? creep
Exit: (15) cntSide([], [], 0, 0) ? creep
^ Call: (15) _L348 is 0+0 ? creep
^ Exit: (15) 0 is 0+0 ? creep
^ Call: (15) _L349 is 0+1 ? creep
^ Exit: (15) 1 is 0+1 ? creep
Exit: (14) cntSide([0], [1], 0, 1) ? creep
^ Call: (14) _L323 is 0+14 ? creep
^ Exit: (14) 14 is 0+14 ? creep
^ Call: (14) _L324 is 1+0 ? creep
^ Exit: (14) 1 is 1+0 ? creep
Exit: (13) cntSide([14, 0], [0, 1], 14, 1) ? creep
^ Call: (13) _L298 is 14+4 ? creep
^ Exit: (13) 18 is 14+4 ? creep
^ Call: (13) _L299 is 1+1 ? creep
^ Exit: (13) 2 is 1+1 ? creep
Exit: (12) cntSide([4, 14, 0], [1, 0, 1], 18, 2) ? creep
^ Call: (12) _L273 is 18+0 ? creep
^ Exit: (12) 18 is 18+0 ? creep
66
^ Call: (12) _L274 is 2+0 ? creep
^ Exit: (12) 2 is 2+0 ? creep
Exit: (11) cntSide([0, 4, 14, 0], [0, 1, 0, 1], 18, 2) ? creep
^ Call: (11) _L248 is 18+0 ? creep
^ Exit: (11) 18 is 18+0 ? creep
^ Call: (11) _L249 is 2+4 ? creep
^ Exit: (11) 6 is 2+4 ? creep
Exit: (10) cntSide([0, 0, 4, 14, 0], [4, 0, 1, 0, 1], 18, 6) ? creep
^ Call: (10) _L182 is 18+2 ? creep
^ Exit: (10) 20 is 18+2 ? creep
^ Call: (10) _L197 is 6+2 ? creep
^ Exit: (10) 8 is 6+2 ? creep
Exit: (9) cntSide([2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 20, 8) ? creep
Call: (9) cntSide([0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], _L183, _L199) ? creep
Call: (10) cntSide([1, 1, 4, 14, 0], [4, 0, 1, 0, 1], _L248, _L249) ? creep
Call: (11) cntSide([1, 4, 14, 0], [0, 1, 0, 1], _L273, _L274) ? creep
Call: (12) cntSide([4, 14, 0], [1, 0, 1], _L298, _L299) ? creep
Call: (13) cntSide([14, 0], [0, 1], _L323, _L324) ? creep
Call: (14) cntSide([0], [1], _L348, _L349) ? creep
Call: (15) cntSide([], [], _L373, _L374) ? creep
Exit: (15) cntSide([], [], 0, 0) ? creep
^ Call: (15) _L348 is 0+0 ?
creep
^ Exit: (15) 0 is 0+0 ? creep
^ Call: (15) _L349 is 0+1 ? creep
^ Exit: (15) 1 is 0+1 ? creep
Exit: (14) cntSide([0], [1], 0, 1) ? creep
^ Call: (14) _L323 is 0+14 ? creep
^ Exit: (14) 14 is 0+14 ? creep
^ Call: (14) _L324 is 1+0 ? creep
^ Exit: (14) 1 is 1+0 ? creep
Exit: (13) cntSide([14, 0], [0, 1], 14, 1) ? creep
^ Call: (13) _L298 is 14+4 ? creep
^ Exit: (13) 18 is 14+4 ? creep
^ Call: (13) _L299 is 1+1 ? creep
^ Exit: (13) 2 is 1+1 ? creep
Exit: (12) cntSide([4, 14, 0], [1, 0, 1], 18, 2) ? creep
^ Call: (12) _L273 is 18+1 ? creep
^ Exit: (12) 19 is 18+1 ? creep
67
^ Call: (12) _L274 is 2+0 ? creep
^ Exit: (12) 2 is 2+0 ? creep
Exit: (11) cntSide([1, 4, 14, 0], [0, 1, 0, 1], 19, 2) ? creep
^ Call: (11) _L248 is 19+1 ? creep
^ Exit: (11) 20 is 19+1 ? creep
^ Call: (11) _L249 is 2+4 ? creep
^ Exit: (11) 6 is 2+4 ? creep
Exit: (10) cntSide([1, 1, 4, 14, 0], [4, 0, 1, 0, 1], 20, 6) ? creep
^ Call: (10) _L183 is 20+0 ? creep
^ Exit: (10) 20 is 20+0 ? creep
^ Call: (10) _L199 is 6+2 ? creep
^ Exit: (10) 8 is 6+2 ? creep
Exit: (9) cntSide([0, 1, 1, 4, 14, 0], [2, 4, 0, 1, 0, 1], 20, 8) ? creep
Exit: (8) stonesSide(i5, a5, 20, 8, 20, 8) ? creep
Call: (8) captureNum(i5, _L184, _L185) ? creep
Call: (9) board(i5, i5, _L260, _L261, _L184, _L185) ? creep
Exit: (9) board(i5, i5, [2, 0, 0, 4, 14, 0], [2, 4, 0, 1, 0, 1], 8, 12) ? creep
Exit: (8) captureNum(i5, 8, 12) ? creep
^ Call: (8) 8=<12 ? creep
^ Exit: (8) 8=<12 ? creep
^ Call: (8) 20-20<2 ? creep
^ Exit: (8) 20-20<2 ? creep
Exit: (7) isStonesSide(i5, a5) ? creep
Yes
68
Download