Renamed user_data_s to userdata_s.
[cboard.git] / src / engine.c
blob5a702564a88ad66d2d9e26f45e563669721c9f5c
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 "common.h"
45 #include "engine.h"
47 #ifdef WITH_DMALLOC
48 #include <dmalloc.h>
49 #endif
51 void send_to_engine(GAME *g, const char *format, ...)
53 va_list ap;
54 int len;
55 char *line;
56 int try = 0;
57 struct userdata_s *d = g->data;
59 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
60 return;
62 d->engine->status = ENGINE_THINKING;
63 update_status_window(*g);
64 refresh_all();
66 va_start(ap, format);
67 #ifdef HAVE_VASPRINTF
68 len = vasprintf(&line, format, ap);
69 #else
70 line = Malloc(LINE_MAX);
71 len = vsnprintf(line, LINE_MAX, format, ap);
72 #endif
73 va_end(ap);
75 while (1) {
76 int n;
77 fd_set fds;
78 struct timeval tv;
80 FD_ZERO(&fds);
81 FD_SET(d->engine->fd[ENGINE_OUT_FD], &fds);
83 tv.tv_sec = 0;
84 tv.tv_usec = 0;
86 if ((n = select(d->engine->fd[ENGINE_OUT_FD] + 1, NULL, &fds, NULL,
87 &tv)) > 0) {
88 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &fds)) {
89 n = write(d->engine->fd[ENGINE_OUT_FD], line, len);
91 if (n == -1) {
92 if (errno == EAGAIN)
93 continue;
95 if (kill(d->engine->pid, 0) == -1) {
96 message(ERROR, ANYKEY, "Could not write to engine. "
97 "Process no longer exists.");
98 d->engine->status = ENGINE_OFFLINE;
99 //update_status_window(NULL);
100 return;
103 message(ERROR, ANYKEY, "Attempt #%i. write(): %s", ++try,
104 strerror(errno));
105 continue;
108 if (len != n) {
109 message(NULL, ANYKEY, "try #%i: write() error to engine. "
110 "Expected %i, got %i.", ++try, len, n);
111 continue;
114 break;
117 else {
118 /* timeout */
122 free(line);
125 #ifndef UNIX98
126 /* From the Xboard and screen packages. */
127 static int get_pty(char *pty_name)
129 int i;
130 int fd;
132 for (i = 0; PTY_MAJOR[i]; i++) {
133 int n;
135 for (n = 0; PTY_MINOR[n]; n++) {
136 sprintf(pty_name, "%spty%c%c", _PATH_DEV, PTY_MAJOR[i],
137 PTY_MINOR[n]);
139 if ((fd = open(pty_name, O_RDWR | O_NOCTTY)) == -1)
140 continue;
142 sprintf(pty_name, "%stty%c%c", _PATH_DEV, PTY_MAJOR[i],
143 PTY_MINOR[n]);
145 if (access(pty_name, R_OK | W_OK) == -1) {
146 close(fd);
147 continue;
150 return fd;
154 return -1;
156 #endif
158 static char **parseargs(char *str)
160 char **pptr, *s;
161 char arg[255];
162 int n = 0;
163 int quote = 0;
164 int lastchar = 0;
165 int i;
167 if (!str)
168 return NULL;
170 if (!(pptr = malloc(sizeof(char *))))
171 return NULL;
173 for (i = 0, s = str; *s; lastchar = *s++) {
174 if ((*s == '\"' || *s == '\'') && lastchar != '\\') {
175 quote = (quote) ? 0 : 1;
176 continue;
179 if (*s == ' ' && !quote) {
180 arg[i] = 0;
181 pptr = realloc(pptr, (n + 2) * sizeof(char *));
182 pptr[n++] = strdup(arg);
183 arg[0] = i = 0;
184 continue;
187 if ((i + 1) == sizeof(arg))
188 continue;
190 arg[i++] = *s;
193 arg[i] = 0;
195 if (arg[0]) {
196 pptr = realloc(pptr, (n + 2) * sizeof(char *));
197 pptr[n++] = strdup(arg);
200 pptr[n] = NULL;
201 return pptr;
204 /* Is this dangerous if pty permissions are wrong? */
205 static pid_t init_chess_engine(GAME *g, char **args)
207 pid_t pid;
208 int from[2], to[2];
209 struct userdata_s *d = g->data;
210 #ifndef UNIX98
211 char pty[FILENAME_MAX];
213 if ((to[1] = get_pty(pty)) == -1) {
214 errno = 0;
215 return -1;
217 #else
218 if ((to[1] = open("/dev/ptmx", O_RDWR | O_NOCTTY)) == -1) {
219 return -1;
222 if (grantpt(to[1]) == -1) {
223 return -1;
226 if (unlockpt(to[1]) == -1) {
227 return -1;
229 #endif
231 from[0] = to[1];
232 errno = 0;
234 #ifndef UNIX98
235 if ((to[0] = open(pty, O_RDWR, 0)) == -1)
236 #else
237 if ((to[0] = open(ptsname(to[1]), O_RDWR, 0)) == -1)
238 #endif
239 return -1;
241 from[1] = to[0];
242 errno = 0;
244 switch ((pid = fork())) {
245 case -1:
246 return -2;
247 case 0:
248 dup2(to[0], STDIN_FILENO);
249 dup2(from[1], STDOUT_FILENO);
250 close(to[0]);
251 close(to[1]);
252 close(from[0]);
253 close(from[1]);
254 dup2(STDOUT_FILENO, STDERR_FILENO);
255 execvp(args[0], args);
256 _exit(EXIT_FAILURE);
257 default:
258 break;
261 sleep(1);
263 if (kill(pid, 0) == -1)
264 return -2;
266 close(to[0]);
267 close(from[1]);
269 d->engine->fd[ENGINE_IN_FD] = from[0];
270 d->engine->fd[ENGINE_OUT_FD] = to[1];
271 fcntl(d->engine->fd[ENGINE_IN_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
272 fcntl(d->engine->fd[ENGINE_OUT_FD], F_SETFL, O_NONBLOCK | O_DIRECT);
273 d->engine->pid = pid;
274 return 0;
277 void stop_engine(GAME *g)
279 struct userdata_s *d = g->data;
281 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
282 return;
284 send_to_engine(g, "quit\n");
286 if (kill(d->engine->pid, 0) != -1)
287 kill(d->engine->pid, SIGTERM);
289 if (kill(d->engine->pid, 0) != -1)
290 kill(d->engine->pid, SIGKILL);
293 void set_engine_defaults(GAME *g, char **init)
295 int i;
297 if (!init)
298 return;
300 for (i = 0; init[i]; i++)
301 send_to_engine(g, "%s\n", init[i]);
304 int start_chess_engine(GAME *g)
306 char **args;
307 int i;
308 int ret = 1;
309 struct userdata_s *d = g->data;
311 args = parseargs(config.engine_cmd);
313 d->engine = Calloc(1, sizeof(struct engine_s));
314 d->engine->status = ENGINE_INITIALIZING;
315 update_status_window(*g);
316 refresh_all();
318 switch (init_chess_engine(g, args)) {
319 case -1:
320 /* Pty allocation. */
321 message(ERROR, ANYKEY, "Could not allocate PTY");
322 d->engine->status = ENGINE_OFFLINE;
323 break;
324 case -2:
325 /* Could not execute engine. */
326 message(ERROR, ANYKEY, "%s: %s", args[0], strerror(errno));
327 d->engine->status = ENGINE_OFFLINE;
328 break;
329 default:
330 ret = 0;
331 set_engine_defaults(g, config.einit);
332 d->engine->status = ENGINE_READY;
333 break;
336 for (i = 0; args[i]; i++)
337 free(args[i]);
339 free(args);
340 update_status_window(*g);
341 return ret;
344 /* Once the PGN parser has been well tested, validate_move() from the human
345 * move can disappear.
347 void parse_gnuchess_line(GAME *g, char *str)
349 char m[MAX_SAN_MOVE_LEN + 1] = {0}, *p = m;
350 int count;
351 struct userdata_s *d = g->data;
353 /* Human move. Add it to the move history. */
354 if (sscanf(str, "%*d%*1[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m, &count) == 1) {
356 if (validate_move(g, m)) {
357 invalid_move(0, m);
358 return;
362 if (history_total(g->hp) == 0 && g->side == BLACK)
363 SET_FLAG(g->flags, GF_BLACK_OPENING);
365 history_add(g, p);
366 SET_FLAG(g->flags, GF_MODIFIED);
367 pgn_switch_turn(g);
368 str += count;
369 return;
372 /* Engine move. */
373 if (sscanf(str, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m,
374 &count) == 1) {
375 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
376 * need to convert them.
378 //FIXME
379 if ((p = pgn_a2a4tosan(g, g->b, m)) == NULL)
380 return;
382 if (pgn_validate_move(g, g->b, p)) {
383 invalid_move(0, p);
384 RETURN(d);
387 if (history_total(g->hp) == 0 && g->side == BLACK)
388 SET_FLAG(g->flags, GF_BLACK_OPENING);
390 history_add(g, p);
391 SET_FLAG(g->flags, GF_MODIFIED);
392 pgn_switch_turn(g);
393 str += count;
395 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
396 //history_update_board(g, g.htotal); FIXME
397 RETURN(d);
400 if (d && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
401 send_to_engine(g, "go\n");
402 update_cursor(*g, g->hindex);
403 return;
406 RETURN(d);
409 if (TEST_FLAG(g->flags, GF_GAMEOVER)) {
410 //history_update_board(g); FIXME
411 RETURN(d);
414 /* Miscellaneous one-liners. */
416 /* The engine is now reading a FIFO. Dump what we need to it. */
417 // FIXME FEN
418 if (strncmp(str, "pgnload ", 8) == 0) {
419 if (save_pgn(config.fifo, 1, gindex)) {
420 oldhistorytotal = 0;
421 return;
423 else {
424 history_free(g->hp, g->hindex + 1);
425 CLEAR_FLAG(g->flags, GF_GAMEOVER);
426 SET_FLAG(g->flags, GF_MODIFIED);
429 return;
432 /* 'switch' command. */
433 if (strncmp(str, "White to move", 13) == 0) {
434 g->side = g->turn = WHITE;
435 RETURN(d);
437 else if (strncmp(str, "Black to move", 13) == 0) {
438 g->side = g->turn = BLACK;
439 RETURN(d);
442 /* Bad engine command or move. */
443 if (strncmp(str, "Illegal move: ", 14) == 0) {
444 RETURN(d);
448 static void parse_engine_line(GAME *g, char *line)
450 line = trim(line);
452 if (!*line)
453 return;
455 parse_gnuchess_line(g, line);
458 void parse_engine_output(GAME *g, char *str)
460 char buf[LINE_MAX], *p = buf;
462 while (*str) {
463 *p = '\0';
465 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
466 * 'g'o command. */
467 if (*str == ':') {
468 p = buf;
469 str++;
470 continue;
473 if (*str == '\n') {
474 *p = '\0';
475 parse_engine_line(g, buf);
476 str++;
477 p = buf;
478 continue;
481 *p++ = *str++;