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