1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2024 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
28 #include <sys/types.h>
46 #ifdef HAVE_SYS_WAIT_H
59 send_signal_to_engine (pid_t pid
, int sig
)
61 if (kill (pid
, sig
) == -1)
68 send_to_engine (GAME g
, int status
, const char *format
, ...)
73 struct userdata_s
*d
= g
->data
;
75 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
||
76 TEST_FLAG (d
->flags
, CF_HUMAN
))
79 va_start (ap
, format
);
81 len
= vasprintf (&line
, format
, ap
);
83 line
= Malloc (LINE_MAX
);
84 len
= vsnprintf (line
, LINE_MAX
, format
, ap
);
95 FD_SET (d
->engine
->fd
[ENGINE_OUT_FD
], &fds
);
100 if ((n
= select (d
->engine
->fd
[ENGINE_OUT_FD
] + 1, NULL
, &fds
, NULL
,
103 if (FD_ISSET (d
->engine
->fd
[ENGINE_OUT_FD
], &fds
))
105 n
= write (d
->engine
->fd
[ENGINE_OUT_FD
], line
, len
);
115 if (kill (d
->engine
->pid
, 0) == -1)
117 message (ERROR_STR
, ANY_KEY_STR
,
118 _("Could not write to engine. "
119 "Process no longer exists."));
120 d
->engine
->status
= ENGINE_OFFLINE
;
124 message (ERROR_STR
, ANY_KEY_STR
,
125 "write() error to engine: %s", strerror (errno
));
126 d
->engine
->status
= ENGINE_OFFLINE
;
132 message (NULL
, ANY_KEY_STR
,
134 ("short write() count to engine. Expected %i, got %i."),
136 d
->engine
->status
= ENGINE_OFFLINE
;
149 if (d
->engine
->status
== ENGINE_OFFLINE
)
152 d
->engine
->status
= status
;
155 return d
->engine
->status
== ENGINE_OFFLINE
? 1 : 0;
159 /* From the Xboard and screen packages. */
161 get_pty (char *pty_name
)
166 for (i
= 0; PTY_MAJOR
[i
]; i
++)
170 for (n
= 0; PTY_MINOR
[n
]; n
++)
172 sprintf (pty_name
, "%spty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
175 if ((fd
= open (pty_name
, O_RDWR
| O_NOCTTY
)) == -1)
178 sprintf (pty_name
, "%stty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
181 if (access (pty_name
, R_OK
| W_OK
) == -1)
196 parseargs (char *str
)
208 if (!(pptr
= Malloc (sizeof (char *))))
211 for (i
= 0, s
= str
; *s
; lastchar
= *s
++)
213 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\')
215 quote
= (quote
) ? 0 : 1;
219 if (*s
== ' ' && !quote
)
222 pptr
= Realloc (pptr
, (n
+ 2) * sizeof (char *));
223 pptr
[n
++] = strdup (arg
);
228 if ((i
+ 1) == sizeof (arg
))
238 pptr
= Realloc (pptr
, (n
+ 2) * sizeof (char *));
239 pptr
[n
++] = strdup (arg
);
247 init_chess_engine (GAME g
)
249 struct userdata_s
*d
= g
->data
;
252 if (start_chess_engine (g
) > 0)
258 tmp
= pgn_game_to_fen (g
, d
->b
);
259 add_engine_command (g
, ENGINE_READY
, "setboard %s\n", tmp
);
262 if (config
.fmpolyglot
&& !TEST_FLAG (d
->flags
, CF_HUMAN
) )
264 add_engine_command (g
, ENGINE_READY
, "%s\n",
265 g
->side
== WHITE
? "white" : "black");
266 if (g
->turn
== WHITE
&& g
->side
== BLACK
)
267 add_engine_command (g
, ENGINE_READY
, "%s\n", "go");
268 else if (g
->turn
== BLACK
&& g
->side
== WHITE
)
269 add_engine_command (g
, ENGINE_READY
, "%s\n", "go");
275 /* Is this dangerous if pty permissions are wrong? */
277 exec_chess_engine (GAME g
, char **args
)
281 struct userdata_s
*d
= g
->data
;
282 int tries
= config
.engine_timeout
;
284 char pty
[FILENAME_MAX
];
286 if ((to
[1] = get_pty (pty
)) == -1)
292 if ((to
[1] = open ("/dev/ptmx", O_RDWR
| O_NOCTTY
)) == -1)
297 if (grantpt (to
[1]) == -1)
302 if (unlockpt (to
[1]) == -1)
312 if ((to
[0] = open (pty
, O_RDWR
, 0)) == -1)
314 if ((to
[0] = open (ptsname (to
[1]), O_RDWR
| O_NOCTTY
, 0)) == -1)
321 switch ((pid
= fork ()))
326 dup2 (to
[0], STDIN_FILENO
);
327 dup2 (from
[1], STDOUT_FILENO
);
332 dup2 (STDOUT_FILENO
, STDERR_FILENO
);
333 execvp (args
[0], args
);
334 _exit (EXIT_FAILURE
);
341 if (send_signal_to_engine (pid
, 0) && !tries
)
350 d
->engine
->fd
[ENGINE_IN_FD
] = from
[0];
351 d
->engine
->fd
[ENGINE_OUT_FD
] = to
[1];
353 if (fcntl (d
->engine
->fd
[ENGINE_IN_FD
], F_SETFL
, O_NONBLOCK
) == -1)
356 if (fcntl (d
->engine
->fd
[ENGINE_OUT_FD
], F_SETFL
, O_NONBLOCK
) == -1)
359 d
->engine
->pid
= pid
;
366 struct userdata_s
*d
= g
->data
;
369 if (!d
->engine
|| d
->engine
->pid
== -1
370 || d
->engine
->status
== ENGINE_OFFLINE
)
373 send_to_engine (g
, ENGINE_READY
, "quit\n");
375 if (!send_signal_to_engine (d
->engine
->pid
, 0))
377 if (!send_signal_to_engine (d
->engine
->pid
, SIGTERM
))
378 send_signal_to_engine (d
->engine
->pid
, SIGKILL
);
381 waitpid (d
->engine
->pid
, &s
, WNOHANG
);
383 d
->engine
->status
= ENGINE_OFFLINE
;
387 set_engine_defaults (GAME g
, wchar_t ** init
)
394 for (i
= 0; init
[i
]; i
++)
395 add_engine_command (g
, ENGINE_READY
, "%ls\n", init
[i
]);
399 start_chess_engine (GAME g
)
404 struct userdata_s
*d
= g
->data
;
406 if (d
->engine
&& d
->engine
->status
!= ENGINE_OFFLINE
)
409 args
= parseargs (config
.engine_cmd
);
412 d
->engine
= Calloc (1, sizeof (struct engine_s
));
414 d
->engine
->status
= ENGINE_INITIALIZING
;
415 update_status_window (g
);
419 switch (exec_chess_engine (g
, args
))
422 /* Pty allocation. */
423 message (ERROR_STR
, ANY_KEY_STR
, "Could not allocate PTY");
424 d
->engine
->status
= ENGINE_OFFLINE
;
427 /* Could not execute engine. */
428 message (ERROR_STR
, ANY_KEY_STR
, "%s: %s", args
[0], strerror (errno
));
429 d
->engine
->status
= ENGINE_OFFLINE
;
433 d
->engine
->status
= ENGINE_READY
;
434 set_engine_defaults (g
, config
.einit
);
438 for (i
= 0; args
[i
]; i
++)
446 parse_xboard_line (GAME g
, char *str
)
448 char m
[MAX_SAN_MOVE_LEN
+ 1] = { 0 }, *p
= m
;
449 struct userdata_s
*d
= g
->data
;
456 // 1. a2a4 (gnuchess)
460 // gnuchess echos our input move so we don't need the duplicate (above).
461 if (*p
== '.' && *(p
+ 1) == ' ' && *(p
+ 2) != '.')
464 // 1. ... a2a4 (gnuchess/engine move)
465 if (*p
== '.' && *(p
+ 1) == ' ' && *(p
+ 2) == '.')
467 while (*p
== ' ' || *p
== '.')
471 if (strncmp (str
, "move ", 5) == 0)
474 if (strlen (p
) > MAX_SAN_MOVE_LEN
)
477 // We should now have the real move which may be in SAN or frfr format.
478 if (sscanf (p
, "%[0-9a-hprnqkxPRNBQKO+=#-]%n", m
, &count
) == 1)
483 * For engine commands (the '|' key). Don't try and validate them.
485 if (count
!= strlen (p
))
488 if (TEST_FLAG (g
->flags
, GF_GAMEOVER
))
497 p
= m
+ strlen (m
) - 1;
499 // Test SAN or a2a4 format.
500 if (!isdigit (*p
) && *p
!= 'O' && *p
!= '+' && *p
!= '#'
501 && ((*(p
- 1) != '=' || !isdigit (*(p
- 1)))
502 && pgn_piece_to_int (*p
) == -1))
506 if ((n
= pgn_parse_move (g
, d
->b
, &buf
, &frfr
)) != E_PGN_OK
)
508 invalid_move (d
->n
+ 1, n
, m
);
512 strcpy (d
->pm_frfr
, frfr
);
515 pgn_history_add (g
, d
->b
, buf
);
516 SET_FLAG (d
->flags
, CF_MODIFIED
);
519 if (TEST_FLAG (d
->flags
, CF_ENGINE_LOOP
))
522 update_cursor (g
, g
->hindex
);
524 if (TEST_FLAG (g
->flags
, GF_GAMEOVER
))
530 add_engine_command (g
, ENGINE_THINKING
, "go\n");
534 if (TEST_FLAG (gp
->flags
, GF_GAMEOVER
))
541 if (g
->side
== g
->turn
)
545 char *cmd
= string_replace (config
.turn_cmd
, "%m", buf
);
566 d
->engine
->status
= ENGINE_THINKING
;
573 parse_engine_output (GAME g
, char *str
)
575 char buf
[255], *p
= buf
;
579 if (*str
== '\n' || *str
== '\r')
585 append_enginebuf (g
, buf
);
586 parse_xboard_line (g
, buf
);
605 send_engine_command (GAME g
)
607 struct userdata_s
*d
= g
->data
;
611 if (!d
->engine
|| !d
->engine
->queue
)
614 q
= d
->engine
->queue
;
616 if (send_to_engine (g
, q
[0]->status
, "%s", q
[0]->line
))
622 for (i
= 0; q
[i
]; i
++)
635 free (d
->engine
->queue
);
636 d
->engine
->queue
= NULL
;
641 add_engine_command (GAME g
, int s
, const char *fmt
, ...)
645 struct userdata_s
*d
= g
->data
;
646 struct queue_s
**q
= NULL
;
649 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
651 if (init_chess_engine (g
))
658 q
= d
->engine
->queue
;
661 for (i
= 0; q
[i
]; i
++);
663 q
= Realloc (q
, (i
+ 2) * sizeof (struct queue_s
*));
665 #ifdef HAVE_VASPRINTF
666 vasprintf (&line
, fmt
, ap
);
668 line
= Malloc (LINE_MAX
+ 1);
669 vsnprintf (line
, LINE_MAX
, fmt
, ap
);
672 q
[i
] = Malloc (sizeof (struct queue_s
));
674 q
[i
++]->status
= (s
== -1) ? d
->engine
->status
: s
;
676 d
->engine
->queue
= q
;