Autotest clients: Describe WFAIL column in README
[pachi.git] / gtp.c
blobf912650a6f103422fb31975bdb2cf973e0fa97e6
1 #define DEBUG
2 #include <assert.h>
3 #include <ctype.h>
4 #include <stdarg.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
9 #include "board.h"
10 #include "debug.h"
11 #include "engine.h"
12 #include "gtp.h"
13 #include "mq.h"
14 #include "uct/uct.h"
15 #include "version.h"
17 void
18 gtp_prefix(char prefix, int id)
20 if (id >= 0)
21 printf("%c%d ", prefix, id);
22 else
23 printf("%c ", prefix);
26 void
27 gtp_flush(void)
29 putchar('\n');
30 fflush(stdout);
33 void
34 gtp_output(char prefix, int id, va_list params)
36 gtp_prefix(prefix, id);
37 char *s;
38 while ((s = va_arg(params, char *))) {
39 fputs(s, stdout);
41 putchar('\n');
42 gtp_flush();
45 void
46 gtp_reply(int id, ...)
48 va_list params;
49 va_start(params, id);
50 gtp_output('=', id, params);
51 va_end(params);
54 void
55 gtp_error(int id, ...)
57 va_list params;
58 va_start(params, id);
59 gtp_output('?', id, params);
60 va_end(params);
64 /* XXX: THIS IS TOTALLY INSECURE!!!!
65 * Even basic input checking is missing. */
67 void
68 gtp_parse(struct board *board, struct engine *engine, char *buf)
70 #define next_tok(to_) \
71 to_ = next; \
72 next = next + strcspn(next, " \t\r\n"); \
73 if (*next) { \
74 *next = 0; next++; \
75 next += strspn(next, " \t\r\n"); \
78 if (strchr(buf, '#'))
79 *strchr(buf, '#') = 0;
81 char *cmd, *next = buf;
82 next_tok(cmd);
84 int id = -1;
85 if (isdigit(*cmd)) {
86 id = atoi(cmd);
87 next_tok(cmd);
90 if (!*cmd)
91 return;
93 if (!strcasecmp(cmd, "protocol_version")) {
94 gtp_reply(id, "2", NULL);
96 } else if (!strcasecmp(cmd, "name")) {
97 /* KGS hack */
98 gtp_reply(id, "Pachi ", engine->name, NULL);
100 } else if (!strcasecmp(cmd, "version")) {
101 gtp_reply(id, PACHI_VERSION, ": ", engine->comment, NULL);
103 /* TODO: known_command */
105 } else if (!strcasecmp(cmd, "list_commands")) {
106 gtp_reply(id, "protocol_version\nname\nversion\nlist_commands\nquit\nboardsize\nclear_board\nkomi\nplay\ngenmove\nset_free_handicap\nplace_free_handicap\nfinal_status_list\nkgs-chat", NULL);
108 } else if (!strcasecmp(cmd, "quit")) {
109 gtp_reply(id, NULL);
110 exit(0);
112 } else if (!strcasecmp(cmd, "boardsize")) {
113 char *arg;
114 next_tok(arg);
115 board_resize(board, atoi(arg));
117 gtp_reply(id, NULL);
119 } else if (!strcasecmp(cmd, "clear_board")) {
120 if (board->es) {
121 assert(engine->done_board_state);
122 engine->done_board_state(engine, board);
124 board_clear(board);
125 if (DEBUGL(1))
126 board_print(board, stderr);
127 gtp_reply(id, NULL);
129 } else if (!strcasecmp(cmd, "komi")) {
130 char *arg;
131 next_tok(arg);
132 sscanf(arg, "%f", &board->komi);
134 if (DEBUGL(1))
135 board_print(board, stderr);
136 gtp_reply(id, NULL);
138 } else if (!strcasecmp(cmd, "play")) {
139 struct move m;
141 char *arg;
142 next_tok(arg);
143 m.color = str2stone(arg);
144 next_tok(arg);
145 coord_t *c = str2coord(arg, board_size(board));
146 m.coord = *c; coord_done(c);
148 if (DEBUGL(1))
149 fprintf(stderr, "got move %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
150 engine->notify_play(engine, board, &m);
151 if (board_play(board, &m) < 0) {
152 if (DEBUGL(0)) {
153 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
154 board_print(board, stderr);
156 gtp_error(id, "illegal move", NULL);
157 } else {
158 if (DEBUGL(1))
159 board_print(board, stderr);
160 gtp_reply(id, NULL);
163 } else if (!strcasecmp(cmd, "genmove")) {
164 char *arg;
165 next_tok(arg);
166 enum stone color = str2stone(arg);
167 coord_t *c = engine->genmove(engine, board, color);
168 struct move m = { *c, color };
169 board_play(board, &m);
170 char *str = coord2str(*c, board);
171 if (DEBUGL(1))
172 fprintf(stderr, "playing move %s\n", str);
173 if (DEBUGL(1)) {
174 board_print_custom(board, stderr, engine->printhook);
176 gtp_reply(id, str, NULL);
177 free(str); coord_done(c);
179 } else if (!strcasecmp(cmd, "set_free_handicap")) {
180 struct move m;
181 m.color = S_BLACK;
183 char *arg;
184 next_tok(arg);
185 do {
186 coord_t *c = str2coord(arg, board_size(board));
187 m.coord = *c; coord_done(c);
188 if (DEBUGL(1))
189 fprintf(stderr, "setting handicap %d,%d\n", coord_x(m.coord, board), coord_y(m.coord, board));
191 if (board_play(board, &m) < 0) {
192 if (DEBUGL(0))
193 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
194 gtp_error(id, "illegal move", NULL);
196 board->handicap++;
197 next_tok(arg);
198 } while (*arg);
199 if (DEBUGL(1))
200 board_print(board, stderr);
201 gtp_reply(id, NULL);
203 /* TODO: Engine should choose free handicap; however, it tends to take
204 * overly long to think it all out, and unless it's clever its
205 * handicap stones won't be of much help. ;-) */
206 } else if (!strcasecmp(cmd, "place_free_handicap")
207 || !strcasecmp(cmd, "fixed_handicap")) {
208 char *arg;
209 next_tok(arg);
210 int stones = atoi(arg);
212 gtp_prefix('=', id);
213 board_handicap(board, stones, stdout);
214 if (DEBUGL(1))
215 board_print(board, stderr);
216 putchar('\n');
217 gtp_flush();
219 } else if (!strcasecmp(cmd, "final_score")) {
220 struct move_queue q = { .moves = 0 };
221 if (engine->dead_group_list)
222 engine->dead_group_list(engine, board, &q);
223 float score = board_official_score(board, &q);
224 char str[64];
225 if (DEBUGL(1))
226 fprintf(stderr, "counted score %.1f\n", score);
227 if (score == 0) {
228 gtp_reply(id, "0", NULL);
229 } else if (score > 0) {
230 snprintf(str, 64, "W+%.1f", score);
231 gtp_reply(id, str, NULL);
232 } else {
233 snprintf(str, 64, "B+%.1f", -score);
234 gtp_reply(id, str, NULL);
237 /* XXX: This is a huge hack. */
238 } else if (!strcasecmp(cmd, "final_status_list")) {
239 char *arg;
240 next_tok(arg);
241 struct move_queue q = { .moves = 0 };
242 if (engine->dead_group_list)
243 engine->dead_group_list(engine, board, &q);
244 /* else we return empty list - i.e. engine not supporting
245 * this assumes all stones alive at the game end. */
246 if (!strcasecmp(arg, "dead")) {
247 gtp_prefix('=', id);
248 for (int i = 0; i < q.moves; i++) {
249 foreach_in_group(board, q.move[i]) {
250 printf("%s ", coord2sstr(c, board));
251 } foreach_in_group_end;
252 putchar('\n');
254 if (!q.moves)
255 putchar('\n');
256 gtp_flush();
257 } else if (!strcasecmp(arg, "seki") || !strcasecmp(arg, "alive")) {
258 gtp_prefix('=', id);
259 bool printed_group = false;
260 foreach_point(board) { // foreach_group, effectively
261 group_t g = group_at(board, c);
262 if (!g || g != c) continue;
264 for (int i = 0; i < q.moves; i++) {
265 if (q.move[i] == g)
266 goto next_group;
268 foreach_in_group(board, g) {
269 printf("%s ", coord2sstr(c, board));
270 } foreach_in_group_end;
271 putchar('\n');
272 printed_group = true;
273 next_group:;
274 } foreach_point_end;
275 if (!printed_group)
276 putchar('\n');
277 gtp_flush();
278 } else {
279 gtp_error(id, "illegal status specifier", NULL);
282 /* Custom commands for handling UCT opening book */
283 } else if (!strcasecmp(cmd, "uct_genbook")) {
284 /* Board must be initialized properly, as if for genmove;
285 * makes sense only as 'uct_genbook b'. */
286 char *arg;
287 next_tok(arg);
288 enum stone color = str2stone(arg);
289 if (uct_genbook(engine, board, color))
290 gtp_reply(id, NULL);
291 else
292 gtp_error(id, "error generating book", NULL);
294 } else if (!strcasecmp(cmd, "uct_dumpbook")) {
295 char *arg;
296 next_tok(arg);
297 enum stone color = str2stone(arg);
298 uct_dumpbook(engine, board, color);
299 gtp_reply(id, NULL);
301 } else if (!strcasecmp(cmd, "kgs-chat")) {
302 char *loc;
303 next_tok(loc);
304 char *src;
305 next_tok(src);
306 char *msg;
307 next_tok(msg);
308 char *reply = NULL;
309 if (engine->chat)
310 reply = engine->chat(engine, board, msg);
311 if (reply)
312 gtp_reply(id, reply, NULL);
313 else
314 gtp_error(id, "unknown chat command", NULL);
316 } else {
317 gtp_error(id, "unknown command", NULL);
320 #undef next_tok