Added .data to userdata_s.
[cboard.git] / src / cboard.c
blob70f8b3bfedcffb06a7a1933ec0e2c92c87c539d5
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;
547 void file_browser_abort(struct menu_input_s *m)
549 pushkey = -1;
552 void file_browser(void *arg)
554 struct menu_key_s **keys = NULL;
555 struct input_s *in = arg;
556 char *p;
557 char path[FILENAME_MAX];
559 if (config.savedirectory) {
560 if ((p = word_expand(config.savedirectory)) == NULL)
561 return;
563 strncpy(path, p, sizeof(path));
565 if (access(path, R_OK) == -1) {
566 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
567 getcwd(path, sizeof(path));
570 else
571 getcwd(path, sizeof(path));
573 in->arg = strdup(path);
574 add_menu_key(&keys, '\n', file_browser_select);
575 add_menu_key(&keys, KEY_F(1), file_browser_help);
576 add_menu_key(&keys, '~', file_browser_home);
577 add_menu_key(&keys, KEY_ESCAPE, file_browser_abort);
578 construct_menu(LINES - 4, 0, -1, -1, NULL, 0, get_file_items, keys, in,
579 file_browser_finalize);
580 return;
583 void do_game_write(char *filename, char *mode, int start, int end)
585 char *command = NULL;
586 FILE *fp;
587 int i;
588 struct userdata_s *d;
590 if (command) {
591 if ((fp = popen(command, "w")) == NULL) {
592 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
593 goto error;
596 else {
597 if ((fp = fopen(filename, mode)) == NULL) {
598 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
599 goto error;
603 for (i = (start == -1) ? 0 : start; i < end; i++) {
604 d = game[i].data;
605 pgn_write(fp, game[i]);
606 CLEAR_FLAG(d->flags, CF_MODIFIED);
609 if (command)
610 pclose(fp);
611 else
612 fclose(fp);
614 if (start == -1)
615 strncpy(loadfile, filename, sizeof(loadfile));
617 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
618 update_all(game[gindex]);
619 return;
621 error:
622 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
623 update_all(game[gindex]);
626 struct save_game_s {
627 char *filename;
628 char *mode;
629 int start;
630 int end;
633 void do_save_game_overwrite_confirm(WIN *win)
635 char *mode = "w";
636 struct save_game_s *s = win->data;
638 switch (win->c) {
639 case 'a':
640 if (pgn_is_compressed(s->filename) == E_PGN_OK) {
641 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
642 goto done;
645 mode = "a";
646 break;
647 case 'o':
648 mode = "w+";
649 break;
650 default:
651 goto done;
654 do_game_write(s->filename, mode, s->start, s->end);
656 done:
657 free(s->filename);
658 free(s);
661 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
662 * game index number.
664 // FIXME command (compression)
665 void save_pgn(char *filename, int saveindex)
667 char buf[FILENAME_MAX];
668 struct stat st;
669 int end = (saveindex == -1) ? gtotal : saveindex + 1;
670 struct save_game_s *s;
672 if (filename[0] != '/' && config.savedirectory) {
673 if (stat(config.savedirectory, &st) == -1) {
674 if (errno == ENOENT) {
675 if (mkdir(config.savedirectory, 0755) == -1) {
676 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
677 strerror(errno));
678 return;
681 else {
682 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
683 strerror(errno));
684 return;
688 stat(config.savedirectory, &st);
690 if (!S_ISDIR(st.st_mode)) {
691 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
692 return;
695 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
696 filename = buf;
699 if (access(filename, W_OK) == 0) {
700 s = Malloc(sizeof(struct save_game_s));
701 s->filename = strdup(filename);
702 s->start = saveindex;
703 s->end = end;
704 construct_message(NULL, GAME_SAVE_OVERWRITE_PROMPT, 1, NULL, NULL,
705 s, do_save_game_overwrite_confirm, 0, "%s \"%s\"",
706 E_FILEEXISTS, filename);
707 return;
710 do_game_write(filename, "a", saveindex, end);
713 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
715 if (pgn_piece_to_int(piece) == ROOK && col == 7
716 && row == 7 &&
717 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
718 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
719 if (mod)
720 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
721 return 1;
723 else if (pgn_piece_to_int(piece) == ROOK && col == 0
724 && row == 7 &&
725 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
726 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
727 if (mod)
728 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
729 return 1;
731 else if (pgn_piece_to_int(piece) == ROOK && col == 7
732 && row == 0 &&
733 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
734 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
735 if (mod)
736 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
737 return 1;
739 else if (pgn_piece_to_int(piece) == ROOK && col == 0
740 && row == 0 &&
741 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
742 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
743 if (mod)
744 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
745 return 1;
747 else if (pgn_piece_to_int(piece) == KING && col == 4
748 && row == 7 &&
749 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
750 TEST_FLAG(g->flags, GF_WK_CASTLE))
752 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
753 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
754 if (mod) {
755 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
756 TEST_FLAG(g->flags, GF_WQ_CASTLE))
757 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
758 else
759 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
761 return 1;
763 else if (pgn_piece_to_int(piece) == KING && col == 4
764 && row == 0 &&
765 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
766 TEST_FLAG(g->flags, GF_BK_CASTLE))
768 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
769 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
770 if (mod) {
771 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
772 TEST_FLAG(g->flags, GF_BQ_CASTLE))
773 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
774 else
775 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
777 return 1;
780 return 0;
783 static void draw_board(GAME *g)
785 int row, col;
786 int bcol = 0, brow = 0;
787 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
788 int ncols = 0, offset = 1;
789 unsigned coords_y = 8;
790 struct userdata_s *d = g->data;
792 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
793 update_cursor(*g, g->hindex);
795 for (row = 0; row < maxy; row++) {
796 bcol = 0;
798 for (col = 0; col < maxx; col++) {
799 int attrwhich = -1;
800 chtype attrs = 0;
801 unsigned char piece;
803 if (row == 0 || row == maxy - 2) {
804 if (col == 0)
805 mvwaddch(boardw, row, col,
806 LINE_GRAPHIC((row) ?
807 ACS_LLCORNER | CP_BOARD_GRAPHICS :
808 ACS_ULCORNER | CP_BOARD_GRAPHICS));
809 else if (col == maxx - 2)
810 mvwaddch(boardw, row, col,
811 LINE_GRAPHIC((row) ?
812 ACS_LRCORNER | CP_BOARD_GRAPHICS :
813 ACS_URCORNER | CP_BOARD_GRAPHICS));
814 else if (!(col % 4))
815 mvwaddch(boardw, row, col,
816 LINE_GRAPHIC((row) ?
817 ACS_BTEE | CP_BOARD_GRAPHICS :
818 ACS_TTEE | CP_BOARD_GRAPHICS));
819 else {
820 if (col != maxx - 1)
821 mvwaddch(boardw, row, col,
822 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
825 continue;
828 if ((row % 2) && col == maxx - 1 && coords_y) {
829 wattron(boardw, CP_BOARD_COORDS);
830 mvwprintw(boardw, row, col, "%d", coords_y--);
831 wattroff(boardw, CP_BOARD_COORDS);
832 continue;
835 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
836 if (!(row % 2))
837 mvwaddch(boardw, row, col,
838 LINE_GRAPHIC((col) ?
839 ACS_RTEE | CP_BOARD_GRAPHICS :
840 ACS_LTEE | CP_BOARD_GRAPHICS));
841 else
842 mvwaddch(boardw, row, col,
843 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
845 continue;
848 if ((row % 2) && !(col % 4) && row != maxy - 1) {
849 mvwaddch(boardw, row, col,
850 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
851 continue;
854 if (!(col % 4) && row != maxy - 1) {
855 mvwaddch(boardw, row, col,
856 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
857 continue;
860 if ((row % 2)) {
861 if ((col % 4)) {
862 if (ncols++ == 8) {
863 offset++;
864 ncols = 1;
867 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
868 && (offset % 2)))
869 attrwhich = BLACK;
870 else
871 attrwhich = WHITE;
873 if (config.validmoves && d->b[brow][bcol].valid) {
874 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
875 CP_BOARD_MOVES_BLACK;
877 else
878 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
879 CP_BOARD_BLACK;
881 if (row == ROWTOMATRIX(d->c_row) && col ==
882 COLTOMATRIX(d->c_col)) {
883 attrs = CP_BOARD_CURSOR;
886 if (row == ROWTOMATRIX(d->sp.srow) &&
887 col == COLTOMATRIX(d->sp.scol)) {
888 attrs = CP_BOARD_SELECTED;
891 if (row == maxy - 1)
892 attrs = 0;
894 mvwaddch(boardw, row, col, ' ' | attrs);
896 if (row == maxy - 1)
897 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
898 else {
899 if (config.details && d->b[row / 2][bcol].enpassant)
900 piece = 'x';
901 else
902 piece = d->b[row / 2][bcol].icon;
904 if (config.details && castling_state(g, d->b, brow,
905 bcol, piece, 0))
906 attrs |= A_REVERSE;
908 if (g->side == WHITE && isupper(piece))
909 attrs |= A_BOLD;
910 else if (g->side == BLACK && islower(piece))
911 attrs |= A_BOLD;
913 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
915 CLEAR_FLAG(attrs, A_BOLD);
916 CLEAR_FLAG(attrs, A_REVERSE);
919 waddch(boardw, ' ' | attrs);
920 col += 2;
921 bcol++;
924 else {
925 if (col != maxx - 1)
926 mvwaddch(boardw, row, col,
927 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
931 brow = row / 2;
934 mvwaddch(boardw, maxy - 1, maxx - 2, (config.details) ? '!' : ' ');
937 void invalid_move(int n, int e, const char *m)
939 if (curses_initialized)
940 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
941 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
942 else
943 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
944 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
947 static void update_clock(GAME *g, struct itimerval it)
949 struct userdata_s *d = g->data;
950 long n;
952 if (g->turn == WHITE) {
953 d->wc.tv_sec += it.it_value.tv_sec;
954 d->wc.tv_usec += it.it_value.tv_usec;
956 if (d->wc.tv_usec > 1000000 - 1) {
957 d->wc.tv_sec += d->wc.tv_usec / 1000000;
958 d->wc.tv_usec = d->wc.tv_usec % 1000000;
961 else {
962 d->bc.tv_sec += it.it_value.tv_sec;
963 d->bc.tv_usec += it.it_value.tv_usec;
965 if (d->bc.tv_usec > 1000000 - 1) {
966 d->bc.tv_sec += d->bc.tv_usec / 1000000;
967 d->bc.tv_usec = d->bc.tv_usec % 1000000;
971 d->elapsed = d->wc.tv_sec + d->bc.tv_sec;
972 n = d->wc.tv_usec + d->bc.tv_usec;
973 d->elapsed += (n > 1000000 - 1) ? n / 1000000 : 0;
975 if (TEST_FLAG(d->flags, CF_CLOCK)) {
976 if (d->elapsed >= d->limit) {
977 SET_FLAG(g->flags, GF_GAMEOVER);
978 pgn_tag_add(&g->tag, "Result", "1/2-1/2");
983 void do_validate_move(char *m)
985 struct userdata_s *d = game[gindex].data;
986 int n;
988 if (TEST_FLAG(d->flags, CF_HUMAN)) {
989 if ((n = pgn_parse_move(&game[gindex], d->b, &m)) != E_PGN_OK) {
990 invalid_move(d->n + 1, n, m);
991 free(m);
992 return;
995 pgn_history_add(&game[gindex], m);
996 pgn_switch_turn(&game[gindex]);
998 else {
999 if ((n = pgn_validate_move(&game[gindex], d->b, &m)) != E_PGN_OK) {
1000 invalid_move(d->n + 1, n, m);
1001 free(m);
1002 return;
1005 add_engine_command(&game[gindex], ENGINE_THINKING, "%s\n", m);
1008 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1010 if (config.validmoves)
1011 pgn_reset_valid_moves(d->b);
1013 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1014 d->mode = MODE_HISTORY;
1015 else
1016 SET_FLAG(d->flags, CF_MODIFIED);
1018 d->paused = 0;
1019 free(m);
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 x_grid_chars[d->sp.scol - 1],
1049 d->sp.srow, x_grid_chars[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(PROMOTION_TITLE, PROMOTION_PROMPT, 1, NULL, NULL,
1055 str, do_promotion_piece_finalize, 0, "%s", PROMOTION_TEXT);
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)
1076 static char buf[16];
1077 int h = 0, m = 0, s = 0;
1078 int n = t.tv_sec;
1080 h = n / 3600;
1081 m = (n % 3600) / 60;
1082 s = (n % 3600) % 60;
1083 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i.%.2i", h, m, s,
1084 (int)t.tv_usec / 10000);
1085 return buf;
1088 void update_status_window(GAME g)
1090 int i = 0;
1091 char *buf;
1092 char tmp[15], *engine, *mode;
1093 int w;
1094 char *p;
1095 int maxy, maxx;
1096 int len;
1097 struct userdata_s *d = g.data;
1099 getmaxyx(statusw, maxy, maxx);
1100 w = maxx - 2 - 8;
1101 len = maxx - 2;
1102 buf = Malloc(len);
1104 *tmp = '\0';
1105 p = tmp;
1107 if (TEST_FLAG(d->flags, CF_DELETE)) {
1108 *p++ = '(';
1109 *p++ = 'x';
1110 i++;
1113 if (TEST_FLAG(g.flags, GF_PERROR)) {
1114 if (!i)
1115 *p++ = '(';
1116 else
1117 *p++ = '/';
1119 *p++ = '!';
1120 i++;
1123 if (TEST_FLAG(d->flags, CF_MODIFIED)) {
1124 if (!i)
1125 *p++ = '(';
1126 else
1127 *p++ = '/';
1129 *p++ = '*';
1130 i++;
1133 if (*tmp != '\0')
1134 *p++ = ')';
1136 *p = '\0';
1138 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1139 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1140 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1141 (*tmp) ? tmp : "");
1142 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1144 switch (d->mode) {
1145 case MODE_HISTORY:
1146 mode = MODE_HISTORY_STR;
1147 break;
1148 case MODE_EDIT:
1149 mode = MODE_EDIT_STR;
1150 break;
1151 case MODE_PLAY:
1152 mode = MODE_PLAY_STR;
1153 break;
1154 default:
1155 mode = UNKNOWN;
1156 break;
1159 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1161 if (d->mode == MODE_PLAY) {
1162 if (TEST_FLAG(d->flags, CF_HUMAN))
1163 strncat(buf, " (human/human)", len - 1);
1164 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1165 strncat(buf, " (engine/engine)", len - 1);
1166 else
1167 strncat(buf, " (human/engine)", len - 1);
1170 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1172 if (d->engine) {
1173 switch (d->engine->status) {
1174 case ENGINE_THINKING:
1175 engine = ENGINE_PONDER_STR;
1176 break;
1177 case ENGINE_READY:
1178 engine = ENGINE_READY_STR;
1179 break;
1180 case ENGINE_INITIALIZING:
1181 engine = ENGINE_INITIALIZING_STR;
1182 break;
1183 case ENGINE_OFFLINE:
1184 engine = ENGINE_OFFLINE_STR;
1185 break;
1186 default:
1187 engine = UNKNOWN;
1188 break;
1191 else
1192 engine = ENGINE_OFFLINE_STR;
1194 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1195 wattron(statusw, CP_STATUS_ENGINE);
1196 mvwaddstr(statusw, 5, 9, engine);
1197 wattroff(statusw, CP_STATUS_ENGINE);
1199 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1200 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1202 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR, w,
1203 clock_to_char((TEST_FLAG(d->flags, CF_CLOCK)) ?
1204 d->limit - d->elapsed : 0));
1206 strncpy(tmp, WHITE_STR, sizeof(tmp));
1207 tmp[0] = toupper(tmp[0]);
1208 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->wc));
1210 strncpy(tmp, BLACK_STR, sizeof(tmp));
1211 tmp[0] = toupper(tmp[0]);
1212 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->bc));
1213 free(buf);
1215 for (i = 1; i < maxx - 4; i++)
1216 mvwprintw(statusw, maxy - 2, i, " ");
1218 if (!status.notify)
1219 status.notify = strdup(GAME_HELP_PROMPT);
1221 wattron(statusw, CP_STATUS_NOTIFY);
1222 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1223 status.notify);
1224 wattroff(statusw, CP_STATUS_NOTIFY);
1227 void update_history_window(GAME g)
1229 char buf[HISTORY_WIDTH - 1];
1230 HISTORY *h = NULL;
1231 int n, total;
1232 int t = pgn_history_total(g.hp);
1234 n = (g.hindex + 1) / 2;
1236 if (t % 2)
1237 total = (t + 1) / 2;
1238 else
1239 total = t / 2;
1241 if (t)
1242 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1243 (movestep == 1) ? HISTORY_PLY_STEP : "");
1244 else
1245 strncpy(buf, UNAVAILABLE, sizeof(buf));
1247 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1248 HISTORY_WIDTH - 13, buf);
1250 h = pgn_history_by_n(g.hp, g.hindex);
1251 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1252 n = 0;
1254 if (h && ((h->comment) || h->nag[0])) {
1255 strncat(buf, " (v", sizeof(buf));
1256 n++;
1259 if (h && h->rav) {
1260 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1261 n++;
1264 if (g.ravlevel) {
1265 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1266 n++;
1269 if (n)
1270 strncat(buf, ")", sizeof(buf));
1272 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1273 HISTORY_WIDTH - 13, buf);
1275 h = pgn_history_by_n(g.hp, g.hindex - 1);
1276 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1277 n = 0;
1279 if (h && ((h->comment) || h->nag[0])) {
1280 strncat(buf, " (V", sizeof(buf));
1281 n++;
1284 if (h && h->rav) {
1285 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1286 n++;
1289 if (g.ravlevel) {
1290 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1291 n++;
1294 if (n)
1295 strncat(buf, ")", sizeof(buf));
1297 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1298 HISTORY_WIDTH - 13, buf);
1301 void update_tag_window(TAG **t)
1303 int i;
1304 int w = TAG_WIDTH - 10;
1306 for (i = 0; i < 7; i++)
1307 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w,
1308 str_etc(t[i]->value, w, 0));
1311 void append_enginebuf(char *line)
1313 int i = 0;
1315 if (enginebuf)
1316 for (i = 0; enginebuf[i]; i++);
1318 if (i >= LINES - 3) {
1319 free(enginebuf[0]);
1321 for (i = 0; enginebuf[i+1]; i++)
1322 enginebuf[i] = enginebuf[i+1];
1324 enginebuf[i] = strdup(line);
1326 else {
1327 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1328 enginebuf[i++] = strdup(line);
1329 enginebuf[i] = NULL;
1333 void update_engine_window()
1335 int i;
1337 if (!enginebuf)
1338 return;
1340 wmove(enginew, 0, 0);
1341 wclrtobot(enginew);
1343 if (enginebuf) {
1344 for (i = 0; enginebuf[i]; i++)
1345 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1348 window_draw_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1349 CP_MESSAGE_BORDER);
1352 void toggle_engine_window()
1354 if (!enginew) {
1355 enginew = newwin(LINES, COLS, 0, 0);
1356 enginep = new_panel(enginew);
1357 window_draw_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1358 CP_MESSAGE_BORDER);
1359 hide_panel(enginep);
1362 if (panel_hidden(enginep)) {
1363 update_engine_window();
1364 top_panel(enginep);
1365 refresh_all();
1367 else {
1368 hide_panel(enginep);
1369 refresh_all();
1373 void refresh_all()
1375 update_panels();
1376 doupdate();
1379 void update_all(GAME g)
1381 update_status_window(g);
1382 update_history_window(g);
1383 update_tag_window(g.tag);
1384 update_engine_window();
1387 static void game_next_prev(GAME g, int n, int count)
1389 if (gtotal < 2)
1390 return;
1392 if (n == 1) {
1393 if (gindex + count > gtotal - 1) {
1394 if (count != 1)
1395 gindex = gtotal - 1;
1396 else
1397 gindex = 0;
1399 else
1400 gindex += count;
1402 else {
1403 if (gindex - count < 0) {
1404 if (count != 1)
1405 gindex = 0;
1406 else
1407 gindex = gtotal - 1;
1409 else
1410 gindex -= count;
1414 static void delete_game(int which)
1416 GAME *g = NULL;
1417 int gi = 0;
1418 int i;
1419 struct userdata_s *d;
1421 for (i = 0; i < gtotal; i++) {
1422 d = game[i].data;
1424 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
1425 pgn_free(game[i]);
1426 continue;
1429 g = Realloc(g, (gi + 1) * sizeof(GAME));
1430 memcpy(&g[gi], &game[i], sizeof(GAME));
1431 g[gi].tag = game[i].tag;
1432 g[gi].history = game[i].history;
1433 g[gi].hp = game[i].hp;
1434 gi++;
1437 game = g;
1438 gtotal = gi;
1440 if (which != -1) {
1441 if (which + 1 >= gtotal)
1442 gindex = gtotal - 1;
1443 else
1444 gindex = which;
1446 else
1447 gindex = gtotal - 1;
1449 game[gindex].hp = game[gindex].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(E_REGEXEC_TITLE, ANYKEY, "%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;
1501 update_all(game[gindex]);
1503 for (i = x = 0; i < gtotal; i++) {
1504 d = game[i].data;
1506 if (TEST_FLAG(d->flags, CF_DELETE))
1507 x++;
1510 if (x == gtotal) {
1511 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1512 d = game[n].data;
1513 CLEAR_FLAG(d->flags, CF_DELETE);
1514 return 1;
1517 return 0;
1520 static int find_game_exp(char *str, int which, int count)
1522 char *nstr = NULL, *exp = NULL;
1523 regex_t nexp, vexp;
1524 int ret = -1;
1525 int g = 0;
1526 char buf[255], *tmp;
1527 char errbuf[255];
1528 int found = 0;
1529 int incr = (which == 0) ? -(1) : 1;
1531 strncpy(buf, str, sizeof(buf));
1532 tmp = buf;
1534 if (strstr(tmp, ":") != NULL) {
1535 nstr = strsep(&tmp, ":");
1537 if ((ret = regcomp(&nexp, nstr,
1538 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
1539 regerror(ret, &nexp, errbuf, sizeof(errbuf));
1540 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1541 ret = g = -1;
1542 goto cleanup;
1546 exp = tmp;
1548 if (exp == NULL)
1549 goto cleanup;
1551 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
1552 regerror(ret, &vexp, errbuf, sizeof(errbuf));
1553 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1554 ret = -1;
1555 goto cleanup;
1558 ret = -1;
1560 for (g = gindex + incr, found = 0; ; g += incr) {
1561 int t;
1563 if (g == gindex)
1564 break;
1566 if (g == gtotal)
1567 g = 0;
1568 else if (g < 0)
1569 g = gtotal - 1;
1571 for (t = 0; game[g].tag[t]; t++) {
1572 if (nstr) {
1573 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
1574 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
1575 if (count == ++found) {
1576 ret = g;
1577 goto cleanup;
1582 else {
1583 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
1584 if (count == ++found) {
1585 ret = g;
1586 goto cleanup;
1592 ret = -1;
1595 cleanup:
1596 if (nstr)
1597 regfree(&nexp);
1599 if (g != -1)
1600 regfree(&vexp);
1602 return ret;
1606 * Updates the notification line in the status window then refreshes the
1607 * status window.
1609 void update_status_notify(GAME g, char *fmt, ...)
1611 va_list ap;
1612 #ifdef HAVE_VASPRINTF
1613 char *line;
1614 #else
1615 char line[COLS];
1616 #endif
1618 if (!fmt) {
1619 if (status.notify) {
1620 free(status.notify);
1621 status.notify = NULL;
1623 if (curses_initialized)
1624 update_status_window(g);
1627 return;
1630 va_start(ap, fmt);
1631 #ifdef HAVE_VASPRINTF
1632 vasprintf(&line, fmt, ap);
1633 #else
1634 vsnprintf(line, sizeof(line), fmt, ap);
1635 #endif
1636 va_end(ap);
1638 if (status.notify)
1639 free(status.notify);
1641 status.notify = strdup(line);
1643 #ifdef HAVE_VASPRINTF
1644 free(line);
1645 #endif
1646 if (curses_initialized)
1647 update_status_window(g);
1650 int rav_next_prev(GAME *g, BOARD b, int n)
1652 // Next RAV.
1653 if (n) {
1654 if ((!g->ravlevel && g->hp[g->hindex - 1]->rav == NULL) ||
1655 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
1656 return 1;
1658 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
1659 g->rav[g->ravlevel].hp = g->hp;
1660 g->rav[g->ravlevel].flags = g->flags;
1661 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
1662 g->rav[g->ravlevel].hindex = g->hindex;
1663 g->hp = (!g->ravlevel) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav;
1664 g->hindex = 0;
1665 g->ravlevel++;
1666 pgn_board_update(g, b, g->hindex + 1);
1667 return 0;
1670 if (g->ravlevel - 1 < 0)
1671 return 1;
1673 // Previous RAV.
1674 g->ravlevel--;
1675 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
1676 free(g->rav[g->ravlevel].fen);
1677 g->hp = g->rav[g->ravlevel].hp;
1678 g->flags = g->rav[g->ravlevel].flags;
1679 g->hindex = g->rav[g->ravlevel].hindex;
1680 return 0;
1683 static void draw_window_decor()
1685 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
1686 move_panel(boardp, 0, COLS - BOARD_WIDTH);
1687 wbkgd(boardw, CP_BOARD_WINDOW);
1688 wbkgd(statusw, CP_STATUS_WINDOW);
1689 window_draw_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
1690 CP_STATUS_TITLE, CP_STATUS_BORDER);
1691 wbkgd(tagw, CP_TAG_WINDOW);
1692 window_draw_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
1693 CP_TAG_BORDER);
1694 wbkgd(historyw, CP_HISTORY_WINDOW);
1695 window_draw_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
1696 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
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(game[gindex]);
1718 void stop_clock()
1720 memset(&clock_timer, 0, sizeof(struct itimerval));
1721 setitimer(ITIMER_REAL, &clock_timer, NULL);
1724 void start_clock()
1726 if (clock_timer.it_interval.tv_usec)
1727 return;
1729 clock_timer.it_value.tv_sec = 0;
1730 clock_timer.it_value.tv_usec = 100000;
1731 clock_timer.it_interval.tv_sec = 0;
1732 clock_timer.it_interval.tv_usec = 100000;
1733 setitimer(ITIMER_REAL, &clock_timer, NULL);
1736 static void update_clocks()
1738 int i;
1739 struct userdata_s *d;
1740 struct itimerval it;
1742 getitimer(ITIMER_REAL, &it);
1744 for (i = 0; i < gtotal; i++) {
1745 d = game[i].data;
1747 if (d->mode == MODE_PLAY) {
1748 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
1749 continue;
1750 else if (d->paused == -1) {
1751 if (game[i].side == game[i].turn) {
1752 d->paused = 1;
1753 continue;
1757 update_clock(&game[i], it);
1762 static int init_chess_engine(GAME *g)
1764 struct userdata_s *d = g->data;
1765 int w, x;
1767 if (start_chess_engine(g) > 0) {
1768 d->sp.icon = 0;
1769 return 1;
1772 x = pgn_tag_find(g->tag, "FEN");
1773 w = pgn_tag_find(g->tag, "SetUp");
1775 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
1776 (x >= 0 && w == -1))
1777 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
1778 else
1779 add_engine_command(g, ENGINE_READY, "setboard %s\n",
1780 pgn_game_to_fen(*g, d->b));
1782 return 0;
1785 static int parse_clock_input(struct userdata_s *d, char *str)
1787 char *p = str;
1788 long n = 0;
1789 int t = 0;
1790 int plus = 0;
1792 if (*p == '+') {
1793 plus = 1;
1794 p++;
1797 while (*p) {
1798 if (isdigit(*p)) {
1799 t = atoi(p);
1801 while (isdigit(*p))
1802 p++;
1804 continue;
1807 if (!t && *p != ' ')
1808 return 1;
1810 switch (*p) {
1811 case 'H':
1812 case 'h':
1813 n += t * (60 * 60);
1814 t = 0;
1815 break;
1816 case 'M':
1817 case 'm':
1818 n += t * 60;
1819 t = 0;
1820 break;
1821 case 'S':
1822 case 's':
1823 n += t;
1824 t = 0;
1825 break;
1826 case ' ':
1827 t = 0;
1828 break;
1829 default:
1830 return 1;
1833 p++;
1836 if (t)
1837 n += t;
1839 if (!n) {
1840 d->limit = 0;
1841 CLEAR_FLAG(d->flags, CF_CLOCK);
1843 else {
1844 SET_FLAG(d->flags, CF_CLOCK);
1846 if (plus)
1847 d->limit += n;
1848 else
1849 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
1852 return 0;
1855 void do_clock_input_finalize(WIN *win)
1857 struct userdata_s *d = game[gindex].data;
1858 struct input_data_s *in = win->data;
1860 if (!in->str)
1861 return;
1863 if (parse_clock_input(d, in->str))
1864 cmessage(ERROR, ANYKEY, "Invalid time specification");
1866 free(in->str);
1867 free(in);
1870 void do_engine_command_finalize(WIN *win)
1872 struct userdata_s *d = game[gindex].data;
1873 struct input_data_s *in = win->data;
1874 int x;
1876 if (!in->str) {
1877 free(in);
1878 return;
1881 x = d->engine->status;
1882 send_to_engine(&game[gindex], -1, "%s\n", in->str);
1883 d->engine->status = x;
1884 free(in->str);
1885 free(in);
1888 static void historymode_keys(chtype);
1889 static int playmode_keys(chtype c)
1891 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
1892 struct userdata_s *d = game[gindex].data;
1893 int editmode = (d->mode == MODE_EDIT) ? 1 : 0;
1894 chtype p;
1895 int w, x;
1896 struct input_data_s *in;
1898 switch (c) {
1899 case 'C':
1900 in = Calloc(1, sizeof(struct input_data_s));
1901 in->efunc = do_clock_input_finalize;
1902 construct_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL, NULL, 0,
1903 in, -1);
1904 break;
1905 case 'H':
1906 TOGGLE_FLAG(d->flags, CF_HUMAN);
1908 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
1909 pgn_history_total(game[gindex].hp)) {
1910 if (init_chess_engine(&game[gindex]))
1911 break;
1914 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
1916 if (d->engine)
1917 d->engine->status = ENGINE_READY;
1919 update_all(game[gindex]);
1920 break;
1921 case 'E':
1922 if (!d)
1923 break;
1925 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
1926 CLEAR_FLAG(d->flags, CF_HUMAN);
1928 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
1929 pgn_board_update(&game[gindex], d->b,
1930 pgn_history_total(game[gindex].hp));
1931 add_engine_command(&game[gindex], ENGINE_READY,
1932 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
1935 update_all(game[gindex]);
1936 break;
1937 case '|':
1938 if (!d->engine)
1939 break;
1941 if (d->engine->status == ENGINE_OFFLINE)
1942 break;
1944 x = d->engine->status;
1945 in = Calloc(1, sizeof(struct input_data_s));
1946 in->efunc = do_engine_command_finalize;
1947 construct_input(ENGINE_CMD_TITLE, NULL, 1, 1, NULL, NULL, NULL, 0,
1948 in, -1);
1949 break;
1950 case '\015':
1951 case '\n':
1952 pushkey = keycount = 0;
1953 update_status_notify(game[gindex], NULL);
1955 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
1956 (!d->engine || d->engine->status == ENGINE_THINKING)) {
1957 beep();
1958 break;
1961 if (!d->sp.icon)
1962 break;
1964 d->sp.row = d->c_row;
1965 d->sp.col = d->c_col;
1967 if (editmode) {
1968 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
1969 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
1970 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
1971 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
1972 d->sp.icon = d->sp.srow = d->sp.scol = 0;
1973 break;
1976 move_to_engine(&game[gindex]);
1977 break;
1978 case ' ':
1979 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
1980 d->engine->status == ENGINE_OFFLINE) && !editmode) {
1981 if (init_chess_engine(&game[gindex]))
1982 break;
1985 if (d->sp.icon || (!editmode && d->engine &&
1986 d->engine->status == ENGINE_THINKING)) {
1987 beep();
1988 break;
1991 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
1992 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
1994 if (d->sp.icon == ' ') {
1995 d->sp.icon = 0;
1996 break;
1999 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2000 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2001 if (pgn_history_total(game[gindex].hp)) {
2002 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2003 d->sp.icon = 0;
2004 break;
2006 else {
2007 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR)
2008 break;
2010 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2011 pgn_switch_turn(&game[gindex]);
2013 if (game[gindex].side != BLACK)
2014 pgn_switch_side(&game[gindex]);
2018 d->sp.srow = d->c_row;
2019 d->sp.scol = d->c_col;
2021 if (!editmode && config.validmoves)
2022 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2024 if (!editmode) {
2025 CLEAR_FLAG(d->flags, CF_NEW);
2026 start_clock();
2029 break;
2030 case 'w':
2031 pgn_switch_side(&game[gindex]);
2032 pgn_switch_turn(&game[gindex]);
2033 add_engine_command(&game[gindex], -1,
2034 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2035 update_status_window(game[gindex]);
2036 break;
2037 case 'u':
2038 if (!pgn_history_total(game[gindex].hp))
2039 break;
2041 if (d->engine && d->engine->status == ENGINE_READY) {
2042 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2043 d->engine->status = ENGINE_READY;
2046 game[gindex].hindex -= 2;
2047 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2048 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2049 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2050 update_history_window(game[gindex]);
2051 break;
2052 case 'a':
2053 historymode_keys(c);
2054 break;
2055 case 'd':
2056 config.details = (config.details) ? 0 : 1;
2057 break;
2058 case 'p':
2059 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex].turn !=
2060 game[gindex].side) {
2061 d->paused = -1;
2062 break;
2065 d->paused = (d->paused) ? 0 : 1;
2066 break;
2067 case 'g':
2068 if (TEST_FLAG(d->flags, CF_HUMAN))
2069 break;
2071 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2072 if (init_chess_engine(&game[gindex]))
2073 break;
2076 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2077 break;
2078 default:
2079 if (!d->engine)
2080 break;
2082 if (config.keys) {
2083 for (x = 0; config.keys[x]; x++) {
2084 if (config.keys[x]->c == c) {
2085 switch (config.keys[x]->type) {
2086 case KEY_DEFAULT:
2087 add_engine_command(&game[gindex], -1, "%s\n",
2088 config.keys[x]->str);
2089 break;
2090 case KEY_SET:
2091 if (!keycount)
2092 break;
2094 add_engine_command(&game[gindex], -1,
2095 "%s %i\n", config.keys[x]->str, keycount);
2096 keycount = 0;
2097 break;
2098 case KEY_REPEAT:
2099 if (!keycount)
2100 break;
2102 for (w = 0; w < keycount; w++)
2103 add_engine_command(&game[gindex], -1,
2104 "%s\n", config.keys[x]->str);
2105 keycount = 0;
2106 break;
2111 update_status_notify(game[gindex], NULL);
2113 break;
2116 return 0;
2119 void do_edit_insert_finalize(WIN *win)
2121 struct userdata_s *d = win->data;
2123 if (pgn_piece_to_int(win->c) == -1)
2124 return;
2126 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = win->c;
2129 static void editmode_keys(chtype c)
2131 struct userdata_s *d = game[gindex].data;
2133 switch (c) {
2134 case '\015':
2135 case '\n':
2136 case ' ':
2137 playmode_keys(c);
2138 break;
2139 case 'd':
2140 if (d->sp.icon)
2141 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2142 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2143 else
2144 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2145 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2147 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2148 break;
2149 case 'w':
2150 pgn_switch_turn(&game[gindex]);
2151 update_all(game[gindex]);
2152 break;
2153 case 'c':
2154 castling_state(&game[gindex], d->b, RANKTOBOARD(d->c_row),
2155 FILETOBOARD(d->c_col),
2156 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2157 break;
2158 case 'i':
2159 construct_message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, 0, NULL, NULL,
2160 d->b, do_edit_insert_finalize, 0, "%s", GAME_EDIT_TEXT);
2161 break;
2162 case 'p':
2163 if (d->c_row == 6 || d->c_row == 3) {
2164 pgn_reset_enpassant(d->b);
2165 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2167 break;
2168 default:
2169 break;
2173 void do_annotate_finalize(WIN *win)
2175 struct userdata_s *d = game[gindex].data;
2176 struct input_data_s *in = win->data;
2177 HISTORY *h = in->data;
2178 int len;
2180 if (!in->str) {
2181 if (h->comment) {
2182 free(h->comment);
2183 h->comment = NULL;
2186 else {
2187 len = strlen(in->str) + 1;
2188 h->comment = Realloc(h->comment, len);
2189 strncpy(h->comment, in->str, len);
2192 free(in->str);
2193 free(in);
2194 SET_FLAG(d->flags, CF_MODIFIED);
2195 update_all(game[gindex]);
2198 void do_find_move_exp_finalize(int init, int c)
2200 int n;
2201 struct userdata_s *d = game[gindex].data;
2202 static int firstrun;
2203 static regex_t r;
2204 int ret;
2205 char errbuf[255];
2207 if (init || !firstrun) {
2208 if (!firstrun)
2209 regfree(&r);
2211 if ((ret = regcomp(&r, moveexp, REG_EXTENDED|REG_NOSUB)) != 0) {
2212 regerror(ret, &r, errbuf, sizeof(errbuf));
2213 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2214 return;
2217 firstrun = 1;
2220 if ((n = find_move_exp(game[gindex], r,
2221 (c == '[') ? 0 : 1, (keycount) ? keycount : 1)) == -1)
2222 return;
2224 game[gindex].hindex = n;
2225 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2226 update_all(game[gindex]);
2229 void do_find_move_exp(WIN *win)
2231 struct input_data_s *in = win->data;
2232 int *n = in->data;
2233 int c = *n;
2235 if (in->str) {
2236 strncpy(moveexp, in->str, sizeof(moveexp));
2238 if (c == '/')
2239 c = ']';
2241 do_find_move_exp_finalize(1, c);
2242 free(in->str);
2245 free(in->data);
2246 free(in);
2249 void do_move_jump_finalize(int n)
2251 struct userdata_s *d = game[gindex].data;
2253 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2254 return;
2256 keycount = 0;
2257 update_status_notify(game[gindex], NULL);
2258 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2259 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2260 update_all(game[gindex]);
2263 void do_move_jump(WIN *win)
2265 struct input_data_s *in = win->data;
2267 if (!in->str || !isinteger(in->str)) {
2268 if (in->str)
2269 free(in->str);
2271 free(in);
2272 return;
2275 do_move_jump_finalize(atoi(in->str));
2276 free(in->str);
2277 free(in);
2280 static void historymode_keys(chtype c)
2282 int n;
2283 char buf[COLS - 4];
2284 struct userdata_s *d = game[gindex].data;
2285 struct input_data_s *in;
2286 int *p;
2288 switch (c) {
2289 case 'd':
2290 config.details = (config.details) ? 0 : 1;
2291 break;
2292 case ' ':
2293 movestep = (movestep == 1) ? 2 : 1;
2294 update_history_window(game[gindex]);
2295 break;
2296 case KEY_UP:
2297 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2298 config.jumpcount * keycount * movestep :
2299 config.jumpcount * movestep);
2300 update_all(game[gindex]);
2301 break;
2302 case KEY_DOWN:
2303 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2304 config.jumpcount * keycount * movestep :
2305 config.jumpcount * movestep);
2306 update_all(game[gindex]);
2307 break;
2308 case KEY_LEFT:
2309 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2310 keycount * movestep : movestep);
2311 update_all(game[gindex]);
2312 break;
2313 case KEY_RIGHT:
2314 pgn_history_next(&game[gindex], d->b, (keycount) ?
2315 keycount * movestep : movestep);
2316 update_all(game[gindex]);
2317 break;
2318 case 'a':
2319 n = game[gindex].hindex;
2321 if (n && game[gindex].hp[n - 1]->move)
2322 n--;
2323 else
2324 break;
2326 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2327 game[gindex].hp[n]->move);
2328 in = Calloc(1, sizeof(struct input_data_s));
2329 in->data = game[gindex].hp[n];
2330 in->efunc = do_annotate_finalize;
2331 construct_input(buf, game[gindex].hp[n]->comment,
2332 MAX_PGN_LINE_LEN / INPUT_WIDTH, 0, NAG_PROMPT, edit_nag,
2333 NULL, CTRL('T'), in, -1);
2334 break;
2335 case ']':
2336 case '[':
2337 case '/':
2338 if (pgn_history_total(game[gindex].hp) < 2)
2339 break;
2341 in = Calloc(1, sizeof(struct input_data_s));
2342 p = Malloc(sizeof(int));
2343 *p = c;
2344 in->data = p;
2345 in->efunc = do_find_move_exp;
2347 if (!*moveexp || c == '/') {
2348 construct_input(FIND_REGEXP, moveexp, 1, 0, NULL, NULL, NULL,
2349 0, in, -1);
2350 break;
2353 do_find_move_exp_finalize(0, c);
2354 break;
2355 case 'v':
2356 view_annotation(game[gindex].hp[game[gindex].hindex]);
2357 break;
2358 case 'V':
2359 if (game[gindex].hindex - 1 >= 0)
2360 view_annotation(game[gindex].hp[game[gindex].hindex - 1]);
2361 break;
2362 case '-':
2363 case '+':
2364 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2365 update_all(game[gindex]);
2366 break;
2367 case 'j':
2368 if (pgn_history_total(game[gindex].hp) < 2)
2369 break;
2371 if (!keycount) {
2372 in = Calloc(1, sizeof(struct input_data_s));
2373 in->efunc = do_move_jump;
2375 construct_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1, NULL,
2376 NULL, NULL, 0, in, 0);
2377 break;
2380 do_move_jump_finalize(keycount);
2381 break;
2382 default:
2383 break;
2387 static void free_userdata()
2389 int i;
2391 for (i = 0; i < gtotal; i++) {
2392 struct userdata_s *d;
2394 if (game[i].data) {
2395 d = game[i].data;
2397 if (d->engine) {
2398 stop_engine(&game[i]);
2399 free(d->engine);
2402 free(game[i].data);
2403 game[i].data = NULL;
2408 void update_loading_window(int n)
2410 if (!loadingw) {
2411 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2412 loadingp = new_panel(loadingw);
2413 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2416 wmove(loadingw, 0, 0);
2417 wclrtobot(loadingw);
2418 wattron(loadingw, CP_MESSAGE_BORDER);
2419 box(loadingw, ACS_VLINE, ACS_HLINE);
2420 wattroff(loadingw, CP_MESSAGE_BORDER);
2421 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2422 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
2423 gtotal);
2424 refresh_all();
2427 static void init_userdata_once(GAME *g, int n)
2429 struct userdata_s *d = NULL;
2431 d = Calloc(1, sizeof(struct userdata_s));
2432 d->n = n;
2433 d->c_row = 2, d->c_col = 5;
2434 SET_FLAG(d->flags, CF_NEW);
2435 g->data = d;
2437 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
2438 pgn_board_init(d->b);
2441 void init_userdata()
2443 int i;
2445 for (i = 0; i < gtotal; i++)
2446 init_userdata_once(&game[i], i);
2449 void fix_marks(int *start, int *end)
2451 int i;
2453 *start = (*start < 0) ? 0 : *start;
2454 *end = (*end < 0) ? 0 : *end;
2456 if (*start > *end) {
2457 i = *start;
2458 *start = *end;
2459 *end = i + 1;
2462 *end = (*end > gtotal) ? gtotal : *end;
2465 void do_new_game_finalize(GAME *g)
2467 struct userdata_s *d = g->data;
2469 d->mode = MODE_PLAY;
2470 update_status_notify(*g, NULL);
2471 update_all(*g);
2474 void do_new_game_from_scratch(WIN *win)
2476 if (tolower(win->c) != 'y')
2477 return;
2479 stop_clock();
2480 free_userdata();
2481 pgn_parse(NULL);
2482 add_custom_tags(&game[gindex].tag);
2483 init_userdata();
2484 loadfile[0] = 0;
2485 do_new_game_finalize(&game[gindex]);
2488 void do_new_game()
2490 pgn_new_game();
2491 add_custom_tags(&game[gindex].tag);
2492 init_userdata_once(&game[gindex], gindex);
2493 do_new_game_finalize(&game[gindex]);
2496 void do_game_delete_finalize(int n)
2498 struct userdata_s *d;
2500 delete_game((!n) ? gindex : -1);
2501 d = game[gindex].data;
2502 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2503 update_all(game[gindex]);
2506 void do_game_delete_confirm(WIN *win)
2508 int *n;
2510 if (tolower(win->c) != 'y') {
2511 free(win->data);
2512 return;
2516 n = (int *)win->data;
2517 do_game_delete_finalize(*n);
2518 free(win->data);
2521 void do_game_delete()
2523 char *tmp = NULL;
2524 int i, n;
2525 struct userdata_s *d;
2526 int *p;
2528 if (gtotal < 2) {
2529 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2530 return;
2533 tmp = NULL;
2535 for (i = n = 0; i < gtotal; i++) {
2536 d = game[i].data;
2538 if (TEST_FLAG(d->flags, CF_DELETE))
2539 n++;
2542 if (!n)
2543 tmp = GAME_DELETE_GAME_TEXT;
2544 else {
2545 if (n == gtotal) {
2546 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2547 return;
2550 tmp = GAME_DELETE_ALL_TEXT;
2553 if (config.deleteprompt) {
2554 p = Malloc(sizeof(int));
2555 *p = n;
2556 construct_message(NULL, YESNO, 1, NULL, NULL, p,
2557 do_game_delete_confirm, 0, tmp);
2558 return;
2561 do_game_delete_finalize(n);
2564 void do_history_mode_finalize(struct userdata_s *d)
2566 pushkey = 0;
2567 d->mode = MODE_PLAY;
2568 update_all(game[gindex]);
2571 void do_history_mode_confirm(WIN *win)
2573 struct userdata_s *d = game[gindex].data;
2575 switch (win->c) {
2576 case 'R':
2577 case 'r':
2578 pgn_history_free(game[gindex].hp,
2579 game[gindex].hindex);
2580 pgn_board_update(&game[gindex], d->b,
2581 pgn_history_total(game[gindex].hp));
2582 break;
2583 #if 0
2584 case 'C':
2585 case 'c':
2586 if (pgn_history_rav_new(&game[gindex], d->b,
2587 game[gindex].hindex) != E_PGN_OK)
2588 return;
2590 break;
2591 #endif
2592 default:
2593 return;
2596 if (!TEST_FLAG(d->flags, CF_HUMAN))
2597 add_engine_command(&game[gindex], ENGINE_READY,
2598 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2600 do_history_mode_finalize(d);
2603 void do_find_game_exp_finalize(int c)
2605 struct userdata_s *d = game[gindex].data;
2606 int n;
2608 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
2609 (keycount) ? keycount : 1)) == -1)
2610 return;
2612 gindex = n;
2613 d = game[gindex].data;
2615 if (pgn_history_total(game[gindex].hp))
2616 d->mode = MODE_HISTORY;
2618 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2619 update_all(game[gindex]);
2622 void do_find_game_exp(WIN *win)
2624 struct input_data_s *in = win->data;
2625 int *n = in->data;
2626 int c = *n;
2628 if (in->str) {
2629 strncpy(gameexp, in->str, sizeof(gameexp));
2631 if (c == '?')
2632 c = '}';
2634 do_find_game_exp_finalize(c);
2635 free(in->str);
2638 free(in->data);
2639 free(in);
2642 void do_game_jump_finalize(int n)
2644 struct userdata_s *d;
2646 if (--n > gtotal - 1 || n < 0)
2647 return;
2649 gindex = n;
2650 d = game[gindex].data;
2651 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2652 update_status_notify(game[gindex], NULL);
2653 update_all(game[gindex]);
2656 void do_game_jump(WIN *win)
2658 struct input_data_s *in = win->data;
2660 if (!in->str || !isinteger(in->str)) {
2661 if (in->str)
2662 free(in->str);
2664 free(in);
2665 return;
2668 do_game_jump_finalize(atoi(in->str));
2669 free(in->str);
2670 free(in);
2673 void do_load_file(WIN *win)
2675 FILE *fp;
2676 struct input_data_s *in = win->data;
2677 char *tmp = in->str;
2678 struct userdata_s *d;
2680 if (!in->str) {
2681 free(in);
2682 return;
2685 if ((tmp = word_expand(tmp)) == NULL)
2686 goto done;
2688 if ((fp = pgn_open(tmp)) == NULL) {
2689 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2690 goto done;
2693 free_userdata();
2696 * FIXME what is the game state after a parse error?
2698 if (pgn_parse(fp) == E_PGN_ERR) {
2699 del_panel(loadingp);
2700 delwin(loadingw);
2701 loadingw = NULL;
2702 loadingp = NULL;
2703 init_userdata();
2704 update_all(game[gindex]);
2705 goto done;
2708 del_panel(loadingp);
2709 delwin(loadingw);
2710 loadingw = NULL;
2711 loadingp = NULL;
2712 init_userdata();
2713 strncpy(loadfile, tmp, sizeof(loadfile));
2714 d = game[gindex].data;
2716 if (pgn_history_total(game[gindex].hp))
2717 d->mode = MODE_HISTORY;
2719 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2720 update_all(game[gindex]);
2722 done:
2723 if (in->str)
2724 free(in->str);
2726 free(in);
2729 void do_game_save(WIN *win)
2731 struct input_data_s *in = win->data;
2732 int *x = in->data;
2733 int n = *x;
2734 char *tmp = in->str;
2735 char tfile[FILENAME_MAX];
2736 char *p;
2738 if (!tmp || (tmp = word_expand(tmp)) == NULL)
2739 goto done;
2741 if (pgn_is_compressed(tmp)) {
2742 p = tmp + strlen(tmp) - 1;
2744 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
2745 *(p-3) != '.') {
2746 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2747 tmp = tfile;
2750 else {
2751 if ((p = strchr(tmp, '.')) != NULL) {
2752 if (strcmp(p, ".pgn") != 0) {
2753 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2754 tmp = tfile;
2757 else {
2758 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2759 tmp = tfile;
2763 save_pgn(tmp, n);
2765 done:
2766 if (in->str)
2767 free(in->str);
2769 free(in->data);
2770 free(in);
2773 void do_get_game_save_input(int n)
2775 struct input_data_s *in = Calloc(1, sizeof(struct input_data_s));
2776 int *p = Malloc(sizeof(int));
2778 in->efunc = do_game_save;
2779 *p = n;
2780 in->data = p;
2782 construct_input(GAME_SAVE_TITLE, loadfile, 1, 1, BROWSER_PROMPT,
2783 file_browser, NULL, '\t', in, -1);
2786 void do_game_save_multi_confirm(WIN *win)
2788 int i;
2790 if (win->c == 'c')
2791 i = gindex;
2792 else if (win->c == 'a')
2793 i = -1;
2794 else {
2795 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2796 return;
2799 do_get_game_save_input(i);
2802 void do_more_help(WIN *);
2803 void do_main_help(WIN *win)
2806 switch (win->c) {
2807 case 'p':
2808 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0,
2809 NULL, NULL, NULL, do_more_help, 0, "%s",
2810 playhelp);
2811 break;
2812 case 'h':
2813 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0,
2814 NULL, NULL, NULL, do_more_help, 0, "%s",
2815 historyhelp);
2816 break;
2817 case 'e':
2818 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0,
2819 NULL, NULL, NULL, do_more_help, 0, "%s",
2820 edithelp);
2821 break;
2822 case 'g':
2823 construct_message(GAME_HELP_INDEX_TITLE, ANYKEY, 0,
2824 NULL, NULL, NULL, do_more_help, 0, "%s",
2825 gamehelp);
2826 break;
2827 default:
2828 break;
2832 void do_more_help(WIN *win)
2834 if (win->c == KEY_F(1))
2835 construct_message(GAME_HELP_INDEX_PROMPT, ANYKEY, 0, NULL, NULL, NULL,
2836 do_main_help, 0, "%s", mainhelp);
2839 // Global and other keys.
2840 static int globalkeys(chtype c)
2842 char *p;
2843 int n, i;
2844 struct userdata_s *d = game[gindex].data;
2845 struct input_data_s *in;
2847 switch (c) {
2848 case 'W':
2849 toggle_engine_window();
2850 break;
2851 case KEY_F(10):
2852 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
2853 "and %i color pairs\nCopyright 2002-2006 %s",
2854 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
2855 COLOR_PAIRS, PACKAGE_BUGREPORT);
2856 break;
2857 case 'h':
2858 if (d->mode != MODE_HISTORY) {
2859 if (!pgn_history_total(game[gindex].hp) ||
2860 (d->engine && d->engine->status == ENGINE_THINKING))
2861 return 1;
2863 d->mode = MODE_HISTORY;
2864 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2865 update_all(game[gindex]);
2866 return 1;
2869 // FIXME Resuming from previous history could append to a RAV.
2870 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2871 if (!pushkey)
2872 construct_message(NULL, GAME_RESUME_HISTORY_TEXT, 0,
2873 NULL, NULL, NULL, do_history_mode_confirm, 0,
2874 "%s", GAME_RESUME_HISTORY_TEXT);
2876 return 1;
2878 else {
2879 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2880 return 1;
2883 do_history_mode_finalize(d);
2884 return 1;
2885 case '>':
2886 case '<':
2887 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2888 keycount : 1);
2889 d = game[gindex].data;
2891 if (delete_count) {
2892 if (c == '>') {
2893 markend = markstart + delete_count;
2894 delete_count = 0;
2896 else {
2897 markend = markstart - delete_count;
2898 delete_count = -1; // to fix gindex in the other direction
2901 pushkey = 'x';
2902 fix_marks(&markstart, &markend);
2905 if (d->mode != MODE_EDIT)
2906 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2908 update_status_notify(game[gindex], NULL);
2909 update_all(game[gindex]);
2910 return 1;
2911 case '}':
2912 case '{':
2913 case '?':
2914 if (gtotal < 2)
2915 return 1;
2917 in = Calloc(1, sizeof(struct input_data_s));
2918 p = Malloc(sizeof(int));
2919 *p = c;
2920 in->data = p;
2921 in->efunc = do_find_game_exp;
2923 if (!*gameexp || c == '?') {
2924 construct_input(GAME_FIND_EXPRESSION_TITLE, gameexp, 1, 0,
2925 GAME_FIND_EXPRESSION_PROMPT, NULL, NULL, 0, in, -1);
2926 break;
2929 do_find_game_exp_finalize(c);
2930 return 1;
2931 case 'J':
2932 if (gtotal < 2)
2933 return 1;
2935 in = Calloc(1, sizeof(struct input_data_s));
2936 in->efunc = do_game_jump;
2938 if (!keycount) {
2939 construct_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL, NULL,
2940 0, in, 0);
2941 return 1;
2944 do_game_jump_finalize(keycount);
2945 return 1;
2946 case 'x':
2947 pushkey = 0;
2949 if (gtotal < 2)
2950 return 1;
2952 if (keycount && delete_count == 0) {
2953 markstart = gindex;
2954 delete_count = keycount;
2955 update_status_notify(game[gindex], "%s (delete)",
2956 status.notify);
2957 return 1;
2960 if (markstart >= 0 && markend >= 0) {
2961 for (i = markstart; i < markend; i++) {
2962 if (toggle_delete_flag(i)) {
2963 update_all(game[gindex]);
2964 return 1;
2968 gindex = (delete_count < 0) ? markstart : i - 1;
2969 update_all(game[gindex]);
2971 else {
2972 if (toggle_delete_flag(gindex))
2973 return 1;
2976 markstart = markend = -1;
2977 delete_count = 0;
2978 update_status_window(game[gindex]);
2979 return 1;
2980 case 'X':
2981 do_game_delete();
2982 return 1;
2983 case 'T':
2984 edit_tags(game[gindex], d->b, 1);
2985 return 1;
2986 case 't':
2987 edit_tags(game[gindex], d->b, 0);
2988 return 1;
2989 case 'r':
2990 in = Calloc(1, sizeof(struct input_data_s));
2991 in->efunc = do_load_file;
2992 construct_input(GAME_LOAD_TITLE, NULL, 1, 1, BROWSER_PROMPT,
2993 file_browser, NULL, '\t', in, -1);
2994 return 1;
2995 case 'S':
2996 case 's':
2997 if (gtotal > 1) {
2998 construct_message(NULL, GAME_SAVE_MULTI_PROMPT, 1, NULL,
2999 NULL, NULL, do_game_save_multi_confirm, 0, "%s",
3000 GAME_SAVE_MULTI_TEXT);
3001 return 1;
3004 do_get_game_save_input(-1);
3005 return 1;
3006 case KEY_F(1):
3007 switch (d->mode) {
3008 case MODE_PLAY:
3009 construct_message(GAME_HELP_PLAY_TITLE, ANYKEY, 0,
3010 NULL, NULL, NULL, do_more_help, 0, "%s",
3011 playhelp);
3012 break;
3013 case MODE_HISTORY:
3014 construct_message(GAME_HELP_HISTORY_TITLE, ANYKEY, 0,
3015 NULL, NULL, NULL, do_more_help, 0, "%s",
3016 historyhelp);
3017 break;
3018 case MODE_EDIT:
3019 construct_message(GAME_HELP_EDIT_TITLE, ANYKEY, 0,
3020 NULL, NULL, NULL, do_more_help, 0, "%s",
3021 edithelp);
3022 break;
3023 default:
3024 break;
3027 return 1;
3028 case 'n':
3029 do_new_game();
3030 return 1;
3031 case 'N':
3032 construct_message(NULL, YESNO, 1, NULL, NULL, NULL,
3033 do_new_game_from_scratch, 0, "%s", GAME_NEW_PROMPT);
3034 return 1;
3035 case CTRL('L'):
3036 endwin();
3037 keypad(boardw, TRUE);
3038 refresh_all();
3039 return 1;
3040 case KEY_ESCAPE:
3041 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3042 markend = markstart = 0;
3044 if (keycount) {
3045 keycount = 0;
3046 update_status_notify(game[gindex], NULL);
3049 if (config.validmoves)
3050 pgn_reset_valid_moves(d->b);
3052 return 1;
3053 case '0' ... '9':
3054 n = c - '0';
3056 if (keycount)
3057 keycount = keycount * 10 + n;
3058 else
3059 keycount = n;
3061 update_status_notify(game[gindex], "Repeat %i", keycount);
3062 return -1;
3063 case KEY_UP:
3064 if (d->mode == MODE_HISTORY)
3065 return 0;
3067 if (keycount) {
3068 d->c_row += keycount;
3069 pushkey = '\n';
3071 else
3072 d->c_row++;
3074 if (d->c_row > 8)
3075 d->c_row = 1;
3077 return 1;
3078 case KEY_DOWN:
3079 if (d->mode == MODE_HISTORY)
3080 return 0;
3082 if (keycount) {
3083 d->c_row -= keycount;
3084 pushkey = '\n';
3085 update_status_notify(game[gindex], NULL);
3087 else
3088 d->c_row--;
3090 if (d->c_row < 1)
3091 d->c_row = 8;
3093 return 1;
3094 case KEY_LEFT:
3095 if (d->mode == MODE_HISTORY)
3096 return 0;
3098 if (keycount) {
3099 d->c_col -= keycount;
3100 pushkey = '\n';
3102 else
3103 d->c_col--;
3105 if (d->c_col < 1)
3106 d->c_col = 8;
3108 return 1;
3109 case KEY_RIGHT:
3110 if (d->mode == MODE_HISTORY)
3111 return 0;
3113 if (keycount) {
3114 d->c_col += keycount;
3115 pushkey = '\n';
3117 else
3118 d->c_col++;
3120 if (d->c_col > 8)
3121 d->c_col = 1;
3123 return 1;
3124 case 'e':
3125 if (d->mode != MODE_EDIT && d->mode !=
3126 MODE_PLAY)
3127 return 1;
3129 // Don't edit a running game (for now).
3130 if (pgn_history_total(game[gindex].hp))
3131 return 1;
3133 if (d->mode != MODE_EDIT) {
3134 pgn_board_init_fen(&game[gindex], d->b, NULL);
3135 config.details++;
3136 d->mode = MODE_EDIT;
3137 update_all(game[gindex]);
3138 return 1;
3141 config.details--;
3142 pgn_tag_add(&game[gindex].tag, "FEN",
3143 pgn_game_to_fen(game[gindex], d->b));
3144 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3145 pgn_tag_sort(game[gindex].tag);
3146 d->mode = MODE_PLAY;
3147 update_all(game[gindex]);
3148 return 1;
3149 case 'Q':
3150 quit = 1;
3151 return 1;
3152 case KEY_RESIZE:
3153 do_window_resize();
3154 return 1;
3155 #ifdef DEBUG
3156 case 'D':
3157 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3158 return 1;
3159 #endif
3160 case 0:
3161 default:
3162 break;
3165 return 0;
3168 void game_loop()
3170 int error_recover = 0;
3171 struct userdata_s *d = game[gindex].data;
3173 gindex = gtotal - 1;
3175 if (pgn_history_total(game[gindex].hp))
3176 d->mode = MODE_HISTORY;
3177 else
3178 d->mode = MODE_PLAY;
3180 if (d->mode == MODE_HISTORY) {
3181 pgn_board_update(&game[gindex], d->b,
3182 pgn_history_total(game[gindex].hp));
3185 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3186 movestep = 2;
3187 flushinp();
3188 update_all(game[gindex]);
3189 update_tag_window(game[gindex].tag);
3190 wtimeout(boardw, 70);
3192 while (!quit) {
3193 int c = 0;
3194 int n = 0, i;
3195 char fdbuf[8192] = {0};
3196 int len;
3197 struct timeval tv = {0, 0};
3198 fd_set rfds, wfds;
3199 WIN *win = NULL;
3200 WINDOW *wp = NULL;
3202 FD_ZERO(&rfds);
3204 for (i = 0; i < gtotal; i++) {
3205 d = game[i].data;
3207 if (d->engine) {
3208 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3209 if (d->engine->fd[ENGINE_IN_FD] > n)
3210 n = d->engine->fd[ENGINE_IN_FD];
3212 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3215 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3216 if (d->engine->fd[ENGINE_OUT_FD] > n)
3217 n = d->engine->fd[ENGINE_OUT_FD];
3219 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3224 if (n) {
3225 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3226 for (i = 0; i < gtotal; i++) {
3227 d = game[i].data;
3229 if (d->engine) {
3230 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3231 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3232 sizeof(fdbuf));
3234 if (len == -1) {
3235 if (errno != EAGAIN) {
3236 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3237 strerror(errno));
3238 waitpid(d->engine->pid, &n, 0);
3239 free(d->engine);
3240 d->engine = NULL;
3241 break;
3244 else {
3245 if (len)
3246 parse_engine_output(&game[i], fdbuf);
3250 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3251 if (d->engine->queue)
3252 send_engine_command(&game[i]);
3257 else {
3258 if (n == -1)
3259 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3260 /* timeout */
3264 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3265 d->mode = MODE_HISTORY;
3267 d = game[gindex].data;
3268 error_recover = 0;
3269 draw_board(&game[gindex]);
3270 update_all(game[gindex]);
3271 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3272 refresh_all();
3275 * Finds the top level window in the window stack so we know what
3276 * window the wgetch()ed key belongs to.
3278 if (wins) {
3279 for (i = 0; wins[i]; i++);
3280 win = wins[i-1];
3281 wp = win->w;
3282 wtimeout(wp, WINDOW_TIMEOUT);
3284 else
3285 wp = boardw;
3287 if (!i && pushkey)
3288 c = pushkey;
3289 else {
3290 if (!pushkey) {
3291 if ((c = wgetch(wp)) == ERR)
3292 continue;
3294 else
3295 c = pushkey;
3297 if (win) {
3298 win->c = c;
3301 * Run the function associated with the window. When the
3302 * function returns 0 win->efunc is ran (if not NULL) with
3303 * win as the one and only parameter. Then the window is
3304 * destroyed.
3306 * The exit function may create another window which will
3307 * mess up the window stack when window_destroy() is called.
3308 * So don't destory the window until the top window is
3309 * destroyable. See window_destroy().
3311 if ((*win->func)(win) == 0) {
3312 if (win->efunc)
3313 (*win->efunc)(win);
3315 win->keep = 1;
3316 window_destroy(win);
3319 continue;
3323 if (!keycount && status.notify)
3324 update_status_notify(game[gindex], NULL);
3326 if ((n = globalkeys(c)) == 1) {
3327 keycount = 0;
3328 continue;
3330 else if (n == -1)
3331 continue;
3333 switch (d->mode) {
3334 case MODE_EDIT:
3335 editmode_keys(c);
3336 break;
3337 case MODE_PLAY:
3338 if (playmode_keys(c))
3339 continue;
3340 break;
3341 case MODE_HISTORY:
3342 historymode_keys(c);
3343 break;
3344 default:
3345 break;
3348 keycount = 0;
3352 void usage(const char *pn, int ret)
3354 fprintf((ret) ? stderr : stdout, "%s",
3355 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3356 " -p Load PGN file.\n"
3357 " -V Validate a game file.\n"
3358 " -S Validate and output a PGN formatted game.\n"
3359 " -R Like -S but write a reduced PGN formatted game.\n"
3360 " -t Also write custom PGN tags from config file.\n"
3361 " -E Stop processing on file parsing error (overrides config).\n"
3362 " -v Version information.\n"
3363 " -h This help text.\n");
3365 exit(ret);
3368 void cleanup_all()
3370 int i;
3372 stop_clock();
3373 free_userdata();
3374 pgn_free_all();
3375 free(config.engine_cmd);
3376 free(config.pattern);
3377 free(config.ccfile);
3378 free(config.nagfile);
3379 free(config.configfile);
3381 if (config.keys) {
3382 for (i = 0; config.keys[i]; i++)
3383 free(config.keys[i]->str);
3385 free(config.keys);
3388 if (config.einit) {
3389 for (i = 0; config.einit[i]; i++)
3390 free(config.einit[i]);
3392 free(config.einit);
3395 if (config.tag)
3396 pgn_tag_free(config.tag);
3398 if (curses_initialized) {
3399 del_panel(boardp);
3400 del_panel(historyp);
3401 del_panel(statusp);
3402 del_panel(tagp);
3403 delwin(boardw);
3404 delwin(historyw);
3405 delwin(statusw);
3406 delwin(tagw);
3408 if (enginew) {
3409 del_panel(enginep);
3410 delwin(enginew);
3412 if (enginebuf) {
3413 for (i = 0; enginebuf[i]; i++)
3414 free(enginebuf[i]);
3416 free(enginebuf);
3420 endwin();
3424 void catch_signal(int which)
3426 switch (which) {
3427 case SIGALRM:
3428 update_clocks();
3429 break;
3430 case SIGPIPE:
3431 if (which == SIGPIPE && quit)
3432 break;
3434 if (which == SIGPIPE)
3435 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3437 cleanup_all();
3438 exit(EXIT_FAILURE);
3439 break;
3440 case SIGSTOP:
3441 savetty();
3442 break;
3443 case SIGCONT:
3444 resetty();
3445 keypad(boardw, TRUE);
3446 curs_set(0);
3447 cbreak();
3448 noecho();
3449 break;
3450 case SIGINT:
3451 case SIGTERM:
3452 quit = 1;
3453 break;
3454 default:
3455 break;
3459 void loading_progress(long total, long offset)
3461 int n = (100 * (offset / 100) / (total / 100));
3463 if (curses_initialized)
3464 update_loading_window(n);
3465 else {
3466 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3467 fflush(stderr);
3471 static void set_defaults()
3473 set_config_defaults();
3474 filetype = NO_FILE;
3475 pgn_config_set(PGN_PROGRESS, 1024);
3476 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3479 int main(int argc, char *argv[])
3481 int opt;
3482 struct stat st;
3483 char buf[FILENAME_MAX];
3484 char datadir[FILENAME_MAX];
3485 int ret = EXIT_SUCCESS;
3486 int validate_only = 0, validate_and_write = 0;
3487 int write_custom_tags = 0;
3488 FILE *fp;
3489 int i = 0;
3491 if ((config.pwd = getpwuid(getuid())) == NULL)
3492 err(EXIT_FAILURE, "getpwuid()");
3494 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3495 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3496 config.ccfile = strdup(buf);
3497 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3498 config.nagfile = strdup(buf);
3499 snprintf(buf, sizeof(buf), "%s/config", datadir);
3500 config.configfile = strdup(buf);
3502 if (stat(datadir, &st) == -1) {
3503 if (errno == ENOENT) {
3504 if (mkdir(datadir, 0755) == -1)
3505 err(EXIT_FAILURE, "%s", datadir);
3507 else
3508 err(EXIT_FAILURE, "%s", datadir);
3510 stat(datadir, &st);
3513 if (!S_ISDIR(st.st_mode))
3514 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3516 set_defaults();
3518 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3519 switch (opt) {
3520 case 't':
3521 write_custom_tags = 1;
3522 break;
3523 case 'E':
3524 i = 1;
3525 break;
3526 case 'R':
3527 pgn_config_set(PGN_REDUCED, 1);
3528 case 'S':
3529 validate_and_write = 1;
3530 case 'V':
3531 validate_only = 1;
3532 break;
3533 case 'v':
3534 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3535 COPYRIGHT);
3536 exit(EXIT_SUCCESS);
3537 case 'p':
3538 filetype = PGN_FILE;
3539 strncpy(loadfile, optarg, sizeof(loadfile));
3540 break;
3541 case 'h':
3542 default:
3543 usage(argv[0], EXIT_SUCCESS);
3547 if ((validate_only || validate_and_write) && !*loadfile)
3548 usage(argv[0], EXIT_FAILURE);
3550 if (access(config.configfile, R_OK) == 0)
3551 parse_rcfile(config.configfile);
3553 if (i)
3554 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3556 signal(SIGPIPE, catch_signal);
3557 signal(SIGCONT, catch_signal);
3558 signal(SIGSTOP, catch_signal);
3559 signal(SIGINT, catch_signal);
3560 signal(SIGALRM, catch_signal);
3561 signal(SIGTERM, catch_signal);
3563 srandom(getpid());
3565 switch (filetype) {
3566 case PGN_FILE:
3567 if ((fp = pgn_open(loadfile)) == NULL)
3568 err(EXIT_FAILURE, "%s", loadfile);
3570 ret = pgn_parse(fp);
3571 break;
3572 case FEN_FILE:
3573 //ret = parse_fen_file(loadfile);
3574 break;
3575 case EPD_FILE: // Not implemented.
3576 case NO_FILE:
3577 default:
3578 // No file specified. Empty game.
3579 ret = pgn_parse(NULL);
3580 add_custom_tags(&game[gindex].tag);
3581 break;
3584 if (validate_only || validate_and_write) {
3585 if (validate_and_write) {
3586 for (i = 0; i < gtotal; i++) {
3587 if (write_custom_tags)
3588 add_custom_tags(&game[i].tag);
3590 pgn_write(stdout, game[i]);
3594 cleanup_all();
3595 exit(ret);
3597 else if (ret == E_PGN_ERR)
3598 exit(ret);
3600 init_userdata();
3602 if (initscr() == NULL)
3603 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3604 else
3605 curses_initialized = 1;
3607 if (LINES < 24 || COLS < 80) {
3608 endwin();
3609 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3612 if (has_colors() == TRUE && start_color() == OK)
3613 init_color_pairs();
3615 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3616 boardp = new_panel(boardw);
3617 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3618 COLS - HISTORY_WIDTH);
3619 historyp = new_panel(historyw);
3620 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3621 statusp = new_panel(statusw);
3622 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3623 tagp = new_panel(tagw);
3624 keypad(boardw, TRUE);
3625 // leaveok(boardw, TRUE);
3626 leaveok(tagw, TRUE);
3627 leaveok(statusw, TRUE);
3628 leaveok(historyw, TRUE);
3629 curs_set(0);
3630 cbreak();
3631 noecho();
3632 draw_window_decor();
3633 game_loop();
3634 cleanup_all();
3635 exit(EXIT_SUCCESS);