Added pgn_config_get() to get a value of a configuration variable.
[cboard.git] / src / cboard.c
blob492295a6acad26680a062297bbed094f7859921b
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2007 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 <sys/types.h>
23 #include <sys/time.h>
24 #include <sys/socket.h>
25 #include <sys/stat.h>
26 #include <string.h>
27 #include <panel.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <pwd.h>
31 #include <signal.h>
32 #include <time.h>
33 #include <err.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_STDARG_H
40 #include <stdarg.h>
41 #endif
43 #ifdef HAVE_SYS_WAIT_H
44 #include <sys/wait.h>
45 #endif
47 #ifdef HAVE_REGEX_H
48 #include <regex.h>
49 #endif
51 #include "chess.h"
52 #include "conf.h"
53 #include "window.h"
54 #include "message.h"
55 #include "colors.h"
56 #include "input.h"
57 #include "misc.h"
58 #include "engine.h"
59 #include "strings.h"
60 #include "common.h"
61 #include "menu.h"
62 #include "keys.h"
63 #include "rcfile.h"
64 #include "filebrowser.h"
65 #include "cboard.h"
67 #ifdef DEBUG
68 #include <debug.h>
69 #endif
71 #ifdef WITH_DMALLOC
72 #include <dmalloc.h>
73 #endif
75 void update_cursor(GAME g, int idx)
77 char *p;
78 int len;
79 int t = pgn_history_total(g->hp);
80 struct userdata_s *d = g->data;
83 * If not deincremented then r and c would be the next move.
85 idx--;
87 if (idx > t || idx < 0 || !t || !g->hp[idx]->move) {
88 d->c_row = 2, d->c_col = 5;
89 return;
92 p = g->hp[idx]->move;
93 len = strlen(p);
95 if (*p == 'O') {
96 if (len <= 4)
97 d->c_col = 7;
98 else
99 d->c_col = 3;
101 d->c_row = (g->turn == WHITE) ? 8 : 1;
102 return;
105 p += len;
107 while (!isdigit(*p))
108 p--;
110 d->c_row = RANKTOINT(*p--);
111 d->c_col = FILETOINT(*p);
114 static int init_nag()
116 FILE *fp;
117 char line[LINE_MAX];
118 int i = 0;
120 if ((fp = fopen(config.nagfile, "r")) == NULL) {
121 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
122 return 1;
125 nags = Realloc(nags, (i+2) * sizeof(char *));
126 nags[i++] = strdup(NONE);
127 nags[i] = NULL;
129 while (!feof(fp)) {
130 if (fscanf(fp, " %[^\n] ", line) == 1) {
131 nags = Realloc(nags, (i + 2) * sizeof(char *));
132 nags[i++] = strdup(line);
136 nags[i] = NULL;
137 nag_total = i;
138 return 0;
141 void edit_nag_toggle_item(struct menu_input_s *m)
143 struct input_s *in = m->data;
144 struct input_data_s *id = in->data;
145 HISTORY *h = id->data;
146 int i;
148 if (m->selected == 0) {
149 for (i = 0; i < MAX_PGN_NAG; i++)
150 h->nag[i] = 0;
152 for (i = 0; m->items[i]; i++)
153 m->items[i]->selected = 0;
155 return;
158 for (i = 0; i < MAX_PGN_NAG; i++) {
159 if (h->nag[i] == m->selected)
160 h->nag[i] = m->selected = 0;
161 else {
162 if (!h->nag[i]) {
163 h->nag[i] = m->selected;
164 break;
170 void edit_nag_save(struct menu_input_s *m)
172 pushkey = -1;
175 void edit_nag_help(struct menu_input_s *m)
177 message(NAG_EDIT_HELP, ANYKEY, "%s", naghelp);
180 struct menu_item_s **get_nag_items(WIN *win)
182 int i, n;
183 struct menu_input_s *m = win->data;
184 struct input_s *in = m->data;
185 struct input_data_s *id = in->data;
186 struct menu_item_s **items = m->items;
187 HISTORY *h = id->data;
189 if (items) {
190 for (i = 0; items[i]; i++)
191 free(items[i]);
194 for (i = 0; nags[i]; i++) {
195 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
196 items[i] = Malloc(sizeof(struct menu_item_s));
197 items[i]->name = nags[i];
198 items[i]->value = NULL;
200 for (n = 0; n < MAX_PGN_NAG; n++) {
201 if (h->nag[n] == i) {
202 items[i]->selected = 1;
203 n = -1;
204 break;
208 if (n >= 0)
209 items[i]->selected = 0;
212 items[i] = NULL;
213 m->nofree = 1;
214 m->items = items;
215 return items;
218 void nag_print(WIN *win)
220 struct menu_input_s *m = win->data;
222 mvwprintw(win->w, m->print_line, 1, "%-*s", win->cols - 2, m->item->name);
225 void edit_nag(void *arg)
227 struct menu_key_s **keys = NULL;
229 if (!nags) {
230 if (init_nag())
231 return;
234 add_menu_key(&keys, ' ', edit_nag_toggle_item);
235 add_menu_key(&keys, CTRL('x'), edit_nag_save);
236 add_menu_key(&keys, KEY_F(1), edit_nag_help);
237 construct_menu(0, 0, -1, -1, NAG_EDIT_TITLE, 1, get_nag_items, keys, arg,
238 nag_print, NULL);
239 return;
242 static void *view_nag(void *arg)
244 HISTORY *h = (HISTORY *)arg;
245 char buf[80];
246 char line[LINE_MAX] = {0};
247 int i = 0;
249 snprintf(buf, sizeof(buf), "%s \"%s\"", VIEW_MOVE_NAG, h->move);
251 if (!nags) {
252 if (init_nag())
253 return NULL;
256 for (i = 0; i < MAX_PGN_NAG; i++) {
257 if (!h->nag[i])
258 break;
260 if (h->nag[i] >= nag_total)
261 strncat(line, itoa(h->nag[i]), sizeof(line));
262 else
263 strncat(line, nags[h->nag[i]], sizeof(line));
265 strncat(line, "\n", sizeof(line));
268 line[strlen(line) - 1] = 0;
269 message(buf, ANYKEY, "%s", line);
270 return NULL;
273 void view_annotation(HISTORY *h)
275 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
276 int nag = 0, comment = 0;
278 if (!h)
279 return;
281 if (h->comment && h->comment[0])
282 comment++;
284 if (h->nag[0])
285 nag++;
287 if (!nag && !comment)
288 return;
290 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h->move);
292 if (comment)
293 construct_message(buf, (nag) ? ANY_OTHER_KEY : ANYKEY, 0, 1,
294 (nag) ? VIEW_NAG : NULL,
295 (nag) ? view_nag : NULL, (nag) ? h : NULL, NULL,
296 (nag) ? 'n' : 0, 0, "%s", h->comment);
297 else
298 construct_message(buf, ANY_OTHER_KEY, 0, 1, VIEW_NAG, view_nag, h, NULL,
299 'n', 0, "%s", NO_ANNOTATIONS);
302 void do_game_write(char *filename, char *mode, int start, int end)
304 char *command = NULL;
305 FILE *fp;
306 int i;
307 struct userdata_s *d;
309 if (command) {
310 if ((fp = popen(command, "w")) == NULL) {
311 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
312 goto error;
315 else {
316 if ((fp = fopen(filename, mode)) == NULL) {
317 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
318 goto error;
322 for (i = (start == -1) ? 0 : start; i < end; i++) {
323 d = game[i]->data;
324 pgn_write(fp, game[i]);
325 CLEAR_FLAG(d->flags, CF_MODIFIED);
328 if (command)
329 pclose(fp);
330 else
331 fclose(fp);
333 if (start == -1)
334 strncpy(loadfile, filename, sizeof(loadfile));
336 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
337 update_all(game[gindex]);
338 return;
340 error:
341 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
342 update_all(game[gindex]);
345 struct save_game_s {
346 char *filename;
347 char *mode;
348 int start;
349 int end;
352 void do_save_game_overwrite_confirm(WIN *win)
354 char *mode = "w";
355 struct save_game_s *s = win->data;
357 switch (win->c) {
358 case 'a':
359 if (pgn_is_compressed(s->filename) == E_PGN_OK) {
360 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
361 goto done;
364 mode = "a";
365 break;
366 case 'o':
367 mode = "w+";
368 break;
369 default:
370 goto done;
373 do_game_write(s->filename, mode, s->start, s->end);
375 done:
376 free(s->filename);
377 free(s);
380 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
381 * game index number.
383 // FIXME command (compression)
384 void save_pgn(char *filename, int saveindex)
386 char buf[FILENAME_MAX];
387 struct stat st;
388 int end = (saveindex == -1) ? gtotal : saveindex + 1;
389 struct save_game_s *s;
391 if (filename[0] != '/' && config.savedirectory) {
392 if (stat(config.savedirectory, &st) == -1) {
393 if (errno == ENOENT) {
394 if (mkdir(config.savedirectory, 0755) == -1) {
395 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
396 strerror(errno));
397 return;
400 else {
401 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
402 strerror(errno));
403 return;
407 stat(config.savedirectory, &st);
409 if (!S_ISDIR(st.st_mode)) {
410 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
411 return;
414 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
415 filename = buf;
418 if (access(filename, W_OK) == 0) {
419 s = Malloc(sizeof(struct save_game_s));
420 s->filename = strdup(filename);
421 s->start = saveindex;
422 s->end = end;
423 construct_message(NULL, GAME_SAVE_OVERWRITE_PROMPT, 1, 1, NULL, NULL,
424 s, do_save_game_overwrite_confirm, 0, 0, "%s \"%s\"",
425 E_FILEEXISTS, filename);
426 return;
429 do_game_write(filename, "a", saveindex, end);
432 static int castling_state(GAME g, BOARD b, int row, int col, int piece, int mod)
434 if (pgn_piece_to_int(piece) == ROOK && col == 7
435 && row == 7 &&
436 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
437 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
438 if (mod)
439 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
440 return 1;
442 else if (pgn_piece_to_int(piece) == ROOK && col == 0
443 && row == 7 &&
444 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
445 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
446 if (mod)
447 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
448 return 1;
450 else if (pgn_piece_to_int(piece) == ROOK && col == 7
451 && row == 0 &&
452 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
453 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
454 if (mod)
455 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
456 return 1;
458 else if (pgn_piece_to_int(piece) == ROOK && col == 0
459 && row == 0 &&
460 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
461 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
462 if (mod)
463 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
464 return 1;
466 else if (pgn_piece_to_int(piece) == KING && col == 4
467 && row == 7 &&
468 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
469 TEST_FLAG(g->flags, GF_WK_CASTLE))
471 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
472 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
473 if (mod) {
474 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
475 TEST_FLAG(g->flags, GF_WQ_CASTLE))
476 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
477 else
478 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
480 return 1;
482 else if (pgn_piece_to_int(piece) == KING && col == 4
483 && row == 0 &&
484 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
485 TEST_FLAG(g->flags, GF_BK_CASTLE))
487 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
488 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
489 if (mod) {
490 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
491 TEST_FLAG(g->flags, GF_BQ_CASTLE))
492 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
493 else
494 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
496 return 1;
499 return 0;
502 static void draw_board(GAME g)
504 int row, col;
505 int bcol = 0, brow = 0;
506 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
507 int ncols = 0, offset = 1;
508 unsigned coords_y = 8;
509 struct userdata_s *d = g->data;
511 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
512 update_cursor(g, g->hindex);
514 for (row = 0; row < maxy; row++) {
515 bcol = 0;
517 for (col = 0; col < maxx; col++) {
518 int attrwhich = -1;
519 chtype attrs = 0;
520 unsigned char piece;
522 if (row == 0 || row == maxy - 2) {
523 if (col == 0)
524 mvwaddch(boardw, row, col,
525 LINE_GRAPHIC((row) ?
526 ACS_LLCORNER | CP_BOARD_GRAPHICS :
527 ACS_ULCORNER | CP_BOARD_GRAPHICS));
528 else if (col == maxx - 2)
529 mvwaddch(boardw, row, col,
530 LINE_GRAPHIC((row) ?
531 ACS_LRCORNER | CP_BOARD_GRAPHICS :
532 ACS_URCORNER | CP_BOARD_GRAPHICS));
533 else if (!(col % 4))
534 mvwaddch(boardw, row, col,
535 LINE_GRAPHIC((row) ?
536 ACS_BTEE | CP_BOARD_GRAPHICS :
537 ACS_TTEE | CP_BOARD_GRAPHICS));
538 else {
539 if (col != maxx - 1)
540 mvwaddch(boardw, row, col,
541 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
544 continue;
547 if ((row % 2) && col == maxx - 1 && coords_y) {
548 wattron(boardw, CP_BOARD_COORDS);
549 mvwprintw(boardw, row, col, "%d", coords_y--);
550 wattroff(boardw, CP_BOARD_COORDS);
551 continue;
554 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
555 if (!(row % 2))
556 mvwaddch(boardw, row, col,
557 LINE_GRAPHIC((col) ?
558 ACS_RTEE | CP_BOARD_GRAPHICS :
559 ACS_LTEE | CP_BOARD_GRAPHICS));
560 else
561 mvwaddch(boardw, row, col,
562 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
564 continue;
567 if ((row % 2) && !(col % 4) && row != maxy - 1) {
568 mvwaddch(boardw, row, col,
569 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
570 continue;
573 if (!(col % 4) && row != maxy - 1) {
574 mvwaddch(boardw, row, col,
575 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
576 continue;
579 if ((row % 2)) {
580 if ((col % 4)) {
581 if (ncols++ == 8) {
582 offset++;
583 ncols = 1;
586 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
587 && (offset % 2)))
588 attrwhich = BLACK;
589 else
590 attrwhich = WHITE;
592 if (config.validmoves && d->b[brow][bcol].valid) {
593 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
594 CP_BOARD_MOVES_BLACK;
596 else
597 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
598 CP_BOARD_BLACK;
600 if (row == ROWTOMATRIX(d->c_row) && col ==
601 COLTOMATRIX(d->c_col)) {
602 attrs = CP_BOARD_CURSOR;
605 if (row == ROWTOMATRIX(d->sp.srow) &&
606 col == COLTOMATRIX(d->sp.scol)) {
607 attrs = CP_BOARD_SELECTED;
610 if (row == maxy - 1)
611 attrs = 0;
613 mvwaddch(boardw, row, col, ' ' | attrs);
615 if (row == maxy - 1)
616 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
617 else {
618 if (config.details && d->b[row / 2][bcol].enpassant)
619 piece = 'x';
620 else
621 piece = d->b[row / 2][bcol].icon;
623 if (config.details && castling_state(g, d->b, brow,
624 bcol, piece, 0))
625 attrs |= A_REVERSE;
627 if (g->side == WHITE && isupper(piece))
628 attrs |= A_BOLD;
629 else if (g->side == BLACK && islower(piece))
630 attrs |= A_BOLD;
632 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
634 CLEAR_FLAG(attrs, A_BOLD);
635 CLEAR_FLAG(attrs, A_REVERSE);
638 waddch(boardw, ' ' | attrs);
639 col += 2;
640 bcol++;
643 else {
644 if (col != maxx - 1)
645 mvwaddch(boardw, row, col,
646 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
650 brow = row / 2;
654 void invalid_move(int n, int e, const char *m)
656 if (curses_initialized)
657 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
658 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
659 else
660 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
661 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
664 static void update_clock(GAME g, struct itimerval it)
666 struct userdata_s *d = g->data;
667 long n;
669 if (g->turn == WHITE) {
670 d->wc.tv_sec += it.it_value.tv_sec;
671 d->wc.tv_usec += it.it_value.tv_usec;
673 if (d->wc.tv_usec > 1000000 - 1) {
674 d->wc.tv_sec += d->wc.tv_usec / 1000000;
675 d->wc.tv_usec = d->wc.tv_usec % 1000000;
678 else {
679 d->bc.tv_sec += it.it_value.tv_sec;
680 d->bc.tv_usec += it.it_value.tv_usec;
682 if (d->bc.tv_usec > 1000000 - 1) {
683 d->bc.tv_sec += d->bc.tv_usec / 1000000;
684 d->bc.tv_usec = d->bc.tv_usec % 1000000;
688 d->elapsed = d->wc.tv_sec + d->bc.tv_sec;
689 n = d->wc.tv_usec + d->bc.tv_usec;
690 d->elapsed += (n > 1000000 - 1) ? n / 1000000 : 0;
692 if (TEST_FLAG(d->flags, CF_CLOCK)) {
693 if (d->elapsed >= d->limit) {
694 SET_FLAG(g->flags, GF_GAMEOVER);
695 pgn_tag_add(&g->tag, "Result", "1/2-1/2");
700 void do_validate_move(char *m)
702 struct userdata_s *d = game[gindex]->data;
703 int n;
704 char *frfr = NULL;
706 if (TEST_FLAG(d->flags, CF_HUMAN)) {
707 if ((n = pgn_parse_move(game[gindex], d->b, &m, &frfr)) != E_PGN_OK) {
708 invalid_move(d->n + 1, n, m);
709 free(m);
710 return;
713 pgn_history_add(game[gindex], m);
714 pgn_switch_turn(game[gindex]);
716 else {
717 if ((n = pgn_validate_move(game[gindex], d->b, &m, &frfr)) != E_PGN_OK) {
718 invalid_move(d->n + 1, n, m);
719 free(m);
720 return;
723 add_engine_command(game[gindex], ENGINE_THINKING, "%s\n",
724 (config.engine_protocol == 1) ? frfr : m);
727 d->sp.srow = d->sp.scol = d->sp.icon = 0;
729 if (config.validmoves)
730 pgn_reset_valid_moves(d->b);
732 if (TEST_FLAG(game[gindex]->flags, GF_GAMEOVER))
733 d->mode = MODE_HISTORY;
734 else
735 SET_FLAG(d->flags, CF_MODIFIED);
737 d->paused = 0;
738 free(m);
739 return;
742 void do_promotion_piece_finalize(WIN *win)
744 char *p, *str = win->data;
746 if (pgn_piece_to_int(win->c) == -1)
747 return;
749 p = str + strlen(str);
750 *p++ = toupper(win->c);
751 *p = '\0';
752 do_validate_move(str);
755 static void move_to_engine(GAME g)
757 struct userdata_s *d = g->data;
758 char *str;
759 int piece;
761 if (config.validmoves &&
762 !d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].valid)
763 return;
765 str = Malloc(MAX_SAN_MOVE_LEN + 1);
766 snprintf(str, MAX_SAN_MOVE_LEN + 1, "%c%i%c%i",
767 x_grid_chars[d->sp.scol - 1],
768 d->sp.srow, x_grid_chars[d->sp.col - 1], d->sp.row);
770 piece = pgn_piece_to_int(d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon);
772 if (piece == PAWN && (d->sp.row == 8 || d->sp.row == 1)) {
773 construct_message(PROMOTION_TITLE, PROMOTION_PROMPT, 1, 1, NULL, NULL,
774 str, do_promotion_piece_finalize, 0, 0, "%s", PROMOTION_TEXT);
775 return;
778 do_validate_move(str);
781 static char *clock_to_char(long n)
783 static char buf[16];
784 int h = 0, m = 0, s = 0;
786 h = n / 3600;
787 m = (n % 3600) / 60;
788 s = (n % 3600) % 60;
789 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
790 return buf;
793 static char *timeval_to_char(struct timeval t)
795 static char buf[16];
796 int h = 0, m = 0, s = 0;
797 int n = t.tv_sec;
799 h = n / 3600;
800 m = (n % 3600) / 60;
801 s = (n % 3600) % 60;
802 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i.%.2i", h, m, s,
803 (int)t.tv_usec / 10000);
804 return buf;
807 void update_status_window(GAME g)
809 int i = 0;
810 char *buf;
811 char tmp[15], *engine, *mode;
812 int w;
813 char *p;
814 int maxy, maxx;
815 int len;
816 struct userdata_s *d = g->data;
817 int y;
818 int n;
820 getmaxyx(statusw, maxy, maxx);
821 w = maxx - 2 - 8;
822 len = maxx - 2;
823 buf = Malloc(len);
824 y = 2;
826 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
827 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
828 snprintf(buf, len, "%i %s %i", gindex + 1, N_OF_N_STR, gtotal);
829 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
831 *tmp = '\0';
832 p = tmp;
834 if (config.details) {
835 *p++ = 'D';
836 i++;
839 if (TEST_FLAG(d->flags, CF_DELETE)) {
840 if (i)
841 *p++ = '/';
843 *p++ = 'X';
844 i++;
847 if (TEST_FLAG(g->flags, GF_PERROR)) {
848 if (i)
849 *p++ = '/';
851 *p++ = '!';
852 i++;
855 if (TEST_FLAG(d->flags, CF_MODIFIED)) {
856 if (i)
857 *p++ = '/';
859 *p++ = '*';
860 i++;
863 pgn_config_get(PGN_STRICT_CASTLING, &n);
865 if (n == 1) {
866 if (i)
867 *p++ = '/';
869 *p++ = 'C';
870 i++;
873 *p = '\0';
874 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, STATUS_FLAGS_STR, w, tmp);
876 switch (d->mode) {
877 case MODE_HISTORY:
878 mode = MODE_HISTORY_STR;
879 break;
880 case MODE_EDIT:
881 mode = MODE_EDIT_STR;
882 break;
883 case MODE_PLAY:
884 mode = MODE_PLAY_STR;
885 break;
886 default:
887 mode = UNKNOWN;
888 break;
891 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
893 if (d->mode == MODE_PLAY) {
894 if (TEST_FLAG(d->flags, CF_HUMAN))
895 strncat(buf, " (human/human)", len - 1);
896 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
897 strncat(buf, " (engine/engine)", len - 1);
898 else
899 strncat(buf, " (human/engine)", len - 1);
902 mvwprintw(statusw, y++, 1, "%-*s", len, buf);
904 if (d->engine) {
905 switch (d->engine->status) {
906 case ENGINE_THINKING:
907 engine = ENGINE_PONDER_STR;
908 break;
909 case ENGINE_READY:
910 engine = ENGINE_READY_STR;
911 break;
912 case ENGINE_INITIALIZING:
913 engine = ENGINE_INITIALIZING_STR;
914 break;
915 case ENGINE_OFFLINE:
916 engine = ENGINE_OFFLINE_STR;
917 break;
918 default:
919 engine = UNKNOWN;
920 break;
923 else
924 engine = ENGINE_OFFLINE_STR;
926 mvwprintw(statusw, y, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
927 wattron(statusw, CP_STATUS_ENGINE);
928 mvwaddstr(statusw, y++, 9, engine);
929 wattroff(statusw, CP_STATUS_ENGINE);
931 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
932 (g->turn == WHITE) ? WHITE_STR : BLACK_STR);
934 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, STATUS_CLOCK_STR, w,
935 clock_to_char((TEST_FLAG(d->flags, CF_CLOCK)) ?
936 d->limit - d->elapsed : 0));
938 strncpy(tmp, WHITE_STR, sizeof(tmp));
939 tmp[0] = toupper(tmp[0]);
940 mvwprintw(statusw, y++, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->wc));
942 strncpy(tmp, BLACK_STR, sizeof(tmp));
943 tmp[0] = toupper(tmp[0]);
944 mvwprintw(statusw, y++, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->bc));
945 free(buf);
947 for (i = 1; i < maxx - 4; i++)
948 mvwprintw(statusw, maxy - 2, i, " ");
950 if (!status.notify)
951 status.notify = strdup(GAME_HELP_PROMPT);
953 wattron(statusw, CP_STATUS_NOTIFY);
954 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
955 status.notify);
956 wattroff(statusw, CP_STATUS_NOTIFY);
959 void update_history_window(GAME g)
961 char buf[HISTORY_WIDTH - 1];
962 HISTORY *h = NULL;
963 int n, total;
964 int t = pgn_history_total(g->hp);
966 n = (g->hindex + 1) / 2;
968 if (t % 2)
969 total = (t + 1) / 2;
970 else
971 total = t / 2;
973 if (t)
974 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
975 (movestep == 1) ? HISTORY_PLY_STEP : "");
976 else
977 strncpy(buf, UNAVAILABLE, sizeof(buf));
979 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
980 HISTORY_WIDTH - 13, buf);
982 h = pgn_history_by_n(g->hp, g->hindex);
983 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
984 n = 0;
986 if (h && ((h->comment) || h->nag[0])) {
987 strncat(buf, " (Annotated", sizeof(buf));
988 n++;
991 if (h && h->rav) {
992 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
993 n++;
996 if (g->ravlevel) {
997 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
998 n++;
1001 if (n)
1002 strncat(buf, ")", sizeof(buf));
1004 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1005 HISTORY_WIDTH - 13, buf);
1007 h = pgn_history_by_n(g->hp, g->hindex - 1);
1008 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1009 n = 0;
1011 if (h && ((h->comment) || h->nag[0])) {
1012 strncat(buf, " (Annotated", sizeof(buf));
1013 n++;
1016 if (h && h->rav) {
1017 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1018 n++;
1021 if (g->ravlevel) {
1022 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1023 n++;
1026 if (n)
1027 strncat(buf, ")", sizeof(buf));
1029 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1030 HISTORY_WIDTH - 13, buf);
1033 void update_tag_window(TAG **t)
1035 int i;
1036 int w = TAG_WIDTH - 10;
1038 for (i = 0; i < 7; i++)
1039 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w,
1040 str_etc(t[i]->value, w, 0));
1043 void append_enginebuf(GAME g, char *line)
1045 int i = 0;
1046 struct userdata_s *d = g->data;
1048 if (d->engine->enginebuf)
1049 for (i = 0; d->engine->enginebuf[i]; i++);
1051 if (i >= LINES - 3) {
1052 free(d->engine->enginebuf[0]);
1054 for (i = 0; d->engine->enginebuf[i+1]; i++)
1055 d->engine->enginebuf[i] = d->engine->enginebuf[i+1];
1057 d->engine->enginebuf[i] = strdup(line);
1059 else {
1060 d->engine->enginebuf = Realloc(d->engine->enginebuf, (i + 2) * sizeof(char *));
1061 d->engine->enginebuf[i++] = strdup(line);
1062 d->engine->enginebuf[i] = NULL;
1066 void update_engine_window(GAME g)
1068 int i;
1069 struct userdata_s *d = g->data;
1071 if (!d->engine || !d->engine->enginebuf)
1072 return;
1074 wmove(enginew, 0, 0);
1075 wclrtobot(enginew);
1077 if (d->engine->enginebuf) {
1078 for (i = 0; d->engine->enginebuf[i]; i++)
1079 mvwprintw(enginew, i + 2, 1, "%s", d->engine->enginebuf[i]);
1082 window_draw_title(enginew, ENGINE_IO_TITLE, COLS, CP_MESSAGE_TITLE,
1083 CP_MESSAGE_BORDER);
1086 void refresh_all()
1088 update_panels();
1089 doupdate();
1092 void update_all(GAME g)
1094 update_status_window(g);
1095 update_history_window(g);
1096 update_tag_window(g->tag);
1097 update_engine_window(g);
1100 static void game_next_prev(GAME g, int n, int count)
1102 if (gtotal < 2)
1103 return;
1105 if (n == 1) {
1106 if (gindex + count > gtotal - 1) {
1107 if (count != 1)
1108 gindex = gtotal - 1;
1109 else
1110 gindex = 0;
1112 else
1113 gindex += count;
1115 else {
1116 if (gindex - count < 0) {
1117 if (count != 1)
1118 gindex = 0;
1119 else
1120 gindex = gtotal - 1;
1122 else
1123 gindex -= count;
1127 static void delete_game(int which)
1129 GAME *g = NULL;
1130 int gi = 0;
1131 int i;
1132 struct userdata_s *d;
1134 for (i = 0; i < gtotal; i++) {
1135 d = game[i]->data;
1137 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
1138 free_userdata_once(game[i]);
1139 pgn_free(game[i]);
1140 continue;
1143 g = Realloc(g, (gi + 1) * sizeof(GAME *));
1144 g[gi] = Calloc(1, sizeof(struct game_s));
1145 memcpy(g[gi], game[i], sizeof(struct game_s));
1146 g[gi]->tag = game[i]->tag;
1147 g[gi]->history = game[i]->history;
1148 g[gi]->hp = game[i]->hp;
1149 gi++;
1152 game = g;
1153 gtotal = gi;
1155 if (which != -1) {
1156 if (which + 1 >= gtotal)
1157 gindex = gtotal - 1;
1158 else
1159 gindex = which;
1161 else
1162 gindex = gtotal - 1;
1164 game[gindex]->hp = game[gindex]->history;
1168 * FIXME find across multiple games.
1170 static int find_move_exp(GAME g, regex_t r, int which, int count)
1172 int i;
1173 int ret;
1174 char errbuf[255];
1175 int incr;
1176 int found;
1178 incr = (which == 0) ? -1 : 1;
1180 for (i = g->hindex + incr - 1, found = 0; ; i += incr) {
1181 if (i == g->hindex - 1)
1182 break;
1184 if (i >= pgn_history_total(g->hp))
1185 i = 0;
1186 else if (i < 0)
1187 i = pgn_history_total(g->hp) - 1;
1189 // FIXME RAV
1190 ret = regexec(&r, g->hp[i]->move, 0, 0, 0);
1192 if (ret == 0) {
1193 if (count == ++found) {
1194 return i + 1;
1197 else {
1198 if (ret != REG_NOMATCH) {
1199 regerror(ret, &r, errbuf, sizeof(errbuf));
1200 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
1201 return -1;
1206 return -1;
1209 static int toggle_delete_flag(int n)
1211 int i, x;
1212 struct userdata_s *d = game[n]->data;
1214 TOGGLE_FLAG(d->flags, CF_DELETE);
1215 gindex = n;
1216 update_all(game[gindex]);
1218 for (i = x = 0; i < gtotal; i++) {
1219 d = game[i]->data;
1221 if (TEST_FLAG(d->flags, CF_DELETE))
1222 x++;
1225 if (x == gtotal) {
1226 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1227 d = game[n]->data;
1228 CLEAR_FLAG(d->flags, CF_DELETE);
1229 return 1;
1232 return 0;
1235 static int find_game_exp(char *str, int which, int count)
1237 char *nstr = NULL, *exp = NULL;
1238 regex_t nexp, vexp;
1239 int ret = -1;
1240 int g = 0;
1241 char buf[255], *tmp;
1242 char errbuf[255];
1243 int found = 0;
1244 int incr = (which == 0) ? -(1) : 1;
1246 strncpy(buf, str, sizeof(buf));
1247 tmp = buf;
1249 if (strstr(tmp, ":") != NULL) {
1250 nstr = strsep(&tmp, ":");
1252 if ((ret = regcomp(&nexp, nstr,
1253 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
1254 regerror(ret, &nexp, errbuf, sizeof(errbuf));
1255 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1256 ret = g = -1;
1257 goto cleanup;
1261 exp = tmp;
1263 if (exp == NULL)
1264 goto cleanup;
1266 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
1267 regerror(ret, &vexp, errbuf, sizeof(errbuf));
1268 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1269 ret = -1;
1270 goto cleanup;
1273 ret = -1;
1275 for (g = gindex + incr, found = 0; ; g += incr) {
1276 int t;
1278 if (g == gindex)
1279 break;
1281 if (g == gtotal)
1282 g = 0;
1283 else if (g < 0)
1284 g = gtotal - 1;
1286 for (t = 0; game[g]->tag[t]; t++) {
1287 if (nstr) {
1288 if (regexec(&nexp, game[g]->tag[t]->name, 0, 0, 0) == 0) {
1289 if (regexec(&vexp, game[g]->tag[t]->value, 0, 0, 0) == 0) {
1290 if (count == ++found) {
1291 ret = g;
1292 goto cleanup;
1297 else {
1298 if (regexec(&vexp, game[g]->tag[t]->value, 0, 0, 0) == 0) {
1299 if (count == ++found) {
1300 ret = g;
1301 goto cleanup;
1307 ret = -1;
1310 cleanup:
1311 if (nstr)
1312 regfree(&nexp);
1314 if (g != -1)
1315 regfree(&vexp);
1317 return ret;
1321 * Updates the notification line in the status window then refreshes the
1322 * status window.
1324 void update_status_notify(GAME g, char *fmt, ...)
1326 va_list ap;
1327 #ifdef HAVE_VASPRINTF
1328 char *line;
1329 #else
1330 char line[COLS];
1331 #endif
1333 if (!fmt) {
1334 if (status.notify) {
1335 free(status.notify);
1336 status.notify = NULL;
1338 if (curses_initialized)
1339 update_status_window(g);
1342 return;
1345 va_start(ap, fmt);
1346 #ifdef HAVE_VASPRINTF
1347 vasprintf(&line, fmt, ap);
1348 #else
1349 vsnprintf(line, sizeof(line), fmt, ap);
1350 #endif
1351 va_end(ap);
1353 if (status.notify)
1354 free(status.notify);
1356 status.notify = strdup(line);
1358 #ifdef HAVE_VASPRINTF
1359 free(line);
1360 #endif
1361 if (curses_initialized)
1362 update_status_window(g);
1365 int rav_next_prev(GAME g, BOARD b, int n)
1367 // Next RAV.
1368 if (n) {
1369 if ((!g->ravlevel && g->hindex && g->hp[g->hindex - 1]->rav == NULL) ||
1370 (!g->ravlevel && !g->hindex && g->hp[g->hindex]->rav == NULL) ||
1371 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
1372 return 1;
1374 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
1375 g->rav[g->ravlevel].hp = g->hp;
1376 g->rav[g->ravlevel].flags = g->flags;
1377 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(g, b));
1378 g->rav[g->ravlevel].hindex = g->hindex;
1379 g->hp = (!g->ravlevel) ? (g->hindex) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav : g->hp[g->hindex]->rav;
1380 g->hindex = 0;
1381 g->ravlevel++;
1382 pgn_board_update(g, b, g->hindex + 1);
1383 return 0;
1386 if (g->ravlevel - 1 < 0)
1387 return 1;
1389 // Previous RAV.
1390 g->ravlevel--;
1391 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
1392 free(g->rav[g->ravlevel].fen);
1393 g->hp = g->rav[g->ravlevel].hp;
1394 g->flags = g->rav[g->ravlevel].flags;
1395 g->hindex = g->rav[g->ravlevel].hindex;
1396 return 0;
1399 static void draw_window_decor()
1401 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
1402 move_panel(boardp, 0, COLS - BOARD_WIDTH);
1403 wbkgd(boardw, CP_BOARD_WINDOW);
1404 wbkgd(statusw, CP_STATUS_WINDOW);
1405 window_draw_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
1406 CP_STATUS_TITLE, CP_STATUS_BORDER);
1407 wbkgd(tagw, CP_TAG_WINDOW);
1408 window_draw_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
1409 CP_TAG_BORDER);
1410 wbkgd(historyw, CP_HISTORY_WINDOW);
1411 window_draw_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
1412 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
1415 #ifdef HAVE_WRESIZE
1416 static void do_window_resize()
1418 if (LINES < 24 || COLS < 80)
1419 return;
1421 resizeterm(LINES, COLS);
1422 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
1423 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
1424 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
1425 wmove(historyw, 0, 0);
1426 wclrtobot(historyw);
1427 wmove(tagw, 0, 0);
1428 wclrtobot(tagw);
1429 wmove(statusw, 0, 0);
1430 wclrtobot(statusw);
1431 draw_window_decor();
1432 update_all(game[gindex]);
1434 #endif
1436 void stop_clock()
1438 memset(&clock_timer, 0, sizeof(struct itimerval));
1439 setitimer(ITIMER_REAL, &clock_timer, NULL);
1442 void start_clock()
1444 if (clock_timer.it_interval.tv_usec)
1445 return;
1447 clock_timer.it_value.tv_sec = 0;
1448 clock_timer.it_value.tv_usec = 100000;
1449 clock_timer.it_interval.tv_sec = 0;
1450 clock_timer.it_interval.tv_usec = 100000;
1451 setitimer(ITIMER_REAL, &clock_timer, NULL);
1454 static void update_clocks()
1456 int i;
1457 struct userdata_s *d;
1458 struct itimerval it;
1460 getitimer(ITIMER_REAL, &it);
1462 for (i = 0; i < gtotal; i++) {
1463 d = game[i]->data;
1465 if (d && d->mode == MODE_PLAY) {
1466 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
1467 continue;
1468 else if (d->paused == -1) {
1469 if (game[i]->side == game[i]->turn) {
1470 d->paused = 1;
1471 continue;
1475 update_clock(game[i], it);
1480 static int init_chess_engine(GAME g)
1482 struct userdata_s *d = g->data;
1483 int w, x;
1485 if (start_chess_engine(g) > 0) {
1486 d->sp.icon = 0;
1487 return 1;
1490 x = pgn_tag_find(g->tag, "FEN");
1491 w = pgn_tag_find(g->tag, "SetUp");
1493 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
1494 (x >= 0 && w == -1))
1495 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
1496 else
1497 add_engine_command(g, ENGINE_READY, "setboard %s\n",
1498 pgn_game_to_fen(g, d->b));
1500 return 0;
1503 static int parse_clock_input(struct userdata_s *d, char *str)
1505 char *p = str;
1506 long n = 0;
1507 int t = 0;
1508 int plus = 0;
1510 if (*p == '+') {
1511 plus = 1;
1512 p++;
1515 while (*p) {
1516 if (isdigit(*p)) {
1517 t = atoi(p);
1519 while (isdigit(*p))
1520 p++;
1522 continue;
1525 if (!t && *p != ' ')
1526 return 1;
1528 switch (*p) {
1529 case 'H':
1530 case 'h':
1531 n += t * (60 * 60);
1532 t = 0;
1533 break;
1534 case 'M':
1535 case 'm':
1536 n += t * 60;
1537 t = 0;
1538 break;
1539 case 'S':
1540 case 's':
1541 n += t;
1542 t = 0;
1543 break;
1544 case ' ':
1545 t = 0;
1546 break;
1547 default:
1548 return 1;
1551 p++;
1554 if (t)
1555 n += t;
1557 if (!n) {
1558 d->limit = 0;
1559 CLEAR_FLAG(d->flags, CF_CLOCK);
1561 else {
1562 SET_FLAG(d->flags, CF_CLOCK);
1564 if (plus)
1565 d->limit += n;
1566 else
1567 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
1570 return 0;
1573 void do_clock_input_finalize(WIN *win)
1575 struct userdata_s *d = game[gindex]->data;
1576 struct input_data_s *in = win->data;
1578 if (!in->str)
1579 return;
1581 if (parse_clock_input(d, in->str))
1582 cmessage(ERROR, ANYKEY, "Invalid time specification");
1584 free(in->str);
1585 free(in);
1588 void do_engine_command_finalize(WIN *win)
1590 struct userdata_s *d = game[gindex]->data;
1591 struct input_data_s *in = win->data;
1592 int x;
1594 if (!in->str) {
1595 free(in);
1596 return;
1599 x = d->engine->status;
1600 send_to_engine(game[gindex], -1, "%s\n", in->str);
1601 d->engine->status = x;
1602 free(in->str);
1603 free(in);
1606 void do_board_details()
1608 config.details = (config.details) ? 0 : 1;
1611 void do_toggle_strict_castling()
1613 int n;
1615 pgn_config_get(PGN_STRICT_CASTLING, &n);
1617 if (n == 0)
1618 pgn_config_set(PGN_STRICT_CASTLING, 1);
1619 else
1620 pgn_config_set(PGN_STRICT_CASTLING, 0);
1623 void do_play_set_clock()
1625 struct input_data_s *in;
1627 in = Calloc(1, sizeof(struct input_data_s));
1628 in->efunc = do_clock_input_finalize;
1629 construct_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL, NULL, 0, in, -1);
1632 void do_play_toggle_human()
1634 struct userdata_s *d = game[gindex]->data;
1636 TOGGLE_FLAG(d->flags, CF_HUMAN);
1638 if (!TEST_FLAG(d->flags, CF_HUMAN) && pgn_history_total(game[gindex]->hp)) {
1639 if (init_chess_engine(game[gindex]))
1640 return;
1643 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
1645 if (d->engine)
1646 d->engine->status = ENGINE_READY;
1648 update_all(game[gindex]);
1651 void do_play_toggle_engine()
1653 struct userdata_s *d = game[gindex]->data;
1655 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
1656 CLEAR_FLAG(d->flags, CF_HUMAN);
1658 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
1659 pgn_board_update(game[gindex], d->b,
1660 pgn_history_total(game[gindex]->hp));
1661 add_engine_command(game[gindex], ENGINE_READY,
1662 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
1665 update_all(game[gindex]);
1668 void do_play_send_command()
1670 struct userdata_s *d = game[gindex]->data;
1671 struct input_data_s *in;
1673 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
1674 return;
1676 in = Calloc(1, sizeof(struct input_data_s));
1677 in->efunc = do_engine_command_finalize;
1678 construct_input(ENGINE_CMD_TITLE, NULL, 1, 1, NULL, NULL, NULL, 0, in, -1);
1681 void do_play_switch_turn()
1683 pgn_switch_side(game[gindex]);
1684 pgn_switch_turn(game[gindex]);
1685 add_engine_command(game[gindex], -1,
1686 (game[gindex]->side == WHITE) ? "white\n" : "black\n");
1687 update_status_window(game[gindex]);
1690 void do_play_undo()
1692 struct userdata_s *d = game[gindex]->data;
1694 if (!pgn_history_total(game[gindex]->hp))
1695 return;
1697 if (d->engine && d->engine->status == ENGINE_READY) {
1698 add_engine_command(game[gindex], ENGINE_READY, "remove\n");
1699 d->engine->status = ENGINE_READY;
1702 game[gindex]->hindex -= 2;
1703 pgn_history_free(game[gindex]->hp, game[gindex]->hindex);
1704 game[gindex]->hindex = pgn_history_total(game[gindex]->hp);
1705 pgn_board_update(game[gindex], d->b, game[gindex]->hindex);
1706 update_history_window(game[gindex]);
1709 void do_play_toggle_pause()
1711 struct userdata_s *d = game[gindex]->data;
1713 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex]->turn !=
1714 game[gindex]->side) {
1715 d->paused = -1;
1716 return;
1719 d->paused = (d->paused) ? 0 : 1;
1722 void do_play_go()
1724 struct userdata_s *d = game[gindex]->data;
1726 if (TEST_FLAG(d->flags, CF_HUMAN))
1727 return;
1729 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
1730 if (init_chess_engine(game[gindex]))
1731 return;
1734 add_engine_command(game[gindex], ENGINE_THINKING, "go\n");
1737 void do_play_config_command()
1739 struct userdata_s *d = game[gindex]->data;
1740 int x, w;
1742 if (!d->engine)
1743 return;
1745 if (config.keys) {
1746 for (x = 0; config.keys[x]; x++) {
1747 if (config.keys[x]->c == input_c) {
1748 switch (config.keys[x]->type) {
1749 case KEY_DEFAULT:
1750 add_engine_command(game[gindex], -1, "%s\n",
1751 config.keys[x]->str);
1752 break;
1753 case KEY_SET:
1754 if (!keycount)
1755 break;
1757 add_engine_command(game[gindex], -1,
1758 "%s %i\n", config.keys[x]->str, keycount);
1759 keycount = 0;
1760 break;
1761 case KEY_REPEAT:
1762 if (!keycount)
1763 break;
1765 for (w = 0; w < keycount; w++)
1766 add_engine_command(game[gindex], -1,
1767 "%s\n", config.keys[x]->str);
1768 keycount = 0;
1769 break;
1775 update_status_notify(game[gindex], NULL);
1778 void do_play_cancel_selected()
1780 struct userdata_s *d = game[gindex]->data;
1782 d->sp.icon = d->sp.srow = d->sp.scol = 0;
1783 keycount = 0;
1784 update_status_notify(game[gindex], NULL);
1787 void do_play_commit()
1789 struct userdata_s *d = game[gindex]->data;
1791 pushkey = keycount = 0;
1792 update_status_notify(game[gindex], NULL);
1794 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
1795 (!d->engine || d->engine->status == ENGINE_THINKING))
1796 return;
1798 if (!d->sp.icon)
1799 return;
1801 d->sp.row = d->c_row;
1802 d->sp.col = d->c_col;
1803 move_to_engine(game[gindex]);
1806 void do_play_select()
1808 struct userdata_s *d = game[gindex]->data;
1810 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
1811 d->engine->status == ENGINE_OFFLINE)) {
1812 if (init_chess_engine(game[gindex]))
1813 return;
1816 if (d->sp.icon || (d->engine && d->engine->status == ENGINE_THINKING))
1817 return;
1819 d->sp.icon = d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon;
1821 if (pgn_piece_to_int(d->sp.icon) == OPEN_SQUARE) {
1822 d->sp.icon = 0;
1823 return;
1826 if (((islower(d->sp.icon) && game[gindex]->turn != BLACK)
1827 || (isupper(d->sp.icon) && game[gindex]->turn != WHITE))) {
1828 if (pgn_history_total(game[gindex]->hp)) {
1829 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
1830 d->sp.icon = 0;
1831 return;
1833 else {
1834 if (pgn_tag_find(game[gindex]->tag, "FEN") != E_PGN_ERR)
1835 return;
1837 add_engine_command(game[gindex], ENGINE_READY, "black\n");
1838 pgn_switch_turn(game[gindex]);
1840 if (game[gindex]->side != BLACK)
1841 pgn_switch_side(game[gindex]);
1845 d->sp.srow = d->c_row;
1846 d->sp.scol = d->c_col;
1848 if (config.validmoves)
1849 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
1851 CLEAR_FLAG(d->flags, CF_NEW);
1852 start_clock();
1855 /* FIXME: keys with the same function should comma deliminated. */
1856 static char *build_help(struct key_s **keys)
1858 int i, nlen = 1, len, t, n;
1859 char *buf = NULL;
1860 char *p;
1862 if (!keys)
1863 return NULL;
1865 for (i = len = t = 0; keys[i]; i++) {
1866 if (!keys[i]->d)
1867 continue;
1869 if (keys[i]->key) {
1870 if (strlen(keys[i]->key) > nlen) {
1871 nlen = strlen(keys[i]->key);
1872 t += nlen;
1874 else
1875 t++;
1878 if (keys[i]->d) {
1879 if (strlen(keys[i]->d) > len)
1880 len = strlen(keys[i]->d);
1883 t += len;
1884 t += keys[i]->r;
1887 t += 4 + i;
1888 buf = Malloc(t);
1889 p = buf;
1891 for (i = 0; keys[i]; i++) {
1892 if (!keys[i]->d)
1893 continue;
1895 if (keys[i]->key)
1896 n = strlen(keys[i]->key);
1897 else
1898 n = 1;
1900 while (n++ <= nlen)
1901 *p++ = ' ';
1903 *p = 0;
1905 if (keys[i]->key) {
1906 strcat(buf, keys[i]->key);
1907 p = buf + strlen(buf);
1909 else
1910 *p++ = keys[i]->c;
1912 *p++ = ' ';
1913 *p++ = '-';
1914 *p++ = ' ';
1915 *p = 0;
1917 if (keys[i]->d)
1918 strcat(buf, keys[i]->d);
1920 if (keys[i]->r)
1921 strcat(buf, "*");
1923 strcat(buf, "\n");
1924 p = buf + strlen(buf);
1927 return buf;
1930 void do_more_help(WIN *);
1931 void do_main_help(WIN *win)
1933 char *buf;
1935 switch (win->c) {
1936 case 'p':
1937 buf = build_help(play_keys);
1938 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0, 0,
1939 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
1940 break;
1941 case 'h':
1942 buf = build_help(history_keys);
1943 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0, 0,
1944 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
1945 break;
1946 case 'e':
1947 buf = build_help(edit_keys);
1948 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0, 0,
1949 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
1950 break;
1951 case 'g':
1952 buf = build_help(global_keys);
1953 construct_message(GAME_HELP_GAME_TITLE, ANYKEY, 0, 0,
1954 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
1955 break;
1956 default:
1957 break;
1961 void do_more_help(WIN *win)
1963 if (win->c == KEY_F(1) || win->c == CTRL('g'))
1964 construct_message(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT, 0, 0,
1965 NULL, NULL, NULL, do_main_help, 0, 0, "%s", mainhelp);
1968 void do_play_help()
1970 char *buf = build_help(play_keys);
1972 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0, 0, NULL, NULL, buf,
1973 do_more_help, 0, 1, "%s", buf);
1976 void do_play_history_mode()
1978 struct userdata_s *d = game[gindex]->data;
1980 if (!pgn_history_total(game[gindex]->hp) ||
1981 (d->engine && d->engine->status == ENGINE_THINKING))
1982 return;
1984 d->mode = MODE_HISTORY;
1985 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
1986 update_all(game[gindex]);
1989 void do_play_edit_mode()
1991 struct userdata_s *d = game[gindex]->data;
1993 if (pgn_history_total(game[gindex]->hp))
1994 return;
1996 pgn_board_init_fen(game[gindex], d->b, NULL);
1997 config.details++;
1998 d->mode = MODE_EDIT;
1999 update_all(game[gindex]);
2002 void do_edit_insert_finalize(WIN *win)
2004 struct userdata_s *d = win->data;
2006 if (pgn_piece_to_int(win->c) == -1)
2007 return;
2009 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = win->c;
2012 void do_edit_select()
2014 struct userdata_s *d = game[gindex]->data;
2016 if (d->sp.icon)
2017 return;
2019 d->sp.icon = d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon;
2021 if (pgn_piece_to_int(d->sp.icon) == OPEN_SQUARE) {
2022 d->sp.icon = 0;
2023 return;
2026 d->sp.srow = d->c_row;
2027 d->sp.scol = d->c_col;
2030 void do_edit_commit()
2032 int p;
2033 struct userdata_s *d = game[gindex]->data;
2035 pushkey = keycount = 0;
2036 update_status_notify(game[gindex], NULL);
2038 if (!d->sp.icon)
2039 return;
2041 d->sp.row = d->c_row;
2042 d->sp.col = d->c_col;
2043 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
2044 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
2045 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2046 pgn_int_to_piece(game[gindex]->turn, OPEN_SQUARE);
2047 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2050 void do_edit_delete()
2052 struct userdata_s *d = game[gindex]->data;
2054 if (d->sp.icon)
2055 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2056 pgn_int_to_piece(game[gindex]->turn, OPEN_SQUARE);
2057 else
2058 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2059 pgn_int_to_piece(game[gindex]->turn, OPEN_SQUARE);
2061 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2064 void do_edit_cancel_selected()
2066 struct userdata_s *d = game[gindex]->data;
2068 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2069 keycount = 0;
2070 update_status_notify(game[gindex], NULL);
2073 void do_edit_switch_turn()
2075 pgn_switch_turn(game[gindex]);
2076 update_all(game[gindex]);
2079 void do_edit_toggle_castle()
2081 struct userdata_s *d = game[gindex]->data;
2083 castling_state(game[gindex], d->b, RANKTOBOARD(d->c_row),
2084 FILETOBOARD(d->c_col),
2085 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2088 void do_edit_insert()
2090 struct userdata_s *d = game[gindex]->data;
2092 construct_message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, 0, 0, NULL, NULL,
2093 d->b, do_edit_insert_finalize, 0, 0, "%s", GAME_EDIT_TEXT);
2096 void do_edit_enpassant()
2098 struct userdata_s *d = game[gindex]->data;
2100 if (d->c_row == 6 || d->c_row == 3) {
2101 pgn_reset_enpassant(d->b);
2102 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2106 void do_edit_help()
2108 char *buf = build_help(edit_keys);
2110 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0, 0, NULL, NULL, buf,
2111 do_more_help, 0, 1, "%s", buf);
2114 void do_edit_exit()
2116 struct userdata_s *d = game[gindex]->data;
2118 config.details--;
2119 pgn_tag_add(&game[gindex]->tag, "FEN", pgn_game_to_fen(game[gindex], d->b));
2120 pgn_tag_add(&game[gindex]->tag, "SetUp", "1");
2121 pgn_tag_sort(game[gindex]->tag);
2122 d->mode = MODE_PLAY;
2123 update_all(game[gindex]);
2126 void really_do_annotate_finalize(struct input_data_s *in,
2127 struct userdata_s *d)
2129 HISTORY *h = in->data;
2130 int len;
2132 if (!in->str) {
2133 if (h->comment) {
2134 free(h->comment);
2135 h->comment = NULL;
2138 else {
2139 len = strlen(in->str) + 1;
2140 h->comment = Realloc(h->comment, len);
2141 strncpy(h->comment, in->str, len);
2144 free(in->str);
2145 free(in);
2146 SET_FLAG(d->flags, CF_MODIFIED);
2147 update_all(game[gindex]);
2150 void do_annotate_finalize(WIN *win)
2152 struct userdata_s *d = game[gindex]->data;
2153 struct input_data_s *in = win->data;
2155 really_do_annotate_finalize(in, d);
2158 void do_find_move_exp_finalize(int init, int which)
2160 int n;
2161 struct userdata_s *d = game[gindex]->data;
2162 static int firstrun;
2163 static regex_t r;
2164 int ret;
2165 char errbuf[255];
2167 if (init || !firstrun) {
2168 if (!firstrun)
2169 regfree(&r);
2171 if ((ret = regcomp(&r, moveexp, REG_EXTENDED|REG_NOSUB)) != 0) {
2172 regerror(ret, &r, errbuf, sizeof(errbuf));
2173 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2174 return;
2177 firstrun = 1;
2180 if ((n = find_move_exp(game[gindex], r,
2181 (which == -1) ? 0 : 1, (keycount) ? keycount : 1)) == -1)
2182 return;
2184 game[gindex]->hindex = n;
2185 pgn_board_update(game[gindex], d->b, game[gindex]->hindex);
2186 update_all(game[gindex]);
2189 void do_find_move_exp(WIN *win)
2191 struct input_data_s *in = win->data;
2192 int *n = in->data;
2193 int which = *n;
2195 if (in->str) {
2196 strncpy(moveexp, in->str, sizeof(moveexp));
2197 do_find_move_exp_finalize(1, which);
2198 free(in->str);
2201 free(in->data);
2202 free(in);
2205 void do_move_jump_finalize(int n)
2207 struct userdata_s *d = game[gindex]->data;
2209 if (n < 0 || n > (pgn_history_total(game[gindex]->hp) / 2))
2210 return;
2212 keycount = 0;
2213 update_status_notify(game[gindex], NULL);
2214 game[gindex]->hindex = (n) ? n * 2 - 1 : n * 2;
2215 pgn_board_update(game[gindex], d->b, game[gindex]->hindex);
2216 update_all(game[gindex]);
2219 void do_move_jump(WIN *win)
2221 struct input_data_s *in = win->data;
2223 if (!in->str || !isinteger(in->str)) {
2224 if (in->str)
2225 free(in->str);
2227 free(in);
2228 return;
2231 do_move_jump_finalize(atoi(in->str));
2232 free(in->str);
2233 free(in);
2236 struct history_menu_s {
2237 char *line;
2238 int hindex;
2239 int ravlevel;
2240 int move;
2241 int indent;
2244 void free_history_menu_data(struct history_menu_s **h)
2246 int i;
2248 if (!h)
2249 return;
2251 for (i = 0; h[i]; i++) {
2252 free(h[i]->line);
2253 free(h[i]);
2256 free(h);
2259 void get_history_data(HISTORY **hp, struct history_menu_s ***menu, int m,
2260 int turn)
2262 int i, n = 0;
2263 int t = pgn_history_total(hp);
2264 char buf[MAX_SAN_MOVE_LEN + 4];
2265 static int depth;
2266 struct history_menu_s **hmenu = *menu;
2268 if (hmenu)
2269 for (n = 0; hmenu[n]; n++);
2270 else
2271 depth = 0;
2273 for (i = 0; i < t; i++) {
2274 hmenu = Realloc(hmenu, (n + 2) * sizeof(struct history_menu_s *));
2275 hmenu[n] = Malloc(sizeof(struct history_menu_s));
2276 snprintf(buf, sizeof(buf), "%c%s%s", (turn == WHITE) ? 'W' : 'B',
2277 hp[i]->move, (hp[i]->comment || hp[i]->nag[0]) ? " !" : "");
2278 hmenu[n]->line = strdup(buf);
2279 hmenu[n]->hindex = i;
2280 hmenu[n]->indent = 0;
2281 hmenu[n]->ravlevel = depth;
2282 hmenu[n]->move = (n && depth > hmenu[n-1]->ravlevel) ? m++ : m;
2283 n++;
2284 hmenu[n] = NULL;
2286 #if 0
2287 if (hp[i]->rav) {
2288 depth++;
2289 get_history_data(hp[i]->rav, &hmenu, m, turn);
2290 for (n = 0; hmenu[n]; n++);
2291 depth--;
2293 if (depth)
2294 m--;
2296 #endif
2298 turn = (turn == WHITE) ? BLACK : WHITE;
2301 *menu = hmenu;
2304 void history_draw_update(struct menu_input_s *m)
2306 GAME g = m->data;
2307 struct userdata_s *d = g->data;
2309 g->hindex = m->selected + 1;
2310 update_cursor(g, m->selected);
2311 pgn_board_update(g, d->b, m->selected + 1);
2314 struct menu_item_s **get_history_items(WIN *win)
2316 struct menu_input_s *m = win->data;
2317 GAME g = m->data;
2318 struct userdata_s *d = g->data;
2319 struct history_menu_s **hm = d->data;
2320 struct menu_item_s **items = m->items;
2321 int i;
2323 if (!hm) {
2324 get_history_data(g->history, &hm, 0,
2325 TEST_FLAG(g->flags, GF_BLACK_OPENING));
2326 m->selected = g->hindex - 1;
2328 if (m->selected < 0)
2329 m->selected = 0;
2331 m->draw_exit_func = history_draw_update;
2334 d->data = hm;
2336 if (items) {
2337 for (i = 0; items[i]; i++)
2338 free(items[i]);
2340 free(items);
2341 items = NULL;
2344 for (i = 0; hm[i]; i++) {
2345 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
2346 items[i] = Malloc(sizeof(struct menu_item_s));
2347 items[i]->name = hm[i]->line;
2348 items[i]->value = NULL;
2349 items[i]->selected = 0;
2352 if (items)
2353 items[i] = NULL;
2355 m->nofree = 1;
2356 m->items = items;
2357 return items;
2360 void history_menu_quit(struct menu_input_s *m)
2362 pushkey = -1;
2365 void history_menu_exit(WIN *win)
2367 GAME g = win->data;
2368 struct userdata_s *d = g->data;
2369 struct history_menu_s **hm = d->data;
2370 int i;
2372 if (!hm)
2373 return;
2375 for (i = 0; hm[i]; i++) {
2376 free(hm[i]->line);
2377 free(hm[i]);
2380 free(hm);
2381 d->data = NULL;
2384 // FIXME RAV
2385 void history_menu_next(struct menu_input_s *m)
2387 GAME g = m->data;
2388 struct userdata_s *d = g->data;
2389 struct history_menu_s **hm = d->data;
2390 int n, t;
2392 for (t = 0; hm[t]; t++);
2394 if (m->selected + 1 == t)
2395 n = 0;
2396 else
2397 n = hm[m->selected + 1]->hindex;
2399 n++;
2400 g->hindex = n;
2403 // FIXME RAV
2404 void history_menu_prev(struct menu_input_s *m)
2406 GAME g = m->data;
2407 struct userdata_s *d = g->data;
2408 struct history_menu_s **hm = d->data;
2409 int n, t;
2411 for (t = 0; hm[t]; t++);
2413 if (m->selected - 1 < 0)
2414 n = t - 1;
2415 else
2416 n = hm[m->selected - 1]->hindex;
2418 n++;
2419 g->hindex = n;
2422 void history_menu_help(struct menu_input_s *m)
2424 message("History Menu Help", ANYKEY, "%s", history_menu_help_str);
2427 void do_annotate_move(HISTORY *hp)
2429 char buf[COLS - 4];
2430 struct input_data_s *in;
2432 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE, hp->move);
2433 in = Calloc(1, sizeof(struct input_data_s));
2434 in->data = hp;
2435 in->efunc = do_annotate_finalize;
2436 construct_input(buf, hp->comment, MAX_PGN_LINE_LEN / INPUT_WIDTH, 0,
2437 NAG_PROMPT, edit_nag, NULL, CTRL('T'), in, -1);
2440 void history_menu_view_annotation(struct menu_input_s *m)
2442 GAME g = m->data;
2444 // FIXME RAV
2445 view_annotation(g->history[m->selected]);
2448 void history_menu_annotate_finalize(WIN *win)
2450 struct input_data_s *in = win->data;
2451 GAME g = in->moredata;
2452 struct userdata_s *d = g->data;
2453 struct history_menu_s **hm = d->data;
2455 really_do_annotate_finalize(in, d);
2456 free_history_menu_data(hm);
2457 hm = NULL;
2458 get_history_data(g->history, &hm, 0, TEST_FLAG(g->flags, GF_BLACK_OPENING));
2459 d->data = hm;
2460 pushkey = REFRESH_MENU;
2463 void history_menu_annotate(struct menu_input_s *m)
2465 GAME g = m->data;
2466 char buf[COLS - 4];
2467 struct input_data_s *in;
2468 HISTORY *hp = g->history[m->selected]; // FIXME RAV
2470 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE, hp->move);
2471 in = Calloc(1, sizeof(struct input_data_s));
2472 in->data = hp;
2473 in->moredata = m->data;
2474 in->efunc = history_menu_annotate_finalize;
2475 construct_input(buf, hp->comment, MAX_PGN_LINE_LEN / INPUT_WIDTH, 0,
2476 NAG_PROMPT, edit_nag, NULL, CTRL('T'), in, -1);
2479 void history_menu_details(struct menu_input_s *m)
2481 do_board_details();
2484 // FIXME RAV
2485 void history_menu_print(WIN *win)
2487 struct menu_input_s *m = win->data;
2488 GAME g = m->data;
2489 struct userdata_s *d = g->data;
2490 struct history_menu_s **hm = d->data;
2491 struct history_menu_s *h = hm[m->top];
2492 int i;
2493 char *p = m->item->name;
2494 int line = m->print_line - 2;
2496 * Solaris 5.9 doesn't have wattr_get() or any function that requires an
2497 * attr_t data type.
2499 #ifdef HAVE_ATTR_T
2500 attr_t attrs;
2501 short pair;
2502 #endif
2503 int total;
2505 for (total = 0; hm[total]; total++);
2506 #ifdef HAVE_ATTR_T
2507 wattr_get(win->w, &attrs, &pair, NULL);
2508 wattroff(win->w, COLOR_PAIR(pair));
2509 #endif
2510 mvwaddch(win->w, m->print_line, 1, *p++);
2512 if (h->hindex == 0 && line == 0)
2513 waddch(win->w, ACS_ULCORNER | CP_HISTORY_MENU_LG);
2514 else if ((!hm[h->hindex + (win->rows - 5) + 1] && line == win->rows - 5) ||
2515 (m->top + line == total - 1))
2516 waddch(win->w, ACS_LLCORNER | CP_HISTORY_MENU_LG);
2517 else if (hm[m->top + 1]->ravlevel != h->ravlevel || !h->ravlevel)
2518 waddch(win->w, ACS_LTEE | CP_HISTORY_MENU_LG);
2519 else
2520 waddch(win->w, ACS_VLINE | CP_HISTORY_MENU_LG);
2522 #ifdef HAVE_ATTR_T
2523 wattron(win->w, COLOR_PAIR(pair) | attrs);
2524 #endif
2526 for (i = 2; *p; p++, i++)
2527 waddch(win->w, (*p == '!') ? *p | A_BOLD : *p);
2529 while (i++ < win->cols - 2)
2530 waddch(win->w, ' ');
2533 void history_menu(GAME g)
2535 struct menu_key_s **keys = NULL;
2537 add_menu_key(&keys, KEY_ESCAPE, history_menu_quit);
2538 add_menu_key(&keys, KEY_UP, history_menu_prev);
2539 add_menu_key(&keys, KEY_DOWN, history_menu_next);
2540 add_menu_key(&keys, KEY_F(1), history_menu_help);
2541 add_menu_key(&keys, CTRL('a'), history_menu_annotate);
2542 add_menu_key(&keys, CTRL('d'), history_menu_details);
2543 add_menu_key(&keys, '\n', history_menu_view_annotation);
2544 construct_menu(LINES, TAG_WIDTH, 0, 0, HISTORY_MENU_TITLE, 1,
2545 get_history_items, keys, g, history_menu_print, history_menu_exit);
2548 void do_history_menu()
2550 history_menu(game[gindex]);
2553 void do_history_half_move_toggle()
2555 movestep = (movestep == 1) ? 2 : 1;
2556 update_history_window(game[gindex]);
2559 void do_history_jump_next()
2561 struct userdata_s *d = game[gindex]->data;
2563 pgn_history_next(game[gindex], d->b, (keycount > 0) ?
2564 config.jumpcount * keycount * movestep :
2565 config.jumpcount * movestep);
2566 update_all(game[gindex]);
2569 void do_history_jump_prev()
2571 struct userdata_s *d = game[gindex]->data;
2573 pgn_history_prev(game[gindex], d->b, (keycount) ?
2574 config.jumpcount * keycount * movestep :
2575 config.jumpcount * movestep);
2576 update_all(game[gindex]);
2579 void do_history_prev()
2581 struct userdata_s *d = game[gindex]->data;
2583 pgn_history_prev(game[gindex], d->b,
2584 (keycount) ? keycount * movestep : movestep);
2585 update_all(game[gindex]);
2588 void do_history_next()
2590 struct userdata_s *d = game[gindex]->data;
2592 pgn_history_next(game[gindex], d->b, (keycount) ?
2593 keycount * movestep : movestep);
2594 update_all(game[gindex]);
2597 void do_history_mode_finalize(struct userdata_s *d)
2599 pushkey = 0;
2600 d->mode = MODE_PLAY;
2601 update_all(game[gindex]);
2604 void do_history_mode_confirm(WIN *win)
2606 struct userdata_s *d = game[gindex]->data;
2608 switch (win->c) {
2609 case 'R':
2610 case 'r':
2611 pgn_history_free(game[gindex]->hp,
2612 game[gindex]->hindex);
2613 pgn_board_update(game[gindex], d->b,
2614 pgn_history_total(game[gindex]->hp));
2615 break;
2616 #if 0
2617 case 'C':
2618 case 'c':
2619 if (pgn_history_rav_new(game[gindex], d->b,
2620 game[gindex]->hindex) != E_PGN_OK)
2621 return;
2623 break;
2624 #endif
2625 default:
2626 return;
2629 if (!TEST_FLAG(d->flags, CF_HUMAN))
2630 add_engine_command(game[gindex], ENGINE_READY,
2631 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2633 do_history_mode_finalize(d);
2636 void do_history_toggle()
2638 struct userdata_s *d = game[gindex]->data;
2640 // FIXME Resuming from previous history could append to a RAV.
2641 if (game[gindex]->hindex != pgn_history_total(game[gindex]->hp)) {
2642 if (!pushkey)
2643 construct_message(NULL, "(r)esume or abort", 0, 1, NULL, NULL, NULL,
2644 do_history_mode_confirm, 0, 0, "%s",
2645 GAME_RESUME_HISTORY_TEXT);
2647 return;
2649 else {
2650 if (TEST_FLAG(game[gindex]->flags, GF_GAMEOVER))
2651 return;
2654 do_history_mode_finalize(d);
2657 void do_history_annotate()
2659 int n = game[gindex]->hindex;
2661 if (n && game[gindex]->hp[n - 1]->move)
2662 n--;
2663 else
2664 return;
2666 do_annotate_move(game[gindex]->hp[n]);
2669 void do_history_help()
2671 char *buf = build_help(history_keys);
2673 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0, 0, NULL, NULL, buf,
2674 do_more_help, 0, 1, "%s", buf);
2677 void do_history_find(int which)
2679 struct input_data_s *in;
2680 int *p;
2682 if (pgn_history_total(game[gindex]->hp) < 2)
2683 return;
2685 in = Calloc(1, sizeof(struct input_data_s));
2686 p = Malloc(sizeof(int));
2687 *p = which;
2688 in->data = p;
2689 in->efunc = do_find_move_exp;
2691 if (!*moveexp || which == 0) {
2692 construct_input(FIND_REGEXP, moveexp, 1, 0, NULL, NULL, NULL,
2693 0, in, -1);
2694 return;
2697 do_find_move_exp_finalize(0, which);
2700 void do_history_find_new()
2702 do_history_find(0);
2705 void do_history_find_prev()
2707 do_history_find(-1);
2710 void do_history_find_next()
2712 do_history_find(1);
2715 void do_history_rav(int which)
2717 struct userdata_s *d = game[gindex]->data;
2719 rav_next_prev(game[gindex], d->b, which);
2720 update_all(game[gindex]);
2723 void do_history_rav_next()
2725 do_history_rav(1);
2728 void do_history_rav_prev()
2730 do_history_rav(0);
2733 void do_history_jump()
2735 struct input_data_s *in;
2737 if (pgn_history_total(game[gindex]->hp) < 2)
2738 return;
2740 if (!keycount) {
2741 in = Calloc(1, sizeof(struct input_data_s));
2742 in->efunc = do_move_jump;
2744 construct_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1, NULL,
2745 NULL, NULL, 0, in, 0);
2746 return;
2749 do_move_jump_finalize(keycount);
2752 static void free_userdata_once(GAME g)
2754 struct userdata_s *d = g->data;
2756 if (!d)
2757 return;
2759 if (d->engine) {
2760 stop_engine(g);
2762 if (d->engine->enginebuf) {
2763 int n;
2765 for (n = 0; d->engine->enginebuf[n]; n++)
2766 free(d->engine->enginebuf[n]);
2768 free(d->engine->enginebuf);
2771 free(d->engine);
2774 free(d);
2775 g->data = NULL;
2778 static void free_userdata()
2780 int i;
2782 for (i = 0; i < gtotal; i++) {
2783 free_userdata_once(game[i]);
2784 game[i]->data = NULL;
2788 void update_loading_window(int n)
2790 if (!loadingw) {
2791 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2792 loadingp = new_panel(loadingw);
2793 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2796 wmove(loadingw, 0, 0);
2797 wclrtobot(loadingw);
2798 wattron(loadingw, CP_MESSAGE_BORDER);
2799 box(loadingw, ACS_VLINE, ACS_HLINE);
2800 wattroff(loadingw, CP_MESSAGE_BORDER);
2801 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2802 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
2803 gtotal);
2804 refresh_all();
2807 static void init_userdata_once(GAME g, int n)
2809 struct userdata_s *d = NULL;
2811 d = Calloc(1, sizeof(struct userdata_s));
2812 d->n = n;
2813 d->c_row = 2, d->c_col = 5;
2814 SET_FLAG(d->flags, CF_NEW);
2815 g->data = d;
2817 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
2818 pgn_board_init(d->b);
2821 void init_userdata()
2823 int i;
2825 for (i = 0; i < gtotal; i++)
2826 init_userdata_once(game[i], i);
2829 void fix_marks(int *start, int *end)
2831 int i;
2833 *start = (*start < 0) ? 0 : *start;
2834 *end = (*end < 0) ? 0 : *end;
2836 if (*start > *end) {
2837 i = *start;
2838 *start = *end;
2839 *end = i + 1;
2842 *end = (*end > gtotal) ? gtotal : *end;
2845 void do_new_game_finalize(GAME g)
2847 struct userdata_s *d = g->data;
2849 d->mode = MODE_PLAY;
2850 update_status_notify(g, NULL);
2851 update_all(g);
2854 void do_new_game_from_scratch(WIN *win)
2856 if (tolower(win->c) != 'y')
2857 return;
2859 stop_clock();
2860 free_userdata();
2861 pgn_parse(NULL);
2862 add_custom_tags(&game[gindex]->tag);
2863 init_userdata();
2864 loadfile[0] = 0;
2865 do_new_game_finalize(game[gindex]);
2868 void do_new_game()
2870 pgn_new_game();
2871 add_custom_tags(&game[gindex]->tag);
2872 init_userdata_once(game[gindex], gindex);
2873 do_new_game_finalize(game[gindex]);
2876 void do_game_delete_finalize(int n)
2878 struct userdata_s *d;
2880 delete_game((!n) ? gindex : -1);
2881 d = game[gindex]->data;
2882 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
2883 update_all(game[gindex]);
2886 void do_game_delete_confirm(WIN *win)
2888 int *n;
2890 if (tolower(win->c) != 'y') {
2891 free(win->data);
2892 return;
2896 n = (int *)win->data;
2897 do_game_delete_finalize(*n);
2898 free(win->data);
2901 void do_game_delete()
2903 char *tmp = NULL;
2904 int i, n;
2905 struct userdata_s *d;
2906 int *p;
2908 if (gtotal < 2) {
2909 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2910 return;
2913 tmp = NULL;
2915 for (i = n = 0; i < gtotal; i++) {
2916 d = game[i]->data;
2918 if (TEST_FLAG(d->flags, CF_DELETE))
2919 n++;
2922 if (!n)
2923 tmp = GAME_DELETE_GAME_TEXT;
2924 else {
2925 if (n == gtotal) {
2926 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2927 return;
2930 tmp = GAME_DELETE_ALL_TEXT;
2933 if (config.deleteprompt) {
2934 p = Malloc(sizeof(int));
2935 *p = n;
2936 construct_message(NULL, YESNO, 1, 1, NULL, NULL, p,
2937 do_game_delete_confirm, 0, 0, tmp);
2938 return;
2941 do_game_delete_finalize(n);
2944 void do_find_game_exp_finalize(int which)
2946 struct userdata_s *d = game[gindex]->data;
2947 int n;
2949 if ((n = find_game_exp(gameexp, (which == -1) ? 0 : 1,
2950 (keycount) ? keycount : 1)) == -1)
2951 return;
2953 gindex = n;
2954 d = game[gindex]->data;
2956 if (pgn_history_total(game[gindex]->hp))
2957 d->mode = MODE_HISTORY;
2959 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
2960 update_all(game[gindex]);
2963 void do_find_game_exp(WIN *win)
2965 struct input_data_s *in = win->data;
2966 int *n = in->data;
2967 int c = *n;
2969 if (in->str) {
2970 strncpy(gameexp, in->str, sizeof(gameexp));
2972 if (c == '?')
2973 c = '}';
2975 do_find_game_exp_finalize(c);
2976 free(in->str);
2979 free(in->data);
2980 free(in);
2983 void do_game_jump_finalize(int n)
2985 struct userdata_s *d;
2987 if (--n > gtotal - 1 || n < 0)
2988 return;
2990 gindex = n;
2991 d = game[gindex]->data;
2992 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
2993 update_status_notify(game[gindex], NULL);
2994 update_all(game[gindex]);
2997 void do_game_jump(WIN *win)
2999 struct input_data_s *in = win->data;
3001 if (!in->str || !isinteger(in->str)) {
3002 if (in->str)
3003 free(in->str);
3005 free(in);
3006 return;
3009 do_game_jump_finalize(atoi(in->str));
3010 free(in->str);
3011 free(in);
3014 void do_load_file(WIN *win)
3016 FILE *fp;
3017 struct input_data_s *in = win->data;
3018 char *tmp = in->str;
3019 struct userdata_s *d;
3021 if (!in->str) {
3022 free(in);
3023 return;
3026 if ((tmp = pathfix(tmp)) == NULL)
3027 goto done;
3029 if ((fp = pgn_open(tmp)) == NULL) {
3030 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3031 goto done;
3034 free_userdata();
3037 * FIXME what is the game state after a parse error?
3039 if (pgn_parse(fp) == E_PGN_ERR) {
3040 del_panel(loadingp);
3041 delwin(loadingw);
3042 loadingw = NULL;
3043 loadingp = NULL;
3044 init_userdata();
3045 update_all(game[gindex]);
3046 goto done;
3049 del_panel(loadingp);
3050 delwin(loadingw);
3051 loadingw = NULL;
3052 loadingp = NULL;
3053 init_userdata();
3054 strncpy(loadfile, tmp, sizeof(loadfile));
3055 d = game[gindex]->data;
3057 if (pgn_history_total(game[gindex]->hp))
3058 d->mode = MODE_HISTORY;
3060 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
3061 update_all(game[gindex]);
3063 done:
3064 if (in->str)
3065 free(in->str);
3067 free(in);
3070 void do_game_save(WIN *win)
3072 struct input_data_s *in = win->data;
3073 int *x = in->data;
3074 int n = *x;
3075 char *tmp = in->str;
3076 char tfile[FILENAME_MAX];
3077 char *p;
3079 if (!tmp || (tmp = pathfix(tmp)) == NULL)
3080 goto done;
3082 if (pgn_is_compressed(tmp)) {
3083 p = tmp + strlen(tmp) - 1;
3085 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3086 *(p-3) != '.') {
3087 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3088 tmp = tfile;
3091 else {
3092 if ((p = strchr(tmp, '.')) != NULL) {
3093 if (strcmp(p, ".pgn") != 0) {
3094 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3095 tmp = tfile;
3098 else {
3099 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3100 tmp = tfile;
3104 save_pgn(tmp, n);
3106 done:
3107 if (in->str)
3108 free(in->str);
3110 free(in->data);
3111 free(in);
3114 void do_get_game_save_input(int n)
3116 struct input_data_s *in = Calloc(1, sizeof(struct input_data_s));
3117 int *p = Malloc(sizeof(int));
3119 in->efunc = do_game_save;
3120 *p = n;
3121 in->data = p;
3123 construct_input(GAME_SAVE_TITLE, loadfile, 1, 1, BROWSER_PROMPT,
3124 file_browser, NULL, '\t', in, -1);
3127 void do_game_save_multi_confirm(WIN *win)
3129 int i;
3131 if (win->c == 'c')
3132 i = gindex;
3133 else if (win->c == 'a')
3134 i = -1;
3135 else {
3136 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3137 return;
3140 do_get_game_save_input(i);
3143 void do_global_about()
3145 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
3146 "and %i color pairs\nCopyright 2002-2007 %s",
3147 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
3148 COLOR_PAIRS, PACKAGE_BUGREPORT);
3151 void global_game_next_prev(int which)
3153 struct userdata_s *d;
3155 game_next_prev(game[gindex], (which == 1) ? 1 : 0,
3156 (keycount) ? keycount : 1);
3157 d = game[gindex]->data;
3159 if (delete_count) {
3160 if (which == 1) {
3161 markend = markstart + delete_count;
3162 delete_count = 0;
3164 else {
3165 markend = markstart - delete_count;
3166 delete_count = -1; // to fix gindex in the other direction
3169 pushkey = 'x';
3170 fix_marks(&markstart, &markend);
3173 if (d->mode == MODE_HISTORY)
3174 pgn_board_update(game[gindex], d->b, game[gindex]->hindex);
3175 else if (d->mode == MODE_PLAY)
3176 pgn_board_update(game[gindex], d->b, pgn_history_total(game[gindex]->hp));
3178 update_status_notify(game[gindex], NULL);
3179 update_all(game[gindex]);
3182 void do_global_next_game()
3184 global_game_next_prev(1);
3187 void do_global_prev_game()
3189 global_game_next_prev(0);
3192 void global_find(int which)
3194 struct input_data_s *in;
3195 int *p;
3197 if (gtotal < 2)
3198 return;
3200 in = Calloc(1, sizeof(struct input_data_s));
3201 p = Malloc(sizeof(int));
3202 *p = which;
3203 in->data = p;
3204 in->efunc = do_find_game_exp;
3206 if (!*gameexp || which == 0) {
3207 construct_input(GAME_FIND_EXPRESSION_TITLE, gameexp, 1, 0,
3208 GAME_FIND_EXPRESSION_PROMPT, NULL, NULL, 0, in, -1);
3209 return;
3212 do_find_game_exp_finalize(which);
3215 void do_global_find_new()
3217 global_find(0);
3220 void do_global_find_next()
3222 global_find(1);
3225 void do_global_find_prev()
3227 global_find(-1);
3230 void do_global_game_jump()
3232 struct input_data_s *in;
3234 if (gtotal < 2)
3235 return;
3237 in = Calloc(1, sizeof(struct input_data_s));
3238 in->efunc = do_game_jump;
3240 if (!keycount) {
3241 construct_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL, NULL, 0, in,
3243 return;
3246 do_game_jump_finalize(keycount);
3249 void do_global_toggle_delete()
3251 int i;
3253 pushkey = 0;
3255 if (gtotal < 2)
3256 return;
3258 if (keycount && delete_count == 0) {
3259 markstart = gindex;
3260 delete_count = keycount;
3261 update_status_notify(game[gindex], "%s (delete)", status.notify);
3262 return;
3265 if (markstart >= 0 && markend >= 0) {
3266 for (i = markstart; i < markend; i++) {
3267 if (toggle_delete_flag(i)) {
3268 update_all(game[gindex]);
3269 return;
3273 gindex = (delete_count < 0) ? markstart : i - 1;
3274 update_all(game[gindex]);
3276 else {
3277 if (toggle_delete_flag(gindex))
3278 return;
3281 markstart = markend = -1;
3282 delete_count = 0;
3283 update_status_window(game[gindex]);
3286 void do_global_delete_game()
3288 do_game_delete();
3291 void do_global_tag_edit()
3293 struct userdata_s *d = game[gindex]->data;
3295 edit_tags(game[gindex], d->b, 1);
3298 void do_global_tag_view()
3300 struct userdata_s *d = game[gindex]->data;
3302 edit_tags(game[gindex], d->b, 0);
3305 void do_global_resume_game()
3307 struct input_data_s *in;
3309 in = Calloc(1, sizeof(struct input_data_s));
3310 in->efunc = do_load_file;
3311 construct_input(GAME_LOAD_TITLE, NULL, 1, 1, BROWSER_PROMPT, file_browser,
3312 NULL, '\t', in, -1);
3315 void do_global_save_game()
3317 if (gtotal > 1) {
3318 construct_message(NULL, GAME_SAVE_MULTI_PROMPT, 1, 1, NULL, NULL, NULL,
3319 do_game_save_multi_confirm, 0, 0, "%s", GAME_SAVE_MULTI_TEXT);
3320 return;
3323 do_get_game_save_input(-1);
3326 void do_global_new_game()
3328 do_new_game();
3331 void do_global_copy_game()
3333 int g = gindex;
3334 int i, n;
3335 struct userdata_s *d;
3337 do_global_new_game();
3338 n = pgn_history_total(game[g]->history);
3340 // FIXME RAV
3341 for (i = 0; i < n; i++)
3342 pgn_history_add(game[gindex], game[g]->history[i]->move);
3344 n = pgn_tag_total(game[g]->tag);
3346 for (i = 0; i < n; i++)
3347 pgn_tag_add(&game[gindex]->tag, game[g]->tag[i]->name,
3348 game[g]->tag[i]->value);
3350 d = game[gindex]->data;
3351 pgn_board_update(game[gindex], d->b,
3352 pgn_history_total(game[gindex]->hp));
3355 void do_global_new_all()
3357 construct_message(NULL, YESNO, 1, 1, NULL, NULL, NULL,
3358 do_new_game_from_scratch, 0, 0, "%s", GAME_NEW_PROMPT);
3361 void do_global_quit()
3363 quit = 1;
3366 void do_global_toggle_engine_window()
3368 if (!enginew) {
3369 enginew = newwin(LINES, COLS, 0, 0);
3370 enginep = new_panel(enginew);
3371 window_draw_title(enginew, ENGINE_IO_TITLE, COLS, CP_MESSAGE_TITLE,
3372 CP_MESSAGE_BORDER);
3373 hide_panel(enginep);
3376 if (panel_hidden(enginep)) {
3377 update_engine_window(game[gindex]);
3378 top_panel(enginep);
3379 refresh_all();
3381 else {
3382 hide_panel(enginep);
3383 refresh_all();
3387 void do_global_toggle_board_details()
3389 do_board_details();
3392 void do_global_toggle_strict_castling()
3394 do_toggle_strict_castling();
3397 // Global and other keys.
3398 static int globalkeys()
3400 struct userdata_s *d = game[gindex]->data;
3401 int i;
3404 * These cannot be modified and other game mode keys cannot conflict with
3405 * these.
3407 switch (input_c) {
3408 case CTRL('L'):
3409 endwin();
3410 keypad(boardw, TRUE);
3411 refresh_all();
3412 return 1;
3413 case KEY_ESCAPE:
3414 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3415 markend = markstart = 0;
3417 if (keycount) {
3418 keycount = 0;
3419 update_status_notify(game[gindex], NULL);
3422 if (config.validmoves)
3423 pgn_reset_valid_moves(d->b);
3425 return 1;
3426 case '0' ... '9':
3427 i = input_c - '0';
3429 if (keycount)
3430 keycount = keycount * 10 + i;
3431 else
3432 keycount = i;
3434 update_status_notify(game[gindex], "Repeat %i", keycount);
3435 return -1;
3436 case KEY_UP:
3437 if (d->mode == MODE_HISTORY)
3438 return 0;
3440 if (keycount) {
3441 d->c_row += keycount;
3442 pushkey = '\n';
3444 else
3445 d->c_row++;
3447 if (d->c_row > 8)
3448 d->c_row = 1;
3450 return 1;
3451 case KEY_DOWN:
3452 if (d->mode == MODE_HISTORY)
3453 return 0;
3455 if (keycount) {
3456 d->c_row -= keycount;
3457 pushkey = '\n';
3458 update_status_notify(game[gindex], NULL);
3460 else
3461 d->c_row--;
3463 if (d->c_row < 1)
3464 d->c_row = 8;
3466 return 1;
3467 case KEY_LEFT:
3468 if (d->mode == MODE_HISTORY)
3469 return 0;
3471 if (keycount) {
3472 d->c_col -= keycount;
3473 pushkey = '\n';
3475 else
3476 d->c_col--;
3478 if (d->c_col < 1)
3479 d->c_col = 8;
3481 return 1;
3482 case KEY_RIGHT:
3483 if (d->mode == MODE_HISTORY)
3484 return 0;
3486 if (keycount) {
3487 d->c_col += keycount;
3488 pushkey = '\n';
3490 else
3491 d->c_col++;
3493 if (d->c_col > 8)
3494 d->c_col = 1;
3496 return 1;
3497 #ifdef HAVE_WRESIZE
3498 case KEY_RESIZE:
3499 do_window_resize();
3500 return 1;
3501 #endif
3502 case 0:
3503 default:
3504 for (i = 0; global_keys[i]; i++) {
3505 if (input_c == global_keys[i]->c) {
3506 (*global_keys[i]->f)();
3507 return 1;
3510 break;
3513 return 0;
3516 void game_loop()
3518 int error_recover = 0;
3519 struct userdata_s *d = game[gindex]->data;
3520 int macro_match = -1;
3522 gindex = gtotal - 1;
3524 if (pgn_history_total(game[gindex]->hp))
3525 d->mode = MODE_HISTORY;
3526 else
3527 d->mode = MODE_PLAY;
3529 if (d->mode == MODE_HISTORY) {
3530 pgn_board_update(game[gindex], d->b,
3531 pgn_history_total(game[gindex]->hp));
3534 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3535 movestep = 2;
3536 flushinp();
3537 update_all(game[gindex]);
3538 update_tag_window(game[gindex]->tag);
3539 wtimeout(boardw, WINDOW_TIMEOUT);
3541 while (!quit) {
3542 int n = 0, i;
3543 char fdbuf[8192] = {0};
3544 int len;
3545 struct timeval tv = {0, 0};
3546 fd_set rfds, wfds;
3547 WIN *win = NULL;
3548 WINDOW *wp = NULL;
3550 FD_ZERO(&rfds);
3551 FD_ZERO(&wfds);
3553 for (i = 0; i < gtotal; i++) {
3554 d = game[i]->data;
3556 if (d->engine && d->engine->pid != -1) {
3557 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3558 if (d->engine->fd[ENGINE_IN_FD] > n)
3559 n = d->engine->fd[ENGINE_IN_FD];
3561 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3564 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3565 if (d->engine->fd[ENGINE_OUT_FD] > n)
3566 n = d->engine->fd[ENGINE_OUT_FD];
3568 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3573 if (n) {
3574 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3575 for (i = 0; i < gtotal; i++) {
3576 d = game[i]->data;
3578 if (d->engine && d->engine->pid != -1) {
3579 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3580 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3581 sizeof(fdbuf));
3583 if (len == -1) {
3584 if (errno != EAGAIN) {
3585 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3586 strerror(errno));
3587 waitpid(d->engine->pid, &n, 0);
3588 free(d->engine);
3589 d->engine = NULL;
3590 break;
3593 else {
3594 if (len) {
3595 if (d->engine->iobuf)
3596 d->engine->iobuf = Realloc(d->engine->iobuf, len +
3597 strlen(d->engine->iobuf) + 1);
3598 else
3599 d->engine->iobuf = Calloc(1, len + 1);
3601 memcpy(d->engine->iobuf, &fdbuf, len);
3602 d->engine->iobuf[len] = 0;
3605 * The fdbuf is full and no newline
3606 * was found. So we'll append the next
3607 * read() to this games buffer.
3609 if (d->engine->iobuf[strlen(d->engine->iobuf) - 1] != '\n')
3610 continue;
3612 parse_engine_output(game[i], d->engine->iobuf);
3613 free(d->engine->iobuf);
3614 d->engine->iobuf = NULL;
3619 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3620 if (d->engine->queue)
3621 send_engine_command(game[i]);
3626 else {
3627 if (n == -1)
3628 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3629 /* timeout */
3633 d = game[gindex]->data;
3635 if (TEST_FLAG(game[gindex]->flags, GF_GAMEOVER))
3636 d->mode = MODE_HISTORY;
3638 error_recover = 0;
3639 draw_board(game[gindex]);
3640 update_all(game[gindex]);
3641 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3642 refresh_all();
3645 * Finds the top level window in the window stack so we know what
3646 * window the wgetch()ed key belongs to.
3648 if (wins) {
3649 for (i = 0; wins[i]; i++);
3650 win = wins[i-1];
3651 wp = win->w;
3652 wtimeout(wp, WINDOW_TIMEOUT);
3654 else
3655 wp = boardw;
3657 if (!i && pushkey)
3658 input_c = pushkey;
3659 else {
3660 if (!pushkey) {
3661 if (macros && macro_match >= 0) {
3662 if (macros[macro_match]->n >= macros[macro_match]->total) {
3663 macros[macro_match]->n = 0;
3664 macro_match = -1;
3665 continue;
3667 else
3668 input_c = macros[macro_match]->keys[macros[macro_match]->n++];
3670 else {
3671 if ((input_c = wgetch(wp)) == ERR)
3672 continue;
3675 else
3676 input_c = pushkey;
3678 if (win) {
3679 switch (input_c) {
3680 case CTRL('L'):
3681 endwin();
3682 keypad(boardw, TRUE);
3683 refresh_all();
3684 continue;
3687 win->c = input_c;
3690 * Run the function associated with the window. When the
3691 * function returns 0 win->efunc is ran (if not NULL) with
3692 * win as the one and only parameter. Then the window is
3693 * destroyed.
3695 * The exit function may create another window which will
3696 * mess up the window stack when window_destroy() is called.
3697 * So don't destory the window until the top window is
3698 * destroyable. See window_destroy().
3700 if ((*win->func)(win) == 0) {
3701 if (win->efunc)
3702 (*win->efunc)(win);
3704 win->keep = 1;
3705 window_destroy(win);
3708 continue;
3712 if (!keycount && status.notify)
3713 update_status_notify(game[gindex], NULL);
3715 if (macros && macro_match < 0) {
3716 for (i = 0; macros[i]; i++) {
3717 if ((macros[i]->mode == -1 || macros[i]->mode == d->mode) &&
3718 input_c == macros[i]->c) {
3719 input_c = macros[i]->keys[macros[i]->n++];
3720 macro_match = i;
3721 break;
3726 if ((n = globalkeys()) == 1) {
3727 if (macro_match == -1)
3728 keycount = 0;
3730 continue;
3732 else if (n == -1)
3733 continue;
3735 switch (d->mode) {
3736 case MODE_EDIT:
3737 for (i = 0; edit_keys[i]; i++) {
3738 if (input_c == edit_keys[i]->c) {
3739 (*edit_keys[i]->f)();
3740 break;
3743 break;
3744 case MODE_PLAY:
3745 for (i = 0; play_keys[i]; i++) {
3746 if (input_c == play_keys[i]->c) {
3747 (*play_keys[i]->f)();
3748 goto done;
3752 do_play_config_command();
3753 break;
3754 case MODE_HISTORY:
3755 for (i = 0; history_keys[i]; i++) {
3756 if (input_c == history_keys[i]->c) {
3757 (*history_keys[i]->f)();
3758 break;
3761 break;
3762 default:
3763 break;
3766 done:
3767 if (keycount)
3768 update_status_notify(game[gindex], NULL);
3770 keycount = 0;
3774 void usage(const char *pn, int ret)
3776 fprintf((ret) ? stderr : stdout, "%s",
3777 #ifdef DEBUG
3778 "Usage: cboard [-hvD] [-p [-VtRSE] <file>]\n"
3779 " -D Dump libchess debugging info to \"libchess.debug\" (stderr)\n"
3780 #else
3781 "Usage: cboard [-hv] [-p [-VtRSE] <file>]\n"
3782 #endif
3783 " -p Load PGN file.\n"
3784 " -V Validate a game file.\n"
3785 " -S Validate and output a PGN formatted game.\n"
3786 " -R Like -S but write a reduced PGN formatted game.\n"
3787 " -t Also write custom PGN tags from config file.\n"
3788 " -E Stop processing on file parsing error (overrides config).\n"
3789 " -v Version information.\n"
3790 " -h This help text.\n");
3792 exit(ret);
3795 void cleanup_all()
3797 int i;
3799 stop_clock();
3800 free_userdata();
3801 pgn_free_all();
3802 free(config.engine_cmd);
3803 free(config.pattern);
3804 free(config.ccfile);
3805 free(config.nagfile);
3806 free(config.configfile);
3808 if (config.keys) {
3809 for (i = 0; config.keys[i]; i++) {
3810 free(config.keys[i]->str);
3811 free(config.keys[i]);
3814 free(config.keys);
3817 if (config.einit) {
3818 for (i = 0; config.einit[i]; i++)
3819 free(config.einit[i]);
3821 free(config.einit);
3824 if (config.tag)
3825 pgn_tag_free(config.tag);
3827 if (curses_initialized) {
3828 del_panel(boardp);
3829 del_panel(historyp);
3830 del_panel(statusp);
3831 del_panel(tagp);
3832 delwin(boardw);
3833 delwin(historyw);
3834 delwin(statusw);
3835 delwin(tagw);
3837 if (enginew) {
3838 del_panel(enginep);
3839 delwin(enginew);
3842 endwin();
3846 void catch_signal(int which)
3848 switch (which) {
3849 case SIGALRM:
3850 update_clocks();
3851 break;
3852 case SIGPIPE:
3853 if (which == SIGPIPE && quit)
3854 break;
3856 if (which == SIGPIPE)
3857 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3859 cleanup_all();
3860 exit(EXIT_FAILURE);
3861 break;
3862 case SIGSTOP:
3863 savetty();
3864 break;
3865 case SIGCONT:
3866 resetty();
3867 keypad(boardw, TRUE);
3868 curs_set(0);
3869 cbreak();
3870 noecho();
3871 break;
3872 case SIGINT:
3873 case SIGTERM:
3874 quit = 1;
3875 break;
3876 default:
3877 break;
3881 void loading_progress(long total, long offset)
3883 int n = (100 * (offset / 100) / (total / 100));
3885 if (curses_initialized)
3886 update_loading_window(n);
3887 else {
3888 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3889 fflush(stderr);
3893 static void set_defaults()
3895 set_config_defaults();
3896 set_default_keys();
3897 filetype = NO_FILE;
3898 pgn_config_set(PGN_PROGRESS, 1024);
3899 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3902 int main(int argc, char *argv[])
3904 int opt;
3905 struct stat st;
3906 char buf[FILENAME_MAX];
3907 char datadir[FILENAME_MAX];
3908 int ret = EXIT_SUCCESS;
3909 int validate_only = 0, validate_and_write = 0;
3910 int write_custom_tags = 0;
3911 FILE *fp;
3912 int i = 0;
3914 /* Solaris 5.9 */
3915 #ifndef HAVE_PROGNAME
3916 __progname = argv[0];
3917 #endif
3919 if ((config.pwd = getpwuid(getuid())) == NULL)
3920 err(EXIT_FAILURE, "getpwuid()");
3922 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3923 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3924 config.ccfile = strdup(buf);
3925 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3926 config.nagfile = strdup(buf);
3927 snprintf(buf, sizeof(buf), "%s/config", datadir);
3928 config.configfile = strdup(buf);
3930 if (stat(datadir, &st) == -1) {
3931 if (errno == ENOENT) {
3932 if (mkdir(datadir, 0755) == -1)
3933 err(EXIT_FAILURE, "%s", datadir);
3935 else
3936 err(EXIT_FAILURE, "%s", datadir);
3938 stat(datadir, &st);
3941 if (!S_ISDIR(st.st_mode))
3942 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3944 set_defaults();
3946 #ifdef DEBUG
3947 while ((opt = getopt(argc, argv, "DEVtSRhp:v")) != -1) {
3948 #else
3949 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3950 #endif
3951 switch (opt) {
3952 #ifdef DEBUG
3953 case 'D':
3954 unlink("libchess.debug");
3955 pgn_config_set(PGN_DEBUG, 1);
3956 break;
3957 #endif
3958 case 't':
3959 write_custom_tags = 1;
3960 break;
3961 case 'E':
3962 i = 1;
3963 break;
3964 case 'R':
3965 pgn_config_set(PGN_REDUCED, 1);
3966 case 'S':
3967 validate_and_write = 1;
3968 case 'V':
3969 validate_only = 1;
3970 break;
3971 case 'v':
3972 printf("%s (%s, %s)\n%s\n", PACKAGE_STRING, pgn_version(),
3973 curses_version(), COPYRIGHT);
3974 exit(EXIT_SUCCESS);
3975 case 'p':
3976 filetype = PGN_FILE;
3977 strncpy(loadfile, optarg, sizeof(loadfile));
3978 break;
3979 case 'h':
3980 default:
3981 usage(argv[0], EXIT_SUCCESS);
3985 if ((validate_only || validate_and_write) && !*loadfile)
3986 usage(argv[0], EXIT_FAILURE);
3988 if (access(config.configfile, R_OK) == 0)
3989 parse_rcfile(config.configfile);
3991 if (i)
3992 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3994 signal(SIGPIPE, catch_signal);
3995 signal(SIGCONT, catch_signal);
3996 signal(SIGSTOP, catch_signal);
3997 signal(SIGINT, catch_signal);
3998 signal(SIGALRM, catch_signal);
3999 signal(SIGTERM, catch_signal);
4001 srandom(getpid());
4003 switch (filetype) {
4004 case PGN_FILE:
4005 if ((fp = pgn_open(loadfile)) == NULL)
4006 err(EXIT_FAILURE, "%s", loadfile);
4008 ret = pgn_parse(fp);
4009 break;
4010 case FEN_FILE:
4011 //ret = parse_fen_file(loadfile);
4012 break;
4013 case EPD_FILE: // Not implemented.
4014 case NO_FILE:
4015 default:
4016 // No file specified. Empty game.
4017 ret = pgn_parse(NULL);
4018 add_custom_tags(&game[gindex]->tag);
4019 break;
4022 if (validate_only || validate_and_write) {
4023 if (validate_and_write) {
4024 for (i = 0; i < gtotal; i++) {
4025 if (write_custom_tags)
4026 add_custom_tags(&game[i]->tag);
4028 pgn_write(stdout, game[i]);
4032 cleanup_all();
4033 exit(ret);
4035 else if (ret == E_PGN_ERR)
4036 exit(ret);
4038 init_userdata();
4040 if (initscr() == NULL)
4041 errx(EXIT_FAILURE, "%s", E_INITCURSES);
4042 else
4043 curses_initialized = 1;
4045 if (LINES < 24 || COLS < 80) {
4046 endwin();
4047 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
4050 if (has_colors() == TRUE && start_color() == OK)
4051 init_color_pairs();
4053 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
4054 boardp = new_panel(boardw);
4055 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
4056 COLS - HISTORY_WIDTH);
4057 historyp = new_panel(historyw);
4058 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
4059 statusp = new_panel(statusw);
4060 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
4061 tagp = new_panel(tagw);
4062 keypad(boardw, TRUE);
4063 // leaveok(boardw, TRUE);
4064 leaveok(tagw, TRUE);
4065 leaveok(statusw, TRUE);
4066 leaveok(historyw, TRUE);
4067 curs_set(0);
4068 cbreak();
4069 noecho();
4070 draw_window_decor();
4071 game_loop();
4072 cleanup_all();
4073 exit(EXIT_SUCCESS);