t-unit: two_eyes test suite
[pachi.git] / t-unit / test.c
blob6363786f144716fafa2bd31a920574c375e7e3b6
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 "random.h"
14 #include "playout.h"
15 #include "timeinfo.h"
16 #include "playout/moggy.h"
17 #include "replay/replay.h"
18 #include "ownermap.h"
21 static bool board_printed;
23 static void
24 board_print_test(int level, struct board *b)
26 if (!DEBUGL(level) || board_printed)
27 return;
28 board_print(b, stderr);
29 board_printed = true;
32 static void
33 board_load(struct board *b, FILE *f, unsigned int size)
35 board_printed = false;
36 board_resize(b, size);
37 board_clear(b);
38 for (int y = size - 1; y >= 0; y--) {
39 char line[256];
40 if (!fgets(line, sizeof(line), f)) {
41 fprintf(stderr, "Premature EOF.\n");
42 exit(EXIT_FAILURE);
44 line[strlen(line) - 1] = 0; // chomp
45 if (strlen(line) != size * 2 - 1) {
46 fprintf(stderr, "Line not %d char long: %s\n", size * 2 - 1, line);
47 exit(EXIT_FAILURE);
49 for (unsigned int i = 0; i < size * 2; i++) {
50 enum stone s;
51 switch (line[i]) {
52 case '.': s = S_NONE; break;
53 case 'X': s = S_BLACK; break;
54 case 'O': s = S_WHITE; break;
55 default: fprintf(stderr, "Invalid stone '%c'\n", line[i]);
56 exit(EXIT_FAILURE);
58 i++;
59 if (line[i] != ' ' && i/2 < size - 1) {
60 fprintf(stderr, "No space after stone %i: '%c'\n", i/2 + 1, line[i]);
61 exit(EXIT_FAILURE);
63 if (s == S_NONE) continue;
64 struct move m = { .color = s, .coord = coord_xy(b, i/2 + 1, y + 1) };
65 if (board_play(b, &m) < 0) {
66 fprintf(stderr, "Failed to play %s %s\n",
67 stone2str(s), coord2sstr(m.coord, b));
68 board_print(b, stderr);
69 exit(EXIT_FAILURE);
73 int suicides = b->captures[S_BLACK] || b->captures[S_WHITE];
74 assert(!suicides);
77 static void
78 set_ko(struct board *b, char *arg)
80 assert(isalpha(*arg));
81 struct move last;
82 last.coord = str2scoord(arg, board_size(b));
83 last.color = board_at(b, last.coord);
84 assert(last.color == S_BLACK || last.color == S_WHITE);
85 b->last_move = last;
87 /* Sanity checks */
88 group_t g = group_at(b, last.coord);
89 assert(board_group_info(b, g).libs == 1);
90 assert(group_stone_count(b, g, 2) == 1);
91 coord_t lib = board_group_info(b, g).lib[0];
92 assert(board_is_eyelike(b, lib, last.color));
94 b->ko.coord = lib;
95 b->ko.color = stone_other(last.color);
99 static bool
100 test_sar(struct board *b, char *arg)
102 enum stone color = str2stone(arg);
103 arg += 2;
104 coord_t *cc = str2coord(arg, board_size(b));
105 coord_t c = *cc; coord_done(cc);
106 arg += strcspn(arg, " ") + 1;
107 int eres = atoi(arg);
109 board_print_test(2, b);
110 if (DEBUGL(1))
111 printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
113 assert(board_at(b, c) == S_NONE);
114 int rres = is_bad_selfatari(b, color, c);
116 if (rres == eres) {
117 if (DEBUGL(1))
118 printf("OK\n");
119 } else {
120 if (debug_level <= 2) {
121 board_print_test(0, b);
122 printf("sar %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
124 printf("FAILED (%d)\n", rres);
126 return rres == eres;
130 static bool
131 test_ladder(struct board *b, char *arg)
133 enum stone color = str2stone(arg);
134 arg += 2;
135 coord_t *cc = str2coord(arg, board_size(b));
136 coord_t c = *cc; coord_done(cc);
137 arg += strcspn(arg, " ") + 1;
138 int eres = atoi(arg);
140 board_print_test(2, b);
141 if (DEBUGL(1))
142 printf("ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
144 assert(board_at(b, c) == S_NONE);
145 group_t atari_neighbor = board_get_atari_neighbor(b, c, color);
146 assert(atari_neighbor);
147 int rres = is_ladder(b, c, atari_neighbor, true);
149 if (rres == eres) {
150 if (DEBUGL(1))
151 printf("OK\n");
152 } else {
153 if (debug_level <= 2) {
154 board_print_test(0, b);
155 printf("ladder %s %s %d...\t", stone2str(color), coord2sstr(c, b), eres);
157 printf("FAILED (%d)\n", rres);
160 return (rres == eres);
164 static bool
165 test_two_eyes(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("two_eyes %s %d...\t", coord2sstr(c, b), eres);
175 enum stone color = board_at(b, c);
176 assert(color == S_BLACK || color == S_WHITE);
177 int rres = dragon_is_safe(b, group_at(b, c), color);
179 if (rres == eres) {
180 if (DEBUGL(1))
181 printf("OK\n");
182 } else {
183 if (debug_level <= 2) {
184 board_print_test(0, b);
185 printf("two_eyes %s %d...\t", coord2sstr(c, b), eres);
187 printf("FAILED (%d)\n", rres);
189 return rres == eres;
193 static bool
194 test_moggy_moves(struct board *b, char *arg)
196 int runs = 1000;
198 coord_t *cc = str2coord(arg, board_size(b));
199 struct move last;
200 last.coord = *cc; coord_done(cc);
201 last.color = board_at(b, last.coord);
202 assert(last.color == S_BLACK || last.color == S_WHITE);
203 enum stone color = stone_other(last.color);
204 arg += strcspn(arg, " ") + 1;
206 b->last_move = last;
207 board_print(b, stderr); // Always print board so we see last move
209 char e_arg[128]; sprintf(e_arg, "runs=%i", runs);
210 struct engine *e = engine_replay_init(e_arg, b);
212 if (DEBUGL(1))
213 printf("moggy moves %s, %s to play. Sampling moves (%i runs)...\n\n",
214 coord2sstr(last.coord, b), stone2str(color), runs);
216 int played_[b->size2 + 2]; memset(played_, 0, sizeof(played_));
217 int *played = played_ + 2; // allow storing pass/resign
218 int most_played = 0;
219 replay_sample_moves(e, b, color, played, &most_played);
221 /* Show moves stats */
222 for (int k = most_played; k > 0; k--)
223 for (coord_t c = resign; c < b->size2; c++)
224 if (played[c] == k)
225 printf("%3s: %.2f%%\n", coord2str(c, b), (float)k * 100 / runs);
227 engine_done(e);
228 return true; // Not much of a unit test right now =)
231 #define board_empty(b) ((b)->flen == real_board_size(b) * real_board_size(b))
233 static void
234 pick_random_last_move(struct board *b, enum stone to_play)
236 if (board_empty(b))
237 return;
239 int base = fast_random(board_size2(b));
240 for (int i = base; i < base + board_size2(b); i++) {
241 coord_t c = i % board_size2(b);
242 if (board_at(b, c) == stone_other(to_play)) {
243 b->last_move.coord = c;
244 b->last_move.color = board_at(b, c);
245 break;
251 /* Syntax:
252 * moggy status (last_move) coord [coord...]
253 * Play number of random games starting from last_move
255 * moggy status coord [coord...]
256 * moggy status (b) coord [coord...]
257 * Black to play, pick random white last move
259 * moggy status (w) coord [coord...]
260 * White to play, pick random black last move
262 static bool
263 test_moggy_status(struct board *board, char *arg)
265 int games = 4000;
266 coord_t status_at[10];
267 int n = 0;
268 enum stone color = S_BLACK;
269 int pick_random = true; // Pick random last move for each game
271 while (*arg && *arg != '#') {
272 if (*arg == ' ' || *arg == '\t') { arg++; continue; }
273 if (!strncmp(arg, "(b)", 3))
274 color = S_BLACK;
275 else if (!strncmp(arg, "(w)", 3))
276 color = S_WHITE;
277 else if (*arg == '(') { /* Optional "(last_move)" argument */
278 arg++; assert(isalpha(*arg));
279 pick_random = false;
280 struct move last;
281 last.coord = str2scoord(arg, board_size(board));
282 last.color = board_at(board, last.coord);
283 assert(last.color == S_BLACK || last.color == S_WHITE);
284 color = stone_other(last.color);
285 board->last_move = last;
287 else {
288 assert(isalpha(*arg));
289 status_at[n++] = str2scoord(arg, board_size(board));
291 arg += strcspn(arg, " \t");
294 board_print(board, stderr);
295 if (DEBUGL(1)) {
296 printf("moggy status ");
297 for (int i = 0; i < n; i++)
298 printf("%s%s", coord2sstr(status_at[i], board), (i != n-1 ? " " : ""));
299 printf(", %s to play. Playing %i games %s...\n",
300 stone2str(color), games, (pick_random ? "(random last move) " : ""));
303 struct playout_policy *policy = playout_moggy_init(NULL, board, NULL);
304 struct playout_setup setup = { .gamelen = MAX_GAMELEN };
305 struct board_ownermap ownermap;
307 ownermap.playouts = 0;
308 ownermap.map = malloc2(board_size2(board) * sizeof(ownermap.map[0]));
309 memset(ownermap.map, 0, board_size2(board) * sizeof(ownermap.map[0]));
312 /* Get final status estimate after a number of moggy games */
313 int wr = 0;
314 double time_start = time_now();
315 for (int i = 0; i < games; i++) {
316 struct board b;
317 board_copy(&b, board);
318 if (pick_random)
319 pick_random_last_move(&b, color);
321 int score = play_random_game(&setup, &b, color, NULL, &ownermap, policy);
322 if (color == S_WHITE)
323 score = -score;
324 wr += (score > 0);
325 board_done_noalloc(&b);
327 double elapsed = time_now() - time_start;
328 printf("moggy status in %.1fs, %i games/s\n\n", elapsed, (int)((float)games / elapsed));
330 int wr_black = wr * 100 / games;
331 int wr_white = (games - wr) * 100 / games;
332 if (wr_black > wr_white)
333 printf("Winrate: [ black %i%% ] white %i%%\n\n", wr_black, wr_white);
334 else
335 printf("Winrate: black %i%% [ white %i%% ]\n\n", wr_black, wr_white);
337 board_print_ownermap(board, stderr, &ownermap);
339 for (int i = 0; i < n; i++) {
340 coord_t c = status_at[i];
341 enum stone color = (ownermap.map[c][S_BLACK] > ownermap.map[c][S_WHITE] ? S_BLACK : S_WHITE);
342 fprintf(stderr, "%3s owned by %s: %i%%\n",
343 coord2sstr(c, board), stone2str(color),
344 ownermap.map[c][color] * 100 / ownermap.playouts);
347 free(ownermap.map);
348 playout_policy_done(policy);
349 return true; // Not much of a unit test right now =)
352 bool board_undo_stress_test(struct board *orig, char *arg);
354 void
355 unittest(char *filename)
357 FILE *f = fopen(filename, "r");
358 if (!f) {
359 perror(filename);
360 exit(EXIT_FAILURE);
363 int total = 0;
364 int passed = 0;
365 int skipped = 0;
367 struct board *b = board_init(NULL);
368 b->komi = 7.5;
369 char line[256];
371 while (fgets(line, sizeof(line), f)) {
372 line[strlen(line) - 1] = 0; // chomp
373 switch (line[0]) {
374 case '%': printf("\n%s\n", line); continue;
375 case '!': printf("%s...\tSKIPPED\n", line); skipped++; continue;
376 case 0: continue;
378 if (!strncmp(line, "boardsize ", 10)) {
379 board_load(b, f, atoi(line + 10)); continue;
381 if (!strncmp(line, "ko ", 3)) {
382 set_ko(b, line + 3); continue;
385 total++;
386 if (!strncmp(line, "sar ", 4))
387 passed += test_sar(b, line + 4);
388 else if (!strncmp(line, "ladder ", 7))
389 passed += test_ladder(b, line + 7);
390 else if (!strncmp(line, "two_eyes ", 9))
391 passed += test_two_eyes(b, line + 9);
392 else if (!strncmp(line, "moggy moves ", 12))
393 passed += test_moggy_moves(b, line + 12);
394 else if (!strncmp(line, "moggy status ", 13))
395 passed += test_moggy_status(b, line + 13);
396 else if (!strncmp(line, "board_undo_stress_test", 22))
397 passed += board_undo_stress_test(b, line + 22);
398 else {
399 fprintf(stderr, "Syntax error: %s\n", line);
400 exit(EXIT_FAILURE);
404 fclose(f);
406 printf("\n\n----------- [ %i/%i tests passed (%i%%) ] -----------\n\n", passed, total, passed * 100 / total);
407 if (total == passed)
408 printf("\nAll tests PASSED");
409 else {
410 printf("\nSome tests FAILED\n");
411 exit(EXIT_FAILURE);
413 if (skipped > 0)
414 printf(", %d test(s) SKIPPED", skipped);
415 printf("\n");