zzgo-profiled: Adjust rule to games -> -t
[pachi.git] / gtp.c
blob9daa254b6d915c2b122d0b101d4a7b04b203a440
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, struct time_info *ti, 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\nkgs-genmove_cleanup\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 board_clear(board);
121 engine_reset = true;
122 if (DEBUGL(1))
123 board_print(board, stderr);
124 gtp_reply(id, NULL);
126 } else if (!strcasecmp(cmd, "komi")) {
127 char *arg;
128 next_tok(arg);
129 sscanf(arg, "%f", &board->komi);
131 if (DEBUGL(1))
132 board_print(board, stderr);
133 gtp_reply(id, NULL);
135 } else if (!strcasecmp(cmd, "play")) {
136 struct move m;
138 char *arg;
139 next_tok(arg);
140 m.color = str2stone(arg);
141 next_tok(arg);
142 coord_t *c = str2coord(arg, board_size(board));
143 m.coord = *c; coord_done(c);
144 char *reply = NULL;
146 if (DEBUGL(1))
147 fprintf(stderr, "got move %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
148 if (engine->notify_play)
149 reply = engine->notify_play(engine, board, &m);
150 if (board_play(board, &m) < 0) {
151 if (DEBUGL(0)) {
152 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
153 board_print(board, stderr);
155 gtp_error(id, "illegal move", NULL);
156 } else {
157 if (DEBUGL(1))
158 board_print(board, stderr);
159 gtp_reply(id, reply, NULL);
162 } else if (!strcasecmp(cmd, "genmove") || !strcasecmp(cmd, "kgs-genmove_cleanup")) {
163 char *arg;
164 next_tok(arg);
165 enum stone color = str2stone(arg);
166 coord_t *c = engine->genmove(engine, board, ti, color, !strcasecmp(cmd, "kgs-genmove_cleanup"));
167 struct move m = { *c, color };
168 board_play(board, &m);
169 char *str = coord2str(*c, board);
170 if (DEBUGL(1))
171 fprintf(stderr, "playing move %s\n", str);
172 if (DEBUGL(1)) {
173 board_print_custom(board, stderr, engine->printhook);
175 gtp_reply(id, str, NULL);
176 free(str); coord_done(c);
178 } else if (!strcasecmp(cmd, "set_free_handicap")) {
179 struct move m;
180 m.color = S_BLACK;
182 char *arg;
183 next_tok(arg);
184 do {
185 coord_t *c = str2coord(arg, board_size(board));
186 m.coord = *c; coord_done(c);
187 if (DEBUGL(1))
188 fprintf(stderr, "setting handicap %d,%d\n", coord_x(m.coord, board), coord_y(m.coord, board));
190 if (board_play(board, &m) < 0) {
191 if (DEBUGL(0))
192 fprintf(stderr, "! ILLEGAL MOVE %d,%d,%d\n", m.color, coord_x(m.coord, board), coord_y(m.coord, board));
193 gtp_error(id, "illegal move", NULL);
195 board->handicap++;
196 next_tok(arg);
197 } while (*arg);
198 if (DEBUGL(1))
199 board_print(board, stderr);
200 gtp_reply(id, NULL);
202 /* TODO: Engine should choose free handicap; however, it tends to take
203 * overly long to think it all out, and unless it's clever its
204 * handicap stones won't be of much help. ;-) */
205 } else if (!strcasecmp(cmd, "place_free_handicap")
206 || !strcasecmp(cmd, "fixed_handicap")) {
207 char *arg;
208 next_tok(arg);
209 int stones = atoi(arg);
211 gtp_prefix('=', id);
212 board_handicap(board, stones, stdout);
213 if (DEBUGL(1))
214 board_print(board, stderr);
215 putchar('\n');
216 gtp_flush();
218 } else if (!strcasecmp(cmd, "final_score")) {
219 struct move_queue q = { .moves = 0 };
220 if (engine->dead_group_list)
221 engine->dead_group_list(engine, board, &q);
222 float score = board_official_score(board, &q);
223 char str[64];
224 if (DEBUGL(1))
225 fprintf(stderr, "counted score %.1f\n", score);
226 if (score == 0) {
227 gtp_reply(id, "0", NULL);
228 } else if (score > 0) {
229 snprintf(str, 64, "W+%.1f", score);
230 gtp_reply(id, str, NULL);
231 } else {
232 snprintf(str, 64, "B+%.1f", -score);
233 gtp_reply(id, str, NULL);
236 /* XXX: This is a huge hack. */
237 } else if (!strcasecmp(cmd, "final_status_list")) {
238 char *arg;
239 next_tok(arg);
240 struct move_queue q = { .moves = 0 };
241 if (engine->dead_group_list)
242 engine->dead_group_list(engine, board, &q);
243 /* else we return empty list - i.e. engine not supporting
244 * this assumes all stones alive at the game end. */
245 if (!strcasecmp(arg, "dead")) {
246 gtp_prefix('=', id);
247 for (int i = 0; i < q.moves; i++) {
248 foreach_in_group(board, q.move[i]) {
249 printf("%s ", coord2sstr(c, board));
250 } foreach_in_group_end;
251 putchar('\n');
253 if (!q.moves)
254 putchar('\n');
255 gtp_flush();
256 } else if (!strcasecmp(arg, "seki") || !strcasecmp(arg, "alive")) {
257 gtp_prefix('=', id);
258 bool printed_group = false;
259 foreach_point(board) { // foreach_group, effectively
260 group_t g = group_at(board, c);
261 if (!g || g != c) continue;
263 for (int i = 0; i < q.moves; i++) {
264 if (q.move[i] == g)
265 goto next_group;
267 foreach_in_group(board, g) {
268 printf("%s ", coord2sstr(c, board));
269 } foreach_in_group_end;
270 putchar('\n');
271 printed_group = true;
272 next_group:;
273 } foreach_point_end;
274 if (!printed_group)
275 putchar('\n');
276 gtp_flush();
277 } else {
278 gtp_error(id, "illegal status specifier", NULL);
281 /* Custom commands for handling UCT opening book */
282 } else if (!strcasecmp(cmd, "uct_genbook")) {
283 /* Board must be initialized properly, as if for genmove;
284 * makes sense only as 'uct_genbook b'. */
285 char *arg;
286 next_tok(arg);
287 enum stone color = str2stone(arg);
288 if (uct_genbook(engine, board, ti, color))
289 gtp_reply(id, NULL);
290 else
291 gtp_error(id, "error generating book", NULL);
293 } else if (!strcasecmp(cmd, "uct_dumpbook")) {
294 char *arg;
295 next_tok(arg);
296 enum stone color = str2stone(arg);
297 uct_dumpbook(engine, board, color);
298 gtp_reply(id, NULL);
300 } else if (!strcasecmp(cmd, "kgs-chat")) {
301 char *loc;
302 next_tok(loc);
303 char *src;
304 next_tok(src);
305 char *msg;
306 next_tok(msg);
307 char *reply = NULL;
308 if (engine->chat)
309 reply = engine->chat(engine, board, msg);
310 if (reply)
311 gtp_reply(id, reply, NULL);
312 else
313 gtp_error(id, "unknown chat command", NULL);
315 } else {
316 gtp_error(id, "unknown command", NULL);
319 #undef next_tok