├── .gitignore ├── Makefile ├── README.md ├── debug.c ├── main.c ├── michi.c ├── michi.h ├── patterns.c └── tests ├── fix_atari.tst ├── large_pat.tst ├── patterns.prob ├── patterns.spat └── run /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | twogtp/ 3 | michi 4 | michi.log 5 | patterns.prob 6 | patterns.spat 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Normal compilation options for developping 3 | #CFLAGS= -DNDEBUG -g -fshort-enums -Wall -Wno-char-subscripts 4 | #CFLAGS= -O3 -msse4.1 -fshort-enums -Wall -Wno-char-subscripts 5 | 6 | # Normal compilation options for production 7 | CFLAGS= -DNDEBUG -O3 -msse4.1 -fshort-enums -Wall -Wno-char-subscripts 8 | 9 | # Compilation options for running valgrind 10 | #CFLAGS=-O0 -g -Wall -std=gnu99 11 | 12 | # Compilation options for profiling with gprof 13 | #CFLAGS=-pg -O3 -DNDEBUG -msse4.1 -fshort-enums -Wall -Wno-char-subscripts 14 | 15 | OBJS=patterns.o debug.o main.o 16 | BIN=michi 17 | 18 | all: $(BIN) 19 | 20 | michi: $(OBJS) michi.o michi.h 21 | gcc $(CFLAGS) -std=gnu99 -o michi michi.o $(OBJS) -lm 22 | 23 | %.o: %.c michi.h 24 | gcc $(CFLAGS) -c -std=gnu99 $< 25 | 26 | test: 27 | tests/run 28 | 29 | valgrind: michi 30 | valgrind --track-origins=yes ./michi tsdebug 31 | 32 | callgrind: michi 33 | valgrind --tool=callgrind ./michi mcbenchmark 34 | 35 | tags: $(BIN) 36 | ctags *.c *.h 37 | 38 | clean: 39 | rm -f $(OBJS) michi.o michi-debug.o 40 | 41 | veryclean: clean 42 | rm -f $(BIN) 43 | rm -rf tests/output tests/michi.log 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Michi-c --- Michi recoded in C 2 | ============================== 3 | 4 | This is a recoding in C (for speed) of the michi.py code by Petr Baudis avalaible at 5 | https://github.com/pasky/michi. 6 | 7 | Michi stands for "Minimalistic Pachi". This is a Minimalistic Go MCTS Engine. The aims of the project are best explained by the author: 8 | 9 | > Michi aims to be a minimalistic but full-fledged Computer Go program based 10 | > on state-of-art methods (Monte Carlo Tree Search) and written in Python. 11 | > Our goal is to make it easier for new people to enter the domain of 12 | > Computer Go, peek under the hood of a "real" playing engine and be able 13 | > to learn by hassle-free experiments - with the algorithms, add heuristics, 14 | > etc. 15 | 16 | > This is not meant to be a competitive engine; simplicity and clear code is 17 | > preferred over optimization (after all, it's in Python!). But compared to 18 | > other minimalistic engines, this one should be able to beat beginner 19 | > intermediate human players, and I believe that a *fast* implementation 20 | > of exactly the same heuristics would be around 4k KGS or even better. 21 | 22 | Please go on his project page to read more about Michi and to find some information about theory or interesting projects to do. 23 | 24 | Michi-c is distributed under the MIT licence. Now go forth, hack and peruse! 25 | However, as stated above, the main goal of michi and michi-c is to provide a simple and clear code. Therefore, one objective is not to make michi-c grow bigger. 26 | I would like to just correct bugs and/or modify the code to make it clearer or simpler. 27 | 28 | Therefore, a companion project has been setup at 29 | 30 | https://github.com/db3108/michi-c2 31 | 32 | in order to develop a program with better performances and more functionalities while relaxing the objective for brevity. 33 | 34 | Installing 35 | ---------- 36 | 37 | When in the directory that contains michi.c and Makefile, just type the command 38 | (whithout the $ sign) 39 | 40 | $ make 41 | 42 | This will build the michi executable. 43 | 44 | If you have gogui (http://gogui.sourceforge.net/) installed on your system, define the GOGUI variable (export GOGUI=/path/to/gogui/bin) with the location where the gogui executables can be found. Then 45 | 46 | $ make test 47 | 48 | will perform a few (quick) regression tests. The result should be : 49 | 50 | tests/run 51 | 10 passed 52 | 20 passed 53 | 30 passed 54 | 110 passed 55 | 210 passed 56 | 220 passed 57 | 230 passed 58 | 240 passed 59 | 250 passed 60 | 260 passed 61 | Summary: 10/10 passes. 0 unexpected passes, 0 unexpected failures 62 | 10 passed 63 | 20 passed 64 | 30 passed 65 | 40 passed 66 | 50 passed 67 | 60 passed 68 | 70 passed 69 | Summary: 7/7 passes. 0 unexpected passes, 0 unexpected failures 70 | 71 | If the test is not successful you can take a look at the INSTALL file in the 72 | michi-c2 project. There are some advices in case of troubleshooting. 73 | 74 | Usage 75 | ----- 76 | 77 | $ ./michi gtp 78 | 79 | will allow to play a game using the gtp protocol. Type help to get the list of 80 | available commands. 81 | 82 | However it's easier to use michi through the gogui graphical interface. 83 | 84 | http://gogui.sourceforge.net/ 85 | 86 | With gogui, you can also let michi play GNUGo: 87 | 88 | gogui/bin/gogui-twogtp -black './michi.py gtp' -white 'gnugo --mode=gtp --chinese-rules --capture-all-dead' -size 9 -komi 7.5 -verbose -auto 89 | 90 | It is *highly* recommended that you download Michi large-scale pattern files 91 | (patterns.prob, patterns.spat): 92 | 93 | http://pachi.or.cz/michi-pat/ 94 | 95 | Store and unpack them in the current directory for Michi to find. 96 | 97 | You can also try 98 | 99 | $ ./michi mcbenchmark 100 | 101 | this will run 2000 random playouts 102 | 103 | $ ./michi tsdebug 104 | 105 | this will run 1 MCTS tree search. 106 | 107 | All the parameters are hard coded in the michi.h file, which must be modified if you want to play with the code. 108 | 109 | Understanding and Hacking 110 | ------------------------- 111 | 112 | The C code can be read in parallel with the python code. 113 | I have been careful to keep the notations used by Petr (almost) everywhere. 114 | Of course the algorithms are the same (at least functionally) as well as the 115 | parameters. Most of the comments have been retained verbatim. 116 | 117 | Examples where the python and the C codes are different are: 118 | - in the functions gen_playout_moves_xxx(). I have not been able to emulate in 119 | C the generators that are available in python (yield instruction). So these 120 | functions in the C code must compute the whole list of suggestions before 121 | returning. 122 | - computation of blocks does not use regexp as the direct coding is simple. 123 | - need to recode a functionality equivalent to python dictionary (in patterns.c) 124 | 125 | The source is composed in 6 independent parts in michi.c 126 | - Utilities 127 | - Board routines 128 | - Go heuristics 129 | - Monte Carlo Playout policy 130 | - Monte Carlo Tree search 131 | - User Interface (Utilities, Various main programs) 132 | 133 | and pattern code (3x3 and large patterns) which is found in patterns.c 134 | 135 | Go programs heavily use lists or sets of (small) integers. There are many possible implementations that differ by the performance of the various operations we need to perform on these data structures: 136 | - insert one element, 137 | - remove one element, 138 | - enumerate all the elements, 139 | - test "element belongs to ?", 140 | - make the set empty ... 141 | 142 | The two simple implementations Slist and Mark of sets that I needed to make the michi-c program are found in michi.h (inlined for performance). 143 | 144 | Note: Some other concise (but hopefully useful) explanations are given as more detailed comments in the codes themselves. 145 | 146 | Short bibliography 147 | ------------------ 148 | 149 | 1. Martin Mueller, Computer Go, Artificial Intelligence, Vol.134, No 1-2, 150 | pp 145-179, 2002 151 | 2. Remi Coulom. Efficient Selectivity and Backup Operators in Monte-Carlo Tree 152 | Search. Paolo Ciancarini and H. Jaap van den Herik. 5th International 153 | Conference on Computer and Games, May 2006, Turin, Italy. 2006. 154 | 155 | 3. Sylvain Gelly, Yizao Wang, Remi Munos, Olivier Teytaud. Modification of UCT 156 | with Patterns in Monte-Carlo Go. [Research Report] RR-6062, 2006. 157 | 158 | 4. David Stern, Ralf Herbrich, Thore Graepel, Bayesian Pattern Ranking for Move 159 | Prediction in the Game of Go, In Proceedings of the 23rd international 160 | conference on Machine learning, pages 873–880, Pittsburgh, Pennsylvania, 161 | USA, 2006 162 | 5.  Rémi Coulom. Computing Elo Ratings of Move Patterns in the Game of Go. 163 | In ICGA Journal (2007), pp 198-208. 164 | 6. Sylvain Gelly, David Silver. Achieving Master Level Play in 9×9 Computer Go. 165 | Proceedings of the Twenty-Third AAAI Conference on Artificial Intelligence 166 | (2008) 167 | 7. Albert L Zobrist. A New Hashing Method with Application for Game Playing. 168 | 8. Petr Baudis. MCTS with Information Sharing, PhD Thesis, 2011 169 | 9. Robert Sedgewick, Algorithms in C, Addison-Wesley, 1990 170 | 171 | and many other PhD thesis accessible on the WEB 172 | 173 | Note: [ref 1] can be consulted for the definition of Computer Go terms : 174 | points, blocks, eyes, false eyes, liberties, etc. 175 | and historical bibliography 176 | 177 | 178 | -------------------------------------------------------------------------------- /debug.c: -------------------------------------------------------------------------------- 1 | #include "michi.h" 2 | extern char buf[BUFLEN]; 3 | 4 | //============================= messages logging ============================== 5 | FILE *flog; // FILE to log messages 6 | int c1,c2; // counters for warning messages 7 | int nmsg; // number of written log entries 8 | 9 | void too_many_msg(void) 10 | // if too many messages have been logged, print a last error and exit 11 | { 12 | fprintf(stderr,"Too many messages have been written in log file " 13 | " (maximum 100000)\n"); 14 | fprintf(flog,"Too many messages (maximum 100000)\n"); 15 | exit(-1); 16 | } 17 | 18 | void log_fmt_s(char type, const char *msg, const char *s) 19 | // Log a formatted message (string parameter) 20 | { 21 | fprintf(flog, "%c %5d/%3.3d ", type, c1, c2); 22 | fprintf(flog, msg, s); fprintf(flog, "\n"); 23 | if(type == 'E') { 24 | fprintf(stderr,"%c %5d/%3.3d ", type, c1, c2); 25 | fprintf(stderr, msg, s); fprintf(stderr,"\n"); 26 | } 27 | if(nmsg++ > 1000000) too_many_msg(); 28 | } 29 | 30 | void log_fmt_i(char type, const char *msg, int n) 31 | // Log a formatted message (int parameter) 32 | { 33 | sprintf(buf, msg, n); 34 | log_fmt_s(type, "%s", buf); 35 | } 36 | 37 | void log_fmt_p(char type, const char *msg, Point pt) 38 | // Log a formatted message (point parameter) 39 | { 40 | char str[8]; 41 | sprintf(buf, msg, str_coord(pt,str)); 42 | log_fmt_s(type,"%s", buf); 43 | } 44 | 45 | //============================= debug subcommands ============================= 46 | 47 | char decode_env4(int env4, int pt) 48 | { 49 | int hi, lo; 50 | env4 >>= pt; 51 | hi = (env4>>4) & 1; 52 | lo = env4 & 1; 53 | return (hi<<1) + lo; 54 | } 55 | 56 | char decode_env8(int env8, int pt) 57 | { 58 | int c; 59 | if (pt >= 4) 60 | c = decode_env4(env8>>8, pt-4); 61 | else 62 | c = decode_env4(env8&255, pt); 63 | switch(c) { 64 | case 0: return 'O'; 65 | case 1: return 'X'; 66 | case 2: return '.'; 67 | case 3: return '#'; 68 | } 69 | return 0; 70 | } 71 | 72 | void print_env8(int env8) 73 | { 74 | char src[10]; // src 0 1 2 bits in env8 7 0 4 75 | src[0] = decode_env8(env8, 7); // NW 3 4 5 bit 0=LSB 3 . 1 76 | src[1] = decode_env8(env8, 0); // N 6 7 8 6 2 5 77 | src[2] = decode_env8(env8, 4); // NE 78 | src[3] = decode_env8(env8, 3); // W 79 | src[4] = '.'; // Center 80 | src[5] = decode_env8(env8, 1); // E 81 | src[6] = decode_env8(env8, 6); // SW 82 | src[7] = decode_env8(env8, 2); // S 83 | src[8] = decode_env8(env8, 5); // SE 84 | 85 | printf("env8 = %d\n", env8); 86 | printf("%c %c %c\n", src[0], src[1], src[2]); 87 | printf("%c %c %c\n", src[3], src[4], src[5]); 88 | printf("%c %c %c\n", src[6], src[7], src[8]); 89 | } 90 | 91 | void print_marker(Position *pos, Mark *marker) 92 | { 93 | Position pos2 =*pos; 94 | FORALL_POINTS(pos2, p) 95 | if (is_marked(marker, p)) pos2.color[p]='*'; 96 | print_pos(&pos2, stdout, 0); 97 | } 98 | 99 | char* debug(Position *pos) 100 | { 101 | char *command = strtok(NULL," \t\n"), *ret=""; 102 | char *known_commands = "\nenv8\nfix_atari\ngen_playout\nmatch_pat3\n" 103 | "match_pat\nplayout\nprint_mark\nsavepos\nsetpos\n"; 104 | int amaf_map[BOARDSIZE], owner_map[BOARDSIZE]; 105 | Info sizes[BOARDSIZE]; 106 | Point moves[BOARDSIZE]; 107 | 108 | if (strcmp(command, "setpos") == 0) { 109 | char *str = strtok(NULL, " \t\n"); 110 | while (str != NULL) { 111 | Point pt = parse_coord(str); 112 | if (pos->color[pt] == '.') 113 | ret = play_move(pos, pt); // suppose alternate play 114 | else if (pt == PASS_MOVE) 115 | ret = pass_move(pos); 116 | else 117 | ret ="Error Illegal move: point not EMPTY\n"; 118 | str = strtok(NULL, " \t\n"); 119 | } 120 | } 121 | else if (strcmp(command, "savepos") == 0) { 122 | char *filename = strtok(NULL, " \t\n"); 123 | FILE *f=fopen(filename, "w"); 124 | print_pos(pos, f, NULL); 125 | fclose(f); 126 | ret = ""; 127 | } 128 | else if (strcmp(command, "playout") == 0) 129 | mcplayout(pos, amaf_map, owner_map, 1); 130 | else if (strcmp(command, "gen_playout") == 0) { 131 | char *suggestion = strtok(NULL, " \t\n"); 132 | if (suggestion != NULL) { 133 | Point last_moves_neighbors[20]; 134 | make_list_last_moves_neighbors(pos, last_moves_neighbors); 135 | if (strcmp(suggestion, "capture") == 0) 136 | gen_playout_moves_capture(pos, last_moves_neighbors, 137 | 1.0, 0, moves, sizes); 138 | else if (strcmp(suggestion, "pat3") == 0) 139 | gen_playout_moves_pat3(pos, last_moves_neighbors, 140 | 1.0, moves); 141 | ret = slist_str_as_point(moves); 142 | } 143 | else 144 | ret = "Error - missing [capture|pat3]"; 145 | } 146 | else if (strcmp(command, "match_pat") == 0) { 147 | char *str = strtok(NULL, " \t\n"); 148 | if(str == NULL) ret = "Error missing point"; 149 | else { 150 | copy_to_large_board(pos); 151 | Point pt = parse_coord(str); 152 | str = strtok(NULL, " \t\n"); 153 | int verbose=1; 154 | if (str == NULL) 155 | verbose=0; 156 | ret = make_list_pat_matching(pt, verbose); 157 | } 158 | } 159 | else if (strcmp(command, "fix_atari") == 0) { 160 | int is_atari; 161 | char *str = strtok(NULL, " \t\n"); 162 | if (str == NULL) 163 | return ret = "Error -- point missing"; 164 | Point pt = parse_coord(str); 165 | if (pos->color[pt]!='x' && pos->color[pt]!='X') { 166 | ret ="Error given point not occupied by a stone"; 167 | return ret; 168 | } 169 | is_atari = fix_atari(pos,pt, SINGLEPT_NOK, TWOLIBS_TEST,0,moves, sizes); 170 | slist_str_as_point(moves); 171 | int l = strlen(buf); 172 | for (int k=l+1 ; k>=0 ; k--) buf[k+1] = buf[k]; 173 | if (l>0) 174 | buf[1] = ' '; 175 | if (is_atari) buf[0] = '1'; 176 | else buf[0] = '0'; 177 | ret = buf; 178 | } 179 | else if (strcmp(command, "env8") == 0) { 180 | char *str = strtok(NULL, " \t\n"); 181 | if(str == NULL) ret = "Error missing point"; 182 | else { 183 | Point pt = parse_coord(str); 184 | int env8=(pos->env4d[pt]<<8) + pos->env4[pt]; 185 | print_env8(env8); 186 | } 187 | } 188 | else if (strcmp(command, "print_mark") == 0) { 189 | char *str = strtok(NULL, " \t\n"); 190 | Mark *marker=already_suggested; 191 | if (strcmp(str, "mark1") == 0) marker = mark1; 192 | if (strcmp(str, "mark2") == 0) marker = mark2; 193 | print_marker(pos, marker); 194 | ret = ""; 195 | } 196 | else if (strcmp(command, "help") == 0) 197 | ret = known_commands; 198 | return ret; 199 | } 200 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | int michi_console(int argc, char *argv[]); 2 | 3 | int main(int argc, char *argv[]) 4 | { 5 | return michi_console(argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /michi.c: -------------------------------------------------------------------------------- 1 | // michi.c -- A minimalistic Go-playing engine 2 | /* 3 | This is a recoding in C (for speed) of the michi.py code by Petr Baudis 4 | avalaible at https://github.com/pasky/michi . 5 | 6 | (c) 2015 Petr Baudis Denis Blumstein 7 | MIT licence (i.e. almost public domain) 8 | 9 | The following comments are taken almost verbatim from the michi.py code 10 | 11 | A minimalistic Go-playing engine attempting to strike a balance between 12 | brevity, educational value and strength. It can beat GNUGo on 13x13 board 13 | on a modest 4-thread laptop. 14 | 15 | To start reading the code, begin either: 16 | * Bottom up, by looking at the goban implementation - starting with 17 | the 'empty_position' definition below and play_move() function. 18 | * In the middle, by looking at the Monte Carlo playout implementation, 19 | starting with the mcplayout() function. 20 | * Top down, by looking at the MCTS implementation, starting with the 21 | tree_search() function. It is just a loop of tree_descend(), 22 | mcplayout() and tree_update() round and round. 23 | 24 | It may be better to jump around a bit instead of just reading straight 25 | from start to end. 26 | 27 | The C code can be read in parallel with the python code. 28 | I have been careful to keep the notations used by Petr (almost) everywhere. 29 | Of course the algorithms are the same (at least functionally) as well as the 30 | parameters. 31 | 32 | Examples where the python and the C codes are different are: 33 | - in the functions gen_playout_moves_xxx(). I have not been able to emulate in 34 | C the generators that are available in python (yield instruction). So these 35 | functions in the C code must compute the whole list of suggestions before 36 | returning. 37 | - computation of blocks does not use regexp as the direct coding is simple. 38 | - need to recode a functionality equivalent to python dictionary (in patterns.c) 39 | 40 | The source is composed in 7 independent parts 41 | - Utilities 42 | - Board routines 43 | - Go heuristics 44 | - Monte Carlo Playout policy 45 | - Monte Carlo Tree search 46 | - User Interface (Utilities, Various main programs) 47 | - Pattern code (3x3 and large patterns) which is found in patterns.c 48 | 49 | In C, functions prototypes must be declared before use. 50 | In order to avoid these declarations, functions are defined before they are 51 | used, which has the same effect. 52 | This means that the higher level functions are found towards the bottom of 53 | this file. This may not be a good idea in terms of readibility but at least 54 | the order is the same as in the michi python code. 55 | 56 | Short bibliography 57 | ------------------ 58 | [1] Martin Mueller, Computer Go, Artificial Intelligence, Vol.134, No 1-2, 59 | pp 145-179, 2002 60 | [2] Remi Coulom. Efficient Selectivity and Backup Operators in Monte-Carlo Tree 61 | Search. Paolo Ciancarini and H. Jaap van den Herik. 5th International 62 | Conference on Computer and Games, May 2006, Turin, Italy. 2006. 63 | 64 | [3] Sylvain Gelly, Yizao Wang, Remi Munos, Olivier Teytaud. Modification of UCT 65 | with Patterns in Monte-Carlo Go. [Research Report] RR-6062, 2006. 66 | 67 | [4] David Stern, Ralf Herbrich, Thore Graepel, Bayesian Pattern Ranking for Move 68 | Prediction in the Game of Go, In Proceedings of the 23rd international 69 | conference on Machine learning, pages 873–880, Pittsburgh, Pennsylvania, 70 | USA, 2006 71 | [5] Rémi Coulom. Computing Elo Ratings of Move Patterns in the Game of Go. 72 | In ICGA Journal (2007), pp 198-208. 73 | [6] Sylvain Gelly, David Silver. Achieving Master Level Play in 9×9 Computer Go. 74 | Proceedings of the Twenty-Third AAAI Conference on Artificial Intelligence 75 | (2008) 76 | [7] Albert L Zobrist. A New Hashing Method with Application for Game Playing. 77 | Technical Report #88. April 1970 78 | [8] Petr Baudis. MCTS with Information Sharing, PhD Thesis, 2011 79 | [9] Robert Sedgewick, Algorithms in C, Addison-Wesley, 1990 80 | 81 | + many other PhD thesis accessible on the WEB 82 | 83 | [1] can be consulted for the definition of Computer Go terms : 84 | points, blocks, eyes, false eyes, liberties, etc. 85 | and historical bibliography 86 | */ 87 | #include 88 | #include "michi.h" 89 | 90 | void usage() { 91 | fprintf(stderr, "\n\nusage: michi [-z SEED] [command]\n\n" 92 | "where command = gtp|mcdebug|mcbenchmark|tsdebug\n" 93 | " SEED = > 0 (fixed seed) or 0 (random seed)\n"); 94 | exit(-1); 95 | } 96 | 97 | //========================= Definition of Data Structures ===================== 98 | // Given a board of size NxN (N=9, 19, ...), we represent the position 99 | // as an (N+1)*(N+2)+1 string, with '.' (empty), 'X' (to-play player) 100 | // 'x' (other player), and whitespace (off-board border to make rules 101 | // implementation easier). Coordinates are just indices in this string. 102 | // 103 | // -------------------------------- Global Data ------------------------------- 104 | // North East South West NE SE SW NW 105 | static int delta[] = { -N-1, 1, N+1, -1, -N, W, N, -W, 0}; 106 | static char* colstr = "@ABCDEFGHJKLMNOPQRST"; 107 | Mark *mark1, *mark2, *already_suggested; 108 | unsigned int idum=1; 109 | char buf[BUFLEN]; 110 | Point allpoints[BOARDSIZE]; 111 | int PRIOR_CFG[] = {24, 22, 8}; 112 | 113 | //================================== Code ===================================== 114 | // Utilities 115 | char* slist_str_as_int(Slist l) { 116 | buf[0]=0; 117 | for (int k=1, n=l[0] ; k<=n ; k++) { 118 | char s[32]; 119 | sprintf(s, " %d", l[k]); 120 | strcat(buf, s); 121 | } 122 | return buf; 123 | } 124 | char* slist_str_as_point(Slist l) { 125 | buf[0]=0; 126 | for (int k=1, n=l[0] ; k<=n ; k++) { 127 | char str[8], s[8]; 128 | sprintf(s, " %s", str_coord(l[k],str)); 129 | strcat(buf, s); 130 | } 131 | return buf; 132 | } 133 | 134 | unsigned int true_random_seed(void) 135 | // return a true random seed (which depends on the time) 136 | { 137 | unsigned int r1, r2, sec, day; 138 | time_t tm=time(NULL); 139 | struct tm *tcal=localtime(&tm); 140 | sec = tcal->tm_sec + 60*(tcal->tm_min + 60*tcal->tm_hour); 141 | // day is a coarse (but sufficient for the current purpose) approximation 142 | day = tcal->tm_mday + 31*(tcal->tm_mon + 12*tcal->tm_year); 143 | // Park & Miller random generator (same as qdrandom()) 144 | r1 = (1664525*sec) + 1013904223; 145 | r2 = (1664525*day) + 1013904223; 146 | return (r1^r2); 147 | } 148 | 149 | //=============================== Board routines ============================== 150 | char is_eyeish(Position *pos, Point pt) 151 | // test if pt is inside a single-color diamond and return the diamond color or 0 152 | // this could be an eye, but also a false one 153 | { 154 | char eyecolor=0, othercolor=0; 155 | int k; 156 | Point n; 157 | FORALL_NEIGHBORS(pos, pt, k, n) { 158 | char c = pos->color[n]; 159 | if(c == ' ') continue; // ignore OUT of board neighbours 160 | if(c == '.') return 0; 161 | if(eyecolor == 0) { 162 | eyecolor = c; 163 | othercolor = c; SWAP_CASE(othercolor); 164 | } 165 | else if (c == othercolor) return 0; 166 | } 167 | return eyecolor; 168 | } 169 | 170 | char is_eye(Position *pos, Point pt) 171 | // test if pt is an eye and return its color or 0. 172 | // ######### 173 | // Note: this test cannot detect true eyes like . . X . # or X X X 174 | // X . X # X X 175 | // X X . # X X 176 | // . # X X X 177 | { 178 | char eyecolor=is_eyeish(pos, pt), falsecolor=eyecolor; 179 | int at_edge=0, false_count=0, k; 180 | Point d; 181 | if (eyecolor == 0) return 0; 182 | 183 | // Eye-like shape, but it could be a falsified eye 184 | SWAP_CASE(falsecolor); 185 | FORALL_DIAGONAL_NEIGHBORS(pos, pt, k, d) { 186 | if(pos->color[d] == ' ') at_edge = 1; 187 | else if(pos->color[d] == falsecolor) false_count += 1; 188 | } 189 | if (at_edge) false_count += 1; 190 | if (false_count >= 2) return 0; 191 | return eyecolor; 192 | } 193 | 194 | Byte compute_env4(Position *pos, Point pt, int offset) 195 | // Compute value of the environnement of a point (Byte) 196 | // offset=0 for the 4 neighbors, offset=4 for the 4 diagonal neighbors 197 | { 198 | Byte env4=0, hi, lo, c; 199 | for (int k=offset ; k 0:WHITE, 1:BLACK, 2:EMPTY, 3:OUT 202 | if (pos->color[n] == '.') c = 2; 203 | else if(pos->color[n] == ' ') c = 3; 204 | else { 205 | // env4 is computed with real colors on the board 206 | if (pos->n%2==0) { // BLACK to play (X=BLACK, x=WHITE) 207 | if (pos->color[n] == 'X') c = 1; 208 | else c = 0; 209 | } 210 | else { // WHITE to play (X=WHITE, x=BLACK) 211 | if (pos->color[n] == 'X') c = 0; 212 | else c = 1; 213 | } 214 | } 215 | hi = c >> 1; lo = c & 1; 216 | env4 |= ((hi<<4)+lo) << (k-offset); 217 | } 218 | return env4; 219 | } 220 | 221 | void put_stone(Position *pos, Point pt) 222 | // Always put a stone of color 'X'. See discussion on env4 in patterns.c 223 | { 224 | if (pos->n%2 == 0) { // BLACK to play (X=BLACK) 225 | pos->env4[pt+N+1] ^= 0x11; 226 | pos->env4[pt-1] ^= 0x22; 227 | pos->env4[pt-N-1] ^= 0x44; 228 | pos->env4[pt+1] ^= 0x88; 229 | pos->env4d[pt+N] ^= 0x11; 230 | pos->env4d[pt-W] ^= 0x22; 231 | pos->env4d[pt-N] ^= 0x44; 232 | pos->env4d[pt+W] ^= 0x88; 233 | } 234 | else { // WHITE to play (X=WHITE) 235 | pos->env4[pt+N+1] &= 0xEE; 236 | pos->env4[pt-1] &= 0xDD; 237 | pos->env4[pt-N-1] &= 0xBB; 238 | pos->env4[pt+1] &= 0x77; 239 | pos->env4d[pt+N] &= 0xEE; 240 | pos->env4d[pt-W] &= 0xDD; 241 | pos->env4d[pt-N] &= 0xBB; 242 | pos->env4d[pt+W] &= 0x77; 243 | } 244 | pos->color[pt] = 'X'; 245 | } 246 | 247 | void remove_stone(Position *pos, Point pt) 248 | // Always remove a stone of color 'x' (cheat done by caller when undo move) 249 | { 250 | if (pos->n%2 == 0) { // BLACK to play (x=WHITE) 251 | pos->env4[pt+N+1] |= 0x10; 252 | pos->env4[pt-1] |= 0x20; 253 | pos->env4[pt-N-1] |= 0x40; 254 | pos->env4[pt+1] |= 0x80; 255 | pos->env4d[pt+N] |= 0x10; 256 | pos->env4d[pt-W] |= 0x20; 257 | pos->env4d[pt-N] |= 0x40; 258 | pos->env4d[pt+W] |= 0x80; 259 | } 260 | else { // WHITE to play (x=BLACK) 261 | pos->env4[pt+N+1] ^= 0x11; 262 | pos->env4[pt-1] ^= 0x22; 263 | pos->env4[pt-N-1] ^= 0x44; 264 | pos->env4[pt+1] ^= 0x88; 265 | pos->env4d[pt+N] ^= 0x11; 266 | pos->env4d[pt-W] ^= 0x22; 267 | pos->env4d[pt-N] ^= 0x44; 268 | pos->env4d[pt+W] ^= 0x88; 269 | } 270 | pos->color[pt] = '.'; 271 | } 272 | 273 | void dump_env4(Byte env4, Byte true_env4) 274 | { 275 | for (int i=0 ; i<8 ; i++) { 276 | if (i == 4) fprintf(stderr, " "); 277 | if (env4 & 128) 278 | fprintf(stderr, "1"); 279 | else 280 | fprintf(stderr, "0"); 281 | env4 <<= 1; 282 | } 283 | fprintf(stderr, " (true: "); 284 | for (int i=0 ; i<8 ; i++) { 285 | if (i==4) fprintf(stderr, " "); 286 | if (true_env4 & 128) 287 | fprintf(stderr, "1"); 288 | else 289 | fprintf(stderr, "0"); 290 | true_env4 <<= 1; 291 | } 292 | fprintf(stderr, ")\n"); 293 | } 294 | 295 | int env4_OK(Position *pos) 296 | { 297 | FORALL_POINTS(pos,pt) { 298 | if (pos->color[pt] == ' ') continue; 299 | if (pos->env4[pt] != compute_env4(pos, pt, 0)) { 300 | fprintf(stderr, "%s ERR env4 = ", str_coord(pt,buf)); 301 | dump_env4(pos->env4[pt], compute_env4(pos,pt,0)); 302 | return 0; 303 | } 304 | if (pos->env4d[pt] != compute_env4(pos,pt,4)) { 305 | fprintf(stderr, "%s ERR env4d = ", str_coord(pt,buf)); 306 | dump_env4(pos->env4d[pt], compute_env4(pos,pt,4)); 307 | return 0; 308 | } 309 | } 310 | return 1; 311 | } 312 | 313 | char* empty_position(Position *pos) 314 | // Reset pos to an initial board position 315 | { 316 | int k = 0; 317 | for (int col=0 ; col<=N ; col++) pos->color[k++] = ' '; 318 | for (int row=1 ; row<=N ; row++) { 319 | pos->color[k++] = ' '; 320 | for (int col=1 ; col<=N ; col++) pos->color[k++] = '.'; 321 | } 322 | for (int col=0 ; colcolor[k++] = ' '; 323 | FORALL_POINTS(pos, pt) { 324 | if (pos->color[pt] == ' ') continue; 325 | pos->env4[pt] = compute_env4(pos, pt, 0); 326 | pos->env4d[pt] = compute_env4(pos, pt, 4); 327 | } 328 | 329 | pos->ko = pos->last = pos->last2 = 0; 330 | pos->capX = pos->cap = 0; 331 | pos->n = 0; pos->komi = 7.5; 332 | assert(env4_OK(pos)); 333 | return ""; // result OK 334 | } 335 | 336 | void compute_block(Position *pos, Point pt, Slist stones, Slist libs, int nlibs) 337 | // Compute block at pt : list of stones and list of liberties 338 | // Return early when nlibs liberties are found 339 | { 340 | char color=pos->color[pt]; 341 | int head=2, k, tail=1; 342 | Point n; 343 | 344 | mark_init(mark1); slist_clear(libs); 345 | stones[1] = pt; mark(mark1, pt); 346 | while(head>tail) { 347 | pt = stones[tail++]; 348 | FORALL_NEIGHBORS(pos, pt, k, n) 349 | if (!is_marked(mark1, n)) { 350 | mark(mark1, n); 351 | if (pos->color[n] == color) stones[head++] = n; 352 | else if (pos->color[n] == '.') { 353 | slist_push(libs, n); 354 | if (slist_size(libs) >= nlibs) goto finished; 355 | } 356 | } 357 | } 358 | finished: 359 | stones[0] = head-1; 360 | mark_release(mark1); 361 | } 362 | 363 | int capture_block(Position *pos, Slist stones) 364 | { 365 | FORALL_IN_SLIST(stones, pt) remove_stone(pos, pt); 366 | assert(env4_OK(pos)); 367 | return slist_size(stones); 368 | } 369 | 370 | void swap_color(Position *pos) 371 | { 372 | FORALL_POINTS(pos, pt) 373 | SWAP_CASE(pos->color[pt]); 374 | } 375 | 376 | void remove_X_stone(Position *pos, Point pt) 377 | { 378 | (pos->n)++; // cheat to make remove_stone() work 379 | remove_stone(pos, pt); 380 | (pos->n)--; // undo cheat 381 | } 382 | 383 | char* play_move(Position *pos, Point pt) 384 | // Play a move at point pt (color is imposed by alternate play) 385 | { 386 | int captured=0, k; 387 | Point libs[BOARDSIZE], n, stones[BOARDSIZE], pos_capture; 388 | 389 | pos->ko_old = pos->ko; 390 | if (pt == pos->ko) return "Error Illegal move: retakes ko"; 391 | int in_enemy_eye = is_eyeish(pos, pt); 392 | 393 | put_stone(pos, pt); 394 | // Check for captures 395 | pos_capture = 0; 396 | FORALL_NEIGHBORS(pos, pt, k, n) { 397 | if (pos->color[n] != 'x') continue; 398 | compute_block(pos,n,stones, libs, 1); // extremely naive 399 | if (slist_size(libs)==0) { 400 | captured += capture_block(pos, stones); 401 | pos_capture = n; 402 | } 403 | 404 | } 405 | if (captured) { // Set ko 406 | if (captured==1 && in_enemy_eye) pos->ko = pos_capture; 407 | else pos->ko = 0; 408 | } 409 | else { // Test for suicide 410 | pos->ko = 0; 411 | compute_block(pos, pt, stones, libs, 1); 412 | if(slist_size(libs) == 0) { 413 | pos->ko = pos->ko_old; 414 | remove_X_stone(pos, pt); 415 | return "Error Illegal move: suicide"; 416 | } 417 | } 418 | // Finish update of the position 419 | captured += pos->capX; 420 | pos->capX = pos->cap; 421 | pos->cap = captured; 422 | swap_color(pos); 423 | (pos->n)++; 424 | assert(env4_OK(pos)); 425 | pos->last2 = pos->last; 426 | pos->last = pt; 427 | return ""; // Move OK 428 | } 429 | 430 | char* pass_move(Position *pos) 431 | // Pass - i.e. simply flip the position 432 | { 433 | swap_color(pos); (pos->n)++; 434 | pos->last2 = pos->last; 435 | pos->last = pos->ko = 0; 436 | SWAP(int,pos->cap, pos->capX); 437 | return ""; // PASS moVE is always OK 438 | } 439 | 440 | void make_list_neighbors(Position *pos, Point pt, Slist points) 441 | { 442 | slist_clear(points); 443 | if (pt == PASS_MOVE) return; 444 | slist_push(points, pt); 445 | for (int k=0 ; k<8 ; k++) 446 | if (pos->color[pt+delta[k]] != ' ') 447 | slist_push(points, pt+delta[k]); 448 | slist_shuffle(points); 449 | } 450 | 451 | void make_list_last_moves_neighbors(Position *pos, Slist points) 452 | // generate a randomly shuffled list of points including and surrounding 453 | // the last two moves (but with the last move having priority) 454 | { 455 | Point last2_neighbors[12]; 456 | make_list_neighbors(pos, pos->last,points); 457 | make_list_neighbors(pos, pos->last2,last2_neighbors); 458 | FORALL_IN_SLIST(last2_neighbors, n) 459 | slist_insert(points, n); // insert n if it is not already in points 460 | } 461 | 462 | void make_list_neighbor_blocks_in_atari(Position *pos, Slist stones, 463 | Slist breps, Slist libs) 464 | // Return a list of (opponent) blocks in contact with point in stones 465 | // Each block in the list is represented by one of its points brep 466 | { 467 | char color = pos->color[stones[1]]; 468 | int k, maxlibs=2; 469 | Point n, st[BOARDSIZE], l[4]; 470 | 471 | if (color == 'x') color = 'X'; 472 | else color = 'x'; 473 | 474 | mark_init(mark2); slist_clear(breps); slist_clear(libs); 475 | FORALL_IN_SLIST(stones, pt) { 476 | FORALL_NEIGHBORS(pos, pt, k, n) { 477 | if (pos->color[n] == color && !is_marked(mark2, n)) { 478 | compute_block(pos, n, st, l, maxlibs); 479 | if (slist_size(l) == 1) { 480 | slist_push(breps, st[1]); 481 | slist_push(libs, l[1]); 482 | FORALL_IN_SLIST(st, p) 483 | mark(mark2, p); 484 | } 485 | } 486 | } 487 | } 488 | mark_release(mark2); 489 | } 490 | 491 | double score(Position *pos, int owner_map[]) 492 | // compute score for to-play player; this assumes a final position with all 493 | // dead stones captured and only single point eyes on the board ... 494 | { 495 | double s=pos->komi; 496 | int n=-1; 497 | if (pos->n%2==0) { 498 | s = -s; // komi counts negatively for BLACK 499 | n = 1; 500 | } 501 | 502 | FORALL_POINTS(pos,pt) { 503 | char c = pos->color[pt]; 504 | if (c=='.') c = is_eyeish(pos,pt); 505 | if (c=='X') { 506 | s += 1.0; 507 | owner_map[pt] += n; 508 | } 509 | else if (c=='x') { 510 | s -= 1.0; 511 | owner_map[pt] -= n; 512 | } 513 | } 514 | return s; 515 | } 516 | 517 | //================================ Go heuristics ============================== 518 | // The couple of functions read_ladder_attack / fix_atari is maybe the most 519 | // complicated part of the whole program (sadly). 520 | // Feel free to just TREAT IT AS A BLACK-BOX, it's not really that interesting! 521 | 522 | Point read_ladder_attack(Position *pos, Point pt, Slist libs) 523 | // Check if a capturable ladder is being pulled out at pt and return a move 524 | // that continues it in that case. Expects its two liberties in libs. 525 | // Actually, this is a general 2-lib capture exhaustive solver. 526 | { 527 | Point moves[5], sizes[5]; // 4 points should be enough ... 528 | Point move=0; 529 | FORALL_IN_SLIST(libs, l) { 530 | Position pos_l = *pos; 531 | char *ret = play_move(&pos_l, l); 532 | if (ret[0]!=0) continue; // move not legal 533 | // fix_atari() will recursively call read_ladder_attack() back 534 | // however, ignore 2lib groups as we don't have time to chase them 535 | slist_clear(moves); slist_clear(sizes); 536 | int is_atari = fix_atari(&pos_l, pt, SINGLEPT_NOK, TWOLIBS_TEST_NO 537 | , 0, moves, sizes); 538 | // if block is in atari and cannot escape, it is caugth in a ladder 539 | if (is_atari && slist_size(moves) == 0) 540 | move = l; 541 | } 542 | return move; // ladder attack not successful 543 | } 544 | 545 | int line_height(Point pt); 546 | int fix_atari(Position *pos, Point pt, int singlept_ok 547 | , int twolib_test, int twolib_edgeonly, Slist moves, Slist sizes) 548 | // An atari/capture analysis routine that checks the group at Point pt, 549 | // determining whether (i) it is in atari (ii) if it can escape it, 550 | // either by playing on its liberty or counter-capturing another group. 551 | // 552 | // Return 1 (true) if the group is in atari, 0 otherwise 553 | // moves : a list of moves that capture or save blocks 554 | // sizes : list of same lenght as moves (size of corresponding blocks) 555 | // singlept_ok!=0 means that we will not try to save one-point groups 556 | { 557 | int in_atari=1, maxlibs=3; 558 | Point stones[BOARDSIZE], l, libs[5], blocks[256], blibs[256]; 559 | 560 | slist_clear(moves); slist_clear(sizes); 561 | compute_block(pos, pt, stones, libs, maxlibs); 562 | if (singlept_ok && slist_size(stones) == 1) return 0; 563 | if (slist_size(libs) >= 2) { 564 | if (twolib_test && slist_size(libs) == 2 && slist_size(stones) > 1) { 565 | if (twolib_edgeonly 566 | && ((line_height(libs[1]))>0 || (line_height(libs[2]))>0)) { 567 | // no expensive ladder check 568 | return 0; 569 | } 570 | else { 571 | // check that the block cannot be caught in a working ladder 572 | // If it can, that's as good as in atari, a capture threat. 573 | // (Almost - N/A for countercaptures.) 574 | Point ladder_attack = read_ladder_attack(pos, pt, libs); 575 | if (ladder_attack) { 576 | if(slist_insert(moves, ladder_attack)) 577 | slist_push(sizes, slist_size(stones)); 578 | } 579 | } 580 | } 581 | return 0; 582 | } 583 | 584 | if (pos->color[pt] == 'x') { 585 | // - this is opponent's group, that's enough to capture it 586 | if (slist_insert(moves, libs[1])) 587 | slist_push(sizes, slist_size(stones)); 588 | return in_atari; 589 | } 590 | 591 | // This is our group and it is in atari 592 | // Before thinking about defense, what about counter-capturing a neighbor ? 593 | make_list_neighbor_blocks_in_atari(pos, stones, blocks, blibs); 594 | FORALL_IN_SLIST(blibs, l) 595 | if (slist_insert(moves, l)) 596 | slist_push(sizes, slist_size(stones)); 597 | 598 | l = libs[1]; 599 | // We are escaping. 600 | // Will playing our last liberty gain/ at least two liberties? 601 | Position escpos = *pos; 602 | char *ret = play_move(&escpos, l); 603 | if (ret[0]!=0) 604 | return 1; // oops, suicidal move 605 | compute_block(&escpos, l, stones, libs, maxlibs); 606 | if (slist_size(libs) >= 2) { 607 | // Good, there is still some liberty remaining - but if it's just the 608 | // two, check that we are not caught in a ladder... (Except that we 609 | // don't care if we already have some alternative escape routes!) 610 | if (slist_size(moves)>1 611 | || (slist_size(libs)==2 && read_ladder_attack(&escpos,l,libs) == 0) 612 | || (slist_size(libs)>=3)) 613 | if (slist_insert(moves, l)) 614 | slist_push(sizes, slist_size(stones)); 615 | } 616 | return in_atari; 617 | } 618 | 619 | void compute_cfg_distances(Position *pos, Point pt, char cfg_map[BOARDSIZE]) 620 | // Return a board map listing common fate graph distances from a given point. 621 | // This corresponds to the concept of locality while contracting groups to 622 | // single points. 623 | { 624 | int head=1, k, tail=0; 625 | Point fringe[30*BOARDSIZE], n; 626 | 627 | memset(cfg_map, -1, BOARDSIZE); 628 | cfg_map[pt] = 0; 629 | 630 | // flood-fill like mechanics 631 | fringe[0]=pt; 632 | while(head > tail) { 633 | pt = fringe[tail++]; 634 | FORALL_NEIGHBORS(pos, pt, k, n) { 635 | char c = pos->color[n]; 636 | if (c==' ') continue; 637 | if (0 <= cfg_map[n] && cfg_map[n] <= cfg_map[pt]) continue; 638 | int cfg_before = cfg_map[n]; 639 | if (c != '.' && c==pos->color[pt]) 640 | cfg_map[n] = cfg_map[pt]; 641 | else 642 | cfg_map[n] = cfg_map[pt]+1; 643 | if (cfg_before < 0 || cfg_before > cfg_map[n]) { 644 | fringe[head++] = n; 645 | assert(head < 30*BOARDSIZE); 646 | } 647 | } 648 | } 649 | } 650 | 651 | int line_height(Point pt) 652 | // Return the line number above nearest board edge (0 based) 653 | { 654 | div_t d = div(pt,N+1); 655 | int row = d.quot, col=d.rem; 656 | if (row > N/2) row = N+1-row; 657 | if (col > N/2) col = N+1-col; 658 | if (row < col) return row-1; 659 | else return col-1; 660 | } 661 | 662 | int empty_area(Position *pos, Point pt, int dist) 663 | // Check whether there are any stones in Manhattan distance up to dist 664 | { 665 | int k; 666 | Point n; 667 | FORALL_NEIGHBORS(pos, pt, k, n) { 668 | if (pos->color[n]=='x' || pos->color[n]=='X') 669 | return 0; 670 | else if (pos->color[n]=='.' && dist>1 && !empty_area(pos, n, dist-1)) 671 | return 0; 672 | } 673 | return 1; 674 | } 675 | 676 | //========================= Montecarlo playout policy ========================= 677 | int gen_playout_moves_capture(Position *pos, Slist heuristic_set, float prob, 678 | int expensive_ok, Slist moves, Slist sizes) 679 | // Compute list of candidate next moves in the order of preference (capture) 680 | // heuristic_set is the set of coordinates considered for applying heuristics; 681 | // this is the immediate neighborhood of last two moves in the playout, but 682 | // the whole board while prioring the tree. 683 | { 684 | int k, twolib_edgeonly = !expensive_ok; 685 | Point move2[20], size2[20]; 686 | 687 | slist_clear(moves); slist_clear(sizes); 688 | if (random_int(10000) <= prob*10000.0) 689 | FORALL_IN_SLIST(heuristic_set, pt) 690 | if (pos->color[pt]=='x' || pos->color[pt]=='X') { 691 | fix_atari(pos, pt, SINGLEPT_NOK, TWOLIBS_TEST, 692 | twolib_edgeonly, move2, size2); 693 | k=1; 694 | FORALL_IN_SLIST(move2, move) 695 | if (slist_insert(moves, move)) 696 | slist_push(sizes, size2[k++]); 697 | } 698 | return slist_size(moves); 699 | } 700 | 701 | int gen_playout_moves_pat3(Position *pos, Slist heuristic_set, float prob, 702 | Slist moves) 703 | // Compute list of candidate next moves in the order of preference (3x3 pattern) 704 | // heuristic_set is the set of coordinates considered for applying heuristics; 705 | // this is the immediate neighborhood of last two moves in the playout, but 706 | // the whole board while prioring the tree. 707 | { 708 | slist_clear(moves); 709 | mark_init(already_suggested); 710 | if (random_int(1000) <= prob*1000.0) 711 | FORALL_IN_SLIST(heuristic_set, pt) 712 | if (pos->color[pt] == '.' && pat3_match(pos, pt)) 713 | slist_push(moves, pt); 714 | mark_release(already_suggested); 715 | return slist_size(moves); 716 | } 717 | 718 | int gen_playout_moves_random(Position *pos, Point moves[BOARDSIZE], Point i0) 719 | // Generate a list of moves (includes false positives - suicide moves; 720 | // does not include true-eye-filling moves), starting from a given board index 721 | // (that can be used for randomization) 722 | { 723 | slist_clear(moves); 724 | for(Point i=i0 ; icolor[i] != '.') continue; // ignore NOT EMPTY Points 726 | if (is_eye(pos,i) == 'X') continue; // ignore true eyes for player 727 | slist_push(moves, i); 728 | } 729 | for(Point i=BOARD_IMIN-1 ; icolor[i] != '.') continue; // ignore NOT EMPTY Points 731 | if (is_eye(pos,i) == 'X') continue; // ignore true eyes for player 732 | slist_push(moves, i); 733 | } 734 | return slist_size(moves); 735 | } 736 | 737 | Point choose_from(Position *pos, Slist moves, char *kind, int disp) 738 | { 739 | char *ret; 740 | Info sizes[20]; 741 | Point move = PASS_MOVE, ds[20]; 742 | Position saved_pos = *pos; 743 | 744 | FORALL_IN_SLIST(moves, pt) { 745 | if (disp && strcmp(kind, "random")!=0) 746 | fprintf(stderr,"move suggestion (%s) %s\n", kind,str_coord(pt,buf)); 747 | ret = play_move(pos, pt); 748 | if (ret[0] == 0) { // move OK 749 | move = pt; 750 | // check if the suggested move did not turn out to be a self-atari 751 | int r = random_int(10000), tstrej; 752 | if (strcmp(kind,"random") == 0) tstrej = r<=10000.0*PROB_RSAREJECT; 753 | else tstrej = r<= 10000.0*PROB_SSAREJECT; 754 | if (tstrej) { 755 | slist_clear(ds); slist_clear(sizes); 756 | fix_atari(pos, pt, SINGLEPT_OK, TWOLIBS_TEST, 1, ds, sizes); 757 | if (slist_size(ds) > 0) { 758 | if(disp) fprintf(stderr, "rejecting self-atari move %s\n", 759 | str_coord(pt, buf)); 760 | *pos = saved_pos; // undo move; 761 | move = PASS_MOVE; 762 | continue; 763 | } 764 | } 765 | break; 766 | } 767 | } 768 | return move; 769 | } 770 | 771 | double mcplayout(Position *pos, int amaf_map[], int owner_map[], int disp) 772 | // Start a Monte Carlo playout from a given position, return score for to-play 773 | // player at the starting position; amaf_map is board-sized scratchpad recording// who played at a given position first 774 | { 775 | double s=0.0; 776 | int passes=0, start_n=pos->n; 777 | Info sizes[BOARDSIZE]; 778 | Point last_moves_neighbors[20], moves[BOARDSIZE], move; 779 | if(disp) fprintf(stderr, "** SIMULATION **\n"); 780 | 781 | while (passes < 2 && pos->n < MAX_GAME_LEN) { 782 | move = 0; 783 | if(disp) print_pos(pos, stdout, NULL); 784 | // We simply try the moves our heuristics generate, in a particular 785 | // order, but not with 100% probability; this is on the border between 786 | // "rule-based playouts" and "probability distribution playouts". 787 | make_list_last_moves_neighbors(pos, last_moves_neighbors); 788 | 789 | // Capture heuristic suggestions 790 | if (gen_playout_moves_capture(pos, last_moves_neighbors, 791 | PROB_HEURISTIC_CAPTURE, 0, moves, sizes)) 792 | if((move=choose_from(pos, moves, "capture", disp)) != PASS_MOVE) 793 | goto found; 794 | 795 | // 3x3 patterns heuristic suggestions 796 | if (gen_playout_moves_pat3(pos, last_moves_neighbors, 797 | PROB_HEURISTIC_PAT3, moves)) 798 | if((move=choose_from(pos, moves, "pat3", disp)) != PASS_MOVE) 799 | goto found; 800 | 801 | gen_playout_moves_random(pos, moves, BOARD_IMIN-1+random_int(N*W)); 802 | move=choose_from(pos, moves, "random", disp); 803 | found: 804 | if (move == PASS_MOVE) { // No valid move : pass 805 | pass_move(pos); 806 | passes++; 807 | } 808 | else { 809 | if (amaf_map[move] == 0) // mark the point with 1 for BLACK 810 | // pos->n-1 because in michi.py pos is updated after this line 811 | amaf_map[move] = ((pos->n-1)%2==0 ? 1 : -1); 812 | passes=0; 813 | } 814 | } 815 | s = score(pos, owner_map); 816 | if (start_n%2 != pos->n%2) s = -s; 817 | return s; 818 | } 819 | //========================== Montecarlo tree search =========================== 820 | TreeNode* new_tree_node(Position *pos) 821 | { 822 | TreeNode *node = calloc(1,sizeof(TreeNode)); 823 | node->pos = *pos; 824 | node->pv = PRIOR_EVEN; node->pw = PRIOR_EVEN/2; 825 | return node; 826 | } 827 | 828 | void expand(TreeNode *tree) 829 | // add and initialize children to a leaf node 830 | { 831 | char cfg_map[BOARDSIZE]; 832 | int nchildren = 0; 833 | Info sizes[BOARDSIZE]; 834 | Point moves[BOARDSIZE]; 835 | Position pos2; 836 | TreeNode *childset[BOARDSIZE], *node; 837 | if (tree->pos.last!=PASS_MOVE) 838 | compute_cfg_distances(&tree->pos, tree->pos.last, cfg_map); 839 | 840 | // Use light random playout generator to get all the empty points (not eye) 841 | gen_playout_moves_random(&tree->pos, moves, BOARD_IMIN-1); 842 | 843 | tree->children = calloc(slist_size(moves)+2, sizeof(TreeNode*)); 844 | FORALL_IN_SLIST(moves, pt) { 845 | pos2 = tree->pos; 846 | assert(tree->pos.color[pt] == '.'); 847 | char* ret = play_move(&pos2, pt); 848 | if (ret[0] != 0) continue; 849 | // pt is a legal move : we build a new node for it 850 | childset[pt]= tree->children[nchildren++] = new_tree_node(&pos2); 851 | } 852 | tree->nchildren = nchildren; 853 | 854 | // Update the prior for the 'capture' and 3x3 patterns suggestions 855 | gen_playout_moves_capture(&tree->pos, allpoints, 1, 1, moves, sizes); 856 | int k=1; 857 | FORALL_IN_SLIST(moves, pt) { 858 | pos2 = tree->pos; 859 | char* ret = play_move(&pos2, pt); 860 | if (ret[0] != 0) continue; 861 | node = childset[pt]; 862 | if (sizes[k] == 1) { 863 | node->pv += PRIOR_CAPTURE_ONE; 864 | node->pw += PRIOR_CAPTURE_ONE; 865 | } 866 | else { 867 | node->pv += PRIOR_CAPTURE_MANY; 868 | node->pw += PRIOR_CAPTURE_MANY; 869 | } 870 | k++; 871 | } 872 | gen_playout_moves_pat3(&tree->pos, allpoints, 1, moves); 873 | FORALL_IN_SLIST(moves, pt) { 874 | pos2 = tree->pos; 875 | char* ret = play_move(&pos2, pt); 876 | if (ret[0] != 0) continue; 877 | node = childset[pt]; 878 | node->pv += PRIOR_PAT3; 879 | node->pw += PRIOR_PAT3; 880 | } 881 | 882 | // Second pass setting priors, considering each move just once now 883 | copy_to_large_board(&tree->pos); // For large patterns 884 | for (int k=0 ; knchildren ; k++) { 885 | node = tree->children[k]; 886 | Point pt = node->pos.last; 887 | 888 | if (tree->pos.last != PASS_MOVE && cfg_map[pt]-1 < LEN_PRIOR_CFG) { 889 | node->pv += PRIOR_CFG[cfg_map[pt]-1]; 890 | node->pw += PRIOR_CFG[cfg_map[pt]-1]; 891 | } 892 | 893 | int height = line_height(pt); // 0-indexed 894 | if (height <= 2 && empty_area(&tree->pos, pt, 3)) { 895 | // No stones around; negative prior for 1st + 2nd line, positive 896 | // for 3rd line; sanitizes opening and invasions 897 | if (height <= 1) { 898 | node->pv += PRIOR_EMPTYAREA; 899 | node->pw += 0; 900 | } 901 | if (height == 2) { 902 | node->pv += PRIOR_EMPTYAREA; 903 | node->pw += PRIOR_EMPTYAREA; 904 | } 905 | } 906 | 907 | fix_atari(&node->pos, pt, SINGLEPT_OK, TWOLIBS_TEST, !TWOLIBS_EDGE_ONLY, 908 | moves, sizes); 909 | if (slist_size(moves) > 0) { 910 | node->pv += PRIOR_SELFATARI; 911 | node->pw += 0; // negative prior 912 | } 913 | 914 | double patternprob = large_pattern_probability(pt); 915 | if (patternprob > 0.0) { 916 | double pattern_prior = sqrt(patternprob); // tone up 917 | node->pv += pattern_prior * PRIOR_LARGEPATTERN; 918 | node->pw += pattern_prior * PRIOR_LARGEPATTERN; 919 | } 920 | } 921 | 922 | if (tree->nchildren == 0) { 923 | // No possible move, add a pass move 924 | pos2 = tree->pos; 925 | pass_move(&pos2); 926 | tree->children[0] = new_tree_node(&pos2); 927 | tree->nchildren = 1; 928 | } 929 | } 930 | 931 | void free_tree(TreeNode *tree) 932 | // Free memory allocated for the tree 933 | { 934 | if (tree->children != NULL) { 935 | for (TreeNode **child = tree->children ; *child != NULL ; child++) 936 | free_tree(*child); 937 | free(tree->children); 938 | } 939 | free(tree); 940 | } 941 | 942 | double rave_urgency(TreeNode *node) 943 | { 944 | double v = node->v + node->pv; 945 | double expectation = (node->w + node->pw)/v; 946 | if (node->av==0) return expectation; 947 | 948 | double rave_expectation = (double) node->aw / (double) node->av; 949 | double beta = node->av / (node->av + v + (double)v*node->av/RAVE_EQUIV); 950 | return beta * rave_expectation + (1-beta) * expectation; 951 | } 952 | 953 | double winrate(TreeNode *node) 954 | { 955 | double wr; 956 | if (node->v>0) wr = (double) node->w / (double) node->v; 957 | else wr = -0.1; 958 | return wr; 959 | } 960 | 961 | TreeNode* best_move(TreeNode *tree, TreeNode **except) 962 | // best move is the most simulated one (avoiing nodes in except list 963 | { 964 | int vmax=-1; 965 | TreeNode *best=NULL; 966 | 967 | if (tree->children == NULL) return NULL; 968 | 969 | for (TreeNode **child = tree->children ; *child != NULL ; child++) { 970 | if ((*child)->v > vmax) { 971 | int update = 1; 972 | if (except != NULL) 973 | for (TreeNode **n=except ; *n!=NULL ; n++) 974 | if (*child == *n) update=0; 975 | if (update) { 976 | vmax = (*child)->v; 977 | best = (*child); 978 | } 979 | } 980 | } 981 | return best; 982 | } 983 | 984 | TreeNode* most_urgent(TreeNode **children, int nchildren, int disp) 985 | { 986 | int k=0; 987 | double urgency, umax=0; 988 | TreeNode *urgent = children[0]; 989 | 990 | // Randomize the order of the nodes 991 | SHUFFLE(TreeNode *, children, nchildren); 992 | 993 | for (TreeNode **child = children ; *child != NULL ; child++) { 994 | if (disp) 995 | dump_subtree(*child, N_SIMS/50, "", stderr, 0); 996 | urgency = rave_urgency(*child); 997 | if (urgency > umax) { 998 | umax = urgency; 999 | urgent = *child; 1000 | } 1001 | k++; 1002 | } 1003 | return urgent; 1004 | } 1005 | 1006 | int tree_descend(TreeNode *tree, int amaf_map[], int disp, TreeNode **nodes) 1007 | // Descend through the tree to a leaf 1008 | { 1009 | int last=0, passes = 0; 1010 | Point move; 1011 | //tree->v += 1; 1012 | nodes[last] = tree; 1013 | 1014 | while (nodes[last]->children != NULL && passes <2) { 1015 | if (disp) print_pos(&nodes[last]->pos, stderr, NULL); 1016 | // Pick the most urgent child 1017 | TreeNode *node = most_urgent(nodes[last]->children, 1018 | nodes[last]->nchildren, disp); 1019 | nodes[++last] = node; 1020 | move = node->pos.last; 1021 | if (disp) { fprintf(stderr, "chosen "); ppoint(move); } 1022 | 1023 | if (move == PASS_MOVE) passes++; 1024 | else { 1025 | passes = 0; 1026 | if (amaf_map[move] == 0) //Mark the point with 1 for black 1027 | amaf_map[move] = (nodes[last-1]->pos.n%2==0 ? 1 : -1); 1028 | } 1029 | 1030 | if (node->children == NULL && node->v >= EXPAND_VISITS) 1031 | expand(node); 1032 | } 1033 | return last; 1034 | } 1035 | 1036 | void tree_update(TreeNode **nodes,int last,int amaf_map[],double score,int disp) 1037 | // Store simulation result in the tree (nodes is the tree path) 1038 | { 1039 | for (int k=last ; k>=0 ; k--) { // walk nodes from leaf to the root 1040 | TreeNode *n= nodes[k]; 1041 | if(disp) { 1042 | char str[8]; str_coord(n->pos.last,str); 1043 | fprintf(stderr, "updating %s %d\n", str, score<0.0); 1044 | } 1045 | n->v += 1; // TODO put it in tree_descend when parallelize 1046 | n->w += score<0.0; // score is for to-play, node stats for just-played 1047 | 1048 | // Update the node children AMAF stats with moves we made 1049 | // with their color 1050 | int amaf_map_value = (n->pos.n %2 == 0 ? 1 : -1); 1051 | if (n->children != NULL) { 1052 | for (TreeNode **child = n->children ; *child != NULL ; child++) { 1053 | if ((*child)->pos.last == 0) continue; 1054 | if (amaf_map[(*child)->pos.last] == amaf_map_value) { 1055 | if (disp) { 1056 | char str[8]; 1057 | str_coord((*child)->pos.last, str); 1058 | fprintf(stderr, " AMAF updating %s %d\n", str,score>0); 1059 | } 1060 | (*child)->aw += score > 0; // reversed perspective 1061 | (*child)->av += 1; 1062 | } 1063 | } 1064 | } 1065 | score = -score; 1066 | } 1067 | } 1068 | 1069 | Point tree_search(TreeNode *tree, int n, int owner_map[], int disp) 1070 | // Perform MCTS search from a given position for a given #iterations 1071 | { 1072 | double s; 1073 | int *amaf_map=calloc(BOARDSIZE, sizeof(int)), i, last; 1074 | TreeNode *best, *nodes[500]; 1075 | 1076 | // Initialize the root node if necessary 1077 | if (tree->children == NULL) expand(tree); 1078 | memset(owner_map,0,BOARDSIZE*sizeof(int)); 1079 | 1080 | for (i=0 ; i0 && i % REPORT_PERIOD == 0) print_tree_summary(tree, i, stderr); 1083 | last = tree_descend(tree, amaf_map, disp, nodes); 1084 | Position pos = nodes[last]->pos; 1085 | s = mcplayout(&pos, amaf_map, owner_map, disp); 1086 | tree_update(nodes, last, amaf_map, s, disp); 1087 | // Early stop test 1088 | double best_wr = winrate(best_move(tree, NULL)); 1089 | if ( (i>n*0.05 && best_wr > FASTPLAY5_THRES) 1090 | || (i>n*0.2 && best_wr > FASTPLAY20_THRES)) break; 1091 | } 1092 | dump_subtree(tree, N_SIMS/50, "", stderr, 1); 1093 | print_tree_summary(tree, i, stderr); 1094 | best = best_move(tree, NULL); 1095 | 1096 | free(amaf_map); 1097 | if (best->pos.last == PASS_MOVE && best->pos.last2 == PASS_MOVE) 1098 | return PASS_MOVE; 1099 | else if (((double) best->w / (double) best->v) < RESIGN_THRES) 1100 | return RESIGN_MOVE; 1101 | else 1102 | return best->pos.last; 1103 | } 1104 | 1105 | //============================= user interface(s) ============================= 1106 | 1107 | //----------------------------- utility routines ------------------------------ 1108 | void make_pretty(Position *pos, char pretty_board[BOARDSIZE], int *capB 1109 | , int *capW) 1110 | { 1111 | if ((pos->n)%2) { // WHITE to play 1112 | for (int k=0 ; kcolor[k] == 'X') pretty_board[k] = 'O'; 1114 | else if (pos->color[k] == 'x') pretty_board[k] = 'X'; 1115 | else pretty_board[k] = pos->color[k]; 1116 | *capB = pos->cap; 1117 | *capW = pos->capX; 1118 | } 1119 | else { // BLACK to play 1120 | for (int k=0 ; kcolor[k] == 'x') pretty_board[k] = 'O'; 1122 | else pretty_board[k] = pos->color[k]; 1123 | *capW = pos->cap; 1124 | *capB = pos->capX; 1125 | } 1126 | } 1127 | 1128 | void dump_subtree(TreeNode *node, double thres, char *indent, FILE *f 1129 | , int recurse) 1130 | // print this node and all its children with v >= thres. 1131 | { 1132 | char str[8], str_winrate[8], str_rave_winrate[8]; 1133 | str_coord(node->pos.last, str); 1134 | if (node->v) sprintf(str_winrate, "%.3f", winrate(node)); 1135 | else sprintf(str_winrate, "nan"); 1136 | if (node->av) sprintf(str_rave_winrate, "%.3f", (double)node->aw/node->av); 1137 | else sprintf(str_rave_winrate, "nan"); 1138 | fprintf(f,"%s+- %s %5s " 1139 | "(%6d/%-6d, prior %3d/%-3d, rave %6d/%-6d=%5s, urgency %.3f)\n" 1140 | ,indent, str, str_winrate, node->w, node->v, node->pw, node->pv 1141 | ,node->aw, node->av, str_rave_winrate, rave_urgency(node)); 1142 | if (recurse) { 1143 | char new_indent[BUFLEN]; 1144 | sprintf(new_indent,"%s ", indent); 1145 | for (TreeNode **child = node->children ; *child != NULL ; child++) 1146 | if ((*child)->v >= thres) 1147 | dump_subtree((*child), thres, new_indent, f, 0); 1148 | } 1149 | } 1150 | 1151 | void print_tree_summary(TreeNode *tree, int sims, FILE *f) 1152 | { 1153 | char best_seq[32]="", can[128]="", str[8], tmp[32]; 1154 | int k; 1155 | TreeNode *best_node[5]={0,0,0,0,0}, *node; 1156 | 1157 | for (k=0 ; k<5 ; k++) { 1158 | best_node[k] = best_move(tree, best_node); 1159 | if (best_node[k] != NULL) { 1160 | str_coord(best_node[k]->pos.last,str); 1161 | if (best_node[k]->v) 1162 | sprintf(tmp, " %s(%.3f)", str, winrate(best_node[k])); 1163 | else 1164 | sprintf(tmp, " %s(nan)", str); 1165 | strcat(can, tmp); 1166 | } 1167 | } 1168 | node = tree; 1169 | for (k=0 ; k<5 ; k++) { 1170 | node = best_move(node, NULL); 1171 | if (node == NULL) break; 1172 | str_coord(node->pos.last, str); 1173 | strcat(str, " "); 1174 | strcat(best_seq, str); 1175 | } 1176 | fprintf(f,"[%4d] winrate %.3f | seq %s| can %s\n",sims 1177 | ,winrate(best_node[0]), best_seq, can); 1178 | } 1179 | 1180 | Point parse_coord(char *s) 1181 | { 1182 | char c, str[10]; 1183 | int x,y; 1184 | for (int i=0 ; i<9 ; i++) { 1185 | if (s[i]==0) { 1186 | str[i]=0; 1187 | break; 1188 | } 1189 | str[i] = toupper(s[i]); 1190 | } 1191 | if(strcmp(str, "PASS") == 0) return PASS_MOVE; 1192 | sscanf(s, "%c%d", &c, &y); 1193 | c = toupper(c); 1194 | if (c<'J') x = c-'@'; 1195 | else x = c-'@'-1; 1196 | return (N-y+1)*(N+1) + x; 1197 | } 1198 | 1199 | char* str_coord(Point pt, char str[8]) 1200 | { 1201 | if (pt == PASS_MOVE) strcpy(str, "pass"); 1202 | else if (pt == RESIGN_MOVE) strcpy(str, "resign"); 1203 | else { 1204 | int row = pt/(N+1); int col = (pt%(N+1)); 1205 | sprintf(str, "%c%d", '@'+col,N+1-row); 1206 | if (str[0] > 'H') str[0]++; 1207 | } 1208 | return str; 1209 | } 1210 | 1211 | void ppoint(Point pt) { 1212 | char str[8]; 1213 | str_coord(pt,str); 1214 | fprintf(stderr,"%s\n",str); 1215 | } 1216 | 1217 | void print_pos(Position *pos, FILE *f, int *owner_map) 1218 | // Print visualization of the given board position 1219 | { 1220 | char pretty_board[BOARDSIZE], strko[8]; 1221 | int capB, capW; 1222 | 1223 | make_pretty(pos, pretty_board, &capB, &capW); 1224 | fprintf(f,"Move: %-3d Black: %d caps White: %d caps Komi: %.1f", 1225 | pos->n, capB, capW, pos->komi); 1226 | if (pos->ko) 1227 | fprintf(f," ko: %s", str_coord(pos->ko,strko)); 1228 | fprintf(f,"\n"); 1229 | 1230 | for (int row=1, k=N+1, k1=N+1 ; row<=N ; row++) { 1231 | if (pos->last == k+1) fprintf(f, " %-2d(", N-row+1); 1232 | else fprintf(f, " %-2d ", N-row+1); 1233 | k++;k1++; 1234 | for (int col=1 ; col<=N ; col++,k++) { 1235 | fprintf(f, "%c", pretty_board[k]); 1236 | if (pos->last == k+1) fprintf(f, "("); 1237 | else if (pos->last == k) fprintf(f, ")"); 1238 | else fprintf(f, " "); 1239 | } 1240 | if (owner_map) { 1241 | fprintf(f, " "); 1242 | for (int col=1 ; col<=N ; col++,k1++) { 1243 | char c; 1244 | if ((double)owner_map[k1] > 0.6*N_SIMS) c = 'X'; 1245 | else if ((double)owner_map[k1] > 0.3*N_SIMS) c = 'x'; 1246 | else if ((double)owner_map[k1] <-0.6*N_SIMS) c = 'O'; 1247 | else if ((double)owner_map[k1] <-0.3*N_SIMS) c = 'o'; 1248 | else c = '.'; 1249 | fprintf(f, " %c", c); 1250 | } 1251 | } 1252 | fprintf(f, "\n"); 1253 | } 1254 | fprintf(f, " "); 1255 | for (int col=1 ; col<=N ; col++) fprintf(f, "%c ", colstr[col]); 1256 | fprintf(f, "\n\n"); 1257 | } 1258 | 1259 | //----------------------------- Various main programs ------------------------- 1260 | double mcbenchmark(int n, Position *pos, int amaf_map[], int owner_map[]) 1261 | // run n Monte-Carlo playouts from empty position, return avg. score 1262 | { 1263 | double sumscore = 0.0; 1264 | for (int i=0 ; icolor[pt] == '.') 1319 | ret = play_move(pos, pt); // suppose alternate play 1320 | else { 1321 | if(pt == PASS_MOVE) ret = pass_move(pos); 1322 | else ret ="Error Illegal move: point not EMPTY\n"; 1323 | } 1324 | } 1325 | else if (strcmp(command, "genmove") == 0) { 1326 | c2++; game_ongoing = 1; 1327 | Point pt; 1328 | if (pos->last == PASS_MOVE && pos->n>2) { 1329 | log_fmt_s('I', "Opponent pass. I pass", NULL); 1330 | pt = PASS_MOVE; 1331 | } 1332 | else { 1333 | free_tree(tree); 1334 | tree = new_tree_node(pos); 1335 | pt = tree_search(tree, N_SIMS, owner_map, 0); 1336 | } 1337 | if (pt == PASS_MOVE) 1338 | pass_move(pos); 1339 | else if (pt != RESIGN_MOVE) 1340 | play_move(pos, pt); 1341 | ret = str_coord(pt, buf); 1342 | } 1343 | else if (strcmp(command, "cputime") == 0) { 1344 | float time_sec = (float) clock() / (float) CLOCKS_PER_SEC; 1345 | sprintf(buf, "%.3f", time_sec); 1346 | ret = buf; 1347 | } 1348 | else if (strcmp(command, "clear_board") == 0) { 1349 | if (game_ongoing) begin_game(); 1350 | game_ongoing = 0; 1351 | free_tree(tree); 1352 | ret = empty_position(pos); 1353 | tree = new_tree_node(pos); 1354 | } 1355 | else if (strcmp(command, "boardsize") == 0) { 1356 | char *str = strtok(NULL, " \t\n"); 1357 | if(str == NULL) goto finish_command; 1358 | int size = atoi(str); 1359 | if (size != N) { 1360 | sprintf(buf, "Error: Trying to set incompatible boardsize %s" 1361 | " (!= %d)", str, N); 1362 | log_fmt_s('E', buf, NULL); 1363 | ret = buf; 1364 | } 1365 | else 1366 | ret = ""; 1367 | } 1368 | else if (strcmp(command, "komi") == 0) { 1369 | char *str = strtok(NULL, " \t\n"); 1370 | if(str == NULL) goto finish_command; 1371 | float komi = (float) atof(str); 1372 | if (komi != 7.5) { 1373 | sprintf(buf, "Error: Trying to set incompatible komi %s" 1374 | " (!= 7.5)", str); 1375 | log_fmt_s('E', buf, NULL); 1376 | ret = buf; 1377 | } 1378 | else 1379 | ret = ""; 1380 | } 1381 | else if (strcmp(command,"debug") == 0) 1382 | ret = debug(pos); 1383 | else if (strcmp(command,"name") == 0) 1384 | ret = "michi-c"; 1385 | else if (strcmp(command,"version") == 0) 1386 | ret = "simple go program demo"; 1387 | else if (strcmp(command,"protocol_version") == 0) 1388 | ret = "2"; 1389 | else if (strcmp(command,"list_commands") == 0) 1390 | ret = known_commands; 1391 | else if (strcmp(command,"help") == 0) 1392 | ret = known_commands; 1393 | else if (strcmp(command,"known_command") == 0) { 1394 | char *command = strtok(NULL, " \t\n"); 1395 | if (strstr(known_commands,command) != NULL) 1396 | ret = "true"; 1397 | else 1398 | ret = "false"; 1399 | } 1400 | else if (strcmp(command,"quit") == 0) { 1401 | printf("=%s \n\n", cmdid); 1402 | log_hashtable_synthesis(); 1403 | break; 1404 | } 1405 | else { 1406 | sprintf(msg, "Warning: Ignoring unknown command - %s\n", command); 1407 | ret = msg; 1408 | } 1409 | print_pos(pos, stderr, owner_map); 1410 | finish_command: 1411 | if ((ret[0]=='E' && ret[1]=='r') 1412 | || ret[0]=='W') printf("\n?%s %s\n\n", cmdid, ret); 1413 | else printf("\n=%s %s\n\n", cmdid, ret); 1414 | fflush(stdout); 1415 | } 1416 | } 1417 | 1418 | int michi_console(int argc, char *argv[]) 1419 | { 1420 | char *command; 1421 | // Init global data 1422 | flog = fopen("michi.log", "w"); 1423 | setbuf(flog, NULL); // guarantees that log is unbuffered 1424 | make_pat3set(); 1425 | init_large_patterns(); 1426 | already_suggested = calloc(1, sizeof(Mark)); 1427 | mark1 = calloc(1, sizeof(Mark)); mark2 = calloc(1, sizeof(Mark)); 1428 | Position *pos = malloc(sizeof(Position)); 1429 | int *amaf_map=calloc(BOARDSIZE, sizeof(int)); 1430 | int *owner_map=calloc(BOARDSIZE, sizeof(int)); 1431 | empty_position(pos); 1432 | TreeNode *tree = new_tree_node(pos); 1433 | expand(tree); 1434 | slist_clear(allpoints); 1435 | FORALL_POINTS(pos,pt) 1436 | if (pos->color[pt] == '.') slist_push(allpoints,pt); 1437 | 1438 | // check if the user gave a seed for the random generator 1439 | if (argc == 3) { 1440 | sscanf(argv[1], "-z%u", &idum); 1441 | if (idum == 0) 1442 | idum = true_random_seed(); 1443 | command = argv[2]; 1444 | } 1445 | else 1446 | command = argv[1]; 1447 | 1448 | if (argc < 2) // default action 1449 | usage(); 1450 | else if (strcmp(command,"gtp") == 0) 1451 | gtp_io(); 1452 | else if (strcmp(command,"mcdebug") == 0) 1453 | printf("%lf\n", mcplayout(pos, amaf_map, owner_map, 1)); 1454 | else if (strcmp(command,"mcbenchmark") == 0) 1455 | printf("%lf\n", mcbenchmark(2000, pos, amaf_map, owner_map)); 1456 | else if (strcmp(command,"tsdebug") == 0) { 1457 | Point move=tree_search(tree, 100, amaf_map, 0); 1458 | fprintf(stderr, "move = %s\n", str_coord(move,buf)); 1459 | if (move != PASS_MOVE && move != RESIGN_MOVE) 1460 | play_move(&tree->pos,move); 1461 | print_pos(&tree->pos, stderr, NULL); 1462 | } 1463 | else 1464 | usage(); 1465 | free_tree(tree); free(pos); 1466 | free(amaf_map); free(owner_map); 1467 | free(already_suggested); free(mark1); free(mark2); 1468 | fclose(flog); 1469 | return 0; 1470 | } 1471 | -------------------------------------------------------------------------------- /michi.h: -------------------------------------------------------------------------------- 1 | // michi.h -- A minimalistic Go-playing engine (from Petr Baudis michi.py) 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | //========================= Definition of Data Structures ===================== 9 | 10 | // --------------------------- Board Constants -------------------------------- 11 | #define N 13 12 | #define W (N+2) 13 | #define BOARDSIZE ((N+1)*W+1) 14 | #define BOARD_IMIN (N+1) 15 | #define BOARD_IMAX (BOARDSIZE-N-1) 16 | #define LARGE_BOARDSIZE ((N+14)*(N+7)) 17 | #define BUFLEN 256 18 | #define MAX_GAME_LEN (N*N*3) 19 | #define SINGLEPT_OK 1 20 | #define SINGLEPT_NOK 0 21 | #define TWOLIBS_TEST 1 22 | #define TWOLIBS_TEST_NO 0 23 | #define TWOLIBS_EDGE_ONLY 1 24 | // ---------------------------- MCTS Constants -------------------------------- 25 | #define N_SIMS 1400 26 | #define RAVE_EQUIV 3500 27 | #define EXPAND_VISITS 8 28 | #define PRIOR_EVEN 10 // should be even number; 0.5 prior 29 | #define PRIOR_SELFATARI 10 // negative prior 30 | #define PRIOR_CAPTURE_ONE 15 31 | #define PRIOR_CAPTURE_MANY 30 32 | #define PRIOR_PAT3 10 33 | #define PRIOR_LARGEPATTERN 100 // most moves have relatively small probability 34 | extern int PRIOR_CFG[]; // priors for moves in cfg dist. 1, 2, 3 35 | #define LEN_PRIOR_CFG (sizeof(PRIOR_CFG)/sizeof(int)) 36 | #define PRIOR_EMPTYAREA 10 37 | #define REPORT_PERIOD 200 38 | #define PROB_HEURISTIC_CAPTURE 0.9 // probability of heuristic suggestions 39 | #define PROB_HEURISTIC_PAT3 0.95 // being taken in playout 40 | #define PROB_SSAREJECT 0.9 // prob of rejecting suggested self-atari in playout 41 | #define PROB_RSAREJECT 0.5 // prob of rejecting random self-atari in playout 42 | // this is lower than above to allow nakade 43 | #define RESIGN_THRES 0.2 44 | #define FASTPLAY20_THRES 0.8 //if at 20% playouts winrate is >this, stop reading 45 | #define FASTPLAY5_THRES 0.95 //if at 5% playouts winrate is >this, stop reading 46 | 47 | //------------------------------- Data Structures ----------------------------- 48 | typedef unsigned char Byte; 49 | typedef unsigned int Info; 50 | typedef unsigned int Point; 51 | typedef unsigned long long ZobristHash; 52 | typedef Info* Slist; 53 | typedef enum {PASS_MOVE, RESIGN_MOVE, COMPUTER_BLACK, COMPUTER_WHITE} Code; 54 | 55 | typedef struct { // ---------------------- Go Position ------------------------ 56 | // Given a board of size NxN (N=9, 19, ...), we represent the position 57 | // as a (N+1)*(N+2)+1 string, with '.' (empty), 'X' (to-play player) 58 | // 'x' (other player), and whitespace (off-board border to make rules 59 | // implementation easier). Coordinates are just indices in this string. 60 | char color[BOARDSIZE]; // string that hold the state of the board 61 | Byte env4[BOARDSIZE]; // color encoding for the 4 neighbors 62 | Byte env4d[BOARDSIZE]; // color encoding for the 4 diagonal neighbors 63 | int n; // move number 64 | Point ko, ko_old; // position of the ko (0 if no ko) 65 | Point last, last2, last3; // position of the last move and the move before 66 | float komi; // komi for the game 67 | char cap; // number of stones captured by the 'x' player 68 | char capX; // number of stones captured by the 'X' player 69 | } Position; // Go position 70 | 71 | typedef struct tree_node { // ------------ Monte-Carlo tree node -------------- 72 | int v; // number of visits 73 | int w; // number of wins(expected reward is w/v) 74 | int pv; // pv, pw are prior values 75 | int pw; // (node value = w/v + pw/pv) 76 | int av; // av, aw are amaf values ("all moves as first"), 77 | int aw; // used for the RAVE tree policy) 78 | int nchildren; // number of children 79 | Position pos; 80 | struct tree_node **children; 81 | } TreeNode; // Monte-Carlo tree node 82 | 83 | typedef struct { 84 | int value; 85 | int in_use; 86 | int mark[BOARDSIZE]; 87 | } Mark; 88 | 89 | // -------------------------------- Global Data ------------------------------- 90 | extern Byte bit[8]; 91 | extern Byte pat3set[8192]; 92 | extern int npat3; 93 | extern Byte *pat3set_p[15]; 94 | extern Mark *already_suggested, *mark1, *mark2; 95 | extern unsigned int idum; 96 | extern FILE *flog; // FILE to log messages 97 | extern int c1,c2; // counters for messages 98 | 99 | //================================== Code ===================================== 100 | //-------------------------- Functions in debug.c ----------------------------- 101 | void log_fmt_i(char type, const char *msg, int n); 102 | void log_fmt_p(char type, const char *msg, Point i); 103 | void log_fmt_s(char type, const char *msg, const char *s); 104 | char* debug(Position *pos); 105 | //-------------------------- Functions in michi.c ----------------------------- 106 | void dump_subtree(TreeNode *node,double thres,char *indent,FILE *f,int recurse); 107 | int fix_atari(Position *pos, Point pt, int singlept_ok 108 | , int twolib_test, int twolib_edgeonly, Slist moves, Slist sizes); 109 | int gen_playout_moves_capture(Position *pos, Slist heuristic_set, float prob, 110 | int expensive_ok, Slist moves, Slist sizes); 111 | int gen_playout_moves_pat3(Position *pos, Slist heuristic_set, float prob, 112 | Slist moves); 113 | void make_list_last_moves_neighbors(Position *pos, Slist points); 114 | double mcplayout(Position *pos, int amaf_map[], int owner_map[], int disp); 115 | Point parse_coord(char *s); 116 | char* play_move(Position *pos, Point pt); 117 | char* pass_move(Position *pos); 118 | void ppoint(Point pt); 119 | void print_pos(Position *pos, FILE *f, int *owner_map); 120 | void print_tree_summary(TreeNode *tree, int sims, FILE *f); 121 | char* slist_str_as_point(Slist l); 122 | char* str_coord(Point pt, char str[5]); 123 | //------------------------- Functions in patterns.c --------------------------- 124 | void make_pat3set(void); 125 | char* make_list_pat3_matching(Position *pos, Point pt); 126 | char* make_list_pat_matching(Point pt, int verbose); 127 | void init_large_patterns(void); 128 | void copy_to_large_board(Position *pos); 129 | void log_hashtable_synthesis(); 130 | double large_pattern_probability(Point pt); 131 | //-------------------------- Definition of Useful Macros ---------------------- 132 | #ifndef _MSC_VER 133 | #define __INLINE__ static inline 134 | #else 135 | #define __INLINE__ static __inline 136 | #endif 137 | #define FORALL_POINTS(pos,i) for(Point i=BOARD_IMIN ; i0 ; _k--) { \ 148 | int _tmp=random_int(_k); SWAP(T, l[_k], l[_tmp]); \ 149 | } 150 | 151 | //------------ Useful utility functions (inlined for performance) ------------- 152 | // Quick and Dirty random generator (32 bits Linear Congruential Generator) 153 | // Ref: Numerical Recipes in C (W.H. Press & al), 2nd Ed page 284 154 | __INLINE__ unsigned int qdrandom(void) {idum=(1664525*idum)+1013904223; return idum;} 155 | __INLINE__ unsigned int random_int(int n) /* random int between 0 and n-1 */ \ 156 | {unsigned long long r=qdrandom(); return (r*n)>>32;} 157 | 158 | // Go programs manipulates lists or sets of (small) integers a lot. There are 159 | // many possible implementations that differ by the performance of the various 160 | // operations we need to perform on these data structures. 161 | // insert, remove, enumerate elements, test "is in ?", make the set empty ... 162 | // 163 | // Here are 2 simple implementations of sets 164 | // * Slist is a simple list implementation as integer arrays (not sorted) 165 | // - it is slow for finding or inserting without dupplicate except for very 166 | // small sets. 167 | // - but fast to enumerate elements or insert when there is no need to check 168 | // for dupplicates 169 | // * Mark 170 | // - it is very fast for insert, remove elements, making the set empty or to 171 | // test "is in ?" 172 | // - but slow if we need to enumerate all the elements 173 | // These two representations are necessary for performance even with the goal 174 | // of brevity that is a priority for michi (actually the code is very short). 175 | // 176 | // Note: the pat3_set in patterns.c is a representation of a set as bitfield. 177 | 178 | __INLINE__ int slist_size(Slist l) {return l[0];} 179 | __INLINE__ void slist_clear(Slist l) {l[0]=0;} 180 | __INLINE__ void slist_push(Slist l, Info item) {l[l[0]+1]=item;l[0]++;} 181 | __INLINE__ void slist_range(Slist l,int n) 182 | {l[0]=n; for (int k=0;kin_use = 1; m->value++;} 204 | __INLINE__ void mark_release(Mark *m) {m->in_use = 0;} // for debugging 205 | __INLINE__ void mark(Mark *m, Info i) {m->mark[i] = m->value;} 206 | __INLINE__ int is_marked(Mark *m, Info i) {return m->mark[i] == m->value;} 207 | 208 | // Pattern matching 209 | __INLINE__ int pat3_match(Position *pos, Point pt) 210 | { 211 | int env8=(pos->env4d[pt] << 8) + pos->env4[pt], q=env8 >> 3, r=env8 & 7; 212 | return (pat3set[q] & bit[r]) != 0; 213 | } 214 | -------------------------------------------------------------------------------- /patterns.c: -------------------------------------------------------------------------------- 1 | // patterns.c -- Routines for 3x3 patterns and large patterns for michi program 2 | // 3 | // (c) 2015 Denis Blumstein Petr Baudis 4 | // MIT licence (i.e. almost public domain) 5 | #include "michi.h" 6 | 7 | // There are two types of patterns used in michi : 8 | // 9 | // - 3x3 patterns : given the color of the 8 neighbors of the central point 10 | // (4 neighbors and 4 diagonal neighbors) the pat3_match() function returns 11 | // the answer (yes/no) to the question : 12 | // Is there a 3x3 pattern matching at this point ? 13 | // The recognized patterns are defined by the table pat3_src[] below 14 | // 15 | // - Large patterns : given the color of the points in a certain neighborhood 16 | // of the central point, the large_pattern_probability() function returns the 17 | // probability of play at this position. 18 | // This probability is used to bias the search in the MCTS tree. 19 | // 20 | // The point neighborhoods are defined in [4] (see references in michi.c). 21 | // They are symetric under reflexions and rotations of the board. For each 22 | // point, 12 neighborhoods of increasing size are considered, each 23 | // neighborhood includes all the neighborhoods of lesser size. 24 | // In the program, the neighborhoods are defined by the 2 tables : 25 | // pat_gridcular_seq and pat_gridcular_size. These tables are compiled into 26 | // the pat_gridcular_seq1d array. 27 | 28 | // See below for more details on the pattern code. 29 | // 30 | // ================================ 3x3 patterns ============================== 31 | // 32 | // 1 bit is sufficient to store the fact that a pattern matches for a given 33 | // configuration of the 8 neighbors color. This configuration can be encoded 34 | // as a 16 bits integer called env8 (as 2 bits are sufficient to encode one of 35 | // the 4 possible colors of a point). So the set of 3x3 patterns that are 36 | // recognized is represented by an array of 65536 bits (or 8192 bytes). 37 | // 38 | // The used patterns are symetrical wrt the color so we only need a single array 39 | // (pat3set). A bit is set in this array when at least one pattern matches. 40 | // 41 | // These patterns are used in the random playouts, so pattern matching must be 42 | // very fast. To achieve best speed the 2 components of env8, env4 and env4d, 43 | // are stored as arrays in the struct Position and are incrementally updated by 44 | // the routines that modify the board put_stone() and remove_stone() 45 | // 46 | // Note: as the patterns are symetrical wrt to the color, we do not care to 47 | // reverse the color in env4 and env4d after each move. The env4[] and 48 | // env4d[] are defined in terms of BLACK and WHITE rather than 'X' or 'x' 49 | // 50 | Byte bit[8]={1,2,4,8,16,32,64,128}; 51 | Byte pat3set[8192]; // Set of the pat3 patterns (defined in pat3src below) 52 | int npat3; // Number of patterns in pat3src 53 | 54 | // A set of 3x3 patterns is defined by an array of ASCII strings (of length 10) 55 | // with the following encoding 56 | // char pat_src[][10]= { 57 | // "XOX" // X : one of BLACK or WHITE 58 | // "..." // O : the other color 59 | // "???", // . : EMPTY 60 | // "XO." // x : not X i.e O or EMPTY or OUT 61 | // ".X." // o : not O i.e X or EMPTY or OUT 62 | // "?.?", // ? : BLACK or WHITE or EMPTY or OUT 63 | // "###" // # : edge of the goban (out of board) 64 | // "###" 65 | // "###" // string that mark the end of input 66 | // } 67 | 68 | char pat3src[][10] = { 69 | "XOX" // 1- hane pattern - enclosing hane 70 | "..." 71 | "???", 72 | "XO." // 2- hane pattern - non-cutting hane 73 | "..." 74 | "?.?", 75 | "XO?" // 3- hane pattern - magari 76 | "X.." 77 | "x.?", 78 | //"XOO", // hane pattern - thin hane 79 | //"...", 80 | //"?.?", "X", - only for the X player 81 | ".O." // 4- generic pattern - katatsuke or diagonal attachment; 82 | //similar to magari 83 | "X.." 84 | "...", 85 | "XO?" // 5- cut1 pattern (kiri] - unprotected cut 86 | "O.o" 87 | "?o?", 88 | "XO?" // 6- cut1 pattern (kiri] - peeped cut 89 | "O.X" 90 | "???", 91 | "?X?" // 7- cut2 pattern (de] 92 | "O.O" 93 | "ooo", 94 | "OX?" // 8- cut keima 95 | "o.O" 96 | "???", 97 | "X.?" // 9- side pattern - chase 98 | "O.?" 99 | "##?", 100 | "OX?" // 10- side pattern - block side cut 101 | "X.O" 102 | "###", 103 | "?X?" // 11- side pattern - block side connection 104 | "x.O" 105 | "###", 106 | "?XO" // 12- side pattern - sagari 107 | "x.x" 108 | "###", 109 | "?OX" // 13- side pattern - cut 110 | "X.O" 111 | "###", 112 | "###" 113 | "###" 114 | "###" // Mark the end of the pattern list 115 | }; 116 | 117 | int nb=0; 118 | 119 | int code(char color, int p) 120 | { 121 | // Bits set for the 4 neighbours North(1), East(5), South(7), West(3) 122 | // or for the 4 diagonal neighbours NE(2), SE(8), SW(6), NE(0) 123 | int code_W[4] = { 0, 0, 0, 0}; // WHITE(O) 124 | int code_B[4] = {0x01, 0x02, 0x04, 0x08}; // BLACK(X) 125 | int code_E[4] = {0x10, 0x20, 0x40, 0x80}; // EMPTY(.) 126 | int code_O[4] = {0x11, 0x22, 0x44, 0x88}; // OUT (#) 127 | switch(color) { 128 | case 'O': return code_W[p]; 129 | case 'X': return code_B[p]; 130 | case '.': return code_E[p]; 131 | case '#': return code_O[p]; 132 | } 133 | return 0; // can't happen, but make compiler happy 134 | } 135 | 136 | int compute_code(char *src) 137 | // Compute a 16 bits code that completely describes the 3x3 environnement of a 138 | // given point. 139 | // Note: the low 8 bits describe the state of the 4 neighbours, 140 | // the high 8 bits describe the state of the 4 diagonal neighbors 141 | { 142 | // src 0 1 2 bits in env8 7 0 4 143 | int env8=0; // 3 4 5 bit 0=LSB 3 . 1 144 | // 6 7 8 6 2 5 145 | // Neighbours of the central point 146 | env8 |= code(src[1], 0); // North value given by src[1] in position 0 147 | env8 |= code(src[5], 1); // East value given by src[5] in position 1 148 | env8 |= code(src[7], 2); // South value given by src[7] in position 2 149 | env8 |= code(src[3], 3); // West value given by src[3] in position 3 150 | // Diagonal neighbours of the central point 151 | env8 |= code(src[2], 0)<<8;// North/East value given by src[2] in position 0 152 | env8 |= code(src[8], 1)<<8;// South/East value given by src[8] in position 1 153 | env8 |= code(src[6], 2)<<8;// South/West value given by src[6] in position 2 154 | env8 |= code(src[0], 3)<<8;// North/West value given by src[0] in position 3 155 | return env8; 156 | } 157 | 158 | int pat; 159 | void pat_wildexp(char *src, int i) 160 | // Expand wildchar in src[i] 161 | { 162 | char src1[10]; 163 | int env8; 164 | if ( i==9 ) { // all the positions in src are processed -- end of recursion 165 | env8 = compute_code(src); 166 | nb++; 167 | int q = env8 >> 3, r = env8 & 7; 168 | pat3set[q] |= bit[r]; // set the bit corresponding to env8 169 | return; 170 | } 171 | if (src[i] == '?') { 172 | strcpy(src1, src); 173 | src1[i] = 'X'; pat_wildexp(src1, i+1); 174 | src1[i] = 'O'; pat_wildexp(src1, i+1); 175 | src1[i] = '.'; pat_wildexp(src1, i+1); 176 | src1[i] = '#'; pat_wildexp(src1, i+1); 177 | } 178 | else if (src[i] == 'x') { 179 | strcpy(src1, src); 180 | src1[i]='O'; pat_wildexp(src1, i+1); 181 | src1[i]='.'; pat_wildexp(src1, i+1); 182 | src1[i]='#'; pat_wildexp(src1, i+1); 183 | } 184 | else if (src[i] == 'o') { 185 | strcpy(src1, src); 186 | src1[i]='X'; pat_wildexp(src1, i+1); 187 | src1[i]='.'; pat_wildexp(src1, i+1); 188 | src1[i]='#'; pat_wildexp(src1, i+1); 189 | } 190 | else 191 | pat_wildexp(src, i+1); 192 | } 193 | 194 | char *swapcolor(char *src) 195 | { 196 | for (int i=0 ; i<9 ; i++) { 197 | switch (src[i]) { 198 | case 'X': src[i] = 'O'; break; 199 | case 'O': src[i] = 'X'; break; 200 | case 'x': src[i] = 'o'; break; 201 | case 'o': src[i] = 'x'; break; 202 | } 203 | } 204 | return src; 205 | } 206 | 207 | char* horizflip(char *src) 208 | { 209 | SWAP(char, src[0], src[6]); 210 | SWAP(char, src[1], src[7]); 211 | SWAP(char, src[2], src[8]); 212 | return src; 213 | } 214 | 215 | char* vertflip(char *src) 216 | { 217 | SWAP(char, src[0], src[2]); 218 | SWAP(char, src[3], src[5]); 219 | SWAP(char, src[6], src[8]); 220 | return src; 221 | } 222 | 223 | char* rot90(char *src) 224 | { 225 | char t=src[0]; src[0]=src[2]; src[2]=src[8]; src[8]=src[6]; src[6]=t; 226 | t=src[1]; src[1]=src[5]; src[5]=src[7]; src[7]=src[3]; src[3]=t; 227 | return src; 228 | } 229 | 230 | void pat_enumerate3(char *src) 231 | { 232 | char src1[10]; 233 | pat_wildexp(src, 0); 234 | strcpy(src1,src); 235 | pat_wildexp(swapcolor(src1), 0); 236 | } 237 | 238 | void pat_enumerate2(char *src) 239 | { 240 | char src1[10]; 241 | pat_enumerate3(src); 242 | strcpy(src1, src); 243 | pat_enumerate3(horizflip(src1)); 244 | } 245 | 246 | void pat_enumerate1(char *src) 247 | { 248 | char src1[10]; 249 | pat_enumerate2(src); 250 | strcpy(src1, src); 251 | pat_enumerate2(vertflip(src1)); 252 | } 253 | 254 | void pat_enumerate(char *src) 255 | { 256 | char src1[10]; 257 | pat_enumerate1(src); 258 | strcpy(src1, src); 259 | pat_enumerate1(rot90(src1)); 260 | } 261 | 262 | void make_pat3set(void) 263 | { 264 | npat3 = sizeof(pat3src) / 10 - 1; 265 | if (npat3 > 13) { 266 | fprintf(stderr,"Error npat3 too big (%d)\n", npat3); 267 | exit(-1); 268 | } 269 | memset(pat3set,0,8192); 270 | for(int p=0 ; strcmp(pat3src[p], "#########") != 0 ; p++) { 271 | pat = p; 272 | pat_enumerate(pat3src[p]); 273 | } 274 | } 275 | 276 | // =============================== Large patterns ============================= 277 | // 278 | // The sizes of the neighborhoods are large (up to 141 points). Therefore the 279 | // exact configuration of the colors in the neighborhoods cannot be used for 280 | // pattern matching. Instead, a Zobrist signature (see [4] et [7]) of 64 bits 281 | // is computed from all points in the neighborhoods. Then this signature is 282 | // searched in a big hash table that contains the signatures of the patterns 283 | // read in the file patterns.spat. If successful, the search returns the 284 | // probability of the patterns. 285 | // 286 | // A large board with 7 layers of OUT of board points is used in order to avoid 287 | // tests during the computation of the signature for a given point. This large 288 | // board contains only the information on the color of points. It is build by 289 | // copy_to_large_board() which is called only once in the routine expand() while 290 | // pat_match() is called many times. 291 | // 292 | // With the large board it is an easy matter to compute the signature by 293 | // looping on all the points of the neighborhood thanks to the gridcular_seq1d 294 | // array which stores the displacements with respect to the central point. 295 | // 296 | // The hash table "patterns" is computed by init_patterns(). 297 | // It uses internal chaining with double hashing [9]. 298 | // The performance of this hash table is reported in the log file michi.log 299 | // after the compilation of patterns.spat file and at the end of the execution. 300 | // 301 | // ------------------------ Data Structures ----------------------------------- 302 | // Large pattern entry in the hash table 303 | typedef struct hash_t { 304 | ZobristHash key; // 64 bits Zobrist hash 305 | int id; // id of the pattern 306 | float prob; // probability of move triggered by the pattern 307 | } LargePat; 308 | #define KSIZE 25 // key size in bits 309 | #define LENGTH (1<>20) & KMASK, h2=primes[(key>>(20+KSIZE)) & 15], len=1; 382 | nsearchs++; 383 | while (patterns[h].key != key) { 384 | len++; 385 | if (patterns[h].key == 0) { 386 | sum_len_failure += len; 387 | return h; 388 | } 389 | h+=h2; if (h>LENGTH) h -= LENGTH; 390 | } 391 | nsuccess++; 392 | sum_len_success += len; 393 | return h; 394 | } 395 | 396 | int insert_pat(LargePat p) 397 | { 398 | int i = find_pat(p.key); 399 | if (patterns[i].key==0) { 400 | patterns[i] = p; 401 | return i; 402 | } 403 | else 404 | return FOUND; 405 | } 406 | 407 | LargePat build_pat(ZobristHash key, int id, float prob) 408 | { 409 | LargePat pat = {key, id, prob}; 410 | return pat; 411 | } 412 | 413 | // Code: ------------------- Zobrist signature computation -------------------- 414 | void init_stone_color(void) 415 | { 416 | memset(color,0,1024); 417 | color['.'] = 0; // 0: EMPTY 418 | color['#'] = color[' '] = 1; // 1: OUT 419 | color['O'] = color['x'] = 2; // 2: Other or 'x' 420 | color['X'] = 3; // 3: ours 421 | } 422 | 423 | void init_zobrist_hashdata(void) 424 | { 425 | for (int d=0 ; d<141 ; d++) {//d = displacement ... 426 | for (int c=0 ; c<4 ; c++) { 427 | unsigned int d1 = qdrandom(), d2=qdrandom(); 428 | ZobristHash ld1 = d1, ld2=d2; 429 | zobrist_hashdata[d][c] = (ld1<<32) + ld2; 430 | } 431 | } 432 | } 433 | 434 | ZobristHash zobrist_hash(char *pat) { 435 | int l = strlen(pat); 436 | ZobristHash k=0; 437 | for (int i=0 ; iid_max) 543 | id_max = id; 544 | } 545 | return id_max; 546 | rewind(f); 547 | } 548 | 549 | void load_prob_file(FILE *f) 550 | { 551 | float prob; 552 | int id,t1,t2; 553 | 554 | while (fgets(buf, 255, f) != NULL) { 555 | if (buf[0] == '#') continue; 556 | sscanf(buf,"%f %d %d (s:%d)", &prob, &t1, &t2, &id); 557 | probs[id] = prob; 558 | } 559 | } 560 | 561 | int load_spat_file(FILE *f) 562 | { 563 | int d, id, idmax=-1, len, lenmax=0, npats=0; 564 | char strpat[256], strperm[256]; 565 | ZobristHash k; 566 | int permutation[8][141]; 567 | 568 | // compute the 8 permutations of the gridcular positions corresponding 569 | // to the 8 possible reflexions or rotations of pattern 570 | gridcular_enumerate(permutation); 571 | 572 | while (fgets(buf, 255, f) != NULL) { 573 | if (buf[0] == '#') continue; 574 | sscanf(buf,"%d %d %s", &id, &d, strpat); 575 | npats++; 576 | len = strlen(strpat); 577 | if (len > lenmax) { 578 | lenmax = len; 579 | idmax = id; 580 | } 581 | if (id > idmax) 582 | idmax = id; 583 | for (int i=0 ; i< 8 ; i++) { 584 | permute(permutation, i, strpat, strperm); 585 | assert(permutation_OK(permutation)); 586 | k = zobrist_hash(strperm); 587 | LargePat pat = build_pat(k, id, probs[id]); 588 | insert_pat(pat); 589 | } 590 | } 591 | log_fmt_i('I', "read %d patterns", npats); 592 | log_fmt_i('I', "idmax = %d", idmax); 593 | sprintf(buf, "pattern length max = %d (found at %d)", lenmax, idmax); 594 | log_fmt_s('I', buf, NULL); 595 | large_patterns_loaded = 1; 596 | return npats; 597 | } 598 | 599 | // Code: --------------------------- Large Board ------------------------------ 600 | // Large board with border of width 7 (to easily compute neighborhood of points) 601 | 602 | void compute_large_coord(void) 603 | // Compute the position in the large board of any point on the board 604 | { 605 | int lpt, pt; 606 | for (int y=0 ; ycolor[pt] == ' ') continue; 624 | if (pos->color[pt] != large_board[large_coord[pt]]) 625 | return 0; 626 | } 627 | return 1; 628 | } 629 | 630 | void print_large_board(FILE *f) 631 | // Print visualization of the current large board position 632 | { 633 | int k=0; 634 | fprintf(f,"\n\n"); 635 | for (int row=0 ; rowcolor[pt++]; 649 | assert(large_board_OK(pos)); 650 | } 651 | 652 | // Code: ------------------------- Public functions --------------------------- 653 | void init_large_patterns(void) 654 | // Initialize all this stuff 655 | { 656 | FILE *fspat, *fprob; // Files containing large patterns 657 | 658 | // Initializations 659 | init_zobrist_hashdata(); 660 | init_stone_color(); 661 | init_gridcular(pat_gridcular_seq, pat_gridcular_seq1d); 662 | init_large_board(); 663 | 664 | // Load patterns data from files 665 | patterns = calloc(LENGTH, sizeof(LargePat)); 666 | log_fmt_s('I', "Loading pattern probs ...", NULL); 667 | fprob = fopen("patterns.prob", "r"); 668 | if (fprob == NULL) 669 | log_fmt_s('w', "Cannot load pattern file:%s","patterns.prob"); 670 | else { 671 | int id_max = max_pattern_id(fprob); 672 | log_fmt_i('I', "Reading patterns (id_max = %d)", id_max); 673 | probs = calloc(id_max+1, sizeof(float)); 674 | load_prob_file(fprob); 675 | fclose(fprob); 676 | 677 | log_fmt_s('I', "Loading pattern spatial dictionary ...", NULL); 678 | fspat = fopen("patterns.spat", "r"); 679 | if (fspat == NULL) 680 | log_fmt_s('w', "Warning: Cannot load pattern file:%s", 681 | "patterns.spat"); 682 | else { 683 | load_spat_file(fspat); 684 | fclose(fspat); 685 | } 686 | } 687 | if (fprob == NULL || fspat == NULL) { 688 | fprintf(stderr, "Warning: michi cannot load pattern files, " 689 | "It will be much weaker. " 690 | "Consider lowering EXPAND_VISITS %d->2\n", EXPAND_VISITS); 691 | } 692 | log_fmt_s('I', "=========== Hashtable initialization synthesis ==========", 693 | NULL); 694 | // reset the statistics after logging them 695 | log_hashtable_synthesis(); 696 | nsearchs = nsuccess = 0; 697 | sum_len_success=sum_len_failure=0.0; 698 | } 699 | 700 | double large_pattern_probability(Point pt) 701 | // return probability of large-scale pattern at coordinate pt. 702 | // Multiple progressively wider patterns may match a single coordinate, 703 | // we consider the largest one. 704 | { 705 | double prob=-1.0; 706 | int matched_len=0, non_matched_len=0; 707 | ZobristHash k=0; 708 | 709 | if (large_patterns_loaded) 710 | for (int s=1 ; s<13 ; s++) { 711 | int len = pat_gridcular_size[s]; 712 | k = update_zobrist_hash_at_point(large_coord[pt], s, k); 713 | int i = find_pat(k); 714 | if (patterns[i].key==k) { 715 | prob = patterns[i].prob; 716 | matched_len = len; 717 | } 718 | else if (matched_len < non_matched_len && non_matched_len < len) 719 | break; 720 | else 721 | non_matched_len = len; 722 | } 723 | return prob; 724 | } 725 | 726 | char* make_list_pat_matching(Point pt, int verbose) 727 | // Build the list of patterns that match at the point pt 728 | { 729 | ZobristHash k=0; 730 | int i; 731 | char id[16]; 732 | 733 | if (!large_patterns_loaded) return ""; 734 | 735 | buf[0] = 0; 736 | for (int s=1 ; s<13 ; s++) { 737 | k = update_zobrist_hash_at_point(large_coord[pt], s, k); 738 | i = find_pat(k); 739 | if (patterns[i].key == k) { 740 | if (verbose) 741 | sprintf(id,"%d(%.3f) ", patterns[i].id, patterns[i].prob); 742 | else 743 | sprintf(id,"%d ", patterns[i].id); 744 | strcat(buf, id); 745 | } 746 | } 747 | return buf; 748 | } 749 | 750 | void log_hashtable_synthesis() 751 | { 752 | double nkeys=0; 753 | for (int i=0 ; i