Initial multibyte string output.
[cboard.git] / src / cboard.c
blob2093bfa9e0e1ef7f735b1faba4251bff4a124e99
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2013 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 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/time.h>
28 #include <sys/socket.h>
29 #include <sys/stat.h>
30 #include <string.h>
31 #include <ncursesw/panel.h>
32 #include <errno.h>
33 #include <ctype.h>
34 #include <pwd.h>
35 #include <signal.h>
36 #include <time.h>
37 #include <err.h>
38 #include <locale.h>
40 #ifdef HAVE_STDARG_H
41 #include <stdarg.h>
42 #endif
44 #ifdef HAVE_SYS_WAIT_H
45 #include <sys/wait.h>
46 #endif
48 #ifdef HAVE_REGEX_H
49 #include <regex.h>
50 #endif
52 #ifdef WITH_LIBPERL
53 #include "perl.h"
54 #endif
56 #include "chess.h"
57 #include "conf.h"
58 #include "window.h"
59 #include "message.h"
60 #include "colors.h"
61 #include "input.h"
62 #include "misc.h"
63 #include "engine.h"
64 #include "strings.h"
65 #include "common.h"
66 #include "menu.h"
67 #include "keys.h"
68 #include "rcfile.h"
69 #include "filebrowser.h"
71 #ifdef DEBUG
72 #include <debug.h>
73 #endif
75 #ifdef WITH_DMALLOC
76 #include <dmalloc.h>
77 #endif
79 #define COPYRIGHT "Copyright (C) 2002-2013 " PACKAGE_BUGREPORT
80 #define LINE_GRAPHIC(c) ((!config.linegraphics) ? ' ' : c)
81 #define ROWTOMATRIX(r) ((8 - r) * 2 + 2 - 1)
82 #define COLTOMATRIX(c) ((c == 1) ? 1 : c * 4 - 3)
83 #define BOARD_HEIGHT 18
84 #define BOARD_WIDTH 34
85 #define STATUS_HEIGHT 12
86 #define STATUS_WIDTH (COLS - BOARD_WIDTH)
87 #define TAG_HEIGHT LINES - STATUS_HEIGHT - 1
88 #define TAG_WIDTH (COLS - BOARD_WIDTH)
89 #define HISTORY_HEIGHT (LINES - BOARD_HEIGHT)
90 #define HISTORY_WIDTH (COLS - STATUS_WIDTH)
91 #define MAX_VALUE_WIDTH (COLS - 8)
93 enum {
94 UP, DOWN, LEFT, RIGHT
97 static WINDOW *boardw;
98 static PANEL *boardp;
99 static WINDOW *tagw;
100 static PANEL *tagp;
101 static WINDOW *statusw;
102 static PANEL *statusp;
103 static WINDOW *historyw;
104 static PANEL *historyp;
105 static WINDOW *loadingw;
106 static PANEL *loadingp;
107 static WINDOW *enginew;
108 static PANEL *enginep;
110 static char gameexp[255];
111 static char moveexp[255];
112 static struct itimerval clock_timer;
113 static int delete_count = 0;
114 static int markstart = -1, markend = -1;
115 static int keycount;
116 static char loadfile[FILENAME_MAX];
117 static int quit;
118 static int input_c;
120 // Loaded filename from the command line or from the file input dialog.
121 static int filetype;
122 enum {
123 FILE_NONE, FILE_PGN, FILE_FEN, FILE_EPD
126 static char **nags;
127 static int nag_total;
128 static int macro_match;
130 // Status window.
131 static struct {
132 wchar_t *notify; // The status window notification line buffer.
133 } status;
135 static int curses_initialized;
137 // When in history mode a full step is to the next move of the same playing
138 // side. Half stepping is alternating sides.
139 static int movestep;
141 static wchar_t *w_pawn_wchar;
142 static wchar_t *w_rook_wchar;
143 static wchar_t *w_bishop_wchar;
144 static wchar_t *w_knight_wchar;
145 static wchar_t *w_queen_wchar;
146 static wchar_t *w_king_wchar;
147 static wchar_t *b_pawn_wchar;
148 static wchar_t *b_rook_wchar;
149 static wchar_t *b_bishop_wchar;
150 static wchar_t *b_knight_wchar;
151 static wchar_t *b_queen_wchar;
152 static wchar_t *b_king_wchar;
153 static wchar_t *empty_wchar;
155 static void free_userdata_once(GAME g);
157 void update_cursor(GAME g, int idx)
159 char *p;
160 int len;
161 int t = pgn_history_total(g->hp);
162 struct userdata_s *d = g->data;
165 * If not deincremented then r and c would be the next move.
167 idx--;
169 if (idx > t || idx < 0 || !t || !g->hp[idx]->move) {
170 d->c_row = 2, d->c_col = 5;
171 return;
174 p = g->hp[idx]->move;
175 len = strlen(p);
177 if (*p == 'O') {
178 if (len <= 4)
179 d->c_col = 7;
180 else
181 d->c_col = 3;
183 d->c_row = (g->turn == WHITE) ? 8 : 1;
184 return;
187 p += len;
189 while (!isdigit(*p))
190 p--;
192 d->c_row = RANKTOINT(*p--);
193 d->c_col = FILETOINT(*p);
196 static int init_nag()
198 FILE *fp;
199 char line[LINE_MAX];
200 int i = 0;
202 if ((fp = fopen(config.nagfile, "r")) == NULL) {
203 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config.nagfile, strerror(errno));
204 return 1;
207 nags = Realloc(nags, (i+2) * sizeof(char *));
208 nags[i++] = strdup(_("none"));
209 nags[i] = NULL;
211 while (!feof(fp)) {
212 if (fscanf(fp, " %[^\n] ", line) == 1) {
213 nags = Realloc(nags, (i + 2) * sizeof(char *));
214 nags[i++] = strdup(line);
218 nags[i] = NULL;
219 nag_total = i;
220 return 0;
223 void edit_nag_toggle_item(struct menu_input_s *m)
225 struct input_s *in = m->data;
226 struct input_data_s *id = in->data;
227 HISTORY *h = id->data;
228 int i;
230 if (m->selected == 0) {
231 for (i = 0; i < MAX_PGN_NAG; i++)
232 h->nag[i] = 0;
234 for (i = 0; m->items[i]; i++)
235 m->items[i]->selected = 0;
237 return;
240 for (i = 0; i < MAX_PGN_NAG; i++) {
241 if (h->nag[i] == m->selected)
242 h->nag[i] = m->selected = 0;
243 else {
244 if (!h->nag[i]) {
245 h->nag[i] = m->selected;
246 break;
252 void edit_nag_save(struct menu_input_s *m)
254 pushkey = -1;
257 void edit_nag_help(struct menu_input_s *m)
259 message(_("NAG Menu Keys"), _("[ press any key to continue ]"), "%s",
261 " UP/DOWN - previous/next menu item\n"
262 " HOME/END - first/last menu item\n"
263 " PGDN/PGUP - next/previous page\n"
264 " a-zA-Z0-9 - jump to item\n"
265 " SPACE - toggle selected item\n"
266 " CTRL-X - quit with changes"
270 struct menu_item_s **get_nag_items(WIN *win)
272 int i, n;
273 struct menu_input_s *m = win->data;
274 struct input_s *in = m->data;
275 struct input_data_s *id = in->data;
276 struct menu_item_s **items = m->items;
277 HISTORY *h = id->data;
279 if (items) {
280 for (i = 0; items[i]; i++)
281 free(items[i]);
284 for (i = 0; nags[i]; i++) {
285 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
286 items[i] = Malloc(sizeof(struct menu_item_s));
287 items[i]->name = nags[i];
288 items[i]->value = NULL;
290 for (n = 0; n < MAX_PGN_NAG; n++) {
291 if (h->nag[n] == i) {
292 items[i]->selected = 1;
293 n = -1;
294 break;
298 if (n >= 0)
299 items[i]->selected = 0;
302 items[i] = NULL;
303 m->nofree = 1;
304 m->items = items;
305 return items;
308 void nag_print(WIN *win)
310 struct menu_input_s *m = win->data;
312 mvwprintw(win->w, m->print_line, 1, "%-*s", win->cols - 2, m->item->name);
315 void edit_nag(void *arg)
317 struct menu_key_s **keys = NULL;
319 if (!nags) {
320 if (init_nag())
321 return;
324 add_menu_key(&keys, ' ', edit_nag_toggle_item);
325 add_menu_key(&keys, CTRL('x'), edit_nag_save);
326 add_menu_key(&keys, KEY_F(1), edit_nag_help);
327 construct_menu(0, 0, -1, -1, _("Numeric Annotation Glyphs"), 1, get_nag_items, keys, arg,
328 nag_print, NULL);
329 return;
332 static void *view_nag(void *arg)
334 HISTORY *h = (HISTORY *)arg;
335 char buf[80];
336 char line[LINE_MAX] = {0};
337 int i = 0;
339 snprintf(buf, sizeof(buf), "%s \"%s\"", _("Viewing NAG for"), h->move);
341 if (!nags) {
342 if (init_nag())
343 return NULL;
346 for (i = 0; i < MAX_PGN_NAG; i++) {
347 if (!h->nag[i])
348 break;
350 if (h->nag[i] >= nag_total)
351 strncat(line, itoa(h->nag[i]), sizeof(line));
352 else
353 strncat(line, nags[h->nag[i]], sizeof(line));
355 strncat(line, "\n", sizeof(line));
358 line[strlen(line) - 1] = 0;
359 message(buf, _("[ press any key to continue ]"), "%s", line);
360 return NULL;
363 void view_annotation(HISTORY *h)
365 char buf[MAX_SAN_MOVE_LEN + strlen(_("Viewing Annotation for")) + 4];
366 int nag = 0, comment = 0;
368 if (!h)
369 return;
371 if (h->comment && h->comment[0])
372 comment++;
374 if (h->nag[0])
375 nag++;
377 if (!nag && !comment)
378 return;
380 snprintf(buf, sizeof(buf), "%s \"%s\"", _("Viewing Annotation for"), h->move);
382 if (comment)
383 construct_message(buf, (nag) ? _("Any other key to continue") : _("[ press any key to continue ]"), 0, 1,
384 (nag) ? _("Press 'n' to view NAG") : NULL,
385 (nag) ? view_nag : NULL, (nag) ? h : NULL, NULL,
386 (nag) ? 'n' : 0, 0, "%s", h->comment);
387 else
388 construct_message(buf, _("Any other key to continue"), 0, 1, _("Press 'n' to view NAG"), view_nag, h, NULL,
389 'n', 0, "%s", _("No comment text for this move"));
392 int do_game_write(char *filename, char *mode, int start, int end)
394 int i;
395 struct userdata_s *d;
396 PGN_FILE *pgn;
398 i = pgn_open(filename, mode, &pgn);
400 if (i == E_PGN_ERR) {
401 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", filename, strerror(errno));
402 return 1;
404 else if (i == E_PGN_INVALID) {
405 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", filename, _("Not a regular file"));
406 return 1;
409 for (i = (start == -1) ? 0 : start; i < end; i++) {
410 d = game[i]->data;
411 pgn_write(pgn, game[i]);
412 CLEAR_FLAG(d->flags, CF_MODIFIED);
415 if (pgn_close(pgn) != E_PGN_OK)
416 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", strerror(errno));
418 if (start == -1)
419 strncpy(loadfile, filename, sizeof(loadfile));
421 return 0;
424 struct save_game_s {
425 char *filename;
426 char *mode;
427 int start;
428 int end;
431 void do_save_game_overwrite_confirm(WIN *win)
433 char *mode = "w";
434 struct save_game_s *s = win->data;
436 switch (win->c) {
437 case 'a':
438 mode = "a";
439 break;
440 case 'o':
441 mode = "w";
442 break;
443 default:
444 goto done;
447 if (do_game_write(s->filename, mode, s->start, s->end))
448 update_status_notify(gp, "%s", _("Save game failed."));
449 else
450 update_status_notify(gp, "%s", _("Game saved."));
452 done:
453 free(s->filename);
454 free(s);
457 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
458 * game index number.
460 void save_pgn(char *filename, int saveindex)
462 char buf[FILENAME_MAX];
463 struct stat st;
464 int end = (saveindex == -1) ? gtotal : saveindex + 1;
465 struct save_game_s *s;
467 if (filename[0] != '/' && config.savedirectory) {
468 if (stat(config.savedirectory, &st) == -1) {
469 if (errno == ENOENT) {
470 if (mkdir(config.savedirectory, 0755) == -1) {
471 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config.savedirectory,
472 strerror(errno));
473 return;
476 else {
477 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config.savedirectory,
478 strerror(errno));
479 return;
483 stat(config.savedirectory, &st);
485 if (!S_ISDIR(st.st_mode)) {
486 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config.savedirectory, _("Not a directory."));
487 return;
490 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
491 filename = buf;
494 if (access(filename, W_OK) == 0) {
495 s = Malloc(sizeof(struct save_game_s));
496 s->filename = strdup(filename);
497 s->start = saveindex;
498 s->end = end;
499 construct_message(NULL, _("'a' to append, 'o' to overwrite"), 1, 1, NULL, NULL,
500 s, do_save_game_overwrite_confirm, 0, 0, "%s \"%s\"",
501 _("File exists:"), filename);
502 return;
505 if (do_game_write(filename, "a", saveindex, end))
506 update_status_notify(gp, "%s", _("Save game failed."));
507 else
508 update_status_notify(gp, "%s", _("Game saved."));
511 static int castling_state(GAME g, BOARD b, int row, int col, int piece, int mod)
513 if (pgn_piece_to_int(piece) == ROOK && col == 7
514 && row == 7 &&
515 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
516 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
517 if (mod)
518 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
519 return 1;
521 else if (pgn_piece_to_int(piece) == ROOK && col == 0
522 && row == 7 &&
523 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
524 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
525 if (mod)
526 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
527 return 1;
529 else if (pgn_piece_to_int(piece) == ROOK && col == 7
530 && row == 0 &&
531 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
532 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
533 if (mod)
534 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
535 return 1;
537 else if (pgn_piece_to_int(piece) == ROOK && col == 0
538 && row == 0 &&
539 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
540 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
541 if (mod)
542 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
543 return 1;
545 else if (pgn_piece_to_int(piece) == KING && col == 4
546 && row == 7 &&
547 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
548 TEST_FLAG(g->flags, GF_WK_CASTLE))
550 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
551 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
552 if (mod) {
553 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
554 TEST_FLAG(g->flags, GF_WQ_CASTLE))
555 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
556 else
557 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
559 return 1;
561 else if (pgn_piece_to_int(piece) == KING && col == 4
562 && row == 0 &&
563 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
564 TEST_FLAG(g->flags, GF_BK_CASTLE))
566 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
567 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
568 if (mod) {
569 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
570 TEST_FLAG(g->flags, GF_BQ_CASTLE))
571 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
572 else
573 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
575 return 1;
578 return 0;
581 #define IS_ENPASSANT(c) (c == 'x') ? CP_BOARD_ENPASSANT : isupper(c) ? CP_BOARD_WHITE : CP_BOARD_BLACK
582 #define ATTRS(cp) (cp & (A_BOLD|A_STANDOUT|A_BLINK|A_DIM|A_UNDERLINE|A_INVIS|A_REVERSE))
584 static void
585 init_wchar_pieces ()
587 w_pawn_wchar = str_to_wchar ("♙");
588 w_rook_wchar = str_to_wchar ("♖");
589 w_bishop_wchar = str_to_wchar ("♗");
590 w_knight_wchar = str_to_wchar ("♘");
591 w_queen_wchar = str_to_wchar ("♕");
592 w_king_wchar = str_to_wchar ("♔");
593 b_pawn_wchar = str_to_wchar ("♟");
594 b_rook_wchar = str_to_wchar ("♜");
595 b_bishop_wchar = str_to_wchar ("♝");
596 b_knight_wchar = str_to_wchar ("♞");
597 b_queen_wchar = str_to_wchar ("♛");
598 b_king_wchar = str_to_wchar ("♚");
599 empty_wchar = str_to_wchar (" ");
602 static wchar_t *
603 piece_to_wchar (unsigned char p)
605 switch (p)
607 case 'P':
608 return w_pawn_wchar;
609 case 'p':
610 return b_pawn_wchar;
611 case 'R':
612 return w_rook_wchar;
613 case 'r':
614 return b_rook_wchar;
615 case 'B':
616 return w_bishop_wchar;
617 case 'b':
618 return b_bishop_wchar;
619 case 'N':
620 return w_knight_wchar;
621 case 'n':
622 return b_knight_wchar;
623 case 'Q':
624 return w_queen_wchar;
625 case 'q':
626 return b_queen_wchar;
627 case 'K':
628 return w_king_wchar;
629 case 'k':
630 return b_king_wchar;
633 return empty_wchar;
636 void update_board_window(GAME g)
638 int row, col;
639 int bcol = 0, brow = 0;
640 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
641 int ncols = 0, offset = 1;
642 unsigned coords_y = 8;
643 struct userdata_s *d = g->data;
645 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
646 update_cursor(g, g->hindex);
648 for (row = 0; row < maxy; row++) {
649 bcol = 0;
651 for (col = 0; col < maxx; col++) {
652 int attrwhich = -1;
653 chtype attrs = 0, old_attrs = 0;
654 unsigned char p;
656 if (row == 0 || row == maxy - 2) {
657 if (col == 0)
658 mvwaddch(boardw, row, col,
659 LINE_GRAPHIC((row) ?
660 ACS_LLCORNER | CP_BOARD_GRAPHICS :
661 ACS_ULCORNER | CP_BOARD_GRAPHICS));
662 else if (col == maxx - 2)
663 mvwaddch(boardw, row, col,
664 LINE_GRAPHIC((row) ?
665 ACS_LRCORNER | CP_BOARD_GRAPHICS :
666 ACS_URCORNER | CP_BOARD_GRAPHICS));
667 else if (!(col % 4))
668 mvwaddch(boardw, row, col,
669 LINE_GRAPHIC((row) ?
670 ACS_BTEE | CP_BOARD_GRAPHICS :
671 ACS_TTEE | CP_BOARD_GRAPHICS));
672 else {
673 if (col != maxx - 1)
674 mvwaddch(boardw, row, col,
675 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
678 continue;
681 if ((row % 2) && col == maxx - 1 && coords_y) {
682 wattron(boardw, CP_BOARD_COORDS);
683 mvwprintw(boardw, row, col, "%d", coords_y--);
684 wattroff(boardw, CP_BOARD_COORDS);
685 continue;
688 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
689 if (!(row % 2))
690 mvwaddch(boardw, row, col,
691 LINE_GRAPHIC((col) ?
692 ACS_RTEE | CP_BOARD_GRAPHICS :
693 ACS_LTEE | CP_BOARD_GRAPHICS));
694 else
695 mvwaddch(boardw, row, col,
696 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
698 continue;
701 if ((row % 2) && !(col % 4) && row != maxy - 1) {
702 mvwaddch(boardw, row, col,
703 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
704 continue;
707 if (!(col % 4) && row != maxy - 1) {
708 mvwaddch(boardw, row, col,
709 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
710 continue;
713 if ((row % 2)) {
714 if ((col % 4)) {
715 if (ncols++ == 8) {
716 offset++;
717 ncols = 1;
720 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
721 && (offset % 2)))
722 attrwhich = BLACK;
723 else
724 attrwhich = WHITE;
726 p = d->b[row / 2][bcol].icon;
727 int pi = pgn_piece_to_int(p);
729 if (config.details && d->b[row / 2][bcol].enpassant) {
730 p = pi = 'x';
731 attrs = mix_cp(CP_BOARD_ENPASSANT, (attrwhich == WHITE) ? CP_BOARD_WHITE : CP_BOARD_BLACK, ATTRS(CP_BOARD_ENPASSANT), A_FG_B_BG);
734 if (config.validmoves && d->b[brow][bcol].valid) {
735 old_attrs = -1;
737 if (attrwhich == WHITE)
738 attrs = mix_cp(CP_BOARD_MOVES_WHITE, IS_ENPASSANT(p),
739 ATTRS(CP_BOARD_MOVES_WHITE), B_FG_A_BG);
740 else
741 attrs = mix_cp(CP_BOARD_MOVES_BLACK, IS_ENPASSANT(p),
742 ATTRS(CP_BOARD_MOVES_BLACK), B_FG_A_BG);
744 else if (p != 'x')
745 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE : CP_BOARD_BLACK;
747 if (row == ROWTOMATRIX(d->c_row) && col ==
748 COLTOMATRIX(d->c_col)) {
749 attrs = mix_cp(CP_BOARD_CURSOR, IS_ENPASSANT(p),
750 ATTRS(CP_BOARD_CURSOR), B_FG_A_BG);
751 old_attrs = -1;
753 else if (row == ROWTOMATRIX(d->sp.srow) &&
754 col == COLTOMATRIX(d->sp.scol)) {
755 attrs = mix_cp(CP_BOARD_SELECTED, IS_ENPASSANT(p),
756 ATTRS(CP_BOARD_SELECTED), B_FG_A_BG);
757 old_attrs = -1;
760 if (row == maxy - 1)
761 attrs = 0;
763 mvwaddch(boardw, row, col, ' ' | attrs);
765 if (row == maxy - 1)
766 waddch(boardw, _("abcdefgh")[bcol] | CP_BOARD_COORDS);
767 else {
768 if (old_attrs == -1) {
769 old_attrs = attrs;
770 goto printc;
773 old_attrs = attrs;
775 if (pi != OPEN_SQUARE && p != 'x') {
776 if (attrwhich == WHITE) {
777 if (isupper(p))
778 attrs = CP_BOARD_W_W;
779 else
780 attrs = CP_BOARD_W_B;
782 else {
783 if (isupper(p))
784 attrs = CP_BOARD_B_W;
785 else
786 attrs = CP_BOARD_B_B;
790 printc:
791 if (config.details && castling_state(g, d->b, brow,
792 bcol, p, 0)) {
793 attrs = mix_cp(CP_BOARD_CASTLING, attrs,
794 ATTRS(CP_BOARD_CASTLING), A_FG_B_BG);
797 wattron (boardw, attrs);
798 waddwstr (boardw, piece_to_wchar (pi != OPEN_SQUARE ? p : 0));
799 wattroff (boardw, attrs);
800 attrs = old_attrs;
803 waddch(boardw, ' ' | attrs);
804 col += 2;
805 bcol++;
808 else {
809 if (col != maxx - 1)
810 mvwaddch(boardw, row, col,
811 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
815 brow = row / 2;
819 void invalid_move(int n, int e, const char *m)
821 if (curses_initialized)
822 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
823 ? _("Ambiguous move") : _("Invalid move"), m, n);
824 else
825 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
826 ? _("Ambiguous move") : _("Invalid move"), m, n);
829 void gameover(GAME g)
831 struct userdata_s *d = g->data;
833 SET_FLAG(g->flags, GF_GAMEOVER);
834 d->mode = MODE_HISTORY;
835 stop_engine(g);
838 static void update_clock(GAME g, struct itimerval it)
840 struct userdata_s *d = g->data;
842 if (TEST_FLAG(d->flags, CF_CLOCK) && g->turn == WHITE) {
843 d->wclock.elapsed.tv_sec += it.it_value.tv_sec;
844 d->wclock.elapsed.tv_usec += it.it_value.tv_usec;
846 if (d->wclock.elapsed.tv_usec > 1000000 - 1) {
847 d->wclock.elapsed.tv_sec += d->wclock.elapsed.tv_usec / 1000000;
848 d->wclock.elapsed.tv_usec = d->wclock.elapsed.tv_usec % 1000000;
851 if (d->wclock.tc[d->wclock.tcn][1] &&
852 d->wclock.elapsed.tv_sec >= d->wclock.tc[d->wclock.tcn][1]) {
853 pgn_tag_add(&g->tag, "Result", "0-1");
854 gameover(g);
857 else if (TEST_FLAG(d->flags, CF_CLOCK) && g->turn == BLACK) {
858 d->bclock.elapsed.tv_sec += it.it_value.tv_sec;
859 d->bclock.elapsed.tv_usec += it.it_value.tv_usec;
861 if (d->bclock.elapsed.tv_usec > 1000000 - 1) {
862 d->bclock.elapsed.tv_sec += d->bclock.elapsed.tv_usec / 1000000;
863 d->bclock.elapsed.tv_usec = d->bclock.elapsed.tv_usec % 1000000;
866 if (d->bclock.tc[d->bclock.tcn][1] &&
867 d->bclock.elapsed.tv_sec >= d->bclock.tc[d->bclock.tcn][1]) {
868 pgn_tag_add(&g->tag, "Result", "1-0");
869 gameover(g);
873 d->elapsed.tv_sec += it.it_value.tv_sec;
874 d->elapsed.tv_usec += it.it_value.tv_usec;
876 if (d->elapsed.tv_usec > 1000000 - 1) {
877 d->elapsed.tv_sec += d->elapsed.tv_usec / 1000000;
878 d->elapsed.tv_usec = d->elapsed.tv_usec % 1000000;
882 static void update_time_control(GAME g)
884 struct userdata_s *d = g->data;
885 struct clock_s *clk = (g->turn == WHITE) ? &d->wclock : &d->bclock;
887 if (clk->incr)
888 clk->tc[clk->tcn][1] += clk->incr;
890 if (!clk->tc[clk->tcn][1])
891 return;
893 clk->move++;
895 if (!clk->tc[clk->tcn][0] || clk->move >= clk->tc[clk->tcn][0]) {
896 clk->move = 0;
897 clk->tc[clk->tcn + 1][1] += abs(clk->elapsed.tv_sec - clk->tc[clk->tcn][1]);
898 memset(&clk->elapsed, 0, sizeof(clk->elapsed));
899 clk->tcn++;
903 void update_history_window(GAME g)
905 char buf[HISTORY_WIDTH - 1];
906 HISTORY *h = NULL;
907 int n, total;
908 int t = pgn_history_total(g->hp);
910 n = (g->hindex + 1) / 2;
912 if (t % 2)
913 total = (t + 1) / 2;
914 else
915 total = t / 2;
917 if (t)
918 snprintf(buf, sizeof(buf), "%u %s %u%s", n, _("of"), total,
919 (movestep == 1) ? _(" (ply)") : "");
920 else
921 strncpy(buf, _("not available"), sizeof(buf));
923 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, _("Move:"),
924 HISTORY_WIDTH - 13, buf);
926 h = pgn_history_by_n(g->hp, g->hindex);
927 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : _("not available"));
928 n = 0;
930 if (h && ((h->comment) || h->nag[0])) {
931 strncat(buf, " (Annotated", sizeof(buf));
932 n++;
935 if (h && h->rav) {
936 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
937 n++;
940 if (g->ravlevel) {
941 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
942 n++;
945 if (n)
946 strncat(buf, ")", sizeof(buf));
948 mvwprintw(historyw, 3, 1, "%s %-*s", _("Next move:"),
949 HISTORY_WIDTH - 13, buf);
951 h = pgn_history_by_n(g->hp, g->hindex - 1);
952 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : _("not available"));
953 n = 0;
955 if (h && ((h->comment) || h->nag[0])) {
956 strncat(buf, " (Annotated", sizeof(buf));
957 n++;
960 if (h && h->rav) {
961 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
962 n++;
965 if (g->ravlevel) {
966 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
967 n++;
970 if (n)
971 strncat(buf, ")", sizeof(buf));
973 mvwprintw(historyw, 4, 1, "%s %-*s", _("Prev move:"),
974 HISTORY_WIDTH - 13, buf);
977 void do_validate_move(char *m)
979 struct userdata_s *d = gp->data;
980 int n;
981 char *frfr = NULL;
983 if (TEST_FLAG(d->flags, CF_HUMAN)) {
984 if ((n = pgn_parse_move(gp, d->b, &m, &frfr)) != E_PGN_OK) {
985 invalid_move(d->n + 1, n, m);
986 free(m);
987 return;
990 update_time_control(gp);
991 pgn_history_add(gp, d->b, m);
992 pgn_switch_turn(gp);
993 free(frfr);
995 else {
996 if ((n = pgn_validate_move(gp, d->b, &m, &frfr)) != E_PGN_OK) {
997 invalid_move(d->n + 1, n, m);
998 free(m);
999 return;
1002 add_engine_command(gp, ENGINE_THINKING, "%s\n",
1003 (config.engine_protocol == 1) ? frfr : m);
1006 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1008 if (config.validmoves)
1009 pgn_reset_valid_moves(d->b);
1011 if (TEST_FLAG(gp->flags, GF_GAMEOVER))
1012 d->mode = MODE_HISTORY;
1013 else
1014 SET_FLAG(d->flags, CF_MODIFIED);
1016 d->paused = 0;
1017 free(m);
1018 update_history_window(gp);
1019 update_board_window(gp);
1020 return;
1023 void do_promotion_piece_finalize(WIN *win)
1025 char *p, *str = win->data;
1027 if (pgn_piece_to_int(win->c) == -1)
1028 return;
1030 p = str + strlen(str);
1031 *p++ = toupper(win->c);
1032 *p = '\0';
1033 do_validate_move(str);
1036 static void move_to_engine(GAME g)
1038 struct userdata_s *d = g->data;
1039 char *str;
1040 int piece;
1042 if (config.validmoves &&
1043 !d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].valid)
1044 return;
1046 str = Malloc(MAX_SAN_MOVE_LEN + 1);
1047 snprintf(str, MAX_SAN_MOVE_LEN + 1, "%c%i%c%i",
1048 _("abcdefgh")[d->sp.scol - 1],
1049 d->sp.srow, _("abcdefgh")[d->sp.col - 1], d->sp.row);
1051 piece = pgn_piece_to_int(d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon);
1053 if (piece == PAWN && (d->sp.row == 8 || d->sp.row == 1)) {
1054 construct_message(_("Select Pawn Promotion Piece"), _ ("R/N/B/Q"), 1, 1, NULL, NULL,
1055 str, do_promotion_piece_finalize, 0, 0, "%s", _("R = Rook, N = Knight, B = Bishop, Q = Queen"));
1056 return;
1059 do_validate_move(str);
1062 static char *clock_to_char(long n)
1064 static char buf[16];
1065 int h = 0, m = 0, s = 0;
1067 h = n / 3600;
1068 m = (n % 3600) / 60;
1069 s = (n % 3600) % 60;
1070 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
1071 return buf;
1074 static char *timeval_to_char(struct timeval t, long limit)
1076 static char buf[9];
1077 int h = 0, m = 0, s = 0;
1078 int n = limit ? abs(limit - t.tv_sec) : 0;
1080 h = n / 3600;
1081 m = (n % 3600) / 60;
1082 s = (n % 3600) % 60;
1083 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
1084 return buf;
1087 static char *time_control_status(struct clock_s *clk)
1089 static char buf[80];
1091 buf[0] = 0;
1093 if (clk->tc[clk->tcn][0] && clk->tc[clk->tcn + 1][1])
1094 snprintf(buf, sizeof(buf), " M%.2i/%s", abs(clk->tc[clk->tcn][0] - clk->move),
1095 clock_to_char(clk->tc[clk->tcn + 1][1]));
1096 else if (!clk->incr)
1097 return "";
1099 if (clk->incr) {
1100 strncat(buf, " I", sizeof(buf));
1101 strncat(buf, itoa(clk->incr), sizeof(buf));
1104 return buf;
1107 void update_status_window(GAME g)
1109 int i = 0;
1110 char *buf;
1111 char tmp[15], *engine, *mode;
1112 char t[COLS];
1113 int w;
1114 char *p;
1115 int maxy, maxx;
1116 int len;
1117 struct userdata_s *d = g->data;
1118 int y;
1119 int n;
1121 if (!curses_initialized)
1122 return;
1124 getmaxyx(statusw, maxy, maxx);
1125 (void)maxy;
1126 w = maxx - 2 - 8;
1127 len = maxx - 2;
1128 buf = Malloc(len);
1129 y = 2;
1131 wchar_t *loadfilew = loadfile[0] ? str_etc (loadfile, w, 1) : str_to_wchar (_ ("not available"));
1132 mvwprintw(statusw, y++, 1, "%*s %-*ls", 7, _("File:"), w, loadfilew);
1133 free (loadfilew);
1134 snprintf(buf, len, "%i %s %i", gindex + 1, _("of"), gtotal);
1135 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, _("Game:"), w, buf);
1137 *tmp = '\0';
1138 p = tmp;
1140 if (config.details) {
1141 *p++ = 'D';
1142 i++;
1145 if (TEST_FLAG(d->flags, CF_DELETE)) {
1146 if (i)
1147 *p++ = '/';
1149 *p++ = 'X';
1150 i++;
1153 if (TEST_FLAG(g->flags, GF_PERROR)) {
1154 if (i)
1155 *p++ = '/';
1157 *p++ = '!';
1158 i++;
1161 if (TEST_FLAG(d->flags, CF_MODIFIED)) {
1162 if (i)
1163 *p++ = '/';
1165 *p++ = '*';
1166 i++;
1169 pgn_config_get(PGN_STRICT_CASTLING, &n);
1171 if (n == 1) {
1172 if (i)
1173 *p++ = '/';
1175 *p++ = 'C';
1176 i++;
1178 #ifdef WITH_LIBPERL
1179 if (TEST_FLAG(d->flags, CF_PERL)) {
1180 if (i)
1181 *p++ = '/';
1183 *p++ = 'P';
1184 i++;
1186 #endif
1188 *p = '\0';
1189 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, _("Flags:"), w, (tmp[0]) ? tmp : "-");
1191 switch (d->mode) {
1192 case MODE_HISTORY:
1193 mode = _("move history");
1194 break;
1195 case MODE_EDIT:
1196 mode = _("edit");
1197 break;
1198 case MODE_PLAY:
1199 mode = _("play");
1200 break;
1201 default:
1202 mode = _("(empty value)");
1203 break;
1206 snprintf(buf, len - 1, "%*s %s", 7, _("Mode:"), mode);
1208 if (d->mode == MODE_PLAY) {
1209 if (TEST_FLAG(d->flags, CF_HUMAN))
1210 strncat(buf, " (human/human)", len - 1);
1211 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1212 strncat(buf, " (engine/engine)", len - 1);
1213 else
1214 strncat(buf, " (human/engine)", len - 1);
1217 mvwprintw(statusw, y++, 1, "%-*s", len, buf);
1218 free(buf);
1220 if (d->engine) {
1221 switch (d->engine->status) {
1222 case ENGINE_THINKING:
1223 engine = _("pondering...");
1224 break;
1225 case ENGINE_READY:
1226 engine = _("ready");
1227 break;
1228 case ENGINE_INITIALIZING:
1229 engine = _("initializing...");
1230 break;
1231 case ENGINE_OFFLINE:
1232 engine = _("offline");
1233 break;
1234 default:
1235 engine = _("(empty value)");
1236 break;
1239 else
1240 engine = _("offline");
1242 mvwprintw(statusw, y, 1, "%*s %-*s", 7, _("Engine:"), w, " ");
1243 wattron(statusw, CP_STATUS_ENGINE);
1244 mvwaddstr(statusw, y++, 9, engine);
1245 wattroff(statusw, CP_STATUS_ENGINE);
1247 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, _("Turn:"), w,
1248 (g->turn == WHITE) ? _("white") : _("black"));
1250 strncpy(tmp, _("white"), sizeof(tmp));
1251 tmp[0] = toupper(tmp[0]);
1252 snprintf(t, sizeof(t), "%s%s",
1253 timeval_to_char(d->wclock.elapsed, d->wclock.tc[d->wclock.tcn][1]),
1254 time_control_status(&d->wclock));
1255 mvwprintw(statusw, y++, 1, "%*s: %-*s", 6, tmp, w, t);
1257 strncpy(tmp, _("black"), sizeof(tmp));
1258 tmp[0] = toupper(tmp[0]);
1259 snprintf(t, sizeof(t), "%s%s",
1260 timeval_to_char(d->bclock.elapsed, d->bclock.tc[d->bclock.tcn][1]),
1261 time_control_status(&d->bclock));
1262 mvwprintw(statusw, y++, 1, "%*s: %-*s", 6, tmp, w, t);
1264 mvwprintw(statusw, y++, 1, "%*s %-*s", 7, _("Total:"), w,
1265 clock_to_char(d->elapsed.tv_sec));
1267 for (i = 0; i < STATUS_WIDTH; i++)
1268 mvwprintw(stdscr, STATUS_HEIGHT, i, " ");
1270 if (!status.notify)
1271 status.notify = str_to_wchar(_("Type F1 for help"));
1273 wattron(stdscr, CP_STATUS_NOTIFY);
1274 mvwprintw(stdscr, STATUS_HEIGHT, CENTERX(STATUS_WIDTH, status.notify),
1275 "%ls", status.notify);
1276 wattroff(stdscr, CP_STATUS_NOTIFY);
1279 void update_tag_window(TAG **t)
1281 int i, l, w;
1282 int namel = 0, valuel = 0;
1284 for (i = 0; t[i]; i++) {
1285 l = strlen(t[i]->name);
1287 if (l > namel)
1288 namel = l;
1290 l = strlen(t[i]->value);
1292 if (l > valuel)
1293 valuel = l;
1296 w = TAG_WIDTH - namel - 4;
1298 /* FIXME unicode tag pairs */
1299 for (i = 0; t[i] && i < TAG_HEIGHT - 3; i++)
1301 wchar_t *s = str_etc(t[i]->value, w, 0);
1302 mvwprintw(tagw, (i + 2), 1, "%*s: %-*ls", namel, t[i]->name, w, s);
1303 free (s);
1306 for (; i < TAG_HEIGHT - 3; i++)
1307 mvwprintw(tagw, (i + 2), 1, "%*s", namel + w + 2, " ");
1310 void append_enginebuf(GAME g, char *line)
1312 int i = 0;
1313 struct userdata_s *d = g->data;
1315 if (d->engine->enginebuf)
1316 for (i = 0; d->engine->enginebuf[i]; i++);
1318 if (i >= LINES - 3) {
1319 free(d->engine->enginebuf[0]);
1321 for (i = 0; d->engine->enginebuf[i+1]; i++)
1322 d->engine->enginebuf[i] = d->engine->enginebuf[i+1];
1324 d->engine->enginebuf[i] = strdup(line);
1326 else {
1327 d->engine->enginebuf = Realloc(d->engine->enginebuf, (i + 2) * sizeof(char *));
1328 d->engine->enginebuf[i++] = strdup(line);
1329 d->engine->enginebuf[i] = NULL;
1333 void update_engine_window(GAME g)
1335 int i;
1336 struct userdata_s *d = g->data;
1338 if (!d->engine || !d->engine->enginebuf)
1339 return;
1341 wmove(enginew, 0, 0);
1342 wclrtobot(enginew);
1344 if (d->engine->enginebuf) {
1345 for (i = 0; d->engine->enginebuf[i]; i++)
1346 mvwprintw(enginew, i + 2, 1, "%s", d->engine->enginebuf[i]);
1349 window_draw_title(enginew, _("Engine IO Window"), COLS, CP_MESSAGE_TITLE,
1350 CP_MESSAGE_BORDER);
1353 void update_all(GAME g)
1355 struct userdata_s *d = g->data;
1358 * In the middle of a macro. Don't update the screen.
1360 if (macro_match != -1)
1361 return;
1364 * No need to update when the engine window is being shown.
1366 if (enginep && panel_hidden(enginep) == ERR) {
1367 update_panels();
1368 doupdate();
1369 return;
1372 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
1373 update_board_window(g);
1374 update_status_window(g);
1375 update_history_window(g);
1376 update_tag_window(g->tag);
1377 update_engine_window(g);
1378 update_panels();
1379 doupdate();
1382 static void game_next_prev(GAME g, int n, int count)
1384 if (gtotal < 2)
1385 return;
1387 if (n == 1) {
1388 if (gindex + count > gtotal - 1) {
1389 if (count != 1)
1390 gindex = gtotal - 1;
1391 else
1392 gindex = 0;
1394 else
1395 gindex += count;
1397 else {
1398 if (gindex - count < 0) {
1399 if (count != 1)
1400 gindex = 0;
1401 else
1402 gindex = gtotal - 1;
1404 else
1405 gindex -= count;
1408 gp = game[gindex];
1411 static void delete_game(int which)
1413 GAME *g = NULL;
1414 int gi = 0;
1415 int i;
1416 struct userdata_s *d;
1418 for (i = 0; i < gtotal; i++) {
1419 d = game[i]->data;
1421 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
1422 free_userdata_once(game[i]);
1423 pgn_free(game[i]);
1424 continue;
1427 g = Realloc(g, (gi + 1) * sizeof(GAME *));
1428 g[gi] = Calloc(1, sizeof(struct game_s));
1429 memcpy(g[gi], game[i], sizeof(struct game_s));
1430 g[gi]->tag = game[i]->tag;
1431 g[gi]->history = game[i]->history;
1432 g[gi]->hp = game[i]->hp;
1433 gi++;
1436 game = g;
1437 gtotal = gi;
1439 if (which != -1) {
1440 if (which + 1 >= gtotal)
1441 gindex = gtotal - 1;
1442 else
1443 gindex = which;
1445 else
1446 gindex = gtotal - 1;
1448 gp = game[gindex];
1449 gp->hp = gp->history;
1453 * FIXME find across multiple games.
1455 static int find_move_exp(GAME g, regex_t r, int which, int count)
1457 int i;
1458 int ret;
1459 char errbuf[255];
1460 int incr;
1461 int found;
1463 incr = (which == 0) ? -1 : 1;
1465 for (i = g->hindex + incr - 1, found = 0; ; i += incr) {
1466 if (i == g->hindex - 1)
1467 break;
1469 if (i >= pgn_history_total(g->hp))
1470 i = 0;
1471 else if (i < 0)
1472 i = pgn_history_total(g->hp) - 1;
1474 // FIXME RAV
1475 ret = regexec(&r, g->hp[i]->move, 0, 0, 0);
1477 if (ret == 0) {
1478 if (count == ++found) {
1479 return i + 1;
1482 else {
1483 if (ret != REG_NOMATCH) {
1484 regerror(ret, &r, errbuf, sizeof(errbuf));
1485 cmessage(_("Error Matching Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf);
1486 return -1;
1491 return -1;
1494 static int toggle_delete_flag(int n)
1496 int i, x;
1497 struct userdata_s *d = game[n]->data;
1499 TOGGLE_FLAG(d->flags, CF_DELETE);
1500 gindex = n;
1502 for (i = x = 0; i < gtotal; i++) {
1503 d = game[i]->data;
1505 if (TEST_FLAG(d->flags, CF_DELETE))
1506 x++;
1509 if (x == gtotal) {
1510 cmessage(NULL, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
1511 d = game[n]->data;
1512 CLEAR_FLAG(d->flags, CF_DELETE);
1513 return 1;
1516 return 0;
1519 static int find_game_exp(char *str, int which, int count)
1521 char *nstr = NULL, *exp = NULL;
1522 regex_t nexp, vexp;
1523 int ret = -1;
1524 int g = 0;
1525 char buf[255], *tmp;
1526 char errbuf[255];
1527 int found = 0;
1528 int incr = (which == 0) ? -(1) : 1;
1530 strncpy(buf, str, sizeof(buf));
1531 tmp = buf;
1533 if (strstr(tmp, ":") != NULL) {
1534 nstr = strsep(&tmp, ":");
1536 if ((ret = regcomp(&nexp, nstr,
1537 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
1538 regerror(ret, &nexp, errbuf, sizeof(errbuf));
1539 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf);
1540 ret = g = -1;
1541 goto cleanup;
1545 exp = tmp;
1547 while (*exp && isspace(*exp))
1548 exp++;
1550 if (exp == NULL)
1551 goto cleanup;
1553 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
1554 regerror(ret, &vexp, errbuf, sizeof(errbuf));
1555 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf);
1556 ret = -1;
1557 goto cleanup;
1560 ret = -1;
1562 for (g = gindex + incr, found = 0; ; g += incr) {
1563 int t;
1565 if (g == gtotal)
1566 g = 0;
1567 else if (g < 0)
1568 g = gtotal - 1;
1570 if (g == gindex)
1571 break;
1573 for (t = 0; game[g]->tag[t]; t++) {
1574 if (nstr) {
1575 if (regexec(&nexp, game[g]->tag[t]->name, 0, 0, 0) == 0) {
1576 if (regexec(&vexp, game[g]->tag[t]->value, 0, 0, 0) == 0) {
1577 if (count == ++found) {
1578 ret = g;
1579 goto cleanup;
1584 else {
1585 if (regexec(&vexp, game[g]->tag[t]->value, 0, 0, 0) == 0) {
1586 if (count == ++found) {
1587 ret = g;
1588 goto cleanup;
1594 ret = -1;
1597 cleanup:
1598 if (nstr)
1599 regfree(&nexp);
1601 if (g != -1)
1602 regfree(&vexp);
1604 return ret;
1608 * Updates the notification line in the status window then refreshes the
1609 * status window.
1611 void update_status_notify(GAME g, char *fmt, ...)
1613 va_list ap;
1614 #ifdef HAVE_VASPRINTF
1615 char *line;
1616 #else
1617 char line[COLS];
1618 #endif
1620 if (!fmt) {
1621 if (status.notify) {
1622 free(status.notify);
1623 status.notify = NULL;
1626 return;
1629 va_start(ap, fmt);
1630 #ifdef HAVE_VASPRINTF
1631 vasprintf(&line, fmt, ap);
1632 #else
1633 vsnprintf(line, sizeof(line), fmt, ap);
1634 #endif
1635 va_end(ap);
1637 if (status.notify)
1638 free(status.notify);
1640 status.notify = str_to_wchar(line);
1642 #ifdef HAVE_VASPRINTF
1643 free(line);
1644 #endif
1647 int rav_next_prev(GAME g, BOARD b, int n)
1649 // Next RAV.
1650 if (n) {
1651 if ((!g->ravlevel && g->hindex && g->hp[g->hindex - 1]->rav == NULL) ||
1652 (!g->ravlevel && !g->hindex && g->hp[g->hindex]->rav == NULL) ||
1653 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
1654 return 1;
1656 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
1657 g->rav[g->ravlevel].hp = g->hp;
1658 g->rav[g->ravlevel].flags = g->flags;
1659 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(g, b));
1660 g->rav[g->ravlevel].hindex = g->hindex;
1661 g->hp = (!g->ravlevel) ? (g->hindex) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav : g->hp[g->hindex]->rav;
1662 g->hindex = 0;
1663 g->ravlevel++;
1664 pgn_board_update(g, b, g->hindex + 1);
1665 return 0;
1668 if (g->ravlevel - 1 < 0)
1669 return 1;
1671 // Previous RAV.
1672 g->ravlevel--;
1673 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
1674 free(g->rav[g->ravlevel].fen);
1675 g->hp = g->rav[g->ravlevel].hp;
1676 g->flags = g->rav[g->ravlevel].flags;
1677 g->hindex = g->rav[g->ravlevel].hindex;
1678 return 0;
1681 static void draw_window_decor()
1683 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
1684 move_panel(boardp, 0, COLS - BOARD_WIDTH);
1685 move_panel(statusp, 0, 0);
1686 wbkgd(boardw, CP_BOARD_WINDOW);
1687 wbkgd(statusw, CP_STATUS_WINDOW);
1688 window_draw_title(statusw, _("Game Status"), STATUS_WIDTH,
1689 CP_STATUS_TITLE, CP_STATUS_BORDER);
1690 wbkgd(tagw, CP_TAG_WINDOW);
1691 window_draw_title(tagw, _("Roster Tags"), TAG_WIDTH, CP_TAG_TITLE,
1692 CP_TAG_BORDER);
1693 wbkgd(historyw, CP_HISTORY_WINDOW);
1694 window_draw_title(historyw, _("Move History"), HISTORY_WIDTH,
1695 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
1698 #ifdef HAVE_WRESIZE
1699 static void do_window_resize()
1701 if (LINES < 24 || COLS < 80)
1702 return;
1704 resizeterm(LINES, COLS);
1705 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
1706 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
1707 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
1708 wmove(historyw, 0, 0);
1709 wclrtobot(historyw);
1710 wmove(tagw, 0, 0);
1711 wclrtobot(tagw);
1712 wmove(statusw, 0, 0);
1713 wclrtobot(statusw);
1714 draw_window_decor();
1715 update_all(gp);
1717 #endif
1719 void stop_clock()
1721 memset(&clock_timer, 0, sizeof(struct itimerval));
1722 setitimer(ITIMER_REAL, &clock_timer, NULL);
1725 void start_clock(GAME g)
1727 struct userdata_s *d = g->data;
1729 if (clock_timer.it_interval.tv_usec)
1730 return;
1732 memset(&d->elapsed, 0, sizeof(struct timeval));
1733 clock_timer.it_value.tv_sec = 0;
1734 clock_timer.it_value.tv_usec = 100000;
1735 clock_timer.it_interval.tv_sec = 0;
1736 clock_timer.it_interval.tv_usec = 100000;
1737 setitimer(ITIMER_REAL, &clock_timer, NULL);
1740 static void update_clocks()
1742 int i;
1743 struct userdata_s *d;
1744 struct itimerval it;
1745 int update = 0;
1747 getitimer(ITIMER_REAL, &it);
1749 for (i = 0; i < gtotal; i++) {
1750 d = game[i]->data;
1752 if (d && d->mode == MODE_PLAY) {
1753 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
1754 continue;
1755 else if (d->paused == -1) {
1756 if (game[i]->side == game[i]->turn) {
1757 d->paused = 1;
1758 continue;
1762 update_clock(game[i], it);
1764 if (game[i] == gp)
1765 update = 1;
1769 if (update) {
1770 update_status_window(gp);
1771 update_panels();
1772 doupdate();
1776 #define SKIP_SPACE(str) { while (isspace(*str)) str++; }
1778 static int parse_clock_time(char **str)
1780 char *p = *str;
1781 int n = 0, t = 0;
1783 SKIP_SPACE(p);
1785 if (!isdigit(*p))
1786 return -1;
1788 while (*p) {
1789 if (isdigit(*p)) {
1790 t = atoi(p);
1792 while (isdigit(*p))
1793 p++;
1795 continue;
1798 switch (*p) {
1799 case 'H':
1800 case 'h':
1801 n += t * (60 * 60);
1802 t = 0;
1803 break;
1804 case 'M':
1805 case 'm':
1806 n += t * 60;
1807 t = 0;
1808 break;
1809 case 'S':
1810 case 's':
1811 n += t;
1812 t = 0;
1813 break;
1814 case ' ':
1815 p++;
1816 case '/':
1817 case '+':
1818 goto done;
1819 default:
1820 *str = p;
1821 return -1;
1824 p++;
1827 done:
1828 n += t;
1829 *str = p;
1830 return n;
1833 static int parse_clock_input(struct clock_s *clk, char *str, int *incr)
1835 char *p = str;
1836 long n = 0;
1837 int plus = 0;
1838 int m = 0;
1839 int tc = 0;
1841 SKIP_SPACE(p);
1843 if (!*p)
1844 return 0;
1846 if (*p == '+') {
1847 plus = 1;
1848 p++;
1849 SKIP_SPACE(p);
1851 if (*p == '+')
1852 goto move_incr;
1854 else
1855 memset(clk, 0, sizeof(struct clock_s));
1857 again:
1858 /* Sudden death. */
1859 if (strncasecmp(p, "SD", 2) == 0) {
1860 n = 0;
1861 p += 2;
1862 goto tc;
1865 n = parse_clock_time(&p);
1867 if (n == -1)
1868 return 1;
1870 if (!n)
1871 goto done;
1873 /* Time control. */
1875 if (*p == '/') {
1876 if (plus)
1877 return 1;
1879 /* Sudden death without a previous time control. */
1880 if (!n && !tc)
1881 return 1;
1883 m = n;
1884 p++;
1885 n = parse_clock_time(&p);
1887 if (n == -1)
1888 return 1;
1890 if (tc >= MAX_TC) {
1891 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s (%i)", _("Maximum number of time controls reached"), MAX_TC);
1892 return 1;
1895 clk->tc[tc][0] = m;
1896 clk->tc[tc++][1] = n;
1897 SKIP_SPACE(p);
1899 if (*p == '+')
1900 goto move_incr;
1902 if (*p)
1903 goto again;
1905 goto done;
1908 if (plus)
1909 *incr = n;
1910 else
1911 clk->tc[clk->tcn][1] = (n <= clk->elapsed.tv_sec) ? clk->elapsed.tv_sec + n : n;
1913 move_incr:
1914 if (*p) {
1915 if (*p++ == '+') {
1916 if (!isdigit(*p))
1917 return 1;
1919 n = parse_clock_time(&p);
1921 if (n == -1 || *p)
1922 return 1;
1924 clk->incr = n;
1926 SKIP_SPACE(p);
1928 if (*p)
1929 return 1;
1931 else
1932 return 1;
1935 done:
1936 return 0;
1939 static int parse_which_clock(struct clock_s *clk, char *str)
1941 struct clock_s tmp;
1942 int incr = 0;
1944 memcpy(&tmp, clk, sizeof(struct clock_s));
1946 if (parse_clock_input(&tmp, str, &incr)) {
1947 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), _("Invalid clock specification"));
1948 return 1;
1951 memcpy(clk, &tmp, sizeof(struct clock_s));
1952 clk->tc[clk->tcn][1] += incr;
1953 return 0;
1956 void do_clock_input_finalize(WIN *win)
1958 struct userdata_s *d = gp->data;
1959 struct input_data_s *in = win->data;
1960 char *p = in->str;
1962 if (!in->str) {
1963 free(in);
1964 return;
1967 SKIP_SPACE(p);
1969 if (tolower(*p) == 'w') {
1970 p++;
1972 if (parse_which_clock(&d->wclock, p))
1973 goto done;
1975 else if (tolower(*p) == 'b') {
1976 p++;
1978 if (parse_which_clock(&d->bclock, p))
1979 goto done;
1981 else {
1982 if (parse_which_clock(&d->wclock, p))
1983 goto done;
1985 if (parse_which_clock(&d->bclock, p))
1986 goto done;
1989 if (!d->wclock.tc[0][1] && !d->bclock.tc[0][1])
1990 CLEAR_FLAG(d->flags, CF_CLOCK);
1991 else
1992 SET_FLAG(d->flags, CF_CLOCK);
1994 done:
1995 free(in->str);
1996 free(in);
1999 void do_engine_command_finalize(WIN *win)
2001 struct userdata_s *d = gp->data;
2002 struct input_data_s *in = win->data;
2003 int x;
2005 if (!in->str) {
2006 free(in);
2007 return;
2010 if (!d->engine)
2011 goto done;
2013 x = d->engine->status;
2014 send_to_engine(gp, -1, "%s\n", in->str);
2015 d->engine->status = x;
2017 done:
2018 free(in->str);
2019 free(in);
2022 void do_board_details()
2024 config.details = (config.details) ? 0 : 1;
2027 void do_toggle_strict_castling()
2029 int n;
2031 pgn_config_get(PGN_STRICT_CASTLING, &n);
2033 if (n == 0)
2034 pgn_config_set(PGN_STRICT_CASTLING, 1);
2035 else
2036 pgn_config_set(PGN_STRICT_CASTLING, 0);
2039 void do_play_set_clock()
2041 struct input_data_s *in;
2043 in = Calloc(1, sizeof(struct input_data_s));
2044 in->efunc = do_clock_input_finalize;
2045 construct_input(_("Set Clock"), NULL, 1, 1,
2046 _ ("Format: [W | B] [+]T[+I] | ++I | M/T [M/T [...] [SD/T]] [+I]\n" \
2047 "T = time (hms), I = increment, M = moves per, SD = sudden death\ne.g., 30m or 4m+12s or 35/90m SD/30m"),
2048 NULL, NULL, 0, in, INPUT_HIST_CLOCK, -1);
2051 void do_play_toggle_human()
2053 struct userdata_s *d = gp->data;
2055 TOGGLE_FLAG(d->flags, CF_HUMAN);
2057 if (!TEST_FLAG(d->flags, CF_HUMAN) && pgn_history_total(gp->hp)) {
2058 if (init_chess_engine(gp))
2059 return;
2062 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2064 if (d->engine)
2065 d->engine->status = ENGINE_READY;
2068 void do_play_toggle_engine()
2070 struct userdata_s *d = gp->data;
2072 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2073 CLEAR_FLAG(d->flags, CF_HUMAN);
2075 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2076 pgn_board_update(gp, d->b,
2077 pgn_history_total(gp->hp));
2078 add_engine_command(gp, ENGINE_READY,
2079 "setboard %s\n", pgn_game_to_fen(gp, d->b));
2084 * This will send a command to the engine skipping the command queue.
2086 void do_play_send_command()
2088 struct userdata_s *d = gp->data;
2089 struct input_data_s *in;
2091 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2092 if (init_chess_engine(gp))
2093 return;
2096 in = Calloc(1, sizeof(struct input_data_s));
2097 in->efunc = do_engine_command_finalize;
2098 construct_input(_("Engine Command"), NULL, 1, 1, NULL, NULL, NULL, 0, in, INPUT_HIST_ENGINE, -1);
2101 void do_play_switch_turn()
2103 struct userdata_s *d = gp->data;
2105 pgn_switch_side(gp);
2106 pgn_switch_turn(gp);
2108 if (!TEST_FLAG(d->flags, CF_HUMAN))
2109 add_engine_command(gp, -1,
2110 (gp->side == WHITE) ? "white\n" : "black\n");
2112 update_status_window(gp);
2115 void do_play_undo()
2117 struct userdata_s *d = gp->data;
2119 if (!pgn_history_total(gp->hp))
2120 return;
2122 if (keycount) {
2123 if (gp->hindex - keycount < 0)
2124 gp->hindex = 0;
2125 else
2126 gp->hindex -= keycount * 2;
2128 else {
2129 if (gp->hindex - 2 < 0)
2130 gp->hindex = 0;
2131 else
2132 gp->hindex -= 2;
2135 pgn_history_free(gp->hp, gp->hindex);
2136 gp->hindex = pgn_history_total(gp->hp);
2137 pgn_board_update(gp, d->b, gp->hindex);
2139 if (d->engine && d->engine->status == ENGINE_READY) {
2140 add_engine_command(gp, ENGINE_READY, "setboard %s\n",
2141 pgn_game_to_fen(gp, d->b));
2142 d->engine->status = ENGINE_READY;
2145 update_history_window(gp);
2148 void do_play_toggle_pause()
2150 struct userdata_s *d = gp->data;
2152 if (!TEST_FLAG(d->flags, CF_HUMAN) && gp->turn !=
2153 gp->side) {
2154 d->paused = -1;
2155 return;
2158 d->paused = (d->paused) ? 0 : 1;
2161 void do_play_go()
2163 struct userdata_s *d = gp->data;
2165 if (TEST_FLAG(d->flags, CF_HUMAN))
2166 return;
2168 add_engine_command(gp, ENGINE_THINKING, "go\n");
2171 void do_play_config_command()
2173 int x, w;
2175 if (config.keys) {
2176 for (x = 0; config.keys[x]; x++) {
2177 if (config.keys[x]->c == input_c) {
2178 switch (config.keys[x]->type) {
2179 case KEY_DEFAULT:
2180 add_engine_command(gp, -1, "%s\n",
2181 config.keys[x]->str);
2182 break;
2183 case KEY_SET:
2184 if (!keycount)
2185 break;
2187 add_engine_command(gp, -1,
2188 "%s %i\n", config.keys[x]->str, keycount);
2189 keycount = 0;
2190 break;
2191 case KEY_REPEAT:
2192 if (!keycount)
2193 break;
2195 for (w = 0; w < keycount; w++)
2196 add_engine_command(gp, -1,
2197 "%s\n", config.keys[x]->str);
2198 keycount = 0;
2199 break;
2205 update_status_notify(gp, NULL);
2208 void do_play_cancel_selected()
2210 struct userdata_s *d = gp->data;
2212 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2213 keycount = 0;
2214 update_status_notify(gp, NULL);
2217 void do_play_commit()
2219 struct userdata_s *d = gp->data;
2221 pushkey = keycount = 0;
2222 update_status_notify(gp, NULL);
2224 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2225 (!d->engine || d->engine->status == ENGINE_THINKING))
2226 return;
2228 if (!d->sp.icon)
2229 return;
2231 d->sp.row = d->c_row;
2232 d->sp.col = d->c_col;
2233 move_to_engine(gp);
2236 void do_play_select()
2238 struct userdata_s *d = gp->data;
2240 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2241 d->engine->status == ENGINE_OFFLINE)) {
2242 if (init_chess_engine(gp))
2243 return;
2246 if (d->sp.icon || (d->engine && d->engine->status == ENGINE_THINKING))
2247 return;
2249 d->sp.icon = d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon;
2251 if (pgn_piece_to_int(d->sp.icon) == OPEN_SQUARE) {
2252 d->sp.icon = 0;
2253 return;
2256 if (((islower(d->sp.icon) && gp->turn != BLACK)
2257 || (isupper(d->sp.icon) && gp->turn != WHITE))) {
2258 message(NULL, _("[ press any key to continue ]"), "%s", _("It is not your turn to move. You can switch sides "));
2259 d->sp.icon = 0;
2260 return;
2261 #if 0
2262 if (pgn_history_total(gp->hp)) {
2263 message(NULL, _("[ press any key to continue ]"), "%s", _("It is not your turn to move. You can switch sides "));
2264 d->sp.icon = 0;
2265 return;
2267 else {
2268 if (pgn_tag_find(gp->tag, "FEN") != E_PGN_ERR)
2269 return;
2271 add_engine_command(gp, ENGINE_READY, "black\n");
2272 pgn_switch_turn(gp);
2274 if (gp->side != BLACK)
2275 pgn_switch_side(gp);
2277 #endif
2280 d->sp.srow = d->c_row;
2281 d->sp.scol = d->c_col;
2283 if (config.validmoves)
2284 pgn_find_valid_moves(gp, d->b, d->sp.scol, d->sp.srow);
2286 CLEAR_FLAG(d->flags, CF_NEW);
2287 start_clock(gp);
2290 /* FIXME: keys with the same function should comma deliminated. */
2291 static char *build_help(struct key_s **keys)
2293 int i, nlen = 1, len, t, n;
2294 char *buf = NULL;
2295 char *p;
2297 if (!keys)
2298 return NULL;
2300 for (i = len = t = 0; keys[i]; i++) {
2301 if (!keys[i]->d)
2302 continue;
2304 if (keys[i]->key) {
2305 if (strlen(keys[i]->key) > nlen) {
2306 nlen = strlen(keys[i]->key);
2307 t += nlen;
2309 else
2310 t++;
2313 if (keys[i]->d) {
2314 if (strlen(keys[i]->d) > len)
2315 len = strlen(keys[i]->d);
2318 t += len;
2319 t += keys[i]->r;
2322 t += 4 + i;
2323 buf = Malloc(t);
2324 p = buf;
2326 for (i = 0; keys[i]; i++) {
2327 if (!keys[i]->d)
2328 continue;
2330 if (keys[i]->key)
2331 n = strlen(keys[i]->key);
2332 else
2333 n = 1;
2335 while (n++ <= nlen)
2336 *p++ = ' ';
2338 *p = 0;
2340 if (keys[i]->key) {
2341 strcat(buf, keys[i]->key);
2342 p = buf + strlen(buf);
2344 else
2345 *p++ = keys[i]->c;
2347 *p++ = ' ';
2348 *p++ = '-';
2349 *p++ = ' ';
2350 *p = 0;
2352 if (keys[i]->d)
2353 strcat(buf, keys[i]->d);
2355 if (keys[i]->r)
2356 strcat(buf, "*");
2358 strcat(buf, "\n");
2359 p = buf + strlen(buf);
2362 return buf;
2365 void do_more_help(WIN *);
2366 void do_main_help(WIN *win)
2368 char *buf;
2370 switch (win->c) {
2371 case 'p':
2372 buf = build_help(play_keys);
2373 construct_message(_("Play Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2374 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
2375 break;
2376 case 'h':
2377 buf = build_help(history_keys);
2378 construct_message(_("History Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2379 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
2380 break;
2381 case 'e':
2382 buf = build_help(edit_keys);
2383 construct_message(_("Edit Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2384 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
2385 break;
2386 case 'g':
2387 buf = build_help(global_keys);
2388 construct_message(_("Global Game Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2389 NULL, NULL, buf, do_more_help, 0, 1, "%s", buf);
2390 break;
2391 default:
2392 break;
2396 void do_more_help(WIN *win)
2398 if (win->c == KEY_F(1) || win->c == CTRL('g'))
2399 construct_message(_("Command Key Index"),
2400 _ ("p/h/e/g or any other key to quit"), 0, 0,
2401 NULL, NULL, NULL, do_main_help, 0, 0, "%s",
2403 "p - play mode keys\n"
2404 "h - history mode keys\n"
2405 "e - board edit mode keys\n"
2406 "g - global game keys"
2410 void do_play_help()
2412 char *buf = build_help(play_keys);
2414 construct_message(_("Play Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL, NULL, buf,
2415 do_more_help, 0, 1, "%s", buf);
2418 void do_play_history_mode()
2420 struct userdata_s *d = gp->data;
2422 if (!pgn_history_total(gp->hp) ||
2423 (d->engine && d->engine->status == ENGINE_THINKING))
2424 return;
2426 d->mode = MODE_HISTORY;
2427 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
2430 void do_play_edit_mode()
2432 struct userdata_s *d = gp->data;
2434 if (pgn_history_total(gp->hp))
2435 return;
2437 pgn_board_init_fen(gp, d->b, NULL);
2438 config.details++;
2439 d->mode = MODE_EDIT;
2442 void do_edit_insert_finalize(WIN *win)
2444 struct userdata_s *d = win->data;
2446 if (pgn_piece_to_int(win->c) == -1)
2447 return;
2449 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = win->c;
2452 void do_edit_select()
2454 struct userdata_s *d = gp->data;
2456 if (d->sp.icon)
2457 return;
2459 d->sp.icon = d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon;
2461 if (pgn_piece_to_int(d->sp.icon) == OPEN_SQUARE) {
2462 d->sp.icon = 0;
2463 return;
2466 d->sp.srow = d->c_row;
2467 d->sp.scol = d->c_col;
2470 void do_edit_commit()
2472 int p;
2473 struct userdata_s *d = gp->data;
2475 pushkey = keycount = 0;
2476 update_status_notify(gp, NULL);
2478 if (!d->sp.icon)
2479 return;
2481 d->sp.row = d->c_row;
2482 d->sp.col = d->c_col;
2483 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
2484 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
2485 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2486 pgn_int_to_piece(gp->turn, OPEN_SQUARE);
2487 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2490 void do_edit_delete()
2492 struct userdata_s *d = gp->data;
2494 if (d->sp.icon)
2495 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2496 pgn_int_to_piece(gp->turn, OPEN_SQUARE);
2497 else
2498 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2499 pgn_int_to_piece(gp->turn, OPEN_SQUARE);
2501 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2504 void do_edit_cancel_selected()
2506 struct userdata_s *d = gp->data;
2508 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2509 keycount = 0;
2510 update_status_notify(gp, NULL);
2513 void do_edit_switch_turn()
2515 pgn_switch_turn(gp);
2518 void do_edit_toggle_castle()
2520 struct userdata_s *d = gp->data;
2522 castling_state(gp, d->b, RANKTOBOARD(d->c_row),
2523 FILETOBOARD(d->c_col),
2524 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2527 void do_edit_insert()
2529 struct userdata_s *d = gp->data;
2531 construct_message(_("Insert Piece"), _("P=pawn, R=rook, N=knight, B=bishop, "), 0, 0, NULL, NULL,
2532 d->b, do_edit_insert_finalize, 0, 0, "%s", _("Type the piece letter to insert. Lowercase "));
2535 void do_edit_enpassant()
2537 struct userdata_s *d = gp->data;
2539 if (d->c_row == 6 || d->c_row == 3) {
2540 pgn_reset_enpassant(d->b);
2541 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2545 void do_edit_help()
2547 char *buf = build_help(edit_keys);
2549 construct_message(_("Edit Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL, NULL, buf,
2550 do_more_help, 0, 1, "%s", buf);
2553 void do_edit_exit()
2555 struct userdata_s *d = gp->data;
2557 config.details--;
2558 pgn_tag_add(&gp->tag, "FEN", pgn_game_to_fen(gp, d->b));
2559 pgn_tag_add(&gp->tag, "SetUp", "1");
2560 pgn_tag_sort(gp->tag);
2561 pgn_board_update(gp, d->b, gp->hindex);
2562 d->mode = MODE_PLAY;
2565 void really_do_annotate_finalize(struct input_data_s *in,
2566 struct userdata_s *d)
2568 HISTORY *h = in->data;
2569 int len;
2571 if (!in->str) {
2572 if (h->comment) {
2573 free(h->comment);
2574 h->comment = NULL;
2577 else {
2578 len = strlen(in->str) + 1;
2579 h->comment = Realloc(h->comment, len);
2580 strncpy(h->comment, in->str, len);
2583 free(in->str);
2584 free(in);
2585 SET_FLAG(d->flags, CF_MODIFIED);
2588 void do_annotate_finalize(WIN *win)
2590 struct userdata_s *d = gp->data;
2591 struct input_data_s *in = win->data;
2593 really_do_annotate_finalize(in, d);
2596 void do_find_move_exp_finalize(int init, int which)
2598 int n;
2599 struct userdata_s *d = gp->data;
2600 static int firstrun;
2601 static regex_t r;
2602 int ret;
2603 char errbuf[255];
2605 if (init || !firstrun) {
2606 if (!firstrun)
2607 regfree(&r);
2609 if ((ret = regcomp(&r, moveexp, REG_EXTENDED|REG_NOSUB)) != 0) {
2610 regerror(ret, &r, errbuf, sizeof(errbuf));
2611 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf);
2612 return;
2615 firstrun = 1;
2618 if ((n = find_move_exp(gp, r,
2619 (which == -1) ? 0 : 1, (keycount) ? keycount : 1)) == -1)
2620 return;
2622 gp->hindex = n;
2623 pgn_board_update(gp, d->b, gp->hindex);
2626 void do_find_move_exp(WIN *win)
2628 struct input_data_s *in = win->data;
2629 int *n = in->data;
2630 int which = *n;
2632 if (in->str) {
2633 strncpy(moveexp, in->str, sizeof(moveexp));
2634 do_find_move_exp_finalize(1, which);
2635 free(in->str);
2638 free(in->data);
2639 free(in);
2642 void do_move_jump_finalize(int n)
2644 struct userdata_s *d = gp->data;
2646 if (n < 0 || n > (pgn_history_total(gp->hp) / 2))
2647 return;
2649 keycount = 0;
2650 update_status_notify(gp, NULL);
2651 gp->hindex = (n) ? n * 2 - 1 : n * 2;
2652 pgn_board_update(gp, d->b, gp->hindex);
2655 void do_move_jump(WIN *win)
2657 struct input_data_s *in = win->data;
2659 if (!in->str || !isinteger(in->str)) {
2660 if (in->str)
2661 free(in->str);
2663 free(in);
2664 return;
2667 do_move_jump_finalize(atoi(in->str));
2668 free(in->str);
2669 free(in);
2672 struct history_menu_s {
2673 char *line;
2674 int hindex;
2675 int ravlevel;
2676 int move;
2677 int indent;
2680 void free_history_menu_data(struct history_menu_s **h)
2682 int i;
2684 if (!h)
2685 return;
2687 for (i = 0; h[i]; i++) {
2688 free(h[i]->line);
2689 free(h[i]);
2692 free(h);
2695 void get_history_data(HISTORY **hp, struct history_menu_s ***menu, int m,
2696 int turn)
2698 int i, n = 0;
2699 int t = pgn_history_total(hp);
2700 char buf[MAX_SAN_MOVE_LEN + 4];
2701 static int depth;
2702 struct history_menu_s **hmenu = *menu;
2704 if (hmenu)
2705 for (n = 0; hmenu[n]; n++);
2706 else
2707 depth = 0;
2709 for (i = 0; i < t; i++) {
2710 hmenu = Realloc(hmenu, (n + 2) * sizeof(struct history_menu_s *));
2711 hmenu[n] = Malloc(sizeof(struct history_menu_s));
2712 snprintf(buf, sizeof(buf), "%c%s%s", (turn == WHITE) ? 'W' : 'B',
2713 hp[i]->move, (hp[i]->comment || hp[i]->nag[0]) ? " !" : "");
2714 hmenu[n]->line = strdup(buf);
2715 hmenu[n]->hindex = i;
2716 hmenu[n]->indent = 0;
2717 hmenu[n]->ravlevel = depth;
2718 hmenu[n]->move = (n && depth > hmenu[n-1]->ravlevel) ? m++ : m;
2719 n++;
2720 hmenu[n] = NULL;
2722 #if 0
2723 if (hp[i]->rav) {
2724 depth++;
2725 get_history_data(hp[i]->rav, &hmenu, m, turn);
2726 for (n = 0; hmenu[n]; n++);
2727 depth--;
2729 if (depth)
2730 m--;
2732 #endif
2734 turn = (turn == WHITE) ? BLACK : WHITE;
2737 *menu = hmenu;
2740 void history_draw_update(struct menu_input_s *m)
2742 GAME g = m->data;
2743 struct userdata_s *d = g->data;
2745 g->hindex = m->selected + 1;
2746 update_cursor(g, m->selected);
2747 pgn_board_update(g, d->b, m->selected + 1);
2750 struct menu_item_s **get_history_items(WIN *win)
2752 struct menu_input_s *m = win->data;
2753 GAME g = m->data;
2754 struct userdata_s *d = g->data;
2755 struct history_menu_s **hm = d->data;
2756 struct menu_item_s **items = m->items;
2757 int i;
2759 if (!hm) {
2760 get_history_data(g->history, &hm, 0,
2761 TEST_FLAG(g->flags, GF_BLACK_OPENING));
2762 m->selected = g->hindex - 1;
2764 if (m->selected < 0)
2765 m->selected = 0;
2767 m->draw_exit_func = history_draw_update;
2770 d->data = hm;
2772 if (items) {
2773 for (i = 0; items[i]; i++)
2774 free(items[i]);
2776 free(items);
2777 items = NULL;
2780 for (i = 0; hm[i]; i++) {
2781 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
2782 items[i] = Malloc(sizeof(struct menu_item_s));
2783 items[i]->name = hm[i]->line;
2784 items[i]->value = NULL;
2785 items[i]->selected = 0;
2788 if (items)
2789 items[i] = NULL;
2791 m->nofree = 1;
2792 m->items = items;
2793 return items;
2796 void history_menu_quit(struct menu_input_s *m)
2798 pushkey = -1;
2801 void history_menu_exit(WIN *win)
2803 GAME g = win->data;
2804 struct userdata_s *d = g->data;
2805 struct history_menu_s **hm = d->data;
2806 int i;
2808 if (!hm)
2809 return;
2811 for (i = 0; hm[i]; i++) {
2812 free(hm[i]->line);
2813 free(hm[i]);
2816 free(hm);
2817 d->data = NULL;
2820 // FIXME RAV
2821 void history_menu_next(struct menu_input_s *m)
2823 GAME g = m->data;
2824 struct userdata_s *d = g->data;
2825 struct history_menu_s **hm = d->data;
2826 int n, t;
2828 for (t = 0; hm[t]; t++);
2830 if (m->selected + 1 == t)
2831 n = 0;
2832 else
2833 n = hm[m->selected + 1]->hindex;
2835 n++;
2836 g->hindex = n;
2839 // FIXME RAV
2840 void history_menu_prev(struct menu_input_s *m)
2842 GAME g = m->data;
2843 struct userdata_s *d = g->data;
2844 struct history_menu_s **hm = d->data;
2845 int n, t;
2847 for (t = 0; hm[t]; t++);
2849 if (m->selected - 1 < 0)
2850 n = t - 1;
2851 else
2852 n = hm[m->selected - 1]->hindex;
2854 n++;
2855 g->hindex = n;
2858 void history_menu_help(struct menu_input_s *m)
2860 message("History Menu Help", _("[ press any key to continue ]"), "%s",
2862 " UP/DOWN - previous/next menu item\n"
2863 " HOME/END - first/last menu item\n"
2864 " PGDN/PGUP - next/previous page\n"
2865 " a-zA-Z0-9 - jump to item\n"
2866 " CTRL-a - annotate the selected move\n"
2867 " ENTER - view annotation\n"
2868 " CTRL-d - toggle board details\n"
2869 " ESCAPE - quit"
2873 void do_annotate_move(HISTORY *hp)
2875 char buf[COLS - 4];
2876 struct input_data_s *in;
2878 snprintf(buf, sizeof(buf), "%s \"%s\"", _("Editing Annotation for"), hp->move);
2879 in = Calloc(1, sizeof(struct input_data_s));
2880 in->data = hp;
2881 in->efunc = do_annotate_finalize;
2882 construct_input(buf, hp->comment, MAX_PGN_LINE_LEN / INPUT_WIDTH, 0,
2883 _("Type CTRL-t to edit NAG"), edit_nag, NULL, CTRL('T'), in, -1, -1);
2886 void history_menu_view_annotation(struct menu_input_s *m)
2888 GAME g = m->data;
2890 // FIXME RAV
2891 view_annotation(g->history[m->selected]);
2894 void history_menu_annotate_finalize(WIN *win)
2896 struct input_data_s *in = win->data;
2897 GAME g = in->moredata;
2898 struct userdata_s *d = g->data;
2899 struct history_menu_s **hm = d->data;
2901 really_do_annotate_finalize(in, d);
2902 free_history_menu_data(hm);
2903 hm = NULL;
2904 get_history_data(g->history, &hm, 0, TEST_FLAG(g->flags, GF_BLACK_OPENING));
2905 d->data = hm;
2906 pushkey = REFRESH_MENU;
2909 void history_menu_annotate(struct menu_input_s *m)
2911 GAME g = m->data;
2912 char buf[COLS - 4];
2913 struct input_data_s *in;
2914 HISTORY *hp = g->history[m->selected]; // FIXME RAV
2916 snprintf(buf, sizeof(buf), "%s \"%s\"", _("Editing Annotation for"), hp->move);
2917 in = Calloc(1, sizeof(struct input_data_s));
2918 in->data = hp;
2919 in->moredata = m->data;
2920 in->efunc = history_menu_annotate_finalize;
2921 construct_input(buf, hp->comment, MAX_PGN_LINE_LEN / INPUT_WIDTH, 0,
2922 _("Type CTRL-t to edit NAG"), edit_nag, NULL, CTRL('T'), in, -1, -1);
2925 void history_menu_details(struct menu_input_s *m)
2927 do_board_details();
2930 // FIXME RAV
2931 void history_menu_print(WIN *win)
2933 struct menu_input_s *m = win->data;
2934 GAME g = m->data;
2935 struct userdata_s *d = g->data;
2936 struct history_menu_s **hm = d->data;
2937 struct history_menu_s *h = hm[m->top];
2938 int i;
2939 char *p = m->item->name;
2940 int line = m->print_line - 2;
2942 * Solaris 5.9 doesn't have wattr_get() or any function that requires an
2943 * attr_t data type.
2945 #ifdef HAVE_ATTR_T
2946 attr_t attrs;
2947 short pair;
2948 #endif
2949 int total;
2951 for (total = 0; hm[total]; total++);
2952 #ifdef HAVE_ATTR_T
2953 wattr_get(win->w, &attrs, &pair, NULL);
2954 wattroff(win->w, COLOR_PAIR(pair));
2955 #endif
2956 mvwaddch(win->w, m->print_line, 1,
2957 *p == 'W' ? *p | mix_cp(CP_BOARD_WHITE, CP_HISTORY_WINDOW, ATTRS(CP_BOARD_WHITE), A_FG_B_BG) : *p | mix_cp(CP_BOARD_BLACK, CP_HISTORY_WINDOW, ATTRS(CP_BOARD_BLACK), A_FG_B_BG));
2958 p++;
2960 if (h->hindex == 0 && line == 0)
2961 waddch(win->w, ACS_ULCORNER | CP_HISTORY_MENU_LG);
2962 else if ((!hm[h->hindex + (win->rows - 5) + 1] && line == win->rows - 5) ||
2963 (m->top + line == total - 1))
2964 waddch(win->w, ACS_LLCORNER | CP_HISTORY_MENU_LG);
2965 else if (hm[m->top + 1]->ravlevel != h->ravlevel || !h->ravlevel)
2966 waddch(win->w, ACS_LTEE | CP_HISTORY_MENU_LG);
2967 else
2968 waddch(win->w, ACS_VLINE | CP_HISTORY_MENU_LG);
2970 #ifdef HAVE_ATTR_T
2971 wattron(win->w, COLOR_PAIR(pair) | attrs);
2972 #endif
2974 for (i = 2; *p; p++, i++)
2975 waddch(win->w, (*p == '!') ? *p | A_BOLD : *p);
2977 while (i++ < win->cols - 2)
2978 waddch(win->w, ' ');
2981 void history_menu(GAME g)
2983 struct menu_key_s **keys = NULL;
2985 add_menu_key(&keys, KEY_ESCAPE, history_menu_quit);
2986 add_menu_key(&keys, KEY_UP, history_menu_prev);
2987 add_menu_key(&keys, KEY_DOWN, history_menu_next);
2988 add_menu_key(&keys, KEY_F(1), history_menu_help);
2989 add_menu_key(&keys, CTRL('a'), history_menu_annotate);
2990 add_menu_key(&keys, CTRL('d'), history_menu_details);
2991 add_menu_key(&keys, '\n', history_menu_view_annotation);
2992 construct_menu(LINES, TAG_WIDTH, 0, 0, _("Move History Tree"), 1,
2993 get_history_items, keys, g, history_menu_print, history_menu_exit);
2996 void do_history_menu()
2998 history_menu(gp);
3001 void do_history_half_move_toggle()
3003 movestep = (movestep == 1) ? 2 : 1;
3004 update_history_window(gp);
3007 void do_history_jump_next()
3009 struct userdata_s *d = gp->data;
3011 pgn_history_next(gp, d->b, (keycount > 0) ?
3012 config.jumpcount * keycount * movestep :
3013 config.jumpcount * movestep);
3016 void do_history_jump_prev()
3018 struct userdata_s *d = gp->data;
3020 pgn_history_prev(gp, d->b, (keycount) ?
3021 config.jumpcount * keycount * movestep :
3022 config.jumpcount * movestep);
3025 void do_history_prev()
3027 struct userdata_s *d = gp->data;
3029 pgn_history_prev(gp, d->b,
3030 (keycount) ? keycount * movestep : movestep);
3033 void do_history_next()
3035 struct userdata_s *d = gp->data;
3037 pgn_history_next(gp, d->b, (keycount) ?
3038 keycount * movestep : movestep);
3041 void do_history_mode_finalize(struct userdata_s *d)
3043 pushkey = 0;
3044 d->mode = MODE_PLAY;
3047 void do_history_mode_confirm(WIN *win)
3049 struct userdata_s *d = gp->data;
3051 switch (win->c) {
3052 case 'R':
3053 case 'r':
3054 pgn_history_free(gp->hp,
3055 gp->hindex);
3056 pgn_board_update(gp, d->b,
3057 pgn_history_total(gp->hp));
3058 break;
3059 #if 0
3060 case 'C':
3061 case 'c':
3062 if (pgn_history_rav_new(gp, d->b,
3063 gp->hindex) != E_PGN_OK)
3064 return;
3066 break;
3067 #endif
3068 default:
3069 return;
3072 if (!TEST_FLAG(d->flags, CF_HUMAN))
3073 add_engine_command(gp, ENGINE_READY,
3074 "setboard %s\n", pgn_game_to_fen(gp, d->b));
3076 do_history_mode_finalize(d);
3079 void do_history_toggle()
3081 struct userdata_s *d = gp->data;
3083 // FIXME Resuming from previous history could append to a RAV.
3084 if (gp->hindex != pgn_history_total(gp->hp)) {
3085 if (!pushkey)
3086 construct_message(NULL, "(r)esume or abort", 0, 1, NULL, NULL, NULL,
3087 do_history_mode_confirm, 0, 0, "%s",
3088 _("Resuming a game from previous "));
3090 return;
3092 else {
3093 if (TEST_FLAG(gp->flags, GF_GAMEOVER))
3094 return;
3097 do_history_mode_finalize(d);
3100 void do_history_annotate()
3102 int n = gp->hindex;
3104 if (n && gp->hp[n - 1]->move)
3105 n--;
3106 else
3107 return;
3109 do_annotate_move(gp->hp[n]);
3112 void do_history_help()
3114 char *buf = build_help(history_keys);
3116 construct_message(_("History Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL, NULL, buf,
3117 do_more_help, 0, 1, "%s", buf);
3120 void do_history_find(int which)
3122 struct input_data_s *in;
3123 int *p;
3125 if (pgn_history_total(gp->hp) < 2)
3126 return;
3128 in = Calloc(1, sizeof(struct input_data_s));
3129 p = Malloc(sizeof(int));
3130 *p = which;
3131 in->data = p;
3132 in->efunc = do_find_move_exp;
3134 if (!*moveexp || which == 0) {
3135 construct_input(_("Find Move Text Expression"), NULL, 1, 0, NULL, NULL, NULL,
3136 0, in, INPUT_HIST_MOVE_EXP, -1);
3137 return;
3140 do_find_move_exp_finalize(0, which);
3143 void do_history_find_new()
3145 do_history_find(0);
3148 void do_history_find_prev()
3150 do_history_find(-1);
3153 void do_history_find_next()
3155 do_history_find(1);
3158 void do_history_rav(int which)
3160 struct userdata_s *d = gp->data;
3162 rav_next_prev(gp, d->b, which);
3165 void do_history_rav_next()
3167 do_history_rav(1);
3170 void do_history_rav_prev()
3172 do_history_rav(0);
3175 void do_history_jump()
3177 struct input_data_s *in;
3179 if (pgn_history_total(gp->hp) < 2)
3180 return;
3182 if (!keycount) {
3183 in = Calloc(1, sizeof(struct input_data_s));
3184 in->efunc = do_move_jump;
3186 construct_input(_("Jump to Move Number"), NULL, 1, 1, NULL,
3187 NULL, NULL, 0, in, -1, 0);
3188 return;
3191 do_move_jump_finalize(keycount);
3194 static void free_userdata_once(GAME g)
3196 struct userdata_s *d = g->data;
3198 if (!d)
3199 return;
3201 if (d->engine) {
3202 stop_engine(g);
3204 if (d->engine->enginebuf) {
3205 int n;
3207 for (n = 0; d->engine->enginebuf[n]; n++)
3208 free(d->engine->enginebuf[n]);
3210 free(d->engine->enginebuf);
3213 if (d->engine->queue) {
3214 struct queue_s **q;
3216 for (q = d->engine->queue; *q; q++)
3217 free(*q);
3219 free(d->engine->queue);
3222 free(d->engine);
3225 #ifdef WITH_LIBPERL
3226 if (d->perlfen)
3227 free(d->perlfen);
3229 if (d->oldfen)
3230 free(d->oldfen);
3231 #endif
3233 free(d);
3234 g->data = NULL;
3237 static void free_userdata()
3239 int i;
3241 for (i = 0; i < gtotal; i++) {
3242 free_userdata_once(game[i]);
3243 game[i]->data = NULL;
3247 void update_loading_window(int n)
3249 if (!loadingw) {
3250 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
3251 loadingp = new_panel(loadingw);
3252 wbkgd(loadingw, CP_MESSAGE_WINDOW);
3255 wmove(loadingw, 0, 0);
3256 wclrtobot(loadingw);
3257 wattron(loadingw, CP_MESSAGE_BORDER);
3258 box(loadingw, ACS_VLINE, ACS_HLINE);
3259 wattroff(loadingw, CP_MESSAGE_BORDER);
3260 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
3261 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
3262 gtotal);
3263 update_panels();
3264 doupdate();
3267 static void init_userdata_once(GAME g, int n)
3269 struct userdata_s *d = NULL;
3271 d = Calloc(1, sizeof(struct userdata_s));
3272 d->n = n;
3273 d->c_row = 2, d->c_col = 5;
3274 SET_FLAG(d->flags, CF_NEW);
3275 g->data = d;
3277 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
3278 pgn_board_init(d->b);
3281 void init_userdata()
3283 int i;
3285 for (i = 0; i < gtotal; i++)
3286 init_userdata_once(game[i], i);
3289 void fix_marks(int *start, int *end)
3291 int i;
3293 *start = (*start < 0) ? 0 : *start;
3294 *end = (*end < 0) ? 0 : *end;
3296 if (*start > *end) {
3297 i = *start;
3298 *start = *end;
3299 *end = i + 1;
3302 *end = (*end > gtotal) ? gtotal : *end;
3305 void do_new_game_finalize(GAME g)
3307 struct userdata_s *d = g->data;
3309 d->mode = MODE_PLAY;
3310 update_status_notify(g, NULL);
3313 void do_new_game_from_scratch(WIN *win)
3315 if (tolower(win->c) != 'y')
3316 return;
3318 stop_clock();
3319 free_userdata();
3320 pgn_parse(NULL);
3321 gp = game[gindex];
3322 add_custom_tags(&gp->tag);
3323 init_userdata();
3324 loadfile[0] = 0;
3325 do_new_game_finalize(gp);
3328 void do_new_game()
3330 pgn_new_game();
3331 gp = game[gindex];
3332 add_custom_tags(&gp->tag);
3333 init_userdata_once(gp, gindex);
3334 do_new_game_finalize(gp);
3337 void do_game_delete_finalize(int n)
3339 struct userdata_s *d;
3341 delete_game((!n) ? gindex : -1);
3342 d = gp->data;
3343 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
3346 void do_game_delete_confirm(WIN *win)
3348 int *n;
3350 if (tolower(win->c) != 'y') {
3351 free(win->data);
3352 return;
3356 n = (int *)win->data;
3357 do_game_delete_finalize(*n);
3358 free(win->data);
3361 void do_game_delete()
3363 char *tmp = NULL;
3364 int i, n;
3365 struct userdata_s *d;
3366 int *p;
3368 if (gtotal < 2) {
3369 cmessage(NULL, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
3370 return;
3373 tmp = NULL;
3375 for (i = n = 0; i < gtotal; i++) {
3376 d = game[i]->data;
3378 if (TEST_FLAG(d->flags, CF_DELETE))
3379 n++;
3382 if (!n)
3383 tmp = _("Delete the current game?");
3384 else {
3385 if (n == gtotal) {
3386 cmessage(NULL, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
3387 return;
3390 tmp = _("Delete all games marked for deletion?");
3393 if (config.deleteprompt) {
3394 p = Malloc(sizeof(int));
3395 *p = n;
3396 construct_message(NULL, _("[ Yes or No ]"), 1, 1, NULL, NULL, p,
3397 do_game_delete_confirm, 0, 0, tmp);
3398 return;
3401 do_game_delete_finalize(n);
3404 void do_find_game_exp_finalize(int which)
3406 struct userdata_s *d = gp->data;
3407 int n;
3409 if ((n = find_game_exp(gameexp, (which == -1) ? 0 : 1,
3410 (keycount) ? keycount : 1)) == -1) {
3411 update_status_notify(gp, "%s", _("No matches found"));
3412 return;
3415 gindex = n;
3416 d = gp->data;
3418 if (pgn_history_total(gp->hp))
3419 d->mode = MODE_HISTORY;
3421 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
3424 void do_find_game_exp(WIN *win)
3426 struct input_data_s *in = win->data;
3427 int *n = in->data;
3428 int c = *n;
3430 if (in->str) {
3431 strncpy(gameexp, in->str, sizeof(gameexp));
3433 if (c == '?')
3434 c = '}';
3436 do_find_game_exp_finalize(c);
3437 free(in->str);
3440 free(in->data);
3441 free(in);
3444 void do_game_jump_finalize(int n)
3446 struct userdata_s *d;
3448 if (--n > gtotal - 1 || n < 0)
3449 return;
3451 gindex = n;
3452 gp = game[gindex];
3453 d = gp->data;
3454 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
3455 update_status_notify(gp, NULL);
3458 void do_game_jump(WIN *win)
3460 struct input_data_s *in = win->data;
3462 if (!in->str || !isinteger(in->str)) {
3463 if (in->str)
3464 free(in->str);
3466 free(in);
3467 return;
3470 do_game_jump_finalize(atoi(in->str));
3471 free(in->str);
3472 free(in);
3475 void do_load_file(WIN *win)
3477 struct input_data_s *in = win->data;
3478 char *tmp = in->str;
3479 struct userdata_s *d;
3480 PGN_FILE *pgn = NULL;
3481 int n;
3483 if (!in->str) {
3484 free(in);
3485 return;
3488 if ((tmp = pathfix(tmp)) == NULL)
3489 goto done;
3491 n = pgn_open(tmp, "r", &pgn);
3493 if (n == E_PGN_ERR) {
3494 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", tmp, strerror(errno));
3495 goto done;
3497 else if (n == E_PGN_INVALID) {
3498 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", tmp, _("Not a regular file"));
3499 goto done;
3502 free_userdata();
3505 * FIXME what is the game state after a parse error?
3507 if (pgn_parse(pgn) == E_PGN_ERR) {
3508 del_panel(loadingp);
3509 delwin(loadingw);
3510 loadingw = NULL;
3511 loadingp = NULL;
3512 init_userdata();
3513 goto done;
3516 del_panel(loadingp);
3517 delwin(loadingw);
3518 loadingw = NULL;
3519 loadingp = NULL;
3520 init_userdata();
3521 strncpy(loadfile, tmp, sizeof(loadfile));
3522 gp = game[gindex];
3523 d = gp->data;
3525 if (pgn_history_total(gp->hp))
3526 d->mode = MODE_HISTORY;
3528 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
3530 done:
3531 pgn_close(pgn);
3533 if (in->str)
3534 free(in->str);
3536 free(in);
3539 void do_game_save(WIN *win)
3541 struct input_data_s *in = win->data;
3542 int *x = in->data;
3543 int n = *x;
3544 char *tmp = in->str;
3545 char tfile[FILENAME_MAX];
3546 char *p;
3547 int i;
3548 struct userdata_s *d;
3550 if (!tmp || (tmp = pathfix(tmp)) == NULL)
3551 goto done;
3553 if (pgn_is_compressed(tmp) == E_PGN_ERR) {
3554 p = tmp + strlen(tmp) - 1;
3556 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3557 *(p-3) != '.') {
3558 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3559 tmp = tfile;
3564 * When in edit mode, update the FEN tag.
3566 if (n == -1) {
3567 for (i = 0; i < gtotal; i++) {
3568 d = game[i]->data;
3570 if (d->mode == MODE_EDIT)
3571 pgn_tag_add(&game[i]->tag, "FEN", pgn_game_to_fen(game[i], d->b));
3574 else {
3575 d = game[n]->data;
3577 if (d->mode == MODE_EDIT)
3578 pgn_tag_add(&game[n]->tag, "FEN", pgn_game_to_fen(game[n], d->b));
3581 save_pgn(tmp, n);
3583 done:
3584 if (in->str)
3585 free(in->str);
3587 free(in->data);
3588 free(in);
3591 void do_get_game_save_input(int n)
3593 struct input_data_s *in = Calloc(1, sizeof(struct input_data_s));
3594 int *p = Malloc(sizeof(int));
3596 in->efunc = do_game_save;
3597 *p = n;
3598 in->data = p;
3600 construct_input(_("Save Game Filename"), loadfile, 1, 1, _("Type TAB for file browser"),
3601 file_browser, NULL, '\t', in, INPUT_HIST_FILE, -1);
3604 void do_game_save_multi_confirm(WIN *win)
3606 int i;
3608 if (win->c == 'c')
3609 i = gindex;
3610 else if (win->c == 'a')
3611 i = -1;
3612 else {
3613 update_status_notify(gp, "%s", _("Save game aborted."));
3614 return;
3617 do_get_game_save_input(i);
3620 void do_global_about()
3622 cmessage("ABOUT", _("[ press any key to continue ]"), "%s\nUsing %s with %i colors "
3623 "and %i color pairs\n%s",
3624 PACKAGE_STRING, curses_version(), COLORS, COLOR_PAIRS,
3625 COPYRIGHT);
3628 void global_game_next_prev(int which)
3630 struct userdata_s *d;
3632 game_next_prev(gp, (which == 1) ? 1 : 0,
3633 (keycount) ? keycount : 1);
3634 d = gp->data;
3636 if (delete_count) {
3637 if (which == 1) {
3638 markend = markstart + delete_count;
3639 delete_count = 0;
3641 else {
3642 markend = markstart - delete_count + 1;
3643 delete_count = -1; // to fix gindex in the other direction
3646 fix_marks(&markstart, &markend);
3647 do_global_toggle_delete();
3650 if (d->mode == MODE_HISTORY)
3651 pgn_board_update(gp, d->b, gp->hindex);
3652 else if (d->mode == MODE_PLAY)
3653 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
3656 void do_global_next_game()
3658 global_game_next_prev(1);
3661 void do_global_prev_game()
3663 global_game_next_prev(0);
3666 void global_find(int which)
3668 struct input_data_s *in;
3669 int *p;
3671 if (gtotal < 2)
3672 return;
3674 in = Calloc(1, sizeof(struct input_data_s));
3675 p = Malloc(sizeof(int));
3676 *p = which;
3677 in->data = p;
3678 in->efunc = do_find_game_exp;
3680 if (!*gameexp || which == 0) {
3681 construct_input(_("Find Game by Tag Expression"), NULL, 1, 0,
3682 _("[name expression:]value expression"), NULL, NULL, 0, in,
3683 INPUT_HIST_GAME_EXP, -1);
3684 return;
3687 do_find_game_exp_finalize(which);
3690 void do_global_find_new()
3692 global_find(0);
3695 void do_global_find_next()
3697 global_find(1);
3700 void do_global_find_prev()
3702 global_find(-1);
3705 void do_global_game_jump()
3707 struct input_data_s *in;
3709 if (gtotal < 2)
3710 return;
3712 in = Calloc(1, sizeof(struct input_data_s));
3713 in->efunc = do_game_jump;
3715 if (!keycount) {
3716 construct_input(_("Jump to Game Number"), NULL, 1, 1, NULL, NULL, NULL, 0, in,
3717 -1, 0);
3718 return;
3721 do_game_jump_finalize(keycount);
3724 void do_global_toggle_delete()
3726 int i;
3728 pushkey = 0;
3730 if (gtotal < 2)
3731 return;
3733 if (keycount && delete_count == 0) {
3734 markstart = gindex;
3735 delete_count = keycount;
3736 update_status_notify(gp, "%s (delete)", status.notify);
3737 return;
3740 if (markstart >= 0 && markend >= 0) {
3741 for (i = markstart; i < markend; i++) {
3742 if (toggle_delete_flag(i)) {
3743 return;
3747 gindex = (delete_count < 0) ? markstart : i - 1;
3749 else {
3750 if (toggle_delete_flag(gindex))
3751 return;
3754 markstart = markend = -1;
3755 delete_count = 0;
3756 update_status_window(gp);
3759 void do_global_delete_game()
3761 do_game_delete();
3764 void do_global_tag_edit()
3766 struct userdata_s *d = gp->data;
3768 edit_tags(gp, d->b, 1);
3771 void do_global_tag_view()
3773 struct userdata_s *d = gp->data;
3775 edit_tags(gp, d->b, 0);
3778 void do_global_resume_game()
3780 struct input_data_s *in;
3782 in = Calloc(1, sizeof(struct input_data_s));
3783 in->efunc = do_load_file;
3784 construct_input(_("Load Filename"), NULL, 1, 1, _("Type TAB for file browser"), file_browser,
3785 NULL, '\t', in, INPUT_HIST_FILE, -1);
3788 void do_global_save_game()
3790 if (gtotal > 1) {
3791 construct_message(NULL, _("Type 'c' or 'a' or any other key "), 1, 1, NULL, NULL, NULL,
3792 do_game_save_multi_confirm, 0, 0, "%s", _("There is more than one game "));
3793 return;
3796 do_get_game_save_input(-1);
3799 void do_global_new_game()
3801 do_new_game();
3804 void do_global_copy_game()
3806 int g = gindex;
3807 int i, n;
3808 struct userdata_s *d;
3810 do_global_new_game();
3811 n = pgn_history_total(game[g]->history);
3812 d = gp->data;
3814 // FIXME RAV
3815 for (i = 0; i < n; i++) {
3816 char *frfr = NULL;
3817 char *move = strdup(game[g]->history[i]->move);
3819 if (pgn_parse_move(gp, d->b, &move, &frfr) != E_PGN_OK) {
3820 SET_FLAG(gp->flags, GF_PERROR);
3821 return;
3824 pgn_history_add(gp, d->b, move);
3825 free(move);
3826 free(frfr);
3827 pgn_switch_turn(gp);
3830 n = pgn_tag_total(game[g]->tag);
3832 for (i = 0; i < n; i++)
3833 pgn_tag_add(&gp->tag, game[g]->tag[i]->name,
3834 game[g]->tag[i]->value);
3836 d = gp->data;
3837 pgn_board_update(gp, d->b,
3838 pgn_history_total(gp->hp));
3841 void do_global_new_all()
3843 construct_message(NULL, _("[ Yes or No ]"), 1, 1, NULL, NULL, NULL,
3844 do_new_game_from_scratch, 0, 0, "%s", _("Really start a new game from scratch?"));
3847 void do_global_quit()
3849 quit = 1;
3852 void do_global_toggle_engine_window()
3854 if (!enginew) {
3855 enginew = newwin(LINES, COLS, 0, 0);
3856 enginep = new_panel(enginew);
3857 window_draw_title(enginew, _("Engine IO Window"), COLS, CP_MESSAGE_TITLE,
3858 CP_MESSAGE_BORDER);
3859 hide_panel(enginep);
3862 if (panel_hidden(enginep)) {
3863 update_engine_window(gp);
3864 top_panel(enginep);
3866 else {
3867 hide_panel(enginep);
3871 void do_global_toggle_board_details()
3873 do_board_details();
3876 void do_global_toggle_strict_castling()
3878 do_toggle_strict_castling();
3881 // Global and other keys.
3882 static int globalkeys()
3884 struct userdata_s *d = gp->data;
3885 int i;
3888 * These cannot be modified and other game mode keys cannot conflict with
3889 * these.
3891 switch (input_c) {
3892 case CTRL('L'):
3893 endwin();
3894 keypad(boardw, TRUE);
3895 wmove(stdscr, 0, 0);
3896 wclrtobot(stdscr);
3897 return 1;
3898 case KEY_ESCAPE:
3899 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3900 markend = markstart = 0;
3902 if (keycount) {
3903 keycount = 0;
3904 update_status_notify(gp, NULL);
3907 if (config.validmoves)
3908 pgn_reset_valid_moves(d->b);
3910 return 1;
3911 case '0' ... '9':
3912 i = input_c - '0';
3914 if (keycount)
3915 keycount = keycount * 10 + i;
3916 else
3917 keycount = i;
3919 update_status_notify(gp, "Repeat %i", keycount);
3920 return -1;
3921 case KEY_UP:
3922 if (d->mode == MODE_HISTORY)
3923 return 0;
3925 if (keycount)
3926 d->c_row += keycount;
3927 else
3928 d->c_row++;
3930 if (d->c_row > 8)
3931 d->c_row = 1;
3933 return 1;
3934 case KEY_DOWN:
3935 if (d->mode == MODE_HISTORY)
3936 return 0;
3938 if (keycount) {
3939 d->c_row -= keycount;
3940 update_status_notify(gp, NULL);
3942 else
3943 d->c_row--;
3945 if (d->c_row < 1)
3946 d->c_row = 8;
3948 return 1;
3949 case KEY_LEFT:
3950 if (d->mode == MODE_HISTORY)
3951 return 0;
3953 if (keycount)
3954 d->c_col -= keycount;
3955 else
3956 d->c_col--;
3958 if (d->c_col < 1)
3959 d->c_col = 8;
3961 return 1;
3962 case KEY_RIGHT:
3963 if (d->mode == MODE_HISTORY)
3964 return 0;
3966 if (keycount)
3967 d->c_col += keycount;
3968 else
3969 d->c_col++;
3971 if (d->c_col > 8)
3972 d->c_col = 1;
3974 return 1;
3975 #ifdef HAVE_WRESIZE
3976 case KEY_RESIZE:
3977 do_window_resize();
3978 return 1;
3979 #endif
3980 case 0:
3981 default:
3982 for (i = 0; global_keys[i]; i++) {
3983 if (input_c == global_keys[i]->c) {
3984 (*global_keys[i]->f)();
3985 return 1;
3988 break;
3991 return 0;
3994 #ifdef WITH_LIBPERL
3995 static void perl_error(const char *fmt, ...)
3997 va_list ap;
3998 char *buf;
4000 va_start(ap, fmt);
4001 vasprintf(&buf, fmt, ap);
4002 va_end(ap);
4004 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", buf);
4005 free(buf);
4008 static void do_perl_finalize(WIN *win)
4010 struct input_data_s *in = win->data;
4011 GAME g = in->data;
4012 struct userdata_s *d = g->data;
4013 char *filename;
4014 char *result = NULL;
4015 char *arg = NULL;
4016 int n;
4018 asprintf(&filename, "%s/perl.pl", config.datadir);
4020 if (!in->str)
4021 goto done;
4023 if (perl_init_file(filename, perl_error))
4024 goto done;
4026 arg = strdup(pgn_game_to_fen(g, d->b));
4028 if (perl_call_sub(trim(in->str), arg, &result))
4029 goto done;
4031 d->perlfen = strdup(pgn_game_to_fen(g, d->b));
4032 d->perlflags = g->flags;
4034 if (pgn_board_init_fen(g, d->b, result) != E_PGN_OK) {
4035 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", _("FEN parse error."));
4036 pgn_board_init_fen(g, d->b, d->perlfen);
4037 g->flags = d->perlflags;
4038 free(d->perlfen);
4039 d->perlfen = NULL;
4040 goto done;
4043 SET_FLAG(d->flags, CF_PERL);
4044 n = pgn_tag_find(g->tag, "FEN");
4046 if (n != E_PGN_ERR)
4047 d->oldfen = strdup(g->tag[n]->value);
4049 pgn_tag_add(&g->tag, "FEN", result);
4050 update_status_notify(g, "%s", _("[ press any key to continue ]"));
4051 update_all(g);
4053 done:
4054 free(result);
4055 free(arg);
4056 free(in->str);
4057 free(in);
4058 free(filename);
4061 void do_global_perl()
4063 struct input_data_s *in;
4065 in = Calloc(1, sizeof(struct input_data_s));
4066 in->data = gp;
4067 in->efunc = do_perl_finalize;
4068 construct_input(_("PERL Subroutine Filter"), NULL, 1, 0, NULL, NULL, NULL, 0, in, INPUT_HIST_PERL, -1);
4070 #endif
4073 * A macro may contain a key that belongs to another macro so macro_match will
4074 * need to be updated to the new index of the matching macro.
4076 static void find_macro(struct userdata_s *d)
4078 int i;
4081 * Macros can't contain macros when in a window.
4083 if (wins)
4084 return;
4086 again:
4087 for (i = 0; macros[i]; i++) {
4088 if ((macros[i]->mode == -1 || macros[i]->mode == d->mode) &&
4089 input_c == macros[i]->c) {
4090 input_c = macros[i]->keys[macros[i]->n++];
4092 if (!macro_depth_n && macro_match > -1) {
4093 macro_depth = realloc(macro_depth, (macro_depth_n + 1) * sizeof(int));
4094 macro_depth[macro_depth_n++] = macro_match;
4097 macro_depth = realloc(macro_depth, (macro_depth_n + 1) * sizeof(int));
4098 macro_depth[macro_depth_n++] = i;
4099 macro_match = i;
4100 goto again;
4106 * Resets the position in each macro to the first key.
4108 static void reset_macros()
4110 int i;
4111 struct userdata_s *d = gp->data;
4113 again:
4114 if (macro_depth_n > 0) {
4115 macro_depth_n--;
4116 macro_match = macro_depth[macro_depth_n];
4118 if (macros[macro_match]->n >= macros[macro_match]->total)
4119 goto again;
4121 input_c = macros[macro_match]->keys[macros[macro_match]->n++];
4122 find_macro(d);
4123 return;
4126 for (i = 0; macros[i]; i++)
4127 macros[i]->n = 0;
4129 free(macro_depth);
4130 macro_depth = NULL;
4131 macro_depth_n = 0;
4132 macro_match = -1;
4135 void game_loop()
4137 struct userdata_s *d;
4139 macro_match = -1;
4140 gindex = gtotal - 1;
4141 gp = game[gindex];
4142 d = gp->data;
4144 if (pgn_history_total(gp->hp))
4145 d->mode = MODE_HISTORY;
4146 else
4147 d->mode = MODE_PLAY;
4149 if (d->mode == MODE_HISTORY)
4150 pgn_board_update(gp, d->b, pgn_history_total(gp->hp));
4152 update_status_notify(gp, "%s", _("Type F1 for help"));
4153 movestep = 2;
4154 flushinp();
4155 update_all(gp);
4156 wtimeout(boardw, WINDOW_TIMEOUT);
4158 while (!quit) {
4159 int n = 0, i;
4160 char fdbuf[8192] = {0};
4161 int len;
4162 struct timeval tv = {0, 0};
4163 fd_set rfds, wfds;
4164 WIN *win = NULL;
4165 WINDOW *wp = NULL;
4167 FD_ZERO(&rfds);
4168 FD_ZERO(&wfds);
4170 for (i = 0; i < gtotal; i++) {
4171 d = game[i]->data;
4173 if (d->engine && d->engine->pid != -1) {
4174 if (d->engine->fd[ENGINE_IN_FD] > 2) {
4175 if (d->engine->fd[ENGINE_IN_FD] > n)
4176 n = d->engine->fd[ENGINE_IN_FD];
4178 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
4181 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
4182 if (d->engine->fd[ENGINE_OUT_FD] > n)
4183 n = d->engine->fd[ENGINE_OUT_FD];
4185 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
4190 if (n) {
4191 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
4192 for (i = 0; i < gtotal; i++) {
4193 d = game[i]->data;
4195 if (d->engine && d->engine->pid != -1) {
4196 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
4197 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
4198 sizeof(fdbuf));
4200 if (len > 0) {
4201 if (d->engine->iobuf)
4202 d->engine->iobuf = Realloc(d->engine->iobuf, d->engine->len + len + 1);
4203 else
4204 d->engine->iobuf = Calloc(1, len + 1);
4206 memcpy(&(d->engine->iobuf[d->engine->len]), &fdbuf, len);
4207 d->engine->len += len;
4208 d->engine->iobuf[d->engine->len] = 0;
4211 * The fdbuf is full or no newline
4212 * was found. So we'll append the next
4213 * read() to this games buffer.
4215 if (d->engine->iobuf[d->engine->len - 1] != '\n')
4216 continue;
4218 parse_engine_output(game[i], d->engine->iobuf);
4219 free(d->engine->iobuf);
4220 d->engine->iobuf = NULL;
4221 d->engine->len = 0;
4223 else if (len == -1) {
4224 if (errno != EAGAIN) {
4225 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "Engine read(): %s",
4226 strerror(errno));
4227 waitpid(d->engine->pid, &n, 0);
4228 free(d->engine);
4229 d->engine = NULL;
4230 break;
4235 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
4236 if (d->engine->queue)
4237 send_engine_command(game[i]);
4242 else {
4243 if (n == -1)
4244 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "select(): %s", strerror(errno));
4245 /* timeout */
4249 gp = game[gindex];
4250 d = gp->data;
4253 * This is needed to detect terminal resizing.
4255 doupdate();
4258 * Finds the top level window in the window stack so we know what
4259 * window the wgetch()ed key belongs to.
4261 if (wins) {
4262 for (i = 0; wins[i]; i++);
4263 win = wins[i-1];
4264 wp = win->w;
4265 wtimeout(wp, WINDOW_TIMEOUT);
4267 else
4268 wp = boardw;
4270 if (!i && pushkey)
4271 input_c = pushkey;
4272 else {
4273 if (!pushkey) {
4274 if (macros && macro_match >= 0) {
4275 if (macros[macro_match]->n >= macros[macro_match]->total)
4276 reset_macros();
4277 else {
4278 input_c = macros[macro_match]->keys[macros[macro_match]->n++];
4279 find_macro(d);
4282 else {
4283 if ((input_c = wgetch(wp)) == ERR)
4284 continue;
4287 else
4288 input_c = pushkey;
4290 if (win) {
4291 switch (input_c) {
4292 case CTRL('L'):
4293 endwin();
4294 keypad(boardw, TRUE);
4295 wmove(stdscr, 0, 0);
4296 wclrtobot(stdscr);
4297 goto refresh;
4300 win->c = input_c;
4303 * Run the function associated with the window. When the
4304 * function returns 0 win->efunc is ran (if not NULL) with
4305 * win as the one and only parameter. Then the window is
4306 * destroyed.
4308 * The exit function may create another window which will
4309 * mess up the window stack when window_destroy() is called.
4310 * So don't destory the window until the top window is
4311 * destroyable. See window_destroy().
4313 if ((*win->func)(win) == 0) {
4314 if (win->efunc)
4315 (*win->efunc)(win);
4317 win->keep = 1;
4318 window_destroy(win);
4319 update_all(gp);
4322 continue;
4326 if (!keycount && status.notify)
4327 update_status_notify(gp, NULL);
4329 #ifdef WITH_LIBPERL
4330 if (TEST_FLAG(d->flags, CF_PERL)) {
4331 CLEAR_FLAG(d->flags, CF_PERL);
4332 pgn_board_init_fen(gp, d->b, d->perlfen);
4333 gp->flags = d->perlflags;
4334 free(d->perlfen);
4335 pgn_tag_add(&gp->tag, "FEN", d->oldfen);
4336 free(d->oldfen);
4337 d->perlfen = d->oldfen = NULL;
4338 update_all(gp);
4339 continue;
4341 #endif
4343 if (macros && macro_match < 0)
4344 find_macro(d);
4346 if ((n = globalkeys()) == 1) {
4347 if (macro_match == -1)
4348 keycount = 0;
4350 goto refresh;
4352 else if (n == -1)
4353 goto refresh;
4355 switch (d->mode) {
4356 case MODE_EDIT:
4357 for (i = 0; edit_keys[i]; i++) {
4358 if (input_c == edit_keys[i]->c) {
4359 (*edit_keys[i]->f)();
4360 break;
4363 break;
4364 case MODE_PLAY:
4365 for (i = 0; play_keys[i]; i++) {
4366 if (input_c == play_keys[i]->c) {
4367 (*play_keys[i]->f)();
4368 goto done;
4372 do_play_config_command();
4373 break;
4374 case MODE_HISTORY:
4375 for (i = 0; history_keys[i]; i++) {
4376 if (input_c == history_keys[i]->c) {
4377 (*history_keys[i]->f)();
4378 break;
4381 break;
4382 default:
4383 break;
4386 done:
4387 if (keycount)
4388 update_status_notify(gp, NULL);
4390 keycount = 0;
4392 refresh:
4393 update_all(gp);
4397 void usage(const char *pn, int ret)
4399 fprintf((ret) ? stderr : stdout, "%s",
4400 #ifdef DEBUG
4401 "Usage: cboard [-hvCD] [-p [-VtRSE] <file>]\n"
4402 " -D Dump libchess debugging info to \"libchess.debug\" (stderr)\n"
4403 #else
4404 "Usage: cboard [-hvC] [-p [-VtRSE] <file>]\n"
4405 #endif
4406 " -p Load PGN file.\n"
4407 " -V Validate a game file.\n"
4408 " -S Validate and output a PGN formatted game.\n"
4409 " -R Like -S but write a reduced PGN formatted game.\n"
4410 " -t Also write custom PGN tags from config file.\n"
4411 " -E Stop processing on file parsing error (overrides config).\n"
4412 " -C Enable strict castling (overrides config).\n"
4413 " -v Version information.\n"
4414 " -h This help text.\n");
4416 exit(ret);
4419 void cleanup_all()
4421 int i;
4423 stop_clock();
4424 free_userdata();
4425 pgn_free_all();
4426 free(config.engine_cmd);
4427 free(config.pattern);
4428 free(config.ccfile);
4429 free(config.nagfile);
4430 free(config.configfile);
4432 if (config.keys) {
4433 for (i = 0; config.keys[i]; i++) {
4434 free(config.keys[i]->str);
4435 free(config.keys[i]);
4438 free(config.keys);
4441 if (config.einit) {
4442 for (i = 0; config.einit[i]; i++)
4443 free(config.einit[i]);
4445 free(config.einit);
4448 if (config.tag)
4449 pgn_tag_free(config.tag);
4451 free(config.datadir);
4453 if (curses_initialized) {
4454 del_panel(boardp);
4455 del_panel(historyp);
4456 del_panel(statusp);
4457 del_panel(tagp);
4458 delwin(boardw);
4459 delwin(historyw);
4460 delwin(statusw);
4461 delwin(tagw);
4463 if (enginew) {
4464 del_panel(enginep);
4465 delwin(enginew);
4468 endwin();
4471 #ifdef WITH_LIBPERL
4472 perl_cleanup();
4473 #endif
4476 static void signal_save_pgn(int sig)
4478 char *buf;
4479 time_t now;
4480 char *p = config.savedirectory ? config.savedirectory : config.datadir;
4482 time(&now);
4483 asprintf(&buf, "%s/signal-%i-%li.pgn", p, sig, now);
4485 if (do_game_write(buf, "w", 0, gtotal)) {
4486 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", p, strerror(errno));
4487 update_status_notify(gp, "%s", _("Save game failed."));
4490 free(buf);
4491 quit = 1;
4494 void catch_signal(int which)
4496 switch (which) {
4497 case SIGALRM:
4498 update_clocks();
4499 break;
4500 case SIGPIPE:
4501 if (which == SIGPIPE && quit)
4502 break;
4504 if (which == SIGPIPE)
4505 cmessage(NULL, _("[ press any key to continue ]"), "%s", _("Broken pipe. Quitting."));
4507 cleanup_all();
4508 exit(EXIT_FAILURE);
4509 break;
4510 case SIGSTOP:
4511 savetty();
4512 break;
4513 case SIGCONT:
4514 resetty();
4515 keypad(boardw, TRUE);
4516 curs_set(0);
4517 cbreak();
4518 noecho();
4519 break;
4520 case SIGINT:
4521 quit = 1;
4522 break;
4523 case SIGTERM:
4524 signal_save_pgn(which);
4525 break;
4526 default:
4527 break;
4531 void loading_progress(long total, long offset)
4533 int n = (100 * (offset / 100) / (total / 100));
4535 if (curses_initialized)
4536 update_loading_window(n);
4537 else {
4538 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
4539 fflush(stderr);
4543 static void set_defaults()
4545 set_config_defaults();
4546 set_default_keys();
4547 filetype = FILE_NONE;
4548 pgn_config_set(PGN_PROGRESS, 1024);
4549 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
4552 int main(int argc, char *argv[])
4554 int opt;
4555 struct stat st;
4556 char buf[FILENAME_MAX];
4557 char datadir[FILENAME_MAX];
4558 int ret = EXIT_SUCCESS;
4559 int validate_only = 0, validate_and_write = 0;
4560 int write_custom_tags = 0;
4561 int i = 0;
4562 PGN_FILE *pgn;
4564 setlocale (LC_ALL, "");
4565 init_wchar_pieces ();
4567 /* Solaris 5.9 */
4568 #ifndef HAVE_PROGNAME
4569 __progname = argv[0];
4570 #endif
4572 if ((config.pwd = getpwuid(getuid())) == NULL)
4573 err(EXIT_FAILURE, "getpwuid()");
4575 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
4576 config.datadir = strdup(datadir);
4577 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
4578 config.ccfile = strdup(buf);
4579 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
4580 config.nagfile = strdup(buf);
4581 snprintf(buf, sizeof(buf), "%s/config", datadir);
4582 config.configfile = strdup(buf);
4584 if (stat(datadir, &st) == -1) {
4585 if (errno == ENOENT) {
4586 if (mkdir(datadir, 0755) == -1)
4587 err(EXIT_FAILURE, "%s", datadir);
4589 else
4590 err(EXIT_FAILURE, "%s", datadir);
4592 stat(datadir, &st);
4595 if (!S_ISDIR(st.st_mode))
4596 errx(EXIT_FAILURE, "%s: %s", datadir, _("Not a directory."));
4598 set_defaults();
4600 #ifdef DEBUG
4601 while ((opt = getopt(argc, argv, "DCEVtSRhp:v")) != -1) {
4602 #else
4603 while ((opt = getopt(argc, argv, "ECVtSRhp:v")) != -1) {
4604 #endif
4605 switch (opt) {
4606 #ifdef DEBUG
4607 case 'D':
4608 unlink("libchess.debug");
4609 pgn_config_set(PGN_DEBUG, 1);
4610 break;
4611 #endif
4612 case 'C':
4613 pgn_config_set(PGN_STRICT_CASTLING, 1);
4614 break;
4615 case 't':
4616 write_custom_tags = 1;
4617 break;
4618 case 'E':
4619 i = 1;
4620 break;
4621 case 'R':
4622 pgn_config_set(PGN_REDUCED, 1);
4623 case 'S':
4624 validate_and_write = 1;
4625 case 'V':
4626 validate_only = 1;
4627 break;
4628 case 'v':
4629 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
4630 COPYRIGHT);
4631 exit(EXIT_SUCCESS);
4632 case 'p':
4633 filetype = FILE_PGN;
4634 strncpy(loadfile, optarg, sizeof(loadfile));
4635 break;
4636 case 'h':
4637 default:
4638 usage(argv[0], EXIT_SUCCESS);
4642 if ((validate_only || validate_and_write) && !*loadfile)
4643 usage(argv[0], EXIT_FAILURE);
4645 if (access(config.configfile, R_OK) == 0)
4646 parse_rcfile(config.configfile);
4648 if (i)
4649 pgn_config_set(PGN_STOP_ON_ERROR, 1);
4651 signal(SIGPIPE, catch_signal);
4652 signal(SIGCONT, catch_signal);
4653 signal(SIGSTOP, catch_signal);
4654 signal(SIGINT, catch_signal);
4655 signal(SIGALRM, catch_signal);
4656 signal(SIGTERM, catch_signal);
4658 srandom(getpid());
4660 switch (filetype) {
4661 case FILE_PGN:
4662 if (pgn_open(loadfile, "r", &pgn) != E_PGN_OK)
4663 err(EXIT_FAILURE, "%s", loadfile);
4665 ret = pgn_parse(pgn);
4666 pgn_close(pgn);
4667 break;
4668 case FILE_FEN:
4669 //ret = parse_fen_file(loadfile);
4670 break;
4671 case FILE_EPD: // Not implemented.
4672 case FILE_NONE:
4673 default:
4674 // No file specified. Empty game.
4675 ret = pgn_parse(NULL);
4676 gp = game[gindex];
4677 add_custom_tags(&gp->tag);
4678 break;
4681 if (validate_only || validate_and_write) {
4682 if (validate_and_write) {
4683 if (pgn_open("-", "r", &pgn) != E_PGN_OK)
4684 err(EXIT_FAILURE, "pgn_open()");
4686 for (i = 0; i < gtotal; i++) {
4687 if (write_custom_tags)
4688 add_custom_tags(&game[i]->tag);
4690 pgn_write(pgn, game[i]);
4693 pgn_close(pgn);
4696 cleanup_all();
4697 exit(ret);
4699 else if (ret == E_PGN_ERR)
4700 exit(ret);
4702 init_userdata();
4705 * This fixes window resizing in an xterm.
4707 if (getenv("DISPLAY") != NULL) {
4708 putenv("LINES=");
4709 putenv("COLUMNS=");
4712 if (initscr() == NULL)
4713 errx(EXIT_FAILURE, "%s", _("Could not initialize curses."));
4714 else
4715 curses_initialized = 1;
4717 if (LINES < 24 || COLS < 80) {
4718 endwin();
4719 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
4722 if (has_colors() == TRUE && start_color() == OK)
4723 init_color_pairs();
4725 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
4726 boardp = new_panel(boardw);
4727 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
4728 COLS - HISTORY_WIDTH);
4729 historyp = new_panel(historyw);
4730 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, 0, 0);
4731 statusp = new_panel(statusw);
4732 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, STATUS_HEIGHT + 1, 0);
4733 tagp = new_panel(tagw);
4734 keypad(boardw, TRUE);
4735 // leaveok(boardw, TRUE);
4736 leaveok(tagw, TRUE);
4737 leaveok(statusw, TRUE);
4738 leaveok(historyw, TRUE);
4739 curs_set(0);
4740 cbreak();
4741 noecho();
4742 draw_window_decor();
4743 game_loop();
4744 cleanup_all();
4745 exit(EXIT_SUCCESS);