Start timer at play notification, not at genmove.
[pachi.git] / gtp.c
blob8397519631a9ad72c5402186f898b5eeba700f69
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"
16 #include "timeinfo.h"
18 void
19 gtp_prefix(char prefix, int id)
21 if (id >= 0)
22 printf("%c%d ", prefix, id);
23 else
24 printf("%c ", prefix);
27 void
28 gtp_flush(void)
30 putchar('\n');
31 fflush(stdout);
34 void
35 gtp_output(char prefix, int id, va_list params)
37 gtp_prefix(prefix, id);
38 char *s;
39 while ((s = va_arg(params, char *))) {
40 fputs(s, stdout);
42 putchar('\n');
43 gtp_flush();
46 void
47 gtp_reply(int id, ...)
49 va_list params;
50 va_start(params, id);
51 gtp_output('=', id, params);
52 va_end(params);
55 void
56 gtp_error(int id, ...)
58 va_list params;
59 va_start(params, id);
60 gtp_output('?', id, params);
61 va_end(params);
65 /* XXX: THIS IS TOTALLY INSECURE!!!!
66 * Even basic input checking is missing. */
68 void
69 gtp_parse(struct board *board, struct engine *engine, struct time_info *ti, char *buf)
71 #define next_tok(to_) \
72 to_ = next; \
73 next = next + strcspn(next, " \t\r\n"); \
74 if (*next) { \
75 *next = 0; next++; \
76 next += strspn(next, " \t\r\n"); \
79 if (strchr(buf, '#'))
80 *strchr(buf, '#') = 0;
82 char *cmd, *next = buf;
83 next_tok(cmd);
85 int id = -1;
86 if (isdigit(*cmd)) {
87 id = atoi(cmd);
88 next_tok(cmd);
91 if (!*cmd)
92 return;
94 if (!strcasecmp(cmd, "protocol_version")) {
95 gtp_reply(id, "2", NULL);
97 } else if (!strcasecmp(cmd, "name")) {
98 /* KGS hack */
99 gtp_reply(id, "Pachi ", engine->name, NULL);
101 } else if (!strcasecmp(cmd, "version")) {
102 gtp_reply(id, PACHI_VERSION, ": ", engine->comment, NULL);
104 /* TODO: known_command */
106 } else if (!strcasecmp(cmd, "list_commands")) {
107 gtp_reply(id, "protocol_version\nname\nversion\nlist_commands\nquit\nboardsize\nclear_board\nkomi\nplay\ngenmove\nkgs-genmove_cleanup\nset_free_handicap\nplace_free_handicap\nfinal_status_list\nkgs-chat", NULL);
109 } else if (!strcasecmp(cmd, "quit")) {
110 gtp_reply(id, NULL);
111 exit(0);
113 } else if (!strcasecmp(cmd, "boardsize")) {
114 char *arg;
115 next_tok(arg);
116 board_resize(board, atoi(arg));
118 gtp_reply(id, NULL);
120 } else if (!strcasecmp(cmd, "clear_board")) {
121 board_clear(board);
122 engine_reset = true;
123 if (DEBUGL(1))
124 board_print(board, stderr);
125 gtp_reply(id, NULL);
127 } else if (!strcasecmp(cmd, "komi")) {
128 char *arg;
129 next_tok(arg);
130 sscanf(arg, "%f", &board->komi);
132 if (DEBUGL(1))
133 board_print(board, stderr);
134 gtp_reply(id, NULL);
136 } else if (!strcasecmp(cmd, "play")) {
137 struct move m;
139 char *arg;
140 next_tok(arg);
141 m.color = str2stone(arg);
142 next_tok(arg);
143 coord_t *c = str2coord(arg, board_size(board));
144 m.coord = *c; coord_done(c);
145 char *reply = NULL;
147 if (DEBUGL(1))
148 fprintf(stderr, "got move %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
150 time_start_timer(&ti[stone_other(m.color)]); // This is where kgs starts our timer, not at coming genmove
151 if (engine->notify_play)
152 reply = engine->notify_play(engine, board, &m);
153 if (board_play(board, &m) < 0) {
154 if (DEBUGL(0)) {
155 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
156 board_print(board, stderr);
158 gtp_error(id, "illegal move", NULL);
159 } else {
160 if (DEBUGL(1))
161 board_print(board, stderr);
162 gtp_reply(id, reply, NULL);
165 } else if (!strcasecmp(cmd, "genmove") || !strcasecmp(cmd, "kgs-genmove_cleanup")) {
166 char *arg;
167 next_tok(arg);
168 enum stone color = str2stone(arg);
169 time_prepare_move(ti, board);
171 coord_t *c = engine->genmove(engine, board, ti, color, !strcasecmp(cmd, "kgs-genmove_cleanup"));
172 struct move m = { *c, color };
173 board_play(board, &m);
174 char *str = coord2str(*c, board);
175 if (DEBUGL(1))
176 fprintf(stderr, "playing move %s\n", str);
177 if (DEBUGL(1)) {
178 board_print_custom(board, stderr, engine->printhook);
180 gtp_reply(id, str, NULL);
181 free(str); coord_done(c);
183 } else if (!strcasecmp(cmd, "set_free_handicap")) {
184 struct move m;
185 m.color = S_BLACK;
187 char *arg;
188 next_tok(arg);
189 do {
190 coord_t *c = str2coord(arg, board_size(board));
191 m.coord = *c; coord_done(c);
192 if (DEBUGL(1))
193 fprintf(stderr, "setting handicap %d,%d\n", coord_x(m.coord, board), coord_y(m.coord, board));
195 if (board_play(board, &m) < 0) {
196 if (DEBUGL(0))
197 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
198 gtp_error(id, "illegal move", NULL);
200 board->handicap++;
201 next_tok(arg);
202 } while (*arg);
203 if (DEBUGL(1))
204 board_print(board, stderr);
205 gtp_reply(id, NULL);
207 /* TODO: Engine should choose free handicap; however, it tends to take
208 * overly long to think it all out, and unless it's clever its
209 * handicap stones won't be of much help. ;-) */
210 } else if (!strcasecmp(cmd, "place_free_handicap")
211 || !strcasecmp(cmd, "fixed_handicap")) {
212 char *arg;
213 next_tok(arg);
214 int stones = atoi(arg);
216 gtp_prefix('=', id);
217 board_handicap(board, stones, stdout);
218 if (DEBUGL(1))
219 board_print(board, stderr);
220 putchar('\n');
221 gtp_flush();
223 } else if (!strcasecmp(cmd, "final_score")) {
224 struct move_queue q = { .moves = 0 };
225 if (engine->dead_group_list)
226 engine->dead_group_list(engine, board, &q);
227 float score = board_official_score(board, &q);
228 char str[64];
229 if (DEBUGL(1))
230 fprintf(stderr, "counted score %.1f\n", score);
231 if (score == 0) {
232 gtp_reply(id, "0", NULL);
233 } else if (score > 0) {
234 snprintf(str, 64, "W+%.1f", score);
235 gtp_reply(id, str, NULL);
236 } else {
237 snprintf(str, 64, "B+%.1f", -score);
238 gtp_reply(id, str, NULL);
241 /* XXX: This is a huge hack. */
242 } else if (!strcasecmp(cmd, "final_status_list")) {
243 char *arg;
244 next_tok(arg);
245 struct move_queue q = { .moves = 0 };
246 if (engine->dead_group_list)
247 engine->dead_group_list(engine, board, &q);
248 /* else we return empty list - i.e. engine not supporting
249 * this assumes all stones alive at the game end. */
250 if (!strcasecmp(arg, "dead")) {
251 gtp_prefix('=', id);
252 for (int i = 0; i < q.moves; i++) {
253 foreach_in_group(board, q.move[i]) {
254 printf("%s ", coord2sstr(c, board));
255 } foreach_in_group_end;
256 putchar('\n');
258 if (!q.moves)
259 putchar('\n');
260 gtp_flush();
261 } else if (!strcasecmp(arg, "seki") || !strcasecmp(arg, "alive")) {
262 gtp_prefix('=', id);
263 bool printed_group = false;
264 foreach_point(board) { // foreach_group, effectively
265 group_t g = group_at(board, c);
266 if (!g || g != c) continue;
268 for (int i = 0; i < q.moves; i++) {
269 if (q.move[i] == g)
270 goto next_group;
272 foreach_in_group(board, g) {
273 printf("%s ", coord2sstr(c, board));
274 } foreach_in_group_end;
275 putchar('\n');
276 printed_group = true;
277 next_group:;
278 } foreach_point_end;
279 if (!printed_group)
280 putchar('\n');
281 gtp_flush();
282 } else {
283 gtp_error(id, "illegal status specifier", NULL);
286 /* Custom commands for handling UCT opening book */
287 } else if (!strcasecmp(cmd, "uct_genbook")) {
288 /* Board must be initialized properly, as if for genmove;
289 * makes sense only as 'uct_genbook b'. */
290 char *arg;
291 next_tok(arg);
292 enum stone color = str2stone(arg);
293 if (uct_genbook(engine, board, ti, color))
294 gtp_reply(id, NULL);
295 else
296 gtp_error(id, "error generating book", NULL);
298 } else if (!strcasecmp(cmd, "uct_dumpbook")) {
299 char *arg;
300 next_tok(arg);
301 enum stone color = str2stone(arg);
302 uct_dumpbook(engine, board, color);
303 gtp_reply(id, NULL);
305 } else if (!strcasecmp(cmd, "kgs-chat")) {
306 char *loc;
307 next_tok(loc);
308 char *src;
309 next_tok(src);
310 char *msg;
311 next_tok(msg);
312 char *reply = NULL;
313 if (engine->chat)
314 reply = engine->chat(engine, board, msg);
315 if (reply)
316 gtp_reply(id, reply, NULL);
317 else
318 gtp_error(id, "unknown chat command", NULL);
320 } else {
321 gtp_error(id, "unknown command", NULL);
324 #undef next_tok