18 gtp_prefix(char prefix
, int id
)
21 printf("%c%d ", prefix
, id
);
23 printf("%c ", prefix
);
34 gtp_output(char prefix
, int id
, va_list params
)
36 gtp_prefix(prefix
, id
);
38 while ((s
= va_arg(params
, char *))) {
46 gtp_reply(int id
, ...)
50 gtp_output('=', id
, params
);
55 gtp_error(int id
, ...)
59 gtp_output('?', id
, params
);
64 /* XXX: THIS IS TOTALLY INSECURE!!!!
65 * Even basic input checking is missing. */
68 gtp_parse(struct board
*board
, struct engine
*engine
, struct time_info
*ti
, char *buf
)
70 #define next_tok(to_) \
72 next = next + strcspn(next, " \t\r\n"); \
75 next += strspn(next, " \t\r\n"); \
79 *strchr(buf
, '#') = 0;
81 char *cmd
, *next
= buf
;
93 if (!strcasecmp(cmd
, "protocol_version")) {
94 gtp_reply(id
, "2", NULL
);
96 } else if (!strcasecmp(cmd
, "name")) {
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")) {
112 } else if (!strcasecmp(cmd
, "boardsize")) {
115 board_resize(board
, atoi(arg
));
119 } else if (!strcasecmp(cmd
, "clear_board")) {
123 board_print(board
, stderr
);
126 } else if (!strcasecmp(cmd
, "komi")) {
129 sscanf(arg
, "%f", &board
->komi
);
132 board_print(board
, stderr
);
135 } else if (!strcasecmp(cmd
, "play")) {
140 m
.color
= str2stone(arg
);
142 coord_t
*c
= str2coord(arg
, board_size(board
));
143 m
.coord
= *c
; coord_done(c
);
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) {
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
);
158 board_print(board
, stderr
);
159 gtp_reply(id
, reply
, NULL
);
162 } else if (!strcasecmp(cmd
, "genmove") || !strcasecmp(cmd
, "kgs-genmove_cleanup")) {
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
);
171 fprintf(stderr
, "playing move %s\n", str
);
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")) {
185 coord_t
*c
= str2coord(arg
, board_size(board
));
186 m
.coord
= *c
; coord_done(c
);
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) {
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
);
199 board_print(board
, stderr
);
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")) {
209 int stones
= atoi(arg
);
212 board_handicap(board
, stones
, stdout
);
214 board_print(board
, stderr
);
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
);
225 fprintf(stderr
, "counted score %.1f\n", score
);
227 gtp_reply(id
, "0", NULL
);
228 } else if (score
> 0) {
229 snprintf(str
, 64, "W+%.1f", score
);
230 gtp_reply(id
, str
, NULL
);
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")) {
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")) {
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
;
256 } else if (!strcasecmp(arg
, "seki") || !strcasecmp(arg
, "alive")) {
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
++) {
267 foreach_in_group(board
, g
) {
268 printf("%s ", coord2sstr(c
, board
));
269 } foreach_in_group_end
;
271 printed_group
= true;
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'. */
287 enum stone color
= str2stone(arg
);
288 if (uct_genbook(engine
, board
, ti
, color
))
291 gtp_error(id
, "error generating book", NULL
);
293 } else if (!strcasecmp(cmd
, "uct_dumpbook")) {
296 enum stone color
= str2stone(arg
);
297 uct_dumpbook(engine
, board
, color
);
300 } else if (!strcasecmp(cmd
, "kgs-chat")) {
309 reply
= engine
->chat(engine
, board
, msg
);
311 gtp_reply(id
, reply
, NULL
);
313 gtp_error(id
, "unknown chat command", NULL
);
316 gtp_error(id
, "unknown command", NULL
);