| 1 | /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\ |
| 2 | * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see * |
| 3 | * http://www.gnu.org/software/gnugo/ for more information. * |
| 4 | * * |
| 5 | * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, * |
| 6 | * 2008 and 2009 by the Free Software Foundation. * |
| 7 | * * |
| 8 | * This program is free software; you can redistribute it and/or * |
| 9 | * modify it under the terms of the GNU General Public License as * |
| 10 | * published by the Free Software Foundation - version 3 or * |
| 11 | * (at your option) any later version. * |
| 12 | * * |
| 13 | * This program is distributed in the hope that it will be useful, * |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
| 16 | * GNU General Public License in file COPYING for more details. * |
| 17 | * * |
| 18 | * You should have received a copy of the GNU General Public * |
| 19 | * License along with this program; if not, write to the Free * |
| 20 | * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * |
| 21 | * Boston, MA 02111, USA. * |
| 22 | \* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ |
| 23 | |
| 24 | #include "gnugo.h" |
| 25 | |
| 26 | #include <math.h> |
| 27 | #include <stdio.h> |
| 28 | #include <stdlib.h> |
| 29 | #include <string.h> |
| 30 | |
| 31 | #include "interface.h" |
| 32 | |
| 33 | #include "liberty.h" /* to get to the stats */ |
| 34 | |
| 35 | #include "gg_utils.h" |
| 36 | #include "random.h" |
| 37 | #include "sgftree.h" |
| 38 | |
| 39 | void play_solo(Gameinfo* gameinfo, int moves) |
| 40 | { |
| 41 | SGFTree sgftree; |
| 42 | int passes = 0; /* num. consecutive passes */ |
| 43 | float move_value; |
| 44 | double t1, t2; |
| 45 | int save_moves = moves; |
| 46 | |
| 47 | struct stats_data totalstats; |
| 48 | int total_owl_count = 0; |
| 49 | |
| 50 | /* It tends not to be very imaginative in the opening, |
| 51 | * so we scatter a few stones randomly to start with. |
| 52 | * We add two random numbers to reduce the probability |
| 53 | * of playing stones near the edge. |
| 54 | */ |
| 55 | |
| 56 | int n = 6 + 2 * gg_rand() % 5; |
| 57 | int i, j; |
| 58 | |
| 59 | komi = 5.5; |
| 60 | |
| 61 | sgftree_clear(&sgftree); |
| 62 | sgftreeCreateHeaderNode(&sgftree, board_size, komi, handicap); |
| 63 | sgf_write_header(sgftree.root, 1, get_random_seed(), 5.5, handicap, |
| 64 | get_level(), chinese_rules); |
| 65 | |
| 66 | /* Generate some random moves. */ |
| 67 | if (board_size > 6) { |
| 68 | do { |
| 69 | do { |
| 70 | i = (gg_rand() % 4) + (gg_rand() % (board_size - 4)); |
| 71 | j = (gg_rand() % 4) + (gg_rand() % (board_size - 4)); |
| 72 | } while (!is_allowed_move(POS(i, j), gameinfo->to_move)); |
| 73 | |
| 74 | gnugo_play_move(POS(i, j), gameinfo->to_move); |
| 75 | sgftreeAddPlay(&sgftree, gameinfo->to_move, i, j); |
| 76 | sgftreeAddComment(&sgftree, "random move"); |
| 77 | gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); |
| 78 | } while (--n > 0); |
| 79 | } |
| 80 | |
| 81 | t1 = gg_cputime(); |
| 82 | memset(&totalstats, '\0', sizeof(totalstats)); |
| 83 | while (passes < 2 && --moves >= 0) { |
| 84 | int move; |
| 85 | reset_owl_node_counter(); |
| 86 | move = genmove(gameinfo->to_move, &move_value, NULL); |
| 87 | |
| 88 | gnugo_play_move(move, gameinfo->to_move); |
| 89 | sgffile_add_debuginfo(sgftree.lastnode, move_value); |
| 90 | sgftreeAddPlay(&sgftree, gameinfo->to_move, I(move), J(move)); |
| 91 | sgffile_output(&sgftree); |
| 92 | gameinfo->to_move = OTHER_COLOR(gameinfo->to_move); |
| 93 | |
| 94 | if (move == PASS_MOVE) { |
| 95 | passes++; |
| 96 | printf("%s(%d): Pass\n", gameinfo->to_move == BLACK ? "Black" : "White", |
| 97 | movenum); |
| 98 | } else { |
| 99 | passes = 0; |
| 100 | gprintf("%s(%d): %1m\n", gameinfo->to_move == BLACK ? "Black" : "White", |
| 101 | movenum, move); |
| 102 | } |
| 103 | |
| 104 | totalstats.nodes += stats.nodes; |
| 105 | totalstats.read_result_entered += stats.read_result_entered; |
| 106 | totalstats.read_result_hits += stats.read_result_hits; |
| 107 | totalstats.trusted_read_result_hits += stats.trusted_read_result_hits; |
| 108 | total_owl_count += get_owl_node_counter(); |
| 109 | } |
| 110 | t2 = gg_cputime(); |
| 111 | |
| 112 | /* Two passes and it's over. (EMPTY == BOTH) */ |
| 113 | who_wins(EMPTY, stdout); |
| 114 | |
| 115 | { |
| 116 | float score = gnugo_estimate_score(NULL, NULL); |
| 117 | sgfWriteResult(sgftree.root, score, 1); |
| 118 | } |
| 119 | sgffile_output(&sgftree); |
| 120 | |
| 121 | printf("%10d moves played in %0.3f seconds\n", save_moves - moves, t2 - t1); |
| 122 | if (save_moves != moves) |
| 123 | printf("%10.3f seconds/move\n", (t2 - t1) / (save_moves - moves)); |
| 124 | |
| 125 | printf("%10d nodes\n", totalstats.nodes); |
| 126 | printf("%10d read results entered\n", totalstats.read_result_entered); |
| 127 | printf("%10d read result hits\n", totalstats.read_result_hits); |
| 128 | printf("%10d trusted read result hits\n", |
| 129 | totalstats.trusted_read_result_hits); |
| 130 | printf("%10d owl nodes\n", total_owl_count); |
| 131 | } |
| 132 | |
| 133 | /* ================================================================ */ |
| 134 | |
| 135 | /* |
| 136 | * Load SGF file and run genmove(). |
| 137 | */ |
| 138 | |
| 139 | void load_and_analyze_sgf_file(Gameinfo* gameinfo) |
| 140 | { |
| 141 | SGFTree sgftree; |
| 142 | int move; |
| 143 | int next; |
| 144 | float move_value; |
| 145 | |
| 146 | next = gameinfo->to_move; |
| 147 | sgftree = gameinfo->game_record; |
| 148 | |
| 149 | if (metamachine) |
| 150 | sgffile_begindump(&sgftree); |
| 151 | |
| 152 | move = genmove(next, &move_value, NULL); |
| 153 | |
| 154 | gprintf("%s move %1m\n", next == WHITE ? "white (O)" : "black (X)", move); |
| 155 | |
| 156 | if (metamachine) |
| 157 | sgffile_enddump(outfilename); |
| 158 | else { |
| 159 | gnugo_play_move(move, next); |
| 160 | sgftreeAddPlay(&sgftree, next, I(move), J(move)); |
| 161 | sgftreeAddComment(&sgftree, "load and analyze mode"); |
| 162 | sgffile_add_debuginfo(sgftree.lastnode, move_value); |
| 163 | sgffile_output(&sgftree); |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /* |
| 168 | * Load SGF file and score the game |
| 169 | * scoringmode: |
| 170 | * estimate - estimate territorial balance |
| 171 | * finish - finish the game by selfplaying and then count the score quickly |
| 172 | * aftermath - like 'finish' but also play out the aftermath in order to |
| 173 | * get an accurate score |
| 174 | */ |
| 175 | |
| 176 | #define ESTIMATE 0 |
| 177 | #define FINISH 1 |
| 178 | #define AFTERMATH 2 |
| 179 | |
| 180 | void load_and_score_sgf_file(SGFTree* tree, Gameinfo* gameinfo, |
| 181 | const char* scoringmode) |
| 182 | { |
| 183 | int move; |
| 184 | float move_value; |
| 185 | char* tempc = NULL; |
| 186 | char text[250]; |
| 187 | char winner; |
| 188 | int next; |
| 189 | int pass = 0; |
| 190 | int method; |
| 191 | float score; |
| 192 | SGFTree local_tree; |
| 193 | SGFTree* score_tree = tree; |
| 194 | |
| 195 | /* Default scoring method is ESTIMATE since it's fastest. */ |
| 196 | method = ESTIMATE; |
| 197 | if (strcmp(scoringmode, "finish") == 0) |
| 198 | method = FINISH; |
| 199 | else if (strcmp(scoringmode, "aftermath") == 0) |
| 200 | method = AFTERMATH; |
| 201 | |
| 202 | /* For aftermath scoring we compress the previous moves to a static |
| 203 | * board position in the output sgf. This helps a lot when debugging |
| 204 | * scoring mistakes. We don't do this for the finish method, |
| 205 | * however, since users may be better served by having GNU Go's |
| 206 | * selfplay added to the original game record. |
| 207 | */ |
| 208 | if (method == AFTERMATH) { |
| 209 | sgftree_clear(&local_tree); |
| 210 | /* Modify komi to compensate for captured stones. We start at a |
| 211 | * setup position and since there is no standard sgf property to |
| 212 | * tell the number of captured stones, a modified komi is the best |
| 213 | * available solution. |
| 214 | */ |
| 215 | sgftreeCreateHeaderNode(&local_tree, board_size, |
| 216 | komi + black_captured - white_captured, handicap); |
| 217 | sgffile_printboard(&local_tree); |
| 218 | sgfAddProperty(local_tree.lastnode, "PL", |
| 219 | gameinfo->to_move == WHITE ? "W" : "B"); |
| 220 | score_tree = &local_tree; |
| 221 | } |
| 222 | |
| 223 | next = gameinfo->to_move; |
| 224 | reset_engine(); |
| 225 | |
| 226 | /* Complete the game by selfplay for the finish and aftermath methods. */ |
| 227 | if (method != ESTIMATE) { |
| 228 | doing_scoring = 1; |
| 229 | while (pass < 2) { |
| 230 | move = genmove_conservative(next, &move_value); |
| 231 | if (move != PASS_MOVE) { |
| 232 | pass = 0; |
| 233 | gprintf("%d %s move %1m\n", movenum, |
| 234 | next == WHITE ? "white (O)" : "black (X)", move); |
| 235 | } else { |
| 236 | pass++; |
| 237 | gprintf("%d %s move PASS\n", movenum, |
| 238 | next == WHITE ? "white (O)" : "black (X)"); |
| 239 | } |
| 240 | play_move(move, next); |
| 241 | sgffile_add_debuginfo(score_tree->lastnode, move_value); |
| 242 | sgftreeAddPlay(score_tree, next, I(move), J(move)); |
| 243 | sgffile_output(score_tree); |
| 244 | next = OTHER_COLOR(next); |
| 245 | } |
| 246 | doing_scoring = 0; |
| 247 | } |
| 248 | |
| 249 | /* Calculate the score. */ |
| 250 | if (method == AFTERMATH) |
| 251 | score = aftermath_compute_score(next, score_tree); |
| 252 | else |
| 253 | score = gnugo_estimate_score(NULL, NULL); |
| 254 | |
| 255 | if (score < 0.0) { |
| 256 | sprintf(text, "Black wins by %1.1f points\n", -score); |
| 257 | winner = 'B'; |
| 258 | } else if (score > 0.0) { |
| 259 | sprintf(text, "White wins by %1.1f points\n", score); |
| 260 | winner = 'W'; |
| 261 | } else { |
| 262 | sprintf(text, "Jigo\n"); |
| 263 | winner = '0'; |
| 264 | } |
| 265 | fputs(text, stdout); |
| 266 | sgftreeAddComment(score_tree, text); |
| 267 | |
| 268 | /* For the finish and aftermath methods we compare the score with |
| 269 | * what's stored in the game record. |
| 270 | * |
| 271 | * FIXME: No comparison is made if the stored result was 0. Wins by |
| 272 | * time or forfeit are not handled either. |
| 273 | * |
| 274 | * FIXME: Does anybody actually care about this information? Just |
| 275 | * removing this piece of code is a tempting alternative. |
| 276 | */ |
| 277 | if (method != ESTIMATE && sgfGetCharProperty(tree->root, "RE", &tempc)) { |
| 278 | char dummy; |
| 279 | float result; |
| 280 | if (sscanf(tempc, "%1c%f", &dummy, &result) == 2) { |
| 281 | fprintf(stdout, "Result from file: %c+%1.1f\n", dummy, result); |
| 282 | fputs("GNU Go result and result from file are ", stdout); |
| 283 | if (result == fabs(score) && winner == dummy) |
| 284 | fputs("identical\n", stdout); |
| 285 | else |
| 286 | fputs("different\n", stdout); |
| 287 | |
| 288 | } else { |
| 289 | if (tempc[2] == 'R') { |
| 290 | fprintf(stdout, "Result from file: Resign\n"); |
| 291 | fputs("GNU Go result and result from file are ", stdout); |
| 292 | if (tempc[0] == winner) |
| 293 | fputs("identical\n", stdout); |
| 294 | else |
| 295 | fputs("different\n", stdout); |
| 296 | } |
| 297 | } |
| 298 | } |
| 299 | |
| 300 | if (method != ESTIMATE) |
| 301 | sgfWriteResult(score_tree->root, score, 1); |
| 302 | |
| 303 | sgffile_output(score_tree); |
| 304 | } |
| 305 | |
| 306 | /* |
| 307 | * Local Variables: |
| 308 | * tab-width: 4 |
| 309 | * c-basic-offset: 4 |
| 310 | * End: |
| 311 | */ |