Added .draw_menu_exit to menu_input_s.
[cboard.git] / src / cboard.c
blobe513647aa2107aa10eeb626696068c56ac884f87
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <time.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_SYS_WAIT_H
40 #include <sys/wait.h>
41 #endif
43 #ifdef HAVE_WORDEXP_H
44 #include <wordexp.h>
45 #endif
47 #ifdef HAVE_DIRENT_H
48 #include <dirent.h>
49 #endif
51 #ifdef HAVE_REGEX_H
52 #include <regex.h>
53 #endif
55 #include "chess.h"
56 #include "conf.h"
57 #include "window.h"
58 #include "message.h"
59 #include "colors.h"
60 #include "input.h"
61 #include "misc.h"
62 #include "engine.h"
63 #include "rcfile.h"
64 #include "strings.h"
65 #include "common.h"
66 #include "menu.h"
67 #include "cboard.h"
69 #ifdef DEBUG
70 #include "debug.h"
71 #endif
73 #ifdef WITH_DMALLOC
74 #include <dmalloc.h>
75 #endif
77 static char *str_etc(const char *str, int maxlen, int rev)
79 int len;
80 static char buf[80], *p = buf;
81 int i;
83 if (!str)
84 return NULL;
86 len = strlen(str);
87 strncpy(buf, str, sizeof(buf));
89 if (len > maxlen) {
90 if (rev) {
91 p = buf;
92 *p++ = '.';
93 *p++ = '.';
94 *p++ = '.';
96 for (i = 0; i < maxlen + 3; i++)
97 *p++ = buf[(len - maxlen) + i + 3];
99 else {
100 p = buf + maxlen - 4;
101 *p++ = '.';
102 *p++ = '.';
103 *p++ = '.';
106 *p = '\0';
109 return buf;
112 void update_cursor(GAME g, int idx)
114 char *p;
115 int len;
116 int t = pgn_history_total(g.hp);
117 struct userdata_s *d = g.data;
120 * If not deincremented then r and c would be the next move.
122 idx--;
124 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
125 d->c_row = 2, d->c_col = 5;
126 return;
129 p = g.hp[idx]->move;
130 len = strlen(p);
132 if (*p == 'O') {
133 if (len <= 4)
134 d->c_col = 7;
135 else
136 d->c_col = 3;
138 d->c_row = (g.turn == WHITE) ? 1 : 8;
139 return;
142 p += len;
144 while (!isdigit(*p))
145 p--;
147 d->c_row = RANKTOINT(*p--);
148 d->c_col = FILETOINT(*p);
151 static int init_nag()
153 FILE *fp;
154 char line[LINE_MAX];
155 int i = 0;
157 if ((fp = fopen(config.nagfile, "r")) == NULL) {
158 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
159 return 1;
162 nags = Realloc(nags, (i+2) * sizeof(char *));
163 nags[i++] = strdup("None");
164 nags[i] = NULL;
166 while (!feof(fp)) {
167 if (fscanf(fp, " %[^\n] ", line) == 1) {
168 nags = Realloc(nags, (i + 2) * sizeof(char *));
169 nags[i++] = strdup(line);
173 nags[i] = NULL;
174 return 0;
177 void edit_nag_toggle_item(struct menu_input_s *m)
179 struct input_s *in = m->data;
180 struct input_data_s *id = in->data;
181 HISTORY *h = id->data;
182 int i;
184 if (m->selected == 0) {
185 for (i = 0; i < MAX_PGN_NAG; i++)
186 h->nag[i] = 0;
188 for (i = 0; m->items[i]; i++)
189 m->items[i]->selected = 0;
191 return;
194 for (i = 0; i < MAX_PGN_NAG; i++) {
195 if (h->nag[i] == m->selected)
196 h->nag[i] = m->selected = 0;
197 else {
198 if (!h->nag[i]) {
199 h->nag[i] = m->selected;
200 break;
206 void edit_nag_save(struct menu_input_s *m)
208 pushkey = -1;
211 void edit_nag_help(struct menu_input_s *m)
213 message(NAG_EDIT_HELP, ANYKEY, "%s", naghelp);
216 struct menu_item_s **get_nag_items(WIN *win)
218 int i, n;
219 struct menu_input_s *m = win->data;
220 struct input_s *in = m->data;
221 struct input_data_s *id = in->data;
222 struct menu_item_s **items = m->items;
223 HISTORY *h = id->data;
225 if (items) {
226 for (i = 0; items[i]; i++)
227 free(items[i]);
230 for (i = 0; nags[i]; i++) {
231 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
232 items[i] = Malloc(sizeof(struct menu_item_s));
233 items[i]->name = nags[i];
234 items[i]->value = NULL;
236 for (n = 0; n < MAX_PGN_NAG; n++) {
237 if (h->nag[n] == i) {
238 items[i]->selected = 1;
239 n = -1;
240 break;
244 if (n >= 0)
245 items[i]->selected = 0;
248 items[i] = NULL;
249 m->nofree = 1;
250 m->items = items;
251 return items;
254 void edit_nag(void *arg)
256 struct menu_key_s **keys = NULL;
258 if (!nags) {
259 if (init_nag())
260 return;
263 add_menu_key(&keys, ' ', edit_nag_toggle_item);
264 add_menu_key(&keys, CTRL('x'), edit_nag_save);
265 add_menu_key(&keys, KEY_F(1), edit_nag_help);
266 construct_menu(0, 0, -1, -1, NAG_EDIT_TITLE, 1, get_nag_items, keys, arg,
267 NULL);
268 return;
271 static void *view_nag(void *arg)
273 HISTORY *h = (HISTORY *)arg;
274 char buf[80];
275 char line[LINE_MAX] = {0};
276 int i = 0;
278 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
280 if (!nags) {
281 if (init_nag())
282 return NULL;
285 for (i = 0; i < MAX_PGN_NAG; i++) {
286 if (!h->nag[i])
287 break;
289 strncat(line, nags[h->nag[i]], sizeof(line));
290 strncat(line, "\n", sizeof(line));
293 line[strlen(line) - 1] = 0;
294 message(buf, ANYKEY, "%s", line);
295 return NULL;
298 void view_annotation(HISTORY *h)
300 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
301 int nag = 0, comment = 0;
303 if (!h)
304 return;
306 if (h->comment && h->comment[0])
307 comment++;
309 if (h->nag[0])
310 nag++;
312 if (!nag && !comment)
313 return;
315 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h->move);
317 if (comment)
318 construct_message(buf, (nag) ? "Any other key to continue" : ANYKEY, 0,
319 (nag) ? "Press 'n' to view NAG" : NULL,
320 (nag) ? view_nag : NULL, (nag) ? h : NULL, NULL,
321 (nag) ? 'n' : 0, "%s", h->comment);
322 else
323 construct_message(buf, "Any other key to continue", 0,
324 "Press 'n' to view NAG", view_nag, h, NULL, 'n',
325 "%s", "No annotations for this move");
328 static int sort_files(const void *a, const void *b)
330 struct file_s **aa = (struct file_s **)a;
331 struct file_s **bb = (struct file_s **)b;
333 return strcmp((*aa)->name, (*bb)->name);
336 void free_file_browser()
338 int i;
340 if (!files)
341 return;
343 for (i = 0; files[i]; i++) {
344 free(files[i]->name);
345 free(files[i]->path);
346 free(files[i]->st);
347 free(files[i]);
350 free(files);
351 files = NULL;
354 struct menu_item_s **get_file_items(WIN *win)
356 struct menu_input_s *m = win->data;
357 struct input_s *in = m->data;
358 char *path = in->arg;
359 struct menu_item_s **items = m->items;
360 char *p;
361 char pattern[255];
362 wordexp_t w;
363 int i, n = 0;
364 struct stat st;
365 int which = 1;
366 int len;
367 int x = WRDE_NOCMD;
368 int d = 0;
371 * First find directories (including hidden) in the working directory.
372 * Then apply the config.pattern to regular files.
374 if ((p = word_split_append(path, '/', ".* *")) == NULL)
375 return NULL;
377 strncpy(pattern, p, sizeof(pattern));
379 if (items) {
380 for (i = 0; items[i]; i++)
381 free(items[i]);
384 free(items);
385 items = m->items = NULL;
386 free_file_browser();
387 m->nofree = 1;
389 new_we:
390 if (wordexp(pattern, &w, x) != 0) {
391 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
392 return NULL;
395 for (i = 0; i < w.we_wordc; i++) {
396 struct tm *tp;
397 char tbuf[16];
398 char sbuf[64];
400 if (stat(w.we_wordv[i], &st) == -1)
401 continue;
403 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
404 p++;
405 else
406 p = w.we_wordv[i];
408 if (which) {
409 if (!S_ISDIR(st.st_mode))
410 continue;
412 if (p[0] == '.' && p[1] == 0)
413 continue;
415 else {
416 if (S_ISDIR(st.st_mode))
417 continue;
420 files = Realloc(files, (n + 2) * sizeof(struct file_s *));
421 files[n] = Malloc(sizeof(struct file_s));
422 files[n]->path = strdup(w.we_wordv[i]);
423 len = strlen(p) + 2;
424 files[n]->name = Malloc(len);
425 strcpy(files[n]->name, p);
427 if (S_ISDIR(st.st_mode)) {
428 files[n]->name[len - 2] = '/';
429 files[n]->name[len - 1] = 0;
430 p = files[n]->path + strlen(files[n]->path) - 1;
432 if (*p == '.' && *(p - 1) == '.' && *(p - 2) == '/') {
433 p -= 2;
434 *p = 0;
436 if (strlen(files[n]->path)) {
437 while (*p != '/')
438 p--;
440 *p = 0;
443 if (!strlen(files[n]->path)) {
444 p = files[n]->path;
445 *p++ = '/';
446 *p = 0;
451 tp = localtime(&st.st_mtime);
452 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
453 snprintf(sbuf, sizeof(sbuf), "%9i %s", (int)st.st_size, tbuf);
454 files[n]->st = strdup(sbuf);
455 n++;
458 which--;
459 files[n] = NULL;
461 if (which == 0) {
462 d = n;
463 qsort(files, n, sizeof(struct file_s *), sort_files);
465 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
466 return NULL;
468 strncpy(pattern, p, sizeof(pattern));
469 x |= WRDE_REUSE;
470 goto new_we;
473 wordfree(&w);
474 qsort(files + d, i, sizeof(struct file_s *), sort_files);
476 for (i = 0; files[i]; i++) {
477 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
478 items[i] = Malloc(sizeof(struct menu_item_s));
479 items[i]->name = files[i]->name;
480 items[i]->value = files[i]->st;
481 items[i]->selected = 0;
484 if (m->title)
485 free(m->title);
487 m->title = strdup(path);
488 items[i] = NULL;
489 m->items = items;
490 return items;
493 void file_browser_help(struct menu_input_s *m)
495 message(BROWSER_HELP, ANYKEY, "%s", filebrowser_help);
498 void file_browser_select(struct menu_input_s *m)
500 struct input_s *in = m->data;
501 char *path = in->arg;
502 struct stat st;
504 if (stat(files[m->selected]->path, &st) == -1) {
505 message(ERROR, ANYKEY, "%s", strerror(errno));
506 return;
509 if (S_ISDIR(st.st_mode)) {
510 if (access(files[m->selected]->path, R_OK) != 0) {
511 cmessage(files[m->selected]->path, ANYKEY, "%s", strerror(errno));
512 return;
515 free(path);
516 path = strdup(files[m->selected]->path);
517 in->arg = path;
518 m->selected = 0;
519 return;
522 strncpy(in->buf, files[m->selected]->path, sizeof(in->buf));
523 set_field_buffer(in->fields[0], 0, in->buf);
524 pushkey = -1;
527 void file_browser_finalize(WIN *win)
529 struct input_s *in = win->data;
531 free(in->arg);
532 free_file_browser();
535 void file_browser_home(struct menu_input_s *m)
537 struct input_s *in = m->data;
538 char *path = in->arg;
539 struct passwd *pw;
541 free(path);
542 pw = getpwuid(getuid());
543 path = strdup(pw->pw_dir);
544 in->arg = path;
545 m->selected = 0;
548 void file_browser_abort(struct menu_input_s *m)
550 pushkey = -1;
553 void file_browser(void *arg)
555 struct menu_key_s **keys = NULL;
556 struct input_s *in = arg;
557 char *p;
558 char path[FILENAME_MAX];
560 if (config.savedirectory) {
561 if ((p = word_expand(config.savedirectory)) == NULL)
562 return;
564 strncpy(path, p, sizeof(path));
566 if (access(path, R_OK) == -1) {
567 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
568 getcwd(path, sizeof(path));
571 else
572 getcwd(path, sizeof(path));
574 in->arg = strdup(path);
575 add_menu_key(&keys, '\n', file_browser_select);
576 add_menu_key(&keys, KEY_F(1), file_browser_help);
577 add_menu_key(&keys, '~', file_browser_home);
578 add_menu_key(&keys, KEY_ESCAPE, file_browser_abort);
579 construct_menu(LINES - 4, 0, -1, -1, NULL, 0, get_file_items, keys, in,
580 file_browser_finalize);
581 return;
584 void do_game_write(char *filename, char *mode, int start, int end)
586 char *command = NULL;
587 FILE *fp;
588 int i;
589 struct userdata_s *d;
591 if (command) {
592 if ((fp = popen(command, "w")) == NULL) {
593 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
594 goto error;
597 else {
598 if ((fp = fopen(filename, mode)) == NULL) {
599 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
600 goto error;
604 for (i = (start == -1) ? 0 : start; i < end; i++) {
605 d = game[i].data;
606 pgn_write(fp, game[i]);
607 CLEAR_FLAG(d->flags, CF_MODIFIED);
610 if (command)
611 pclose(fp);
612 else
613 fclose(fp);
615 if (start == -1)
616 strncpy(loadfile, filename, sizeof(loadfile));
618 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
619 update_all(game[gindex]);
620 return;
622 error:
623 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
624 update_all(game[gindex]);
627 struct save_game_s {
628 char *filename;
629 char *mode;
630 int start;
631 int end;
634 void do_save_game_overwrite_confirm(WIN *win)
636 char *mode = "w";
637 struct save_game_s *s = win->data;
639 switch (win->c) {
640 case 'a':
641 if (pgn_is_compressed(s->filename) == E_PGN_OK) {
642 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
643 goto done;
646 mode = "a";
647 break;
648 case 'o':
649 mode = "w+";
650 break;
651 default:
652 goto done;
655 do_game_write(s->filename, mode, s->start, s->end);
657 done:
658 free(s->filename);
659 free(s);
662 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
663 * game index number.
665 // FIXME command (compression)
666 void save_pgn(char *filename, int saveindex)
668 char buf[FILENAME_MAX];
669 struct stat st;
670 int end = (saveindex == -1) ? gtotal : saveindex + 1;
671 struct save_game_s *s;
673 if (filename[0] != '/' && config.savedirectory) {
674 if (stat(config.savedirectory, &st) == -1) {
675 if (errno == ENOENT) {
676 if (mkdir(config.savedirectory, 0755) == -1) {
677 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
678 strerror(errno));
679 return;
682 else {
683 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
684 strerror(errno));
685 return;
689 stat(config.savedirectory, &st);
691 if (!S_ISDIR(st.st_mode)) {
692 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
693 return;
696 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
697 filename = buf;
700 if (access(filename, W_OK) == 0) {
701 s = Malloc(sizeof(struct save_game_s));
702 s->filename = strdup(filename);
703 s->start = saveindex;
704 s->end = end;
705 construct_message(NULL, GAME_SAVE_OVERWRITE_PROMPT, 1, NULL, NULL,
706 s, do_save_game_overwrite_confirm, 0, "%s \"%s\"",
707 E_FILEEXISTS, filename);
708 return;
711 do_game_write(filename, "a", saveindex, end);
714 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
716 if (pgn_piece_to_int(piece) == ROOK && col == 7
717 && row == 7 &&
718 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
719 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
720 if (mod)
721 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
722 return 1;
724 else if (pgn_piece_to_int(piece) == ROOK && col == 0
725 && row == 7 &&
726 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
727 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
728 if (mod)
729 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
730 return 1;
732 else if (pgn_piece_to_int(piece) == ROOK && col == 7
733 && row == 0 &&
734 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
735 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
736 if (mod)
737 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
738 return 1;
740 else if (pgn_piece_to_int(piece) == ROOK && col == 0
741 && row == 0 &&
742 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
743 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
744 if (mod)
745 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
746 return 1;
748 else if (pgn_piece_to_int(piece) == KING && col == 4
749 && row == 7 &&
750 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
751 TEST_FLAG(g->flags, GF_WK_CASTLE))
753 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
754 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
755 if (mod) {
756 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
757 TEST_FLAG(g->flags, GF_WQ_CASTLE))
758 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
759 else
760 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
762 return 1;
764 else if (pgn_piece_to_int(piece) == KING && col == 4
765 && row == 0 &&
766 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
767 TEST_FLAG(g->flags, GF_BK_CASTLE))
769 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
770 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
771 if (mod) {
772 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
773 TEST_FLAG(g->flags, GF_BQ_CASTLE))
774 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
775 else
776 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
778 return 1;
781 return 0;
784 static void draw_board(GAME *g)
786 int row, col;
787 int bcol = 0, brow = 0;
788 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
789 int ncols = 0, offset = 1;
790 unsigned coords_y = 8;
791 struct userdata_s *d = g->data;
793 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
794 update_cursor(*g, g->hindex);
796 for (row = 0; row < maxy; row++) {
797 bcol = 0;
799 for (col = 0; col < maxx; col++) {
800 int attrwhich = -1;
801 chtype attrs = 0;
802 unsigned char piece;
804 if (row == 0 || row == maxy - 2) {
805 if (col == 0)
806 mvwaddch(boardw, row, col,
807 LINE_GRAPHIC((row) ?
808 ACS_LLCORNER | CP_BOARD_GRAPHICS :
809 ACS_ULCORNER | CP_BOARD_GRAPHICS));
810 else if (col == maxx - 2)
811 mvwaddch(boardw, row, col,
812 LINE_GRAPHIC((row) ?
813 ACS_LRCORNER | CP_BOARD_GRAPHICS :
814 ACS_URCORNER | CP_BOARD_GRAPHICS));
815 else if (!(col % 4))
816 mvwaddch(boardw, row, col,
817 LINE_GRAPHIC((row) ?
818 ACS_BTEE | CP_BOARD_GRAPHICS :
819 ACS_TTEE | CP_BOARD_GRAPHICS));
820 else {
821 if (col != maxx - 1)
822 mvwaddch(boardw, row, col,
823 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
826 continue;
829 if ((row % 2) && col == maxx - 1 && coords_y) {
830 wattron(boardw, CP_BOARD_COORDS);
831 mvwprintw(boardw, row, col, "%d", coords_y--);
832 wattroff(boardw, CP_BOARD_COORDS);
833 continue;
836 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
837 if (!(row % 2))
838 mvwaddch(boardw, row, col,
839 LINE_GRAPHIC((col) ?
840 ACS_RTEE | CP_BOARD_GRAPHICS :
841 ACS_LTEE | CP_BOARD_GRAPHICS));
842 else
843 mvwaddch(boardw, row, col,
844 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
846 continue;
849 if ((row % 2) && !(col % 4) && row != maxy - 1) {
850 mvwaddch(boardw, row, col,
851 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
852 continue;
855 if (!(col % 4) && row != maxy - 1) {
856 mvwaddch(boardw, row, col,
857 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
858 continue;
861 if ((row % 2)) {
862 if ((col % 4)) {
863 if (ncols++ == 8) {
864 offset++;
865 ncols = 1;
868 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
869 && (offset % 2)))
870 attrwhich = BLACK;
871 else
872 attrwhich = WHITE;
874 if (config.validmoves && d->b[brow][bcol].valid) {
875 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
876 CP_BOARD_MOVES_BLACK;
878 else
879 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
880 CP_BOARD_BLACK;
882 if (row == ROWTOMATRIX(d->c_row) && col ==
883 COLTOMATRIX(d->c_col)) {
884 attrs = CP_BOARD_CURSOR;
887 if (row == ROWTOMATRIX(d->sp.srow) &&
888 col == COLTOMATRIX(d->sp.scol)) {
889 attrs = CP_BOARD_SELECTED;
892 if (row == maxy - 1)
893 attrs = 0;
895 mvwaddch(boardw, row, col, ' ' | attrs);
897 if (row == maxy - 1)
898 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
899 else {
900 if (config.details && d->b[row / 2][bcol].enpassant)
901 piece = 'x';
902 else
903 piece = d->b[row / 2][bcol].icon;
905 if (config.details && castling_state(g, d->b, brow,
906 bcol, piece, 0))
907 attrs |= A_REVERSE;
909 if (g->side == WHITE && isupper(piece))
910 attrs |= A_BOLD;
911 else if (g->side == BLACK && islower(piece))
912 attrs |= A_BOLD;
914 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
916 CLEAR_FLAG(attrs, A_BOLD);
917 CLEAR_FLAG(attrs, A_REVERSE);
920 waddch(boardw, ' ' | attrs);
921 col += 2;
922 bcol++;
925 else {
926 if (col != maxx - 1)
927 mvwaddch(boardw, row, col,
928 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
932 brow = row / 2;
935 mvwaddch(boardw, maxy - 1, maxx - 2, (config.details) ? '!' : ' ');
938 void invalid_move(int n, int e, const char *m)
940 if (curses_initialized)
941 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
942 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
943 else
944 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
945 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
948 static void update_clock(GAME *g, struct itimerval it)
950 struct userdata_s *d = g->data;
951 long n;
953 if (g->turn == WHITE) {
954 d->wc.tv_sec += it.it_value.tv_sec;
955 d->wc.tv_usec += it.it_value.tv_usec;
957 if (d->wc.tv_usec > 1000000 - 1) {
958 d->wc.tv_sec += d->wc.tv_usec / 1000000;
959 d->wc.tv_usec = d->wc.tv_usec % 1000000;
962 else {
963 d->bc.tv_sec += it.it_value.tv_sec;
964 d->bc.tv_usec += it.it_value.tv_usec;
966 if (d->bc.tv_usec > 1000000 - 1) {
967 d->bc.tv_sec += d->bc.tv_usec / 1000000;
968 d->bc.tv_usec = d->bc.tv_usec % 1000000;
972 d->elapsed = d->wc.tv_sec + d->bc.tv_sec;
973 n = d->wc.tv_usec + d->bc.tv_usec;
974 d->elapsed += (n > 1000000 - 1) ? n / 1000000 : 0;
976 if (TEST_FLAG(d->flags, CF_CLOCK)) {
977 if (d->elapsed >= d->limit) {
978 SET_FLAG(g->flags, GF_GAMEOVER);
979 pgn_tag_add(&g->tag, "Result", "1/2-1/2");
984 void do_validate_move(char *m)
986 struct userdata_s *d = game[gindex].data;
987 int n;
989 if (TEST_FLAG(d->flags, CF_HUMAN)) {
990 if ((n = pgn_parse_move(&game[gindex], d->b, &m)) != E_PGN_OK) {
991 invalid_move(d->n + 1, n, m);
992 free(m);
993 return;
996 pgn_history_add(&game[gindex], m);
997 pgn_switch_turn(&game[gindex]);
999 else {
1000 if ((n = pgn_validate_move(&game[gindex], d->b, &m)) != E_PGN_OK) {
1001 invalid_move(d->n + 1, n, m);
1002 free(m);
1003 return;
1006 add_engine_command(&game[gindex], ENGINE_THINKING, "%s\n", m);
1009 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1011 if (config.validmoves)
1012 pgn_reset_valid_moves(d->b);
1014 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1015 d->mode = MODE_HISTORY;
1016 else
1017 SET_FLAG(d->flags, CF_MODIFIED);
1019 d->paused = 0;
1020 free(m);
1021 return;
1024 void do_promotion_piece_finalize(WIN *win)
1026 char *p, *str = win->data;
1028 if (pgn_piece_to_int(win->c) == -1)
1029 return;
1031 p = str + strlen(str);
1032 *p++ = toupper(win->c);
1033 *p = '\0';
1034 do_validate_move(str);
1037 static void move_to_engine(GAME *g)
1039 struct userdata_s *d = g->data;
1040 char *str;
1041 int piece;
1043 if (config.validmoves &&
1044 !d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].valid)
1045 return;
1047 str = Malloc(MAX_SAN_MOVE_LEN + 1);
1048 snprintf(str, MAX_SAN_MOVE_LEN + 1, "%c%i%c%i",
1049 x_grid_chars[d->sp.scol - 1],
1050 d->sp.srow, x_grid_chars[d->sp.col - 1], d->sp.row);
1052 piece = pgn_piece_to_int(d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon);
1054 if (piece == PAWN && (d->sp.row == 8 || d->sp.row == 1)) {
1055 construct_message(PROMOTION_TITLE, PROMOTION_PROMPT, 1, NULL, NULL,
1056 str, do_promotion_piece_finalize, 0, "%s", PROMOTION_TEXT);
1057 return;
1060 do_validate_move(str);
1063 static char *clock_to_char(long n)
1065 static char buf[16];
1066 int h = 0, m = 0, s = 0;
1068 h = n / 3600;
1069 m = (n % 3600) / 60;
1070 s = (n % 3600) % 60;
1071 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
1072 return buf;
1075 static char *timeval_to_char(struct timeval t)
1077 static char buf[16];
1078 int h = 0, m = 0, s = 0;
1079 int n = t.tv_sec;
1081 h = n / 3600;
1082 m = (n % 3600) / 60;
1083 s = (n % 3600) % 60;
1084 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i.%.2i", h, m, s,
1085 (int)t.tv_usec / 10000);
1086 return buf;
1089 void update_status_window(GAME g)
1091 int i = 0;
1092 char *buf;
1093 char tmp[15], *engine, *mode;
1094 int w;
1095 char *p;
1096 int maxy, maxx;
1097 int len;
1098 struct userdata_s *d = g.data;
1100 getmaxyx(statusw, maxy, maxx);
1101 w = maxx - 2 - 8;
1102 len = maxx - 2;
1103 buf = Malloc(len);
1105 *tmp = '\0';
1106 p = tmp;
1108 if (TEST_FLAG(d->flags, CF_DELETE)) {
1109 *p++ = '(';
1110 *p++ = 'x';
1111 i++;
1114 if (TEST_FLAG(g.flags, GF_PERROR)) {
1115 if (!i)
1116 *p++ = '(';
1117 else
1118 *p++ = '/';
1120 *p++ = '!';
1121 i++;
1124 if (TEST_FLAG(d->flags, CF_MODIFIED)) {
1125 if (!i)
1126 *p++ = '(';
1127 else
1128 *p++ = '/';
1130 *p++ = '*';
1131 i++;
1134 if (*tmp != '\0')
1135 *p++ = ')';
1137 *p = '\0';
1139 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1140 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1141 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1142 (*tmp) ? tmp : "");
1143 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1145 switch (d->mode) {
1146 case MODE_HISTORY:
1147 mode = MODE_HISTORY_STR;
1148 break;
1149 case MODE_EDIT:
1150 mode = MODE_EDIT_STR;
1151 break;
1152 case MODE_PLAY:
1153 mode = MODE_PLAY_STR;
1154 break;
1155 default:
1156 mode = UNKNOWN;
1157 break;
1160 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1162 if (d->mode == MODE_PLAY) {
1163 if (TEST_FLAG(d->flags, CF_HUMAN))
1164 strncat(buf, " (human/human)", len - 1);
1165 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1166 strncat(buf, " (engine/engine)", len - 1);
1167 else
1168 strncat(buf, " (human/engine)", len - 1);
1171 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1173 if (d->engine) {
1174 switch (d->engine->status) {
1175 case ENGINE_THINKING:
1176 engine = ENGINE_PONDER_STR;
1177 break;
1178 case ENGINE_READY:
1179 engine = ENGINE_READY_STR;
1180 break;
1181 case ENGINE_INITIALIZING:
1182 engine = ENGINE_INITIALIZING_STR;
1183 break;
1184 case ENGINE_OFFLINE:
1185 engine = ENGINE_OFFLINE_STR;
1186 break;
1187 default:
1188 engine = UNKNOWN;
1189 break;
1192 else
1193 engine = ENGINE_OFFLINE_STR;
1195 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1196 wattron(statusw, CP_STATUS_ENGINE);
1197 mvwaddstr(statusw, 5, 9, engine);
1198 wattroff(statusw, CP_STATUS_ENGINE);
1200 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1201 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1203 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR, w,
1204 clock_to_char((TEST_FLAG(d->flags, CF_CLOCK)) ?
1205 d->limit - d->elapsed : 0));
1207 strncpy(tmp, WHITE_STR, sizeof(tmp));
1208 tmp[0] = toupper(tmp[0]);
1209 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->wc));
1211 strncpy(tmp, BLACK_STR, sizeof(tmp));
1212 tmp[0] = toupper(tmp[0]);
1213 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->bc));
1214 free(buf);
1216 for (i = 1; i < maxx - 4; i++)
1217 mvwprintw(statusw, maxy - 2, i, " ");
1219 if (!status.notify)
1220 status.notify = strdup(GAME_HELP_PROMPT);
1222 wattron(statusw, CP_STATUS_NOTIFY);
1223 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1224 status.notify);
1225 wattroff(statusw, CP_STATUS_NOTIFY);
1228 void update_history_window(GAME g)
1230 char buf[HISTORY_WIDTH - 1];
1231 HISTORY *h = NULL;
1232 int n, total;
1233 int t = pgn_history_total(g.hp);
1235 n = (g.hindex + 1) / 2;
1237 if (t % 2)
1238 total = (t + 1) / 2;
1239 else
1240 total = t / 2;
1242 if (t)
1243 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1244 (movestep == 1) ? HISTORY_PLY_STEP : "");
1245 else
1246 strncpy(buf, UNAVAILABLE, sizeof(buf));
1248 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1249 HISTORY_WIDTH - 13, buf);
1251 h = pgn_history_by_n(g.hp, g.hindex);
1252 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1253 n = 0;
1255 if (h && ((h->comment) || h->nag[0])) {
1256 strncat(buf, " (v", sizeof(buf));
1257 n++;
1260 if (h && h->rav) {
1261 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1262 n++;
1265 if (g.ravlevel) {
1266 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1267 n++;
1270 if (n)
1271 strncat(buf, ")", sizeof(buf));
1273 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1274 HISTORY_WIDTH - 13, buf);
1276 h = pgn_history_by_n(g.hp, g.hindex - 1);
1277 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1278 n = 0;
1280 if (h && ((h->comment) || h->nag[0])) {
1281 strncat(buf, " (V", sizeof(buf));
1282 n++;
1285 if (h && h->rav) {
1286 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1287 n++;
1290 if (g.ravlevel) {
1291 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1292 n++;
1295 if (n)
1296 strncat(buf, ")", sizeof(buf));
1298 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1299 HISTORY_WIDTH - 13, buf);
1302 void update_tag_window(TAG **t)
1304 int i;
1305 int w = TAG_WIDTH - 10;
1307 for (i = 0; i < 7; i++)
1308 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w,
1309 str_etc(t[i]->value, w, 0));
1312 void append_enginebuf(char *line)
1314 int i = 0;
1316 if (enginebuf)
1317 for (i = 0; enginebuf[i]; i++);
1319 if (i >= LINES - 3) {
1320 free(enginebuf[0]);
1322 for (i = 0; enginebuf[i+1]; i++)
1323 enginebuf[i] = enginebuf[i+1];
1325 enginebuf[i] = strdup(line);
1327 else {
1328 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1329 enginebuf[i++] = strdup(line);
1330 enginebuf[i] = NULL;
1334 void update_engine_window()
1336 int i;
1338 if (!enginebuf)
1339 return;
1341 wmove(enginew, 0, 0);
1342 wclrtobot(enginew);
1344 if (enginebuf) {
1345 for (i = 0; enginebuf[i]; i++)
1346 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1349 window_draw_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1350 CP_MESSAGE_BORDER);
1353 void toggle_engine_window()
1355 if (!enginew) {
1356 enginew = newwin(LINES, COLS, 0, 0);
1357 enginep = new_panel(enginew);
1358 window_draw_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1359 CP_MESSAGE_BORDER);
1360 hide_panel(enginep);
1363 if (panel_hidden(enginep)) {
1364 update_engine_window();
1365 top_panel(enginep);
1366 refresh_all();
1368 else {
1369 hide_panel(enginep);
1370 refresh_all();
1374 void refresh_all()
1376 update_panels();
1377 doupdate();
1380 void update_all(GAME g)
1382 update_status_window(g);
1383 update_history_window(g);
1384 update_tag_window(g.tag);
1385 update_engine_window();
1388 static void game_next_prev(GAME g, int n, int count)
1390 if (gtotal < 2)
1391 return;
1393 if (n == 1) {
1394 if (gindex + count > gtotal - 1) {
1395 if (count != 1)
1396 gindex = gtotal - 1;
1397 else
1398 gindex = 0;
1400 else
1401 gindex += count;
1403 else {
1404 if (gindex - count < 0) {
1405 if (count != 1)
1406 gindex = 0;
1407 else
1408 gindex = gtotal - 1;
1410 else
1411 gindex -= count;
1415 static void delete_game(int which)
1417 GAME *g = NULL;
1418 int gi = 0;
1419 int i;
1420 struct userdata_s *d;
1422 for (i = 0; i < gtotal; i++) {
1423 d = game[i].data;
1425 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
1426 pgn_free(game[i]);
1427 continue;
1430 g = Realloc(g, (gi + 1) * sizeof(GAME));
1431 memcpy(&g[gi], &game[i], sizeof(GAME));
1432 g[gi].tag = game[i].tag;
1433 g[gi].history = game[i].history;
1434 g[gi].hp = game[i].hp;
1435 gi++;
1438 game = g;
1439 gtotal = gi;
1441 if (which != -1) {
1442 if (which + 1 >= gtotal)
1443 gindex = gtotal - 1;
1444 else
1445 gindex = which;
1447 else
1448 gindex = gtotal - 1;
1450 game[gindex].hp = game[gindex].history;
1454 * FIXME find across multiple games.
1456 static int find_move_exp(GAME g, regex_t r, int which, int count)
1458 int i;
1459 int ret;
1460 char errbuf[255];
1461 int incr;
1462 int found;
1464 incr = (which == 0) ? -1 : 1;
1466 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
1467 if (i == g.hindex - 1)
1468 break;
1470 if (i >= pgn_history_total(g.hp))
1471 i = 0;
1472 else if (i < 0)
1473 i = pgn_history_total(g.hp) - 1;
1475 // FIXME RAV
1476 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
1478 if (ret == 0) {
1479 if (count == ++found) {
1480 return i + 1;
1483 else {
1484 if (ret != REG_NOMATCH) {
1485 regerror(ret, &r, errbuf, sizeof(errbuf));
1486 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
1487 return -1;
1492 return -1;
1495 static int toggle_delete_flag(int n)
1497 int i, x;
1498 struct userdata_s *d = game[n].data;
1500 TOGGLE_FLAG(d->flags, CF_DELETE);
1501 gindex = n;
1502 update_all(game[gindex]);
1504 for (i = x = 0; i < gtotal; i++) {
1505 d = game[i].data;
1507 if (TEST_FLAG(d->flags, CF_DELETE))
1508 x++;
1511 if (x == gtotal) {
1512 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1513 d = game[n].data;
1514 CLEAR_FLAG(d->flags, CF_DELETE);
1515 return 1;
1518 return 0;
1521 static int find_game_exp(char *str, int which, int count)
1523 char *nstr = NULL, *exp = NULL;
1524 regex_t nexp, vexp;
1525 int ret = -1;
1526 int g = 0;
1527 char buf[255], *tmp;
1528 char errbuf[255];
1529 int found = 0;
1530 int incr = (which == 0) ? -(1) : 1;
1532 strncpy(buf, str, sizeof(buf));
1533 tmp = buf;
1535 if (strstr(tmp, ":") != NULL) {
1536 nstr = strsep(&tmp, ":");
1538 if ((ret = regcomp(&nexp, nstr,
1539 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
1540 regerror(ret, &nexp, errbuf, sizeof(errbuf));
1541 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1542 ret = g = -1;
1543 goto cleanup;
1547 exp = tmp;
1549 if (exp == NULL)
1550 goto cleanup;
1552 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
1553 regerror(ret, &vexp, errbuf, sizeof(errbuf));
1554 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1555 ret = -1;
1556 goto cleanup;
1559 ret = -1;
1561 for (g = gindex + incr, found = 0; ; g += incr) {
1562 int t;
1564 if (g == gindex)
1565 break;
1567 if (g == gtotal)
1568 g = 0;
1569 else if (g < 0)
1570 g = gtotal - 1;
1572 for (t = 0; game[g].tag[t]; t++) {
1573 if (nstr) {
1574 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
1575 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
1576 if (count == ++found) {
1577 ret = g;
1578 goto cleanup;
1583 else {
1584 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
1585 if (count == ++found) {
1586 ret = g;
1587 goto cleanup;
1593 ret = -1;
1596 cleanup:
1597 if (nstr)
1598 regfree(&nexp);
1600 if (g != -1)
1601 regfree(&vexp);
1603 return ret;
1607 * Updates the notification line in the status window then refreshes the
1608 * status window.
1610 void update_status_notify(GAME g, char *fmt, ...)
1612 va_list ap;
1613 #ifdef HAVE_VASPRINTF
1614 char *line;
1615 #else
1616 char line[COLS];
1617 #endif
1619 if (!fmt) {
1620 if (status.notify) {
1621 free(status.notify);
1622 status.notify = NULL;
1624 if (curses_initialized)
1625 update_status_window(g);
1628 return;
1631 va_start(ap, fmt);
1632 #ifdef HAVE_VASPRINTF
1633 vasprintf(&line, fmt, ap);
1634 #else
1635 vsnprintf(line, sizeof(line), fmt, ap);
1636 #endif
1637 va_end(ap);
1639 if (status.notify)
1640 free(status.notify);
1642 status.notify = strdup(line);
1644 #ifdef HAVE_VASPRINTF
1645 free(line);
1646 #endif
1647 if (curses_initialized)
1648 update_status_window(g);
1651 int rav_next_prev(GAME *g, BOARD b, int n)
1653 // Next RAV.
1654 if (n) {
1655 if ((!g->ravlevel && g->hp[g->hindex - 1]->rav == NULL) ||
1656 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
1657 return 1;
1659 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
1660 g->rav[g->ravlevel].hp = g->hp;
1661 g->rav[g->ravlevel].flags = g->flags;
1662 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
1663 g->rav[g->ravlevel].hindex = g->hindex;
1664 g->hp = (!g->ravlevel) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav;
1665 g->hindex = 0;
1666 g->ravlevel++;
1667 pgn_board_update(g, b, g->hindex + 1);
1668 return 0;
1671 if (g->ravlevel - 1 < 0)
1672 return 1;
1674 // Previous RAV.
1675 g->ravlevel--;
1676 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
1677 free(g->rav[g->ravlevel].fen);
1678 g->hp = g->rav[g->ravlevel].hp;
1679 g->flags = g->rav[g->ravlevel].flags;
1680 g->hindex = g->rav[g->ravlevel].hindex;
1681 return 0;
1684 static void draw_window_decor()
1686 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
1687 move_panel(boardp, 0, COLS - BOARD_WIDTH);
1688 wbkgd(boardw, CP_BOARD_WINDOW);
1689 wbkgd(statusw, CP_STATUS_WINDOW);
1690 window_draw_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
1691 CP_STATUS_TITLE, CP_STATUS_BORDER);
1692 wbkgd(tagw, CP_TAG_WINDOW);
1693 window_draw_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
1694 CP_TAG_BORDER);
1695 wbkgd(historyw, CP_HISTORY_WINDOW);
1696 window_draw_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
1697 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
1700 static void do_window_resize()
1702 if (LINES < 24 || COLS < 80)
1703 return;
1705 resizeterm(LINES, COLS);
1706 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
1707 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
1708 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
1709 wmove(historyw, 0, 0);
1710 wclrtobot(historyw);
1711 wmove(tagw, 0, 0);
1712 wclrtobot(tagw);
1713 wmove(statusw, 0, 0);
1714 wclrtobot(statusw);
1715 draw_window_decor();
1716 update_all(game[gindex]);
1719 void stop_clock()
1721 memset(&clock_timer, 0, sizeof(struct itimerval));
1722 setitimer(ITIMER_REAL, &clock_timer, NULL);
1725 void start_clock()
1727 if (clock_timer.it_interval.tv_usec)
1728 return;
1730 clock_timer.it_value.tv_sec = 0;
1731 clock_timer.it_value.tv_usec = 100000;
1732 clock_timer.it_interval.tv_sec = 0;
1733 clock_timer.it_interval.tv_usec = 100000;
1734 setitimer(ITIMER_REAL, &clock_timer, NULL);
1737 static void update_clocks()
1739 int i;
1740 struct userdata_s *d;
1741 struct itimerval it;
1743 getitimer(ITIMER_REAL, &it);
1745 for (i = 0; i < gtotal; i++) {
1746 d = game[i].data;
1748 if (d->mode == MODE_PLAY) {
1749 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
1750 continue;
1751 else if (d->paused == -1) {
1752 if (game[i].side == game[i].turn) {
1753 d->paused = 1;
1754 continue;
1758 update_clock(&game[i], it);
1763 static int init_chess_engine(GAME *g)
1765 struct userdata_s *d = g->data;
1766 int w, x;
1768 if (start_chess_engine(g) > 0) {
1769 d->sp.icon = 0;
1770 return 1;
1773 x = pgn_tag_find(g->tag, "FEN");
1774 w = pgn_tag_find(g->tag, "SetUp");
1776 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
1777 (x >= 0 && w == -1))
1778 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
1779 else
1780 add_engine_command(g, ENGINE_READY, "setboard %s\n",
1781 pgn_game_to_fen(*g, d->b));
1783 return 0;
1786 static int parse_clock_input(struct userdata_s *d, char *str)
1788 char *p = str;
1789 long n = 0;
1790 int t = 0;
1791 int plus = 0;
1793 if (*p == '+') {
1794 plus = 1;
1795 p++;
1798 while (*p) {
1799 if (isdigit(*p)) {
1800 t = atoi(p);
1802 while (isdigit(*p))
1803 p++;
1805 continue;
1808 if (!t && *p != ' ')
1809 return 1;
1811 switch (*p) {
1812 case 'H':
1813 case 'h':
1814 n += t * (60 * 60);
1815 t = 0;
1816 break;
1817 case 'M':
1818 case 'm':
1819 n += t * 60;
1820 t = 0;
1821 break;
1822 case 'S':
1823 case 's':
1824 n += t;
1825 t = 0;
1826 break;
1827 case ' ':
1828 t = 0;
1829 break;
1830 default:
1831 return 1;
1834 p++;
1837 if (t)
1838 n += t;
1840 if (!n) {
1841 d->limit = 0;
1842 CLEAR_FLAG(d->flags, CF_CLOCK);
1844 else {
1845 SET_FLAG(d->flags, CF_CLOCK);
1847 if (plus)
1848 d->limit += n;
1849 else
1850 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
1853 return 0;
1856 void do_clock_input_finalize(WIN *win)
1858 struct userdata_s *d = game[gindex].data;
1859 struct input_data_s *in = win->data;
1861 if (!in->str)
1862 return;
1864 if (parse_clock_input(d, in->str))
1865 cmessage(ERROR, ANYKEY, "Invalid time specification");
1867 free(in->str);
1868 free(in);
1871 void do_engine_command_finalize(WIN *win)
1873 struct userdata_s *d = game[gindex].data;
1874 struct input_data_s *in = win->data;
1875 int x;
1877 if (!in->str) {
1878 free(in);
1879 return;
1882 x = d->engine->status;
1883 send_to_engine(&game[gindex], -1, "%s\n", in->str);
1884 d->engine->status = x;
1885 free(in->str);
1886 free(in);
1889 static void historymode_keys(chtype);
1890 static int playmode_keys(chtype c)
1892 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
1893 struct userdata_s *d = game[gindex].data;
1894 int editmode = (d->mode == MODE_EDIT) ? 1 : 0;
1895 chtype p;
1896 int w, x;
1897 struct input_data_s *in;
1899 switch (c) {
1900 case 'C':
1901 in = Calloc(1, sizeof(struct input_data_s));
1902 in->efunc = do_clock_input_finalize;
1903 construct_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL, NULL, 0,
1904 in, -1);
1905 break;
1906 case 'H':
1907 TOGGLE_FLAG(d->flags, CF_HUMAN);
1909 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
1910 pgn_history_total(game[gindex].hp)) {
1911 if (init_chess_engine(&game[gindex]))
1912 break;
1915 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
1917 if (d->engine)
1918 d->engine->status = ENGINE_READY;
1920 update_all(game[gindex]);
1921 break;
1922 case 'E':
1923 if (!d)
1924 break;
1926 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
1927 CLEAR_FLAG(d->flags, CF_HUMAN);
1929 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
1930 pgn_board_update(&game[gindex], d->b,
1931 pgn_history_total(game[gindex].hp));
1932 add_engine_command(&game[gindex], ENGINE_READY,
1933 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
1936 update_all(game[gindex]);
1937 break;
1938 case '|':
1939 if (!d->engine)
1940 break;
1942 if (d->engine->status == ENGINE_OFFLINE)
1943 break;
1945 x = d->engine->status;
1946 in = Calloc(1, sizeof(struct input_data_s));
1947 in->efunc = do_engine_command_finalize;
1948 construct_input(ENGINE_CMD_TITLE, NULL, 1, 1, NULL, NULL, NULL, 0,
1949 in, -1);
1950 break;
1951 case '\015':
1952 case '\n':
1953 pushkey = keycount = 0;
1954 update_status_notify(game[gindex], NULL);
1956 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
1957 (!d->engine || d->engine->status == ENGINE_THINKING)) {
1958 beep();
1959 break;
1962 if (!d->sp.icon)
1963 break;
1965 d->sp.row = d->c_row;
1966 d->sp.col = d->c_col;
1968 if (editmode) {
1969 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
1970 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
1971 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
1972 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
1973 d->sp.icon = d->sp.srow = d->sp.scol = 0;
1974 break;
1977 move_to_engine(&game[gindex]);
1978 break;
1979 case ' ':
1980 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
1981 d->engine->status == ENGINE_OFFLINE) && !editmode) {
1982 if (init_chess_engine(&game[gindex]))
1983 break;
1986 if (d->sp.icon || (!editmode && d->engine &&
1987 d->engine->status == ENGINE_THINKING)) {
1988 beep();
1989 break;
1992 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
1993 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
1995 if (d->sp.icon == ' ') {
1996 d->sp.icon = 0;
1997 break;
2000 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2001 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2002 if (pgn_history_total(game[gindex].hp)) {
2003 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2004 d->sp.icon = 0;
2005 break;
2007 else {
2008 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR)
2009 break;
2011 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2012 pgn_switch_turn(&game[gindex]);
2014 if (game[gindex].side != BLACK)
2015 pgn_switch_side(&game[gindex]);
2019 d->sp.srow = d->c_row;
2020 d->sp.scol = d->c_col;
2022 if (!editmode && config.validmoves)
2023 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2025 if (!editmode) {
2026 CLEAR_FLAG(d->flags, CF_NEW);
2027 start_clock();
2030 break;
2031 case 'w':
2032 pgn_switch_side(&game[gindex]);
2033 pgn_switch_turn(&game[gindex]);
2034 add_engine_command(&game[gindex], -1,
2035 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2036 update_status_window(game[gindex]);
2037 break;
2038 case 'u':
2039 if (!pgn_history_total(game[gindex].hp))
2040 break;
2042 if (d->engine && d->engine->status == ENGINE_READY) {
2043 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2044 d->engine->status = ENGINE_READY;
2047 game[gindex].hindex -= 2;
2048 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2049 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2050 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2051 update_history_window(game[gindex]);
2052 break;
2053 case 'a':
2054 historymode_keys(c);
2055 break;
2056 case 'd':
2057 config.details = (config.details) ? 0 : 1;
2058 break;
2059 case 'p':
2060 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex].turn !=
2061 game[gindex].side) {
2062 d->paused = -1;
2063 break;
2066 d->paused = (d->paused) ? 0 : 1;
2067 break;
2068 case 'g':
2069 if (TEST_FLAG(d->flags, CF_HUMAN))
2070 break;
2072 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2073 if (init_chess_engine(&game[gindex]))
2074 break;
2077 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2078 break;
2079 default:
2080 if (!d->engine)
2081 break;
2083 if (config.keys) {
2084 for (x = 0; config.keys[x]; x++) {
2085 if (config.keys[x]->c == c) {
2086 switch (config.keys[x]->type) {
2087 case KEY_DEFAULT:
2088 add_engine_command(&game[gindex], -1, "%s\n",
2089 config.keys[x]->str);
2090 break;
2091 case KEY_SET:
2092 if (!keycount)
2093 break;
2095 add_engine_command(&game[gindex], -1,
2096 "%s %i\n", config.keys[x]->str, keycount);
2097 keycount = 0;
2098 break;
2099 case KEY_REPEAT:
2100 if (!keycount)
2101 break;
2103 for (w = 0; w < keycount; w++)
2104 add_engine_command(&game[gindex], -1,
2105 "%s\n", config.keys[x]->str);
2106 keycount = 0;
2107 break;
2112 update_status_notify(game[gindex], NULL);
2114 break;
2117 return 0;
2120 void do_edit_insert_finalize(WIN *win)
2122 struct userdata_s *d = win->data;
2124 if (pgn_piece_to_int(win->c) == -1)
2125 return;
2127 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = win->c;
2130 static void editmode_keys(chtype c)
2132 struct userdata_s *d = game[gindex].data;
2134 switch (c) {
2135 case '\015':
2136 case '\n':
2137 case ' ':
2138 playmode_keys(c);
2139 break;
2140 case 'd':
2141 if (d->sp.icon)
2142 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2143 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2144 else
2145 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2146 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2148 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2149 break;
2150 case 'w':
2151 pgn_switch_turn(&game[gindex]);
2152 update_all(game[gindex]);
2153 break;
2154 case 'c':
2155 castling_state(&game[gindex], d->b, RANKTOBOARD(d->c_row),
2156 FILETOBOARD(d->c_col),
2157 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2158 break;
2159 case 'i':
2160 construct_message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, 0, NULL, NULL,
2161 d->b, do_edit_insert_finalize, 0, "%s", GAME_EDIT_TEXT);
2162 break;
2163 case 'p':
2164 if (d->c_row == 6 || d->c_row == 3) {
2165 pgn_reset_enpassant(d->b);
2166 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2168 break;
2169 default:
2170 break;
2174 void do_annotate_finalize(WIN *win)
2176 struct userdata_s *d = game[gindex].data;
2177 struct input_data_s *in = win->data;
2178 HISTORY *h = in->data;
2179 int len;
2181 if (!in->str) {
2182 if (h->comment) {
2183 free(h->comment);
2184 h->comment = NULL;
2187 else {
2188 len = strlen(in->str) + 1;
2189 h->comment = Realloc(h->comment, len);
2190 strncpy(h->comment, in->str, len);
2193 free(in->str);
2194 free(in);
2195 SET_FLAG(d->flags, CF_MODIFIED);
2196 update_all(game[gindex]);
2199 void do_find_move_exp_finalize(int init, int c)
2201 int n;
2202 struct userdata_s *d = game[gindex].data;
2203 static int firstrun;
2204 static regex_t r;
2205 int ret;
2206 char errbuf[255];
2208 if (init || !firstrun) {
2209 if (!firstrun)
2210 regfree(&r);
2212 if ((ret = regcomp(&r, moveexp, REG_EXTENDED|REG_NOSUB)) != 0) {
2213 regerror(ret, &r, errbuf, sizeof(errbuf));
2214 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2215 return;
2218 firstrun = 1;
2221 if ((n = find_move_exp(game[gindex], r,
2222 (c == '[') ? 0 : 1, (keycount) ? keycount : 1)) == -1)
2223 return;
2225 game[gindex].hindex = n;
2226 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2227 update_all(game[gindex]);
2230 void do_find_move_exp(WIN *win)
2232 struct input_data_s *in = win->data;
2233 int *n = in->data;
2234 int c = *n;
2236 if (in->str) {
2237 strncpy(moveexp, in->str, sizeof(moveexp));
2239 if (c == '/')
2240 c = ']';
2242 do_find_move_exp_finalize(1, c);
2243 free(in->str);
2246 free(in->data);
2247 free(in);
2250 void do_move_jump_finalize(int n)
2252 struct userdata_s *d = game[gindex].data;
2254 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2255 return;
2257 keycount = 0;
2258 update_status_notify(game[gindex], NULL);
2259 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2260 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2261 update_all(game[gindex]);
2264 void do_move_jump(WIN *win)
2266 struct input_data_s *in = win->data;
2268 if (!in->str || !isinteger(in->str)) {
2269 if (in->str)
2270 free(in->str);
2272 free(in);
2273 return;
2276 do_move_jump_finalize(atoi(in->str));
2277 free(in->str);
2278 free(in);
2281 struct history_menu_s {
2282 char *line;
2283 int hindex;
2284 int ravlevel;
2285 int move;
2286 int indent;
2289 void get_history_data(HISTORY **hp, struct history_menu_s ***menu, int m,
2290 int turn)
2292 int i, n = 0;
2293 int t = pgn_history_total(hp);
2294 char buf[MAX_SAN_MOVE_LEN + 3];
2295 static int depth;
2296 struct history_menu_s **hmenu = *menu;
2298 if (hmenu)
2299 for (n = 0; hmenu[n]; n++);
2300 else
2301 depth = 0;
2303 for (i = 0; i < t; i++) {
2304 hmenu = Realloc(hmenu, (n + 2) * sizeof(struct history_menu_s *));
2305 hmenu[n] = Malloc(sizeof(struct history_menu_s));
2306 snprintf(buf, sizeof(buf), "%c%s", (turn == WHITE) ? 'W' : 'B',
2307 hp[i]->move);
2308 hmenu[n]->line = strdup(buf);
2309 hmenu[n]->hindex = i;
2310 hmenu[n]->indent = 0;
2311 hmenu[n]->ravlevel = depth;
2312 hmenu[n]->move = (n && depth > hmenu[n-1]->ravlevel) ? m++ : m;
2313 n++;
2314 hmenu[n] = NULL;
2316 if (hp[i]->rav) {
2317 depth++;
2318 get_history_data(hp[i]->rav, &hmenu, m, turn);
2319 for (n = 0; hmenu[n]; n++);
2320 depth--;
2322 if (depth)
2323 m--;
2326 turn = (turn == WHITE) ? BLACK : WHITE;
2329 *menu = hmenu;
2332 void history_draw_update(struct menu_input_s *m)
2334 GAME *g = m->data;
2335 struct userdata_s *d = g->data;
2337 g->hindex = m->selected + 1;
2338 update_cursor(*g, m->selected);
2339 pgn_board_update(g, d->b, m->selected + 1);
2342 struct menu_item_s **get_history_items(WIN *win)
2344 struct menu_input_s *m = win->data;
2345 GAME *g = m->data;
2346 struct userdata_s *d = g->data;
2347 struct history_menu_s **hm = d->data;
2348 struct menu_item_s **items = m->items;
2349 int i;
2351 if (!hm) {
2352 get_history_data(g->history, &hm, 0,
2353 TEST_FLAG(g->flags, GF_BLACK_OPENING));
2354 m->selected = g->hindex - 1;
2356 if (m->selected < 0)
2357 m->selected = 0;
2359 m->draw_exit_func = history_draw_update;
2362 d->data = hm;
2364 if (items) {
2365 for (i = 0; items[i]; i++)
2366 free(items[i]);
2368 free(items);
2369 items = NULL;
2372 for (i = 0; hm[i]; i++) {
2373 items = Realloc(items, (i+2) * sizeof(struct menu_item_s *));
2374 items[i] = Malloc(sizeof(struct menu_item_s));
2375 items[i]->name = hm[i]->line;
2376 items[i]->value = NULL;
2377 items[i]->selected = 0;
2380 if (items)
2381 items[i] = NULL;
2383 m->nofree = 1;
2384 m->items = items;
2385 return items;
2388 void history_menu_quit(struct menu_input_s *m)
2390 pushkey = -1;
2393 void history_menu_exit(WIN *win)
2395 GAME *g = win->data;
2396 struct userdata_s *d = g->data;
2397 struct history_menu_s **hm = d->data;
2398 int i;
2400 if (!hm)
2401 return;
2403 for (i = 0; hm[i]; i++) {
2404 free(hm[i]->line);
2405 free(hm[i]);
2408 free(hm);
2409 d->data = NULL;
2412 // FIXME RAV
2413 void history_menu_next(struct menu_input_s *m)
2415 GAME *g = m->data;
2416 struct userdata_s *d = g->data;
2417 struct history_menu_s **hm = d->data;
2418 int n, t;
2420 for (t = 0; hm[t]; t++);
2422 if (m->selected + 1 == t)
2423 n = 0;
2424 else
2425 n = hm[m->selected + 1]->hindex;
2427 n++;
2428 g->hindex = n;
2431 // FIXME RAV
2432 void history_menu_prev(struct menu_input_s *m)
2434 GAME *g = m->data;
2435 struct userdata_s *d = g->data;
2436 struct history_menu_s **hm = d->data;
2437 int n, t;
2439 for (t = 0; hm[t]; t++);
2441 if (m->selected - 1 < 0)
2442 n = t - 1;
2443 else
2444 n = hm[m->selected - 1]->hindex;
2446 n++;
2447 g->hindex = n;
2450 void history_menu_help(struct menu_input_s *m)
2452 message("History Menu Help", ANYKEY, "%s", history_menu_help_str);
2455 void do_annotate_move(HISTORY *hp)
2457 char buf[COLS - 4];
2458 struct input_data_s *in;
2460 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE, hp->move);
2461 in = Calloc(1, sizeof(struct input_data_s));
2462 in->data = hp;
2463 in->efunc = do_annotate_finalize;
2464 construct_input(buf, hp->comment, MAX_PGN_LINE_LEN / INPUT_WIDTH, 0,
2465 NAG_PROMPT, edit_nag, NULL, CTRL('T'), in, -1);
2468 void history_menu_annotate(struct menu_input_s *m)
2470 GAME *g = m->data;
2472 // FIXME RAV
2473 do_annotate_move(g->history[m->selected]);
2476 void history_menu(GAME *g)
2478 struct menu_key_s **keys = NULL;
2480 add_menu_key(&keys, KEY_ESCAPE, history_menu_quit);
2481 add_menu_key(&keys, KEY_UP, history_menu_prev);
2482 add_menu_key(&keys, KEY_DOWN, history_menu_next);
2483 add_menu_key(&keys, KEY_F(1), history_menu_help);
2484 add_menu_key(&keys, 'a', history_menu_annotate);
2485 construct_menu(LINES, TAG_WIDTH, 0, 0, "Move History Tree", 1,
2486 get_history_items, keys, g, history_menu_exit);
2489 static void historymode_keys(chtype c)
2491 int n;
2492 struct userdata_s *d = game[gindex].data;
2493 struct input_data_s *in;
2494 int *p;
2496 switch (c) {
2497 case 'M':
2498 history_menu(&game[gindex]);
2499 break;
2500 case 'd':
2501 config.details = (config.details) ? 0 : 1;
2502 break;
2503 case ' ':
2504 movestep = (movestep == 1) ? 2 : 1;
2505 update_history_window(game[gindex]);
2506 break;
2507 case KEY_UP:
2508 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2509 config.jumpcount * keycount * movestep :
2510 config.jumpcount * movestep);
2511 update_all(game[gindex]);
2512 break;
2513 case KEY_DOWN:
2514 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2515 config.jumpcount * keycount * movestep :
2516 config.jumpcount * movestep);
2517 update_all(game[gindex]);
2518 break;
2519 case KEY_LEFT:
2520 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2521 keycount * movestep : movestep);
2522 update_all(game[gindex]);
2523 break;
2524 case KEY_RIGHT:
2525 pgn_history_next(&game[gindex], d->b, (keycount) ?
2526 keycount * movestep : movestep);
2527 update_all(game[gindex]);
2528 break;
2529 case 'a':
2530 n = game[gindex].hindex;
2532 if (n && game[gindex].hp[n - 1]->move)
2533 n--;
2534 else
2535 break;
2537 do_annotate_move(game[gindex].hp[n]);
2538 break;
2539 case ']':
2540 case '[':
2541 case '/':
2542 if (pgn_history_total(game[gindex].hp) < 2)
2543 break;
2545 in = Calloc(1, sizeof(struct input_data_s));
2546 p = Malloc(sizeof(int));
2547 *p = c;
2548 in->data = p;
2549 in->efunc = do_find_move_exp;
2551 if (!*moveexp || c == '/') {
2552 construct_input(FIND_REGEXP, moveexp, 1, 0, NULL, NULL, NULL,
2553 0, in, -1);
2554 break;
2557 do_find_move_exp_finalize(0, c);
2558 break;
2559 case 'v':
2560 view_annotation(game[gindex].hp[game[gindex].hindex]);
2561 break;
2562 case 'V':
2563 if (game[gindex].hindex - 1 >= 0)
2564 view_annotation(game[gindex].hp[game[gindex].hindex - 1]);
2565 break;
2566 case '-':
2567 case '+':
2568 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2569 update_all(game[gindex]);
2570 break;
2571 case 'j':
2572 if (pgn_history_total(game[gindex].hp) < 2)
2573 break;
2575 if (!keycount) {
2576 in = Calloc(1, sizeof(struct input_data_s));
2577 in->efunc = do_move_jump;
2579 construct_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1, NULL,
2580 NULL, NULL, 0, in, 0);
2581 break;
2584 do_move_jump_finalize(keycount);
2585 break;
2586 default:
2587 break;
2591 static void free_userdata()
2593 int i;
2595 for (i = 0; i < gtotal; i++) {
2596 struct userdata_s *d;
2598 if (game[i].data) {
2599 d = game[i].data;
2601 if (d->engine) {
2602 stop_engine(&game[i]);
2603 free(d->engine);
2606 free(game[i].data);
2607 game[i].data = NULL;
2612 void update_loading_window(int n)
2614 if (!loadingw) {
2615 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2616 loadingp = new_panel(loadingw);
2617 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2620 wmove(loadingw, 0, 0);
2621 wclrtobot(loadingw);
2622 wattron(loadingw, CP_MESSAGE_BORDER);
2623 box(loadingw, ACS_VLINE, ACS_HLINE);
2624 wattroff(loadingw, CP_MESSAGE_BORDER);
2625 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2626 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
2627 gtotal);
2628 refresh_all();
2631 static void init_userdata_once(GAME *g, int n)
2633 struct userdata_s *d = NULL;
2635 d = Calloc(1, sizeof(struct userdata_s));
2636 d->n = n;
2637 d->c_row = 2, d->c_col = 5;
2638 SET_FLAG(d->flags, CF_NEW);
2639 g->data = d;
2641 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
2642 pgn_board_init(d->b);
2645 void init_userdata()
2647 int i;
2649 for (i = 0; i < gtotal; i++)
2650 init_userdata_once(&game[i], i);
2653 void fix_marks(int *start, int *end)
2655 int i;
2657 *start = (*start < 0) ? 0 : *start;
2658 *end = (*end < 0) ? 0 : *end;
2660 if (*start > *end) {
2661 i = *start;
2662 *start = *end;
2663 *end = i + 1;
2666 *end = (*end > gtotal) ? gtotal : *end;
2669 void do_new_game_finalize(GAME *g)
2671 struct userdata_s *d = g->data;
2673 d->mode = MODE_PLAY;
2674 update_status_notify(*g, NULL);
2675 update_all(*g);
2678 void do_new_game_from_scratch(WIN *win)
2680 if (tolower(win->c) != 'y')
2681 return;
2683 stop_clock();
2684 free_userdata();
2685 pgn_parse(NULL);
2686 add_custom_tags(&game[gindex].tag);
2687 init_userdata();
2688 loadfile[0] = 0;
2689 do_new_game_finalize(&game[gindex]);
2692 void do_new_game()
2694 pgn_new_game();
2695 add_custom_tags(&game[gindex].tag);
2696 init_userdata_once(&game[gindex], gindex);
2697 do_new_game_finalize(&game[gindex]);
2700 void do_game_delete_finalize(int n)
2702 struct userdata_s *d;
2704 delete_game((!n) ? gindex : -1);
2705 d = game[gindex].data;
2706 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2707 update_all(game[gindex]);
2710 void do_game_delete_confirm(WIN *win)
2712 int *n;
2714 if (tolower(win->c) != 'y') {
2715 free(win->data);
2716 return;
2720 n = (int *)win->data;
2721 do_game_delete_finalize(*n);
2722 free(win->data);
2725 void do_game_delete()
2727 char *tmp = NULL;
2728 int i, n;
2729 struct userdata_s *d;
2730 int *p;
2732 if (gtotal < 2) {
2733 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2734 return;
2737 tmp = NULL;
2739 for (i = n = 0; i < gtotal; i++) {
2740 d = game[i].data;
2742 if (TEST_FLAG(d->flags, CF_DELETE))
2743 n++;
2746 if (!n)
2747 tmp = GAME_DELETE_GAME_TEXT;
2748 else {
2749 if (n == gtotal) {
2750 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2751 return;
2754 tmp = GAME_DELETE_ALL_TEXT;
2757 if (config.deleteprompt) {
2758 p = Malloc(sizeof(int));
2759 *p = n;
2760 construct_message(NULL, YESNO, 1, NULL, NULL, p,
2761 do_game_delete_confirm, 0, tmp);
2762 return;
2765 do_game_delete_finalize(n);
2768 void do_history_mode_finalize(struct userdata_s *d)
2770 pushkey = 0;
2771 d->mode = MODE_PLAY;
2772 update_all(game[gindex]);
2775 void do_history_mode_confirm(WIN *win)
2777 struct userdata_s *d = game[gindex].data;
2779 switch (win->c) {
2780 case 'R':
2781 case 'r':
2782 pgn_history_free(game[gindex].hp,
2783 game[gindex].hindex);
2784 pgn_board_update(&game[gindex], d->b,
2785 pgn_history_total(game[gindex].hp));
2786 break;
2787 #if 0
2788 case 'C':
2789 case 'c':
2790 if (pgn_history_rav_new(&game[gindex], d->b,
2791 game[gindex].hindex) != E_PGN_OK)
2792 return;
2794 break;
2795 #endif
2796 default:
2797 return;
2800 if (!TEST_FLAG(d->flags, CF_HUMAN))
2801 add_engine_command(&game[gindex], ENGINE_READY,
2802 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2804 do_history_mode_finalize(d);
2807 void do_find_game_exp_finalize(int c)
2809 struct userdata_s *d = game[gindex].data;
2810 int n;
2812 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
2813 (keycount) ? keycount : 1)) == -1)
2814 return;
2816 gindex = n;
2817 d = game[gindex].data;
2819 if (pgn_history_total(game[gindex].hp))
2820 d->mode = MODE_HISTORY;
2822 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2823 update_all(game[gindex]);
2826 void do_find_game_exp(WIN *win)
2828 struct input_data_s *in = win->data;
2829 int *n = in->data;
2830 int c = *n;
2832 if (in->str) {
2833 strncpy(gameexp, in->str, sizeof(gameexp));
2835 if (c == '?')
2836 c = '}';
2838 do_find_game_exp_finalize(c);
2839 free(in->str);
2842 free(in->data);
2843 free(in);
2846 void do_game_jump_finalize(int n)
2848 struct userdata_s *d;
2850 if (--n > gtotal - 1 || n < 0)
2851 return;
2853 gindex = n;
2854 d = game[gindex].data;
2855 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2856 update_status_notify(game[gindex], NULL);
2857 update_all(game[gindex]);
2860 void do_game_jump(WIN *win)
2862 struct input_data_s *in = win->data;
2864 if (!in->str || !isinteger(in->str)) {
2865 if (in->str)
2866 free(in->str);
2868 free(in);
2869 return;
2872 do_game_jump_finalize(atoi(in->str));
2873 free(in->str);
2874 free(in);
2877 void do_load_file(WIN *win)
2879 FILE *fp;
2880 struct input_data_s *in = win->data;
2881 char *tmp = in->str;
2882 struct userdata_s *d;
2884 if (!in->str) {
2885 free(in);
2886 return;
2889 if ((tmp = word_expand(tmp)) == NULL)
2890 goto done;
2892 if ((fp = pgn_open(tmp)) == NULL) {
2893 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2894 goto done;
2897 free_userdata();
2900 * FIXME what is the game state after a parse error?
2902 if (pgn_parse(fp) == E_PGN_ERR) {
2903 del_panel(loadingp);
2904 delwin(loadingw);
2905 loadingw = NULL;
2906 loadingp = NULL;
2907 init_userdata();
2908 update_all(game[gindex]);
2909 goto done;
2912 del_panel(loadingp);
2913 delwin(loadingw);
2914 loadingw = NULL;
2915 loadingp = NULL;
2916 init_userdata();
2917 strncpy(loadfile, tmp, sizeof(loadfile));
2918 d = game[gindex].data;
2920 if (pgn_history_total(game[gindex].hp))
2921 d->mode = MODE_HISTORY;
2923 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2924 update_all(game[gindex]);
2926 done:
2927 if (in->str)
2928 free(in->str);
2930 free(in);
2933 void do_game_save(WIN *win)
2935 struct input_data_s *in = win->data;
2936 int *x = in->data;
2937 int n = *x;
2938 char *tmp = in->str;
2939 char tfile[FILENAME_MAX];
2940 char *p;
2942 if (!tmp || (tmp = word_expand(tmp)) == NULL)
2943 goto done;
2945 if (pgn_is_compressed(tmp)) {
2946 p = tmp + strlen(tmp) - 1;
2948 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
2949 *(p-3) != '.') {
2950 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2951 tmp = tfile;
2954 else {
2955 if ((p = strchr(tmp, '.')) != NULL) {
2956 if (strcmp(p, ".pgn") != 0) {
2957 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2958 tmp = tfile;
2961 else {
2962 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2963 tmp = tfile;
2967 save_pgn(tmp, n);
2969 done:
2970 if (in->str)
2971 free(in->str);
2973 free(in->data);
2974 free(in);
2977 void do_get_game_save_input(int n)
2979 struct input_data_s *in = Calloc(1, sizeof(struct input_data_s));
2980 int *p = Malloc(sizeof(int));
2982 in->efunc = do_game_save;
2983 *p = n;
2984 in->data = p;
2986 construct_input(GAME_SAVE_TITLE, loadfile, 1, 1, BROWSER_PROMPT,
2987 file_browser, NULL, '\t', in, -1);
2990 void do_game_save_multi_confirm(WIN *win)
2992 int i;
2994 if (win->c == 'c')
2995 i = gindex;
2996 else if (win->c == 'a')
2997 i = -1;
2998 else {
2999 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3000 return;
3003 do_get_game_save_input(i);
3006 void do_more_help(WIN *);
3007 void do_main_help(WIN *win)
3010 switch (win->c) {
3011 case 'p':
3012 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0,
3013 NULL, NULL, NULL, do_more_help, 0, "%s",
3014 playhelp);
3015 break;
3016 case 'h':
3017 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0,
3018 NULL, NULL, NULL, do_more_help, 0, "%s",
3019 historyhelp);
3020 break;
3021 case 'e':
3022 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0,
3023 NULL, NULL, NULL, do_more_help, 0, "%s",
3024 edithelp);
3025 break;
3026 case 'g':
3027 construct_message(GAME_HELP_INDEX_TITLE, ANYKEY, 0,
3028 NULL, NULL, NULL, do_more_help, 0, "%s",
3029 gamehelp);
3030 break;
3031 default:
3032 break;
3036 void do_more_help(WIN *win)
3038 if (win->c == KEY_F(1))
3039 construct_message(GAME_HELP_INDEX_PROMPT, ANYKEY, 0, NULL, NULL, NULL,
3040 do_main_help, 0, "%s", mainhelp);
3043 // Global and other keys.
3044 static int globalkeys(chtype c)
3046 char *p;
3047 int n, i;
3048 struct userdata_s *d = game[gindex].data;
3049 struct input_data_s *in;
3051 switch (c) {
3052 case 'W':
3053 toggle_engine_window();
3054 break;
3055 case KEY_F(10):
3056 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
3057 "and %i color pairs\nCopyright 2002-2006 %s",
3058 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
3059 COLOR_PAIRS, PACKAGE_BUGREPORT);
3060 break;
3061 case 'h':
3062 if (d->mode != MODE_HISTORY) {
3063 if (!pgn_history_total(game[gindex].hp) ||
3064 (d->engine && d->engine->status == ENGINE_THINKING))
3065 return 1;
3067 d->mode = MODE_HISTORY;
3068 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3069 update_all(game[gindex]);
3070 return 1;
3073 // FIXME Resuming from previous history could append to a RAV.
3074 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
3075 if (!pushkey)
3076 construct_message(NULL, "(r)esume or abort", 0,
3077 NULL, NULL, NULL, do_history_mode_confirm, 0,
3078 "%s", GAME_RESUME_HISTORY_TEXT);
3080 return 1;
3082 else {
3083 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3084 return 1;
3087 do_history_mode_finalize(d);
3088 return 1;
3089 case '>':
3090 case '<':
3091 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
3092 keycount : 1);
3093 d = game[gindex].data;
3095 if (delete_count) {
3096 if (c == '>') {
3097 markend = markstart + delete_count;
3098 delete_count = 0;
3100 else {
3101 markend = markstart - delete_count;
3102 delete_count = -1; // to fix gindex in the other direction
3105 pushkey = 'x';
3106 fix_marks(&markstart, &markend);
3109 if (d->mode != MODE_EDIT)
3110 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3112 update_status_notify(game[gindex], NULL);
3113 update_all(game[gindex]);
3114 return 1;
3115 case '}':
3116 case '{':
3117 case '?':
3118 if (gtotal < 2)
3119 return 1;
3121 in = Calloc(1, sizeof(struct input_data_s));
3122 p = Malloc(sizeof(int));
3123 *p = c;
3124 in->data = p;
3125 in->efunc = do_find_game_exp;
3127 if (!*gameexp || c == '?') {
3128 construct_input(GAME_FIND_EXPRESSION_TITLE, gameexp, 1, 0,
3129 GAME_FIND_EXPRESSION_PROMPT, NULL, NULL, 0, in, -1);
3130 break;
3133 do_find_game_exp_finalize(c);
3134 return 1;
3135 case 'J':
3136 if (gtotal < 2)
3137 return 1;
3139 in = Calloc(1, sizeof(struct input_data_s));
3140 in->efunc = do_game_jump;
3142 if (!keycount) {
3143 construct_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL, NULL,
3144 0, in, 0);
3145 return 1;
3148 do_game_jump_finalize(keycount);
3149 return 1;
3150 case 'x':
3151 pushkey = 0;
3153 if (gtotal < 2)
3154 return 1;
3156 if (keycount && delete_count == 0) {
3157 markstart = gindex;
3158 delete_count = keycount;
3159 update_status_notify(game[gindex], "%s (delete)",
3160 status.notify);
3161 return 1;
3164 if (markstart >= 0 && markend >= 0) {
3165 for (i = markstart; i < markend; i++) {
3166 if (toggle_delete_flag(i)) {
3167 update_all(game[gindex]);
3168 return 1;
3172 gindex = (delete_count < 0) ? markstart : i - 1;
3173 update_all(game[gindex]);
3175 else {
3176 if (toggle_delete_flag(gindex))
3177 return 1;
3180 markstart = markend = -1;
3181 delete_count = 0;
3182 update_status_window(game[gindex]);
3183 return 1;
3184 case 'X':
3185 do_game_delete();
3186 return 1;
3187 case 'T':
3188 edit_tags(game[gindex], d->b, 1);
3189 return 1;
3190 case 't':
3191 edit_tags(game[gindex], d->b, 0);
3192 return 1;
3193 case 'r':
3194 in = Calloc(1, sizeof(struct input_data_s));
3195 in->efunc = do_load_file;
3196 construct_input(GAME_LOAD_TITLE, NULL, 1, 1, BROWSER_PROMPT,
3197 file_browser, NULL, '\t', in, -1);
3198 return 1;
3199 case 'S':
3200 case 's':
3201 if (gtotal > 1) {
3202 construct_message(NULL, GAME_SAVE_MULTI_PROMPT, 1, NULL,
3203 NULL, NULL, do_game_save_multi_confirm, 0, "%s",
3204 GAME_SAVE_MULTI_TEXT);
3205 return 1;
3208 do_get_game_save_input(-1);
3209 return 1;
3210 case KEY_F(1):
3211 switch (d->mode) {
3212 case MODE_PLAY:
3213 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0,
3214 NULL, NULL, NULL, do_more_help, 0, "%s",
3215 playhelp);
3216 break;
3217 case MODE_HISTORY:
3218 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0,
3219 NULL, NULL, NULL, do_more_help, 0, "%s",
3220 historyhelp);
3221 break;
3222 case MODE_EDIT:
3223 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0,
3224 NULL, NULL, NULL, do_more_help, 0, "%s",
3225 edithelp);
3226 break;
3227 default:
3228 break;
3231 return 1;
3232 case 'n':
3233 do_new_game();
3234 return 1;
3235 case 'N':
3236 construct_message(NULL, YESNO, 1, NULL, NULL, NULL,
3237 do_new_game_from_scratch, 0, "%s", GAME_NEW_PROMPT);
3238 return 1;
3239 case CTRL('L'):
3240 endwin();
3241 keypad(boardw, TRUE);
3242 refresh_all();
3243 return 1;
3244 case KEY_ESCAPE:
3245 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3246 markend = markstart = 0;
3248 if (keycount) {
3249 keycount = 0;
3250 update_status_notify(game[gindex], NULL);
3253 if (config.validmoves)
3254 pgn_reset_valid_moves(d->b);
3256 return 1;
3257 case '0' ... '9':
3258 n = c - '0';
3260 if (keycount)
3261 keycount = keycount * 10 + n;
3262 else
3263 keycount = n;
3265 update_status_notify(game[gindex], "Repeat %i", keycount);
3266 return -1;
3267 case KEY_UP:
3268 if (d->mode == MODE_HISTORY)
3269 return 0;
3271 if (keycount) {
3272 d->c_row += keycount;
3273 pushkey = '\n';
3275 else
3276 d->c_row++;
3278 if (d->c_row > 8)
3279 d->c_row = 1;
3281 return 1;
3282 case KEY_DOWN:
3283 if (d->mode == MODE_HISTORY)
3284 return 0;
3286 if (keycount) {
3287 d->c_row -= keycount;
3288 pushkey = '\n';
3289 update_status_notify(game[gindex], NULL);
3291 else
3292 d->c_row--;
3294 if (d->c_row < 1)
3295 d->c_row = 8;
3297 return 1;
3298 case KEY_LEFT:
3299 if (d->mode == MODE_HISTORY)
3300 return 0;
3302 if (keycount) {
3303 d->c_col -= keycount;
3304 pushkey = '\n';
3306 else
3307 d->c_col--;
3309 if (d->c_col < 1)
3310 d->c_col = 8;
3312 return 1;
3313 case KEY_RIGHT:
3314 if (d->mode == MODE_HISTORY)
3315 return 0;
3317 if (keycount) {
3318 d->c_col += keycount;
3319 pushkey = '\n';
3321 else
3322 d->c_col++;
3324 if (d->c_col > 8)
3325 d->c_col = 1;
3327 return 1;
3328 case 'e':
3329 if (d->mode != MODE_EDIT && d->mode !=
3330 MODE_PLAY)
3331 return 1;
3333 // Don't edit a running game (for now).
3334 if (pgn_history_total(game[gindex].hp))
3335 return 1;
3337 if (d->mode != MODE_EDIT) {
3338 pgn_board_init_fen(&game[gindex], d->b, NULL);
3339 config.details++;
3340 d->mode = MODE_EDIT;
3341 update_all(game[gindex]);
3342 return 1;
3345 config.details--;
3346 pgn_tag_add(&game[gindex].tag, "FEN",
3347 pgn_game_to_fen(game[gindex], d->b));
3348 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3349 pgn_tag_sort(game[gindex].tag);
3350 d->mode = MODE_PLAY;
3351 update_all(game[gindex]);
3352 return 1;
3353 case 'Q':
3354 quit = 1;
3355 return 1;
3356 case KEY_RESIZE:
3357 do_window_resize();
3358 return 1;
3359 #ifdef DEBUG
3360 case 'D':
3361 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3362 return 1;
3363 #endif
3364 case 0:
3365 default:
3366 break;
3369 return 0;
3372 void game_loop()
3374 int error_recover = 0;
3375 struct userdata_s *d = game[gindex].data;
3377 gindex = gtotal - 1;
3379 if (pgn_history_total(game[gindex].hp))
3380 d->mode = MODE_HISTORY;
3381 else
3382 d->mode = MODE_PLAY;
3384 if (d->mode == MODE_HISTORY) {
3385 pgn_board_update(&game[gindex], d->b,
3386 pgn_history_total(game[gindex].hp));
3389 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3390 movestep = 2;
3391 flushinp();
3392 update_all(game[gindex]);
3393 update_tag_window(game[gindex].tag);
3394 wtimeout(boardw, 70);
3396 while (!quit) {
3397 int c = 0;
3398 int n = 0, i;
3399 char fdbuf[8192] = {0};
3400 int len;
3401 struct timeval tv = {0, 0};
3402 fd_set rfds, wfds;
3403 WIN *win = NULL;
3404 WINDOW *wp = NULL;
3406 FD_ZERO(&rfds);
3408 for (i = 0; i < gtotal; i++) {
3409 d = game[i].data;
3411 if (d->engine) {
3412 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3413 if (d->engine->fd[ENGINE_IN_FD] > n)
3414 n = d->engine->fd[ENGINE_IN_FD];
3416 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3419 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3420 if (d->engine->fd[ENGINE_OUT_FD] > n)
3421 n = d->engine->fd[ENGINE_OUT_FD];
3423 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3428 if (n) {
3429 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3430 for (i = 0; i < gtotal; i++) {
3431 d = game[i].data;
3433 if (d->engine) {
3434 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3435 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3436 sizeof(fdbuf));
3438 if (len == -1) {
3439 if (errno != EAGAIN) {
3440 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3441 strerror(errno));
3442 waitpid(d->engine->pid, &n, 0);
3443 free(d->engine);
3444 d->engine = NULL;
3445 break;
3448 else {
3449 if (len)
3450 parse_engine_output(&game[i], fdbuf);
3454 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3455 if (d->engine->queue)
3456 send_engine_command(&game[i]);
3461 else {
3462 if (n == -1)
3463 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3464 /* timeout */
3468 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3469 d->mode = MODE_HISTORY;
3471 d = game[gindex].data;
3472 error_recover = 0;
3473 draw_board(&game[gindex]);
3474 update_all(game[gindex]);
3475 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3476 refresh_all();
3479 * Finds the top level window in the window stack so we know what
3480 * window the wgetch()ed key belongs to.
3482 if (wins) {
3483 for (i = 0; wins[i]; i++);
3484 win = wins[i-1];
3485 wp = win->w;
3486 wtimeout(wp, WINDOW_TIMEOUT);
3488 else
3489 wp = boardw;
3491 if (!i && pushkey)
3492 c = pushkey;
3493 else {
3494 if (!pushkey) {
3495 if ((c = wgetch(wp)) == ERR)
3496 continue;
3498 else
3499 c = pushkey;
3501 if (win) {
3502 win->c = c;
3505 * Run the function associated with the window. When the
3506 * function returns 0 win->efunc is ran (if not NULL) with
3507 * win as the one and only parameter. Then the window is
3508 * destroyed.
3510 * The exit function may create another window which will
3511 * mess up the window stack when window_destroy() is called.
3512 * So don't destory the window until the top window is
3513 * destroyable. See window_destroy().
3515 if ((*win->func)(win) == 0) {
3516 if (win->efunc)
3517 (*win->efunc)(win);
3519 win->keep = 1;
3520 window_destroy(win);
3523 continue;
3527 if (!keycount && status.notify)
3528 update_status_notify(game[gindex], NULL);
3530 if ((n = globalkeys(c)) == 1) {
3531 keycount = 0;
3532 continue;
3534 else if (n == -1)
3535 continue;
3537 switch (d->mode) {
3538 case MODE_EDIT:
3539 editmode_keys(c);
3540 break;
3541 case MODE_PLAY:
3542 if (playmode_keys(c))
3543 continue;
3544 break;
3545 case MODE_HISTORY:
3546 historymode_keys(c);
3547 break;
3548 default:
3549 break;
3552 keycount = 0;
3556 void usage(const char *pn, int ret)
3558 fprintf((ret) ? stderr : stdout, "%s",
3559 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3560 " -p Load PGN file.\n"
3561 " -V Validate a game file.\n"
3562 " -S Validate and output a PGN formatted game.\n"
3563 " -R Like -S but write a reduced PGN formatted game.\n"
3564 " -t Also write custom PGN tags from config file.\n"
3565 " -E Stop processing on file parsing error (overrides config).\n"
3566 " -v Version information.\n"
3567 " -h This help text.\n");
3569 exit(ret);
3572 void cleanup_all()
3574 int i;
3576 stop_clock();
3577 free_userdata();
3578 pgn_free_all();
3579 free(config.engine_cmd);
3580 free(config.pattern);
3581 free(config.ccfile);
3582 free(config.nagfile);
3583 free(config.configfile);
3585 if (config.keys) {
3586 for (i = 0; config.keys[i]; i++)
3587 free(config.keys[i]->str);
3589 free(config.keys);
3592 if (config.einit) {
3593 for (i = 0; config.einit[i]; i++)
3594 free(config.einit[i]);
3596 free(config.einit);
3599 if (config.tag)
3600 pgn_tag_free(config.tag);
3602 if (curses_initialized) {
3603 del_panel(boardp);
3604 del_panel(historyp);
3605 del_panel(statusp);
3606 del_panel(tagp);
3607 delwin(boardw);
3608 delwin(historyw);
3609 delwin(statusw);
3610 delwin(tagw);
3612 if (enginew) {
3613 del_panel(enginep);
3614 delwin(enginew);
3616 if (enginebuf) {
3617 for (i = 0; enginebuf[i]; i++)
3618 free(enginebuf[i]);
3620 free(enginebuf);
3624 endwin();
3628 void catch_signal(int which)
3630 switch (which) {
3631 case SIGALRM:
3632 update_clocks();
3633 break;
3634 case SIGPIPE:
3635 if (which == SIGPIPE && quit)
3636 break;
3638 if (which == SIGPIPE)
3639 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3641 cleanup_all();
3642 exit(EXIT_FAILURE);
3643 break;
3644 case SIGSTOP:
3645 savetty();
3646 break;
3647 case SIGCONT:
3648 resetty();
3649 keypad(boardw, TRUE);
3650 curs_set(0);
3651 cbreak();
3652 noecho();
3653 break;
3654 case SIGINT:
3655 case SIGTERM:
3656 quit = 1;
3657 break;
3658 default:
3659 break;
3663 void loading_progress(long total, long offset)
3665 int n = (100 * (offset / 100) / (total / 100));
3667 if (curses_initialized)
3668 update_loading_window(n);
3669 else {
3670 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3671 fflush(stderr);
3675 static void set_defaults()
3677 set_config_defaults();
3678 filetype = NO_FILE;
3679 pgn_config_set(PGN_PROGRESS, 1024);
3680 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3683 int main(int argc, char *argv[])
3685 int opt;
3686 struct stat st;
3687 char buf[FILENAME_MAX];
3688 char datadir[FILENAME_MAX];
3689 int ret = EXIT_SUCCESS;
3690 int validate_only = 0, validate_and_write = 0;
3691 int write_custom_tags = 0;
3692 FILE *fp;
3693 int i = 0;
3695 if ((config.pwd = getpwuid(getuid())) == NULL)
3696 err(EXIT_FAILURE, "getpwuid()");
3698 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3699 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3700 config.ccfile = strdup(buf);
3701 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3702 config.nagfile = strdup(buf);
3703 snprintf(buf, sizeof(buf), "%s/config", datadir);
3704 config.configfile = strdup(buf);
3706 if (stat(datadir, &st) == -1) {
3707 if (errno == ENOENT) {
3708 if (mkdir(datadir, 0755) == -1)
3709 err(EXIT_FAILURE, "%s", datadir);
3711 else
3712 err(EXIT_FAILURE, "%s", datadir);
3714 stat(datadir, &st);
3717 if (!S_ISDIR(st.st_mode))
3718 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3720 set_defaults();
3722 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3723 switch (opt) {
3724 case 't':
3725 write_custom_tags = 1;
3726 break;
3727 case 'E':
3728 i = 1;
3729 break;
3730 case 'R':
3731 pgn_config_set(PGN_REDUCED, 1);
3732 case 'S':
3733 validate_and_write = 1;
3734 case 'V':
3735 validate_only = 1;
3736 break;
3737 case 'v':
3738 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3739 COPYRIGHT);
3740 exit(EXIT_SUCCESS);
3741 case 'p':
3742 filetype = PGN_FILE;
3743 strncpy(loadfile, optarg, sizeof(loadfile));
3744 break;
3745 case 'h':
3746 default:
3747 usage(argv[0], EXIT_SUCCESS);
3751 if ((validate_only || validate_and_write) && !*loadfile)
3752 usage(argv[0], EXIT_FAILURE);
3754 if (access(config.configfile, R_OK) == 0)
3755 parse_rcfile(config.configfile);
3757 if (i)
3758 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3760 signal(SIGPIPE, catch_signal);
3761 signal(SIGCONT, catch_signal);
3762 signal(SIGSTOP, catch_signal);
3763 signal(SIGINT, catch_signal);
3764 signal(SIGALRM, catch_signal);
3765 signal(SIGTERM, catch_signal);
3767 srandom(getpid());
3769 switch (filetype) {
3770 case PGN_FILE:
3771 if ((fp = pgn_open(loadfile)) == NULL)
3772 err(EXIT_FAILURE, "%s", loadfile);
3774 ret = pgn_parse(fp);
3775 break;
3776 case FEN_FILE:
3777 //ret = parse_fen_file(loadfile);
3778 break;
3779 case EPD_FILE: // Not implemented.
3780 case NO_FILE:
3781 default:
3782 // No file specified. Empty game.
3783 ret = pgn_parse(NULL);
3784 add_custom_tags(&game[gindex].tag);
3785 break;
3788 if (validate_only || validate_and_write) {
3789 if (validate_and_write) {
3790 for (i = 0; i < gtotal; i++) {
3791 if (write_custom_tags)
3792 add_custom_tags(&game[i].tag);
3794 pgn_write(stdout, game[i]);
3798 cleanup_all();
3799 exit(ret);
3801 else if (ret == E_PGN_ERR)
3802 exit(ret);
3804 init_userdata();
3806 if (initscr() == NULL)
3807 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3808 else
3809 curses_initialized = 1;
3811 if (LINES < 24 || COLS < 80) {
3812 endwin();
3813 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3816 if (has_colors() == TRUE && start_color() == OK)
3817 init_color_pairs();
3819 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3820 boardp = new_panel(boardw);
3821 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3822 COLS - HISTORY_WIDTH);
3823 historyp = new_panel(historyw);
3824 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3825 statusp = new_panel(statusw);
3826 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3827 tagp = new_panel(tagw);
3828 keypad(boardw, TRUE);
3829 // leaveok(boardw, TRUE);
3830 leaveok(tagw, TRUE);
3831 leaveok(statusw, TRUE);
3832 leaveok(historyw, TRUE);
3833 curs_set(0);
3834 cbreak();
3835 noecho();
3836 draw_window_decor();
3837 game_loop();
3838 cleanup_all();
3839 exit(EXIT_SUCCESS);