1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
25 #include <sys/types.h>
51 void send_to_engine(GAME
*g
, const char *format
, ...)
57 struct userdata_s
*d
= g
->data
;
59 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
62 d
->engine
->status
= ENGINE_THINKING
;
63 update_status_window(*g
);
68 len
= vasprintf(&line
, format
, ap
);
70 line
= Malloc(LINE_MAX
);
71 len
= vsnprintf(line
, LINE_MAX
, format
, ap
);
81 FD_SET(d
->engine
->fd
[ENGINE_OUT_FD
], &fds
);
86 if ((n
= select(d
->engine
->fd
[ENGINE_OUT_FD
] + 1, NULL
, &fds
, NULL
,
88 if (FD_ISSET(d
->engine
->fd
[ENGINE_OUT_FD
], &fds
)) {
89 n
= write(d
->engine
->fd
[ENGINE_OUT_FD
], line
, len
);
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);
103 message(ERROR
, ANYKEY
, "Attempt #%i. write(): %s", ++try,
109 message(NULL
, ANYKEY
, "try #%i: write() error to engine. "
110 "Expected %i, got %i.", ++try, len
, n
);
126 /* From the Xboard and screen packages. */
127 static int get_pty(char *pty_name
)
132 for (i
= 0; PTY_MAJOR
[i
]; i
++) {
135 for (n
= 0; PTY_MINOR
[n
]; n
++) {
136 sprintf(pty_name
, "%spty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
139 if ((fd
= open(pty_name
, O_RDWR
| O_NOCTTY
)) == -1)
142 sprintf(pty_name
, "%stty%c%c", _PATH_DEV
, PTY_MAJOR
[i
],
145 if (access(pty_name
, R_OK
| W_OK
) == -1) {
158 static char **parseargs(char *str
)
170 if (!(pptr
= malloc(sizeof(char *))))
173 for (i
= 0, s
= str
; *s
; lastchar
= *s
++) {
174 if ((*s
== '\"' || *s
== '\'') && lastchar
!= '\\') {
175 quote
= (quote
) ? 0 : 1;
179 if (*s
== ' ' && !quote
) {
181 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
182 pptr
[n
++] = strdup(arg
);
187 if ((i
+ 1) == sizeof(arg
))
196 pptr
= realloc(pptr
, (n
+ 2) * sizeof(char *));
197 pptr
[n
++] = strdup(arg
);
204 /* Is this dangerous if pty permissions are wrong? */
205 static pid_t
init_chess_engine(GAME
*g
, char **args
)
209 struct userdata_s
*d
= g
->data
;
211 char pty
[FILENAME_MAX
];
213 if ((to
[1] = get_pty(pty
)) == -1) {
218 if ((to
[1] = open("/dev/ptmx", O_RDWR
| O_NOCTTY
)) == -1) {
222 if (grantpt(to
[1]) == -1) {
226 if (unlockpt(to
[1]) == -1) {
235 if ((to
[0] = open(pty
, O_RDWR
, 0)) == -1)
237 if ((to
[0] = open(ptsname(to
[1]), O_RDWR
, 0)) == -1)
244 switch ((pid
= fork())) {
248 dup2(to
[0], STDIN_FILENO
);
249 dup2(from
[1], STDOUT_FILENO
);
254 dup2(STDOUT_FILENO
, STDERR_FILENO
);
255 execvp(args
[0], args
);
263 if (kill(pid
, 0) == -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
;
277 void stop_engine(GAME
*g
)
279 struct userdata_s
*d
= g
->data
;
281 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
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
)
300 for (i
= 0; init
[i
]; i
++)
301 send_to_engine(g
, "%s\n", init
[i
]);
304 int start_chess_engine(GAME
*g
)
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
);
318 switch (init_chess_engine(g
, args
)) {
320 /* Pty allocation. */
321 message(ERROR
, ANYKEY
, "Could not allocate PTY");
322 d
->engine
->status
= ENGINE_OFFLINE
;
325 /* Could not execute engine. */
326 message(ERROR
, ANYKEY
, "%s: %s", args
[0], strerror(errno
));
327 d
->engine
->status
= ENGINE_OFFLINE
;
331 set_engine_defaults(g
, config
.einit
);
332 d
->engine
->status
= ENGINE_READY
;
336 for (i
= 0; args
[i
]; i
++)
340 update_status_window(*g
);
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
;
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)) {
362 if (history_total(g
->hp
) == 0 && g
->side
== BLACK
)
363 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
366 SET_FLAG(g
->flags
, GF_MODIFIED
);
373 if (sscanf(str
, "%*d%*1[.]%*1[ ]%*3[.]%*1[ ]%[a-zA-Z0-9+=#-]%n", m
,
375 /* Moves from the engine are in a2a4 format (Xboard protocol) so we
376 * need to convert them.
379 if ((p
= pgn_a2a4tosan(g
, g
->b
, m
)) == NULL
)
382 if (pgn_validate_move(g
, g
->b
, p
)) {
387 if (history_total(g
->hp
) == 0 && g
->side
== BLACK
)
388 SET_FLAG(g
->flags
, GF_BLACK_OPENING
);
391 SET_FLAG(g
->flags
, GF_MODIFIED
);
395 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
396 //history_update_board(g, g.htotal); FIXME
400 if (d
&& TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
401 send_to_engine(g
, "go\n");
402 update_cursor(*g
, g
->hindex
);
409 if (TEST_FLAG(g
->flags
, GF_GAMEOVER
)) {
410 //history_update_board(g); FIXME
414 /* Miscellaneous one-liners. */
416 /* The engine is now reading a FIFO. Dump what we need to it. */
418 if (strncmp(str
, "pgnload ", 8) == 0) {
419 if (save_pgn(config
.fifo
, 1, gindex
)) {
424 history_free(g
->hp
, g
->hindex
+ 1);
425 CLEAR_FLAG(g
->flags
, GF_GAMEOVER
);
426 SET_FLAG(g
->flags
, GF_MODIFIED
);
432 /* 'switch' command. */
433 if (strncmp(str
, "White to move", 13) == 0) {
434 g
->side
= g
->turn
= WHITE
;
437 else if (strncmp(str
, "Black to move", 13) == 0) {
438 g
->side
= g
->turn
= BLACK
;
442 /* Bad engine command or move. */
443 if (strncmp(str
, "Illegal move: ", 14) == 0) {
448 static void parse_engine_line(GAME
*g
, char *line
)
455 parse_gnuchess_line(g
, line
);
458 void parse_engine_output(GAME
*g
, char *str
)
460 char buf
[LINE_MAX
], *p
= buf
;
465 /* FIXME test this ("White ... : ", "Black ... : "). Needed for the
475 parse_engine_line(g
, buf
);