19 gtp_prefix(char prefix
, int id
)
22 printf("%c%d ", prefix
, id
);
24 printf("%c ", prefix
);
35 gtp_output(char prefix
, int id
, va_list params
)
37 gtp_prefix(prefix
, id
);
39 while ((s
= va_arg(params
, char *))) {
47 gtp_reply(int id
, ...)
51 gtp_output('=', id
, params
);
56 gtp_error(int id
, ...)
60 gtp_output('?', id
, params
);
65 /* XXX: THIS IS TOTALLY INSECURE!!!!
66 * Even basic input checking is missing. */
69 gtp_parse(struct board
*board
, struct engine
*engine
, struct time_info
*ti
, char *buf
)
71 #define next_tok(to_) \
73 next = next + strcspn(next, " \t\r\n"); \
76 next += strspn(next, " \t\r\n"); \
80 *strchr(buf
, '#') = 0;
82 char *cmd
, *next
= buf
;
94 if (!strcasecmp(cmd
, "protocol_version")) {
95 gtp_reply(id
, "2", NULL
);
97 } else if (!strcasecmp(cmd
, "name")) {
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\n"
117 "kgs-genmove_cleanup\n"
118 "set_free_handicap\n"
119 "place_free_handicap\n"
120 "final_status_list\n"
124 "kgs-time_settings", NULL
);
126 } else if (!strcasecmp(cmd
, "quit")) {
130 } else if (!strcasecmp(cmd
, "boardsize")) {
133 board_resize(board
, atoi(arg
));
137 } else if (!strcasecmp(cmd
, "clear_board")) {
141 board_print(board
, stderr
);
144 } else if (!strcasecmp(cmd
, "komi")) {
147 sscanf(arg
, "%f", &board
->komi
);
150 board_print(board
, stderr
);
153 } else if (!strcasecmp(cmd
, "play")) {
158 m
.color
= str2stone(arg
);
160 coord_t
*c
= str2coord(arg
, board_size(board
));
161 m
.coord
= *c
; coord_done(c
);
165 fprintf(stderr
, "got move %d,%d,%d\n", m
.color
, coord_x(m
.coord
, board
), coord_y(m
.coord
, board
));
167 // This is where kgs starts our timer, not at coming genmove!
168 time_start_timer(&ti
[stone_other(m
.color
)]);
170 if (engine
->notify_play
)
171 reply
= engine
->notify_play(engine
, board
, &m
);
172 if (board_play(board
, &m
) < 0) {
174 fprintf(stderr
, "! ILLEGAL MOVE %d,%d,%d\n", m
.color
, coord_x(m
.coord
, board
), coord_y(m
.coord
, board
));
175 board_print(board
, stderr
);
177 gtp_error(id
, "illegal move", NULL
);
180 board_print(board
, stderr
);
181 gtp_reply(id
, reply
, NULL
);
184 } else if (!strcasecmp(cmd
, "genmove") || !strcasecmp(cmd
, "kgs-genmove_cleanup")) {
187 enum stone color
= str2stone(arg
);
189 coord_t
*c
= engine
->genmove(engine
, board
, &ti
[color
], color
, !strcasecmp(cmd
, "kgs-genmove_cleanup"));
190 struct move m
= { *c
, color
};
191 board_play(board
, &m
);
192 char *str
= coord2str(*c
, board
);
194 fprintf(stderr
, "playing move %s\n", str
);
196 board_print_custom(board
, stderr
, engine
->printhook
);
198 gtp_reply(id
, str
, NULL
);
199 free(str
); coord_done(c
);
201 /* Account for spent time. If our GTP peer keeps our clock, this will
202 * be overriden by next time_left GTP command properly. */
203 /* (XXX: Except if we pass to byoyomi and the peer doesn't, but that
204 * should be absolutely rare situation and we will just spend a little
205 * less time than we could on next few moves.) */
206 if (ti
[color
].period
!= TT_NULL
&& ti
[color
].dim
== TD_WALLTIME
)
207 time_sub(&ti
[color
], time_now() - ti
[color
].len
.t
.timer_start
);
209 } else if (!strcasecmp(cmd
, "set_free_handicap")) {
216 coord_t
*c
= str2coord(arg
, board_size(board
));
217 m
.coord
= *c
; coord_done(c
);
219 fprintf(stderr
, "setting handicap %d,%d\n", coord_x(m
.coord
, board
), coord_y(m
.coord
, board
));
221 if (board_play(board
, &m
) < 0) {
223 fprintf(stderr
, "! ILLEGAL MOVE %d,%d,%d\n", m
.color
, coord_x(m
.coord
, board
), coord_y(m
.coord
, board
));
224 gtp_error(id
, "illegal move", NULL
);
230 board_print(board
, stderr
);
233 /* TODO: Engine should choose free handicap; however, it tends to take
234 * overly long to think it all out, and unless it's clever its
235 * handicap stones won't be of much help. ;-) */
236 } else if (!strcasecmp(cmd
, "place_free_handicap")
237 || !strcasecmp(cmd
, "fixed_handicap")) {
240 int stones
= atoi(arg
);
243 board_handicap(board
, stones
, stdout
);
245 board_print(board
, stderr
);
249 } else if (!strcasecmp(cmd
, "final_score")) {
250 struct move_queue q
= { .moves
= 0 };
251 if (engine
->dead_group_list
)
252 engine
->dead_group_list(engine
, board
, &q
);
253 float score
= board_official_score(board
, &q
);
256 fprintf(stderr
, "counted score %.1f\n", score
);
258 gtp_reply(id
, "0", NULL
);
259 } else if (score
> 0) {
260 snprintf(str
, 64, "W+%.1f", score
);
261 gtp_reply(id
, str
, NULL
);
263 snprintf(str
, 64, "B+%.1f", -score
);
264 gtp_reply(id
, str
, NULL
);
267 /* XXX: This is a huge hack. */
268 } else if (!strcasecmp(cmd
, "final_status_list")) {
271 struct move_queue q
= { .moves
= 0 };
272 if (engine
->dead_group_list
)
273 engine
->dead_group_list(engine
, board
, &q
);
274 /* else we return empty list - i.e. engine not supporting
275 * this assumes all stones alive at the game end. */
276 if (!strcasecmp(arg
, "dead")) {
278 for (int i
= 0; i
< q
.moves
; i
++) {
279 foreach_in_group(board
, q
.move
[i
]) {
280 printf("%s ", coord2sstr(c
, board
));
281 } foreach_in_group_end
;
287 } else if (!strcasecmp(arg
, "seki") || !strcasecmp(arg
, "alive")) {
289 bool printed_group
= false;
290 foreach_point(board
) { // foreach_group, effectively
291 group_t g
= group_at(board
, c
);
292 if (!g
|| g
!= c
) continue;
294 for (int i
= 0; i
< q
.moves
; i
++) {
298 foreach_in_group(board
, g
) {
299 printf("%s ", coord2sstr(c
, board
));
300 } foreach_in_group_end
;
302 printed_group
= true;
309 gtp_error(id
, "illegal status specifier", NULL
);
312 /* Custom commands for handling UCT opening book */
313 } else if (!strcasecmp(cmd
, "uct_genbook")) {
314 /* Board must be initialized properly, as if for genmove;
315 * makes sense only as 'uct_genbook b'. */
318 enum stone color
= str2stone(arg
);
319 if (uct_genbook(engine
, board
, &ti
[color
], color
))
322 gtp_error(id
, "error generating book", NULL
);
324 } else if (!strcasecmp(cmd
, "uct_dumpbook")) {
327 enum stone color
= str2stone(arg
);
328 uct_dumpbook(engine
, board
, color
);
331 } else if (!strcasecmp(cmd
, "kgs-chat")) {
340 reply
= engine
->chat(engine
, board
, msg
);
342 gtp_reply(id
, reply
, NULL
);
344 gtp_error(id
, "unknown chat command", NULL
);
346 } else if (!strcasecmp(cmd
, "time_left")) {
349 enum stone color
= str2stone(arg
);
351 int time
= atoi(arg
);
353 int stones
= atoi(arg
);
354 if (!ti
[color
].ignore_gtp
) {
355 time_left(&ti
[color
], time
, stones
);
357 if (DEBUGL(2)) fprintf(stderr
, "ignored time info\n");
362 } else if (!strcasecmp(cmd
, "time_settings") || !strcasecmp(cmd
, "kgs-time_settings")) {
365 if (!strcasecmp(cmd
, "kgs-time_settings")) {
366 next_tok(time_system
);
368 time_system
= "canadian";
371 int main_time
= 0, byoyomi_time
= 0, byoyomi_stones
= 0, byoyomi_periods
= 0;
372 if (!strcasecmp(time_system
, "none")) {
374 } else if (!strcasecmp(time_system
, "absolute")) {
376 main_time
= atoi(arg
);
377 } else if (!strcasecmp(time_system
, "byoyomi")) {
379 main_time
= atoi(arg
);
381 byoyomi_time
= atoi(arg
);
383 byoyomi_periods
= atoi(arg
);
384 } else if (!strcasecmp(time_system
, "canadian")) {
386 main_time
= atoi(arg
);
388 byoyomi_time
= atoi(arg
);
390 byoyomi_stones
= atoi(arg
);
394 fprintf(stderr
, "time_settings %d %d/%d*%d\n",
395 main_time
, byoyomi_time
, byoyomi_stones
, byoyomi_periods
);
396 if (!ti
[S_BLACK
].ignore_gtp
) {
397 time_settings(&ti
[S_BLACK
], main_time
, byoyomi_time
, byoyomi_stones
, byoyomi_periods
);
398 ti
[S_WHITE
] = ti
[S_BLACK
];
400 if (DEBUGL(1)) fprintf(stderr
, "ignored time info\n");
406 gtp_error(id
, "unknown command", NULL
);