Fix PGN saving with annotations.
[cboard.git] / src / engine.c
blob8c85ed71071e413f31db5edbe272695298e987ca
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <limits.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <sys/stat.h>
28 #include <sys/time.h>
29 #include <fcntl.h>
30 #include <ctype.h>
31 #include <stdarg.h>
32 #include <errno.h>
33 #include <paths.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #include "chess.h"
40 #include "conf.h"
41 #include "misc.h"
42 #include "strings.h"
43 #include "window.h"
44 #include "engine.h"
46 #ifdef WITH_DMALLOC
47 #include <dmalloc.h>
48 #endif
50 void send_to_engine(const char *format, ...)
52 va_list ap;
53 int len;
54 char *line;
55 int try = 0;
57 va_start(ap, format);
58 #ifdef HAVE_VASPRINTF
59 len = vasprintf(&line, format, ap);
60 #else
61 line = Malloc(LINE_MAX);
62 len = vsnprintf(line, LINE_MAX, format, ap);
63 #endif
64 va_end(ap);
66 while (1) {
67 int n;
68 fd_set fds;
69 struct timeval tv;
71 FD_ZERO(&fds);
72 FD_SET(enginefd[1], &fds);
74 tv.tv_sec = 0;
75 tv.tv_usec = 0;
77 if ((n = select(enginefd[1] + 1, NULL, &fds, NULL, &tv)) > 0) {
78 if (FD_ISSET(enginefd[1], &fds)) {
79 n = write(enginefd[1], line, len);
81 if (n == -1) {
82 if (errno == EAGAIN)
83 continue;
85 if (kill(enginepid, 0) == -1) {
86 message(ERROR, ANYKEY, "Could not write to engine. "
87 "Process no longer exists.");
88 engine_initialized = 0;
89 //update_status_window(NULL);
90 return;
93 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
94 strerror(errno));
95 continue;
98 if (len != n) {
99 message(NULL, ANYKEY, "try #%i: write() error to engine. "
100 "Expected %i, got %i.", ++try, len, n);
101 continue;
104 break;
107 else {
108 /* timeout */
112 free(line);
113 return;
116 #ifndef UNIX98
117 /* From the Xboard and screen packages. */
118 static int get_pty(char *pty_name)
120 int i;
121 int fd;
123 for (i = 0; PTY_MAJOR[i]; i++) {
124 int n;
126 for (n = 0; PTY_MINOR[n]; n++) {
127 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
128 PTY_MINOR[n]);
130 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
131 continue;
133 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
134 PTY_MINOR[n]);
136 if (access(pty_name, R_OK | W_OK) == -1) {
137 close(fd);
138 continue;
141 return fd;
145 return -1;
147 #endif
149 static char **parseargs(char *str)
151 char **pptr, *s;
152 char arg[255];
153 int n = 0;
154 int quote = 0;
155 int lastchar = 0;
156 int i;
158 if (!str)
159 return NULL;
161 if (!(pptr = malloc(sizeof(char *))))
162 return NULL;
164 for (i = 0, s = str; *s; lastchar = *s++) {
165 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
166 quote = (quote) ? 0 : 1;
167 continue;
170 if (*s == ' ' && !quote) {
171 arg[i] = 0;
172 pptr = realloc(pptr, (n + 2) * sizeof(char *));
173 pptr[n++] = strdup(arg);
174 arg[0] = i = 0;
175 continue;
178 if ((i + 1) == sizeof(arg))
179 continue;
181 arg[i++] = *s;
184 arg[i] = 0;
186 if (arg[0]) {
187 pptr = realloc(pptr, (n + 2) * sizeof(char *));
188 pptr[n++] = strdup(arg);
191 pptr[n] = NULL;
192 return pptr;
195 /* Is this dangerous if pty permissions are wrong? */
196 pid_t init_chess_engine(char **args)
198 pid_t pid;
199 int from[2], to[2];
200 #ifndef UNIX98
201 char pty[FILENAME_MAX];
203 if ((to[1] = get_pty(pty)) == -1) {
204 errno = 0;
205 return -1;
207 #else
208 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
209 return -1;
212 if (grantpt(to[1]) == -1) {
213 return -1;
216 if (unlockpt(to[1]) == -1) {
217 return -1;
219 #endif
221 from[0] = to[1];
222 errno = 0;
224 #ifndef UNIX98
225 if ((to[0] = open(pty, O_RDWR | O_NOCTTY)) == -1)
226 #else
227 if ((to[0] = open(ptsname(to[1]), O_RDWR | O_NOCTTY)) == -1)
228 #endif
229 return -1;
231 from[1] = to[0];
232 errno = 0;
234 switch ((pid = fork())) {
235 case -1:
236 return -2;
237 case 0:
238 dup2(to[0], STDIN_FILENO);
239 dup2(from[1], STDOUT_FILENO);
240 close(to[0]);
241 close(to[1]);
242 close(from[0]);
243 close(from[1]);
244 dup2(STDOUT_FILENO, STDERR_FILENO);
245 execvp(args[0], args);
246 _exit(EXIT_FAILURE);
247 default:
248 break;
251 sleep(1);
253 if (kill(pid, 0) == -1)
254 return -2;
256 close(to[0]);
257 close(from[1]);
258 enginefd[0] = from[0];
259 enginefd[1] = to[1];
260 fcntl(enginefd[0], F_SETFL, O_NONBLOCK | O_DIRECT);
261 fcntl(enginefd[1], F_SETFL, O_NONBLOCK | O_DIRECT);
262 engine_initialized = 1;
263 return pid;
266 void set_engine_defaults()
270 void stop_engine()
272 if (!engine_initialized)
273 return;
275 SEND_TO_ENGINE("quit\n");
277 if (kill(enginepid, 0) != -1)
278 kill(enginepid, SIGTERM);
280 if (kill(enginepid, 0) != -1)
281 kill(enginepid, SIGKILL);
283 return;
286 int start_chess_engine()
288 char **args;
289 int i;
291 args = parseargs(config.engine_cmd);
292 enginepid = init_chess_engine(args);
294 switch (enginepid) {
295 case -1:
296 /* Pty allocation. */
297 message(ERROR, ANYKEY, "Could not allocate PTY");
298 break;
299 case -2:
300 /* Could not execute engine. */
301 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
302 break;
303 default:
304 break;
307 for (i = 0; args[i]; i++)
308 free(args[i]);
310 free(args);
312 if (enginepid > 0)
313 set_engine_defaults();
315 //update_status_window();
316 return enginepid;
319 /* Once the PGN parser has been well tested, validate_move() from the human
320 * move can disappear.
322 void parse_gnuchess_line(GAME *g, char *str)
324 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
325 int count;
327 /* Human move. Add it to the move history. */
328 if (sscanf(str, "%*d%*1[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m, &count) == 1) {
330 if (validate_move(g, m)) {
331 invalid_move(0, m);
332 return;
336 if (history_total(g->hp) == 0 && g->side == BLACK)
337 SET_FLAG(g->flags, GF_BLACK_OPENING);
339 history_add(g, p);
340 SET_FLAG(g->flags, GF_MODIFIED);
341 pgn_switch_turn(g);
342 str += count;
343 return;
346 /* Engine move. */
347 if (sscanf(str, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m,
348 &count) == 1) {
349 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
350 * need to convert them.
352 //FIXME
354 if ((p = pgn_a2a4tosan(g, m)) == NULL)
355 return;
359 if (validate_move(g, p)) {
360 invalid_move(0, p);
361 return;
365 if (history_total(g->hp) == 0 && g->side == BLACK)
366 SET_FLAG(g->flags, GF_BLACK_OPENING);
368 history_add(g, p);
369 SET_FLAG(g->flags, GF_MODIFIED);
370 pgn_switch_turn(g);
371 str += count;
373 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
374 //history_update_board(g, g.htotal); FIXME
375 RETURN;
378 RETURN;
381 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
382 //history_update_board(g); FIXME
383 RETURN;
386 /* Miscellaneous one-liners. */
388 /* The engine is now reading a FIFO. Dump what we need to it. */
389 // FIXME FEN
390 if (strncmp(str, "pgnload ", 8) == 0) {
391 if (save_pgn(config.fifo, 1, gindex)) {
392 oldhistorytotal = 0;
393 return;
395 else {
396 history_free(g->hp, g->hindex + 1);
397 CLEAR_FLAG(g->flags, GF_GAMEOVER);
398 SET_FLAG(g->flags, GF_MODIFIED);
401 set_engine_defaults();
402 return;
405 /* 'switch' command. */
406 if (strncmp(str, "White to move", 13) == 0) {
407 g->side = g->turn = WHITE;
408 RETURN;
410 else if (strncmp(str, "Black to move", 13) == 0) {
411 g->side = g->turn = BLACK;
412 RETURN;
415 /* Bad engine command or move. */
416 if (strncmp(str, "Illegal move: ", 14) == 0) {
417 RETURN;
421 static void parse_engine_line(GAME *g, char *line)
423 line = trim(line);
425 if (!*line)
426 return;
428 parse_gnuchess_line(g, line);
431 void parse_engine_output(GAME *g, char *str)
433 char buf[LINE_MAX], *p = buf;
435 while (*str) {
436 *p = '\0';
438 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
439 * 'g'o command. */
440 if (*str == ':') {
441 p = buf;
442 str++;
443 continue;
446 if (*str == '\n') {
447 *p = '\0';
448 parse_engine_line(g, buf);
449 str++;
450 p = buf;
451 continue;
454 *p++ = *str++;