t-unit: can_countercap
[pachi.git] / t-unit / test.c
blob3b2c3c12d6290f5694364d82200fa5e00b835d38
1 #define DEBUG
2 #include <ctype.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <assert.h>
8 #include "board.h"
9 #include "debug.h"
10 #include "tactics/selfatari.h"
11 #include "tactics/dragon.h"
12 #include "tactics/ladder.h"
13 #include "tactics/1lib.h"
14 #include "random.h"
15 #include "playout.h"
16 #include "timeinfo.h"
17 #include "playout/moggy.h"
18 #include "replay/replay.h"
19 #include "ownermap.h"
22 static bool board_printed;
24 static void
25 board_print_test(int level, struct board *b)
27 if (!DEBUGL(level) || board_printed)
28 return;
29 board_print(b, stderr);
30 board_printed = true;
33 static void
34 board_load(struct board *b, FILE *f, unsigned int size)
36 board_printed = false;
37 board_resize(b, size);
38 board_clear(b);
39 for (int y = size - 1; y >= 0; y--) {
40 char line[256];
41 if (!fgets(line, sizeof(line), f)) {
42 fprintf(stderr, "Premature EOF.\n");
43 exit(EXIT_FAILURE);
45 line[strlen(line) - 1] = 0; // chomp
46 if (strlen(line) != size * 2 - 1) {
47 fprintf(stderr, "Line not %d char long: %s\n", size * 2 - 1, line);
48 exit(EXIT_FAILURE);
50 for (unsigned int i = 0; i < size * 2; i++) {
51 enum stone s;
52 switch (line[i]) {
53 case '.': s = S_NONE; break;
54 case 'X': s = S_BLACK; break;
55 case 'O': s = S_WHITE; break;
56 default: fprintf(stderr, "Invalid stone '%c'\n", line[i]);
57 exit(EXIT_FAILURE);
59 i++;
60 if (line[i] != ' ' && i/2 < size - 1) {
61 fprintf(stderr, "No space after stone %i: '%c'\n", i/2 + 1, line[i]);
62 exit(EXIT_FAILURE);
64 if (s == S_NONE) continue;
65 struct move m = { .color = s, .coord = coord_xy(b, i/2 + 1, y + 1) };
66 if (board_play(b, &m) < 0) {
67 fprintf(stderr, "Failed to play %s %s\n",
68 stone2str(s), coord2sstr(m.coord, b));
69 board_print(b, stderr);
70 exit(EXIT_FAILURE);
74 int suicides = b->captures[S_BLACK] || b->captures[S_WHITE];
75 assert(!suicides);
78 static void
79 set_ko(struct board *b, char *arg)
81 assert(isalpha(*arg));
82 struct move last;
83 last.coord = str2scoord(arg, board_size(b));
84 last.color = board_at(b, last.coord);
85 assert(last.color == S_BLACK || last.color == S_WHITE);
86 b->last_move = last;
88 /* Sanity checks */
89 group_t g = group_at(b, last.coord);
90 assert(board_group_info(b, g).libs == 1);
91 assert(group_stone_count(b, g, 2) == 1);
92 coord_t lib = board_group_info(b, g).lib[0];
93 assert(board_is_eyelike(b, lib, last.color));
95 b->ko.coord = lib;
96 b->ko.color = stone_other(last.color);
100 static bool
101 test_sar(struct board *b, char *arg)
103 enum stone color = str2stone(arg);
104 arg += 2;
105 coord_t *cc = str2coord(arg, board_size(b));
106 coord_t c = *cc; coord_done(cc);
107 arg += strcspn(arg, " ") + 1;
108 int eres = atoi(arg);
110 board_print_test(2, b);
111 if (DEBUGL(1))
112 printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
114 assert(board_at(b, c) == S_NONE);
115 int rres = is_bad_selfatari(b, color, c);
117 if (rres == eres) {
118 if (DEBUGL(1))
119 printf("OK\n");
120 } else {
121 if (debug_level <= 2) {
122 board_print_test(0, b);
123 printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
125 printf("FAILED (%d)\n", rres);
127 return rres == eres;
131 static bool
132 test_ladder(struct board *b, char *arg)
134 enum stone color = str2stone(arg);
135 arg += 2;
136 coord_t *cc = str2coord(arg, board_size(b));
137 coord_t c = *cc; coord_done(cc);
138 arg += strcspn(arg, " ") + 1;
139 int eres = atoi(arg);
141 board_print_test(2, b);
142 if (DEBUGL(1))
143 printf("ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
145 assert(board_at(b, c) == S_NONE);
146 group_t atari_neighbor = board_get_atari_neighbor(b, c, color);
147 assert(atari_neighbor);
148 int rres = is_ladder(b, c, atari_neighbor, true);
150 if (rres == eres) {
151 if (DEBUGL(1))
152 printf("OK\n");
153 } else {
154 if (debug_level <= 2) {
155 board_print_test(0, b);
156 printf("ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
158 printf("FAILED (%d)\n", rres);
161 return (rres == eres);
164 static bool
165 test_can_countercapture(struct board *b, char *arg)
167 coord_t c = str2scoord(arg, board_size(b));
168 arg += strcspn(arg, " ") + 1;
169 int eres = atoi(arg);
171 board_print_test(2, b);
172 if (DEBUGL(1))
173 printf("can_countercap %s %d...\t", coord2sstr(c, b), eres);
175 enum stone color = board_at(b, c);
176 group_t g = group_at(b, c);
177 assert(color == S_BLACK || color == S_WHITE);
178 int rres = can_countercapture(b, color, g, color, NULL, 0);
180 if (rres == eres) {
181 if (DEBUGL(1))
182 printf("OK\n");
183 } else {
184 if (debug_level <= 2) {
185 board_print_test(0, b);
186 printf("can_countercap %s %d...\t", coord2sstr(c, b), eres);
188 printf("FAILED (%d)\n", rres);
190 return rres == eres;
194 static bool
195 test_two_eyes(struct board *b, char *arg)
197 coord_t c = str2scoord(arg, board_size(b));
198 arg += strcspn(arg, " ") + 1;
199 int eres = atoi(arg);
201 board_print_test(2, b);
202 if (DEBUGL(1))
203 printf("two_eyes %s %d...\t", coord2sstr(c, b), eres);
205 enum stone color = board_at(b, c);
206 assert(color == S_BLACK || color == S_WHITE);
207 int rres = dragon_is_safe(b, group_at(b, c), color);
209 if (rres == eres) {
210 if (DEBUGL(1))
211 printf("OK\n");
212 } else {
213 if (debug_level <= 2) {
214 board_print_test(0, b);
215 printf("two_eyes %s %d...\t", coord2sstr(c, b), eres);
217 printf("FAILED (%d)\n", rres);
219 return rres == eres;
223 static bool
224 test_moggy_moves(struct board *b, char *arg)
226 int runs = 1000;
228 coord_t *cc = str2coord(arg, board_size(b));
229 struct move last;
230 last.coord = *cc; coord_done(cc);
231 last.color = board_at(b, last.coord);
232 assert(last.color == S_BLACK || last.color == S_WHITE);
233 enum stone color = stone_other(last.color);
234 arg += strcspn(arg, " ") + 1;
236 b->last_move = last;
237 board_print(b, stderr); // Always print board so we see last move
239 char e_arg[128]; sprintf(e_arg, "runs=%i", runs);
240 struct engine *e = engine_replay_init(e_arg, b);
242 if (DEBUGL(1))
243 printf("moggy moves %s, %s to play. Sampling moves (%i runs)...\n\n",
244 coord2sstr(last.coord, b), stone2str(color), runs);
246 int played_[b->size2 + 2]; memset(played_, 0, sizeof(played_));
247 int *played = played_ + 2; // allow storing pass/resign
248 int most_played = 0;
249 replay_sample_moves(e, b, color, played, &most_played);
251 /* Show moves stats */
252 for (int k = most_played; k > 0; k--)
253 for (coord_t c = resign; c < b->size2; c++)
254 if (played[c] == k)
255 printf("%3s: %.2f%%\n", coord2str(c, b), (float)k * 100 / runs);
257 engine_done(e);
258 return true; // Not much of a unit test right now =)
261 #define board_empty(b) ((b)->flen == real_board_size(b) * real_board_size(b))
263 static void
264 pick_random_last_move(struct board *b, enum stone to_play)
266 if (board_empty(b))
267 return;
269 int base = fast_random(board_size2(b));
270 for (int i = base; i < base + board_size2(b); i++) {
271 coord_t c = i % board_size2(b);
272 if (board_at(b, c) == stone_other(to_play)) {
273 b->last_move.coord = c;
274 b->last_move.color = board_at(b, c);
275 break;
281 /* Syntax:
282 * moggy status (last_move) coord [coord...]
283 * Play number of random games starting from last_move
285 * moggy status coord [coord...]
286 * moggy status (b) coord [coord...]
287 * Black to play, pick random white last move
289 * moggy status (w) coord [coord...]
290 * White to play, pick random black last move
292 static bool
293 test_moggy_status(struct board *board, char *arg)
295 int games = 4000;
296 coord_t status_at[10];
297 int n = 0;
298 enum stone color = S_BLACK;
299 int pick_random = true; // Pick random last move for each game
301 while (*arg && *arg != '#') {
302 if (*arg == ' ' || *arg == '\t') { arg++; continue; }
303 if (!strncmp(arg, "(b)", 3))
304 color = S_BLACK;
305 else if (!strncmp(arg, "(w)", 3))
306 color = S_WHITE;
307 else if (*arg == '(') { /* Optional "(last_move)" argument */
308 arg++; assert(isalpha(*arg));
309 pick_random = false;
310 struct move last;
311 last.coord = str2scoord(arg, board_size(board));
312 last.color = board_at(board, last.coord);
313 assert(last.color == S_BLACK || last.color == S_WHITE);
314 color = stone_other(last.color);
315 board->last_move = last;
317 else {
318 assert(isalpha(*arg));
319 status_at[n++] = str2scoord(arg, board_size(board));
321 arg += strcspn(arg, " \t");
324 board_print(board, stderr);
325 if (DEBUGL(1)) {
326 printf("moggy status ");
327 for (int i = 0; i < n; i++)
328 printf("%s%s", coord2sstr(status_at[i], board), (i != n-1 ? " " : ""));
329 printf(", %s to play. Playing %i games %s...\n",
330 stone2str(color), games, (pick_random ? "(random last move) " : ""));
333 struct playout_policy *policy = playout_moggy_init(NULL, board, NULL);
334 struct playout_setup setup = { .gamelen = MAX_GAMELEN };
335 struct board_ownermap ownermap;
337 ownermap.playouts = 0;
338 ownermap.map = malloc2(board_size2(board) * sizeof(ownermap.map[0]));
339 memset(ownermap.map, 0, board_size2(board) * sizeof(ownermap.map[0]));
342 /* Get final status estimate after a number of moggy games */
343 int wr = 0;
344 double time_start = time_now();
345 for (int i = 0; i < games; i++) {
346 struct board b;
347 board_copy(&b, board);
348 if (pick_random)
349 pick_random_last_move(&b, color);
351 int score = play_random_game(&setup, &b, color, NULL, &ownermap, policy);
352 if (color == S_WHITE)
353 score = -score;
354 wr += (score > 0);
355 board_done_noalloc(&b);
357 double elapsed = time_now() - time_start;
358 printf("moggy status in %.1fs, %i games/s\n\n", elapsed, (int)((float)games / elapsed));
360 int wr_black = wr * 100 / games;
361 int wr_white = (games - wr) * 100 / games;
362 if (wr_black > wr_white)
363 printf("Winrate: [ black %i%% ] white %i%%\n\n", wr_black, wr_white);
364 else
365 printf("Winrate: black %i%% [ white %i%% ]\n\n", wr_black, wr_white);
367 board_print_ownermap(board, stderr, &ownermap);
369 for (int i = 0; i < n; i++) {
370 coord_t c = status_at[i];
371 enum stone color = (ownermap.map[c][S_BLACK] > ownermap.map[c][S_WHITE] ? S_BLACK : S_WHITE);
372 fprintf(stderr, "%3s owned by %s: %i%%\n",
373 coord2sstr(c, board), stone2str(color),
374 ownermap.map[c][color] * 100 / ownermap.playouts);
377 free(ownermap.map);
378 playout_policy_done(policy);
379 return true; // Not much of a unit test right now =)
382 bool board_undo_stress_test(struct board *orig, char *arg);
384 void
385 unittest(char *filename)
387 FILE *f = fopen(filename, "r");
388 if (!f) {
389 perror(filename);
390 exit(EXIT_FAILURE);
393 int total = 0;
394 int passed = 0;
395 int skipped = 0;
397 struct board *b = board_init(NULL);
398 b->komi = 7.5;
399 char line[256];
401 while (fgets(line, sizeof(line), f)) {
402 line[strlen(line) - 1] = 0; // chomp
403 switch (line[0]) {
404 case '%': printf("\n%s\n", line); continue;
405 case '!': printf("%s...\tSKIPPED\n", line); skipped++; continue;
406 case 0: continue;
408 if (!strncmp(line, "boardsize ", 10)) {
409 board_load(b, f, atoi(line + 10)); continue;
411 if (!strncmp(line, "ko ", 3)) {
412 set_ko(b, line + 3); continue;
415 total++;
416 if (!strncmp(line, "sar ", 4))
417 passed += test_sar(b, line + 4);
418 else if (!strncmp(line, "ladder ", 7))
419 passed += test_ladder(b, line + 7);
420 else if (!strncmp(line, "can_countercap ", 15))
421 passed += test_can_countercapture(b, line + 15);
422 else if (!strncmp(line, "two_eyes ", 9))
423 passed += test_two_eyes(b, line + 9);
424 else if (!strncmp(line, "moggy moves ", 12))
425 passed += test_moggy_moves(b, line + 12);
426 else if (!strncmp(line, "moggy status ", 13))
427 passed += test_moggy_status(b, line + 13);
428 else if (!strncmp(line, "board_undo_stress_test", 22))
429 passed += board_undo_stress_test(b, line + 22);
430 else {
431 fprintf(stderr, "Syntax error: %s\n", line);
432 exit(EXIT_FAILURE);
436 fclose(f);
438 printf("\n\n----------- [ %i/%i tests passed (%i%%) ] -----------\n\n", passed, total, passed * 100 / total);
439 if (total == passed)
440 printf("\nAll tests PASSED");
441 else {
442 printf("\nSome tests FAILED\n");
443 exit(EXIT_FAILURE);
445 if (skipped > 0)
446 printf(", %d test(s) SKIPPED", skipped);
447 printf("\n");