Temporary fix of NAG parsing bug.
[cboard.git] / src / cboard.c
blob84d1a5f36f8dada109b36049de7eb2ec62071a1f
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_WORDEXP_H
40 #include <wordexp.h>
41 #endif
43 #ifdef HAVE_DIRENT_H
44 #include <dirent.h>
45 #endif
47 #ifdef HAVE_MENU_H
48 #include <menu.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 "colors.h"
59 #include "input.h"
60 #include "misc.h"
61 #include "engine.h"
62 #include "rcfile.h"
63 #include "strings.h"
64 #include "common.h"
65 #include "cboard.h"
67 #ifdef DEBUG
68 #include "debug.h"
69 #endif
71 #ifdef WITH_DMALLOC
72 #include <dmalloc.h>
73 #endif
75 static char *str_etc(const char *str, int maxlen, int rev)
77 int len = strlen(str);
78 static char buf[80], *p = buf;
79 int i;
81 strncpy(buf, str, sizeof(buf));
83 if (len > maxlen) {
84 if (rev) {
85 p = buf;
86 *p++ = '.';
87 *p++ = '.';
88 *p++ = '.';
90 for (i = 0; i < maxlen + 3; i++)
91 *p++ = buf[(len - maxlen) + i + 3];
93 else {
94 p = buf + maxlen - 4;
95 *p++ = '.';
96 *p++ = '.';
97 *p++ = '.';
100 *p = '\0';
103 return buf;
106 void update_cursor(GAME g, int idx)
108 char *p;
109 int len;
110 int t = pgn_history_total(g.hp);
113 * If not deincremented then r and c would be the next move.
115 idx--;
117 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
118 c_row = 2, c_col = 5;
119 return;
122 p = g.hp[idx]->move;
123 len = strlen(p);
125 if (*p == 'O') {
126 if (len <= 4)
127 c_col = 7;
128 else
129 c_col = 3;
131 c_row = (g.turn == WHITE) ? 1 : 8;
132 return;
135 p += len;
137 while (!isdigit(*p))
138 p--;
140 c_row = ROWTOINT(*p--);
141 c_col = COLTOINT(*p);
144 static int init_nag()
146 FILE *fp;
147 char line[LINE_MAX];
148 int i = 0;
150 if ((fp = fopen(config.nagfile, "r")) == NULL) {
151 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
152 return 1;
155 nags = Realloc(nags, 2 * sizeof(char *));
156 nags[0] = NULL;
157 i++;
159 while (!feof(fp)) {
160 if (fscanf(fp, " %[^\n] ", line) == 1) {
161 nags = Realloc(nags, (i + 2) * sizeof(char *));
162 nags[i++] = strdup(line);
166 return 0;
169 void set_menu_vars(int c, int rows, int items, int *item, int *top)
171 int selected = *item;
172 int toppos = *top;
174 switch (c) {
175 case KEY_HOME:
176 selected = toppos = 0;
177 break;
178 case KEY_END:
179 selected = items;
180 toppos = items - rows + 1;
181 break;
182 case KEY_UP:
183 if (selected - 1 < 0) {
184 selected = items;
186 toppos = selected - rows + 1;
188 else {
189 selected--;
191 if (toppos && selected <= toppos)
192 toppos = selected;
194 break;
195 case KEY_DOWN:
196 if (selected + 1 > items )
197 selected = toppos = 0;
198 else {
199 selected++;
201 if (selected - toppos >= rows)
202 toppos++;
204 break;
205 default:
206 toppos = (items > rows) ? items - rows + 1 : 0;
207 break;
210 *item = selected;
211 *top = toppos;
214 int test_nag_selected(unsigned char nag[], int s)
216 int i;
218 for (i = 0; i < MAX_PGN_NAG; i++) {
219 if (nag[i] == s)
220 return i;
223 return -1;
226 char *history_edit_nag(void *arg)
228 WINDOW *win;
229 PANEL *panel;
230 int i = 0, n;
231 int itemcount = 0;
232 int rows, cols;
233 HISTORY *anno = (HISTORY *)arg;
234 int selected = 0;
235 int toppos = 0;
236 int len = 0;
237 int total = 0;
238 unsigned char nag[MAX_PGN_NAG] = {0};
240 if (!nags) {
241 if (init_nag())
242 return NULL;
245 for (i = 1, n = 0; nags[i]; i++) {
246 n = strlen(nags[i]);
248 if (len < n)
249 len = n;
252 total = i;
253 cols = len + 2;
254 rows = (total + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : total + 4;
256 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
257 panel = new_panel(win);
258 cbreak();
259 noecho();
260 keypad(win, TRUE);
261 nl();
262 wbkgd(win, CP_MESSAGE_WINDOW);
263 memcpy(&nag, &anno->nag, sizeof(nag));
265 for (i = 0; i < MAX_PGN_NAG; i++) {
266 if (nag[i])
267 itemcount++;
270 while (1) {
271 int c;
272 char buf[cols - 4];
274 wmove(win, 0, 0);
275 wclrtobot(win);
276 draw_window_title(win, NAG_EDIT_TITLE, cols, CP_HISTORY_TITLE,
277 CP_HISTORY_BORDER);
279 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
280 if ((i == selected && i != 0)|| test_nag_selected(nag, i) != -1) {
281 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
282 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
283 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
284 continue;
287 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
290 snprintf(buf, sizeof(buf), "NAG %i of %i (%i of %i selected) %s",
291 selected + 1, total, itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
292 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
294 nl();
295 refresh_all();
296 c = wgetch(win);
298 switch (c) {
299 int found;
301 case KEY_F(1):
302 help(NAG_EDIT_HELP, ANYKEY, naghelp);
303 break;
304 case KEY_HOME:
305 case KEY_END:
306 case KEY_UP:
307 case KEY_DOWN:
308 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
309 break;
310 case ' ':
311 if (selected == 0) {
312 for (i = 0; i < MAX_PGN_NAG; i++)
313 nag[i] = 0;
315 itemcount = 0;
316 break;
319 if ((found = test_nag_selected(nag, selected)) != -1) {
320 nag[found] = 0;
321 itemcount--;
323 else {
324 if (itemcount + 1 > MAX_PGN_NAG)
325 break;
327 for (i = 0; i < MAX_PGN_NAG; i++) {
328 if (nag[i] == 0) {
329 nag[i] = selected;
330 break;
334 itemcount++;
337 break;
338 case '\n':
339 goto done;
340 break;
341 case KEY_ESCAPE:
342 goto done;
343 break;
344 default:
345 break;
349 done:
350 memcpy(&anno->nag, &nag, sizeof(nag));
351 del_panel(panel);
352 delwin(win);
353 return NULL;
356 static void view_nag(void *arg)
358 HISTORY *h = (HISTORY *)arg;
359 char buf[80];
360 char line[LINE_MAX] = {0};
361 int i = 0;
363 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
365 if (!nags) {
366 if (init_nag())
367 return;
370 for (i = 0; i < MAX_PGN_NAG; i++) {
371 if (!h->nag[i])
372 break;
374 strncat(line, nags[h->nag[i]], sizeof(line));
375 strncat(line, "\n", sizeof(line));
378 line[strlen(line) - 1] = 0;
379 message(buf, ANYKEY, "%s", line);
382 void view_annotation(HISTORY h)
384 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
385 int nag = 0, comment = 0;
387 if (h.comment && h.comment[0])
388 comment++;
390 if (h.nag[0])
391 nag++;
393 if (!nag && !comment)
394 return;
396 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
398 if (comment)
399 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
400 (nag) ? "Press 'n' to view NAG" : NULL,
401 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
402 (nag) ? 'n' : 0, "%s", h.comment);
403 else
404 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
405 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
408 static void cleanup(WINDOW *win, PANEL *panel, struct file_s *files)
410 int i;
412 if (files) {
413 for (i = 0; files[i].name; i++) {
414 free(files[i].path);
415 free(files[i].name);
418 free(files);
421 del_panel(panel);
422 delwin(win);
425 static int sort_files(const void *a, const void *b)
427 const struct file_s *aa = a;
428 const struct file_s *bb = b;
430 return strcmp(aa->name, bb->name);
433 char *file_browser(void *arg)
435 char pattern[FILENAME_MAX];
436 static char path[FILENAME_MAX];
437 static char file[FILENAME_MAX];
438 struct stat st;
439 char *p;
440 int cursor = curs_set(0);
442 if (!*path) {
443 if (config.savedirectory) {
444 if ((p = word_expand(config.savedirectory)) == NULL)
445 return NULL;
447 strncpy(path, p, sizeof(path));
449 if (access(path, R_OK) == -1) {
450 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
451 getcwd(path, sizeof(path));
454 else
455 getcwd(path, sizeof(path));
458 again:
460 * First find directories (including hidden) in the working directory.
461 * Then apply the config.pattern to regular files.
463 if ((p = word_split_append(path, '/', ".* *")) == NULL)
464 return NULL;
466 strncpy(pattern, p, sizeof(pattern));
468 while (1) {
469 WINDOW *win;
470 PANEL *panel;
471 char *tmp = NULL;
472 int rows, cols = 0;
473 int selected = 0;
474 int toppos = 0;
475 int len = strlen(path);
476 wordexp_t w;
477 int i, n = 0;
478 struct file_s *files = NULL;
479 int which = 1;
480 int x = WRDE_NOCMD;
481 int nlen = 0;
483 new_we:
484 if (wordexp(pattern, &w, x) != 0) {
485 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
486 return NULL;
489 for (i = 0; i < w.we_wordc; i++) {
490 struct tm *tp;
491 char tbuf[16];
492 char sbuf[64];
494 if (stat(w.we_wordv[i], &st) == -1)
495 continue;
497 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
498 p++;
499 else
500 p = w.we_wordv[i];
502 if (which) {
503 if (!S_ISDIR(st.st_mode))
504 continue;
506 if (p[0] == '.' && p[1] == 0)
507 continue;
509 else {
510 if (S_ISDIR(st.st_mode))
511 continue;
514 len = strlen(p) + 2;
515 files = Realloc(files, (n + 2) * sizeof(struct file_s));
516 files[n].path = strdup(w.we_wordv[i]);
517 files[n].name = Malloc(len);
518 strncpy(files[n].name, p, len);
520 if (S_ISDIR(st.st_mode))
521 files[n].name[len - 2] = '/';
523 tp = localtime(&st.st_mtime);
524 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
525 snprintf(sbuf, sizeof(sbuf), "%i %s", (int)st.st_size, tbuf);
526 files[n].st = strdup(sbuf);
527 memset(&files[++n], '\0', sizeof(struct file_s));
530 which--;
532 if (which == 0) {
533 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
534 return NULL;
536 strncpy(pattern, p, sizeof(pattern));
537 x |= WRDE_REUSE;
538 goto new_we;
541 wordfree(&w);
542 qsort(files, n, sizeof(struct file_s), sort_files);
544 for (i = x = nlen = 0; i < n; i++) {
545 if (strlen(files[i].name) > nlen)
546 nlen = strlen(files[i].name);
548 if (x < nlen + strlen(files[i].st))
549 x = nlen + strlen(files[i].st);
552 cols = x + 1;
554 if (cols < strlen(path))
555 cols = strlen(path);
557 if (cols < strlen(HELP_PROMPT))
558 cols = strlen(HELP_PROMPT);
560 if (cols > COLS)
561 cols = COLS - 4;
563 cols += 2;
564 rows = (n + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : n + 4;
566 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
567 wbkgd(win, CP_MESSAGE_WINDOW);
568 panel = new_panel(win);
569 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
570 draw_prompt(win, rows - 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
571 cbreak();
572 noecho();
573 keypad(win, TRUE);
574 nl();
576 while (1) {
577 int c;
579 for (i = toppos, c = 2; i < n && c < rows - 2; i++, c++) {
580 if (i == selected) {
581 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
582 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
583 cols - nlen - 2 - 2, files[i].st);
584 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
585 continue;
588 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
589 cols - nlen - 2 - 2, files[i].st);
592 refresh_all();
593 c = wgetch(win);
595 switch (c) {
596 case KEY_HOME:
597 case KEY_END:
598 case KEY_UP:
599 case KEY_DOWN:
600 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
601 break;
602 case '\n':
603 goto gotitem;
604 break;
605 case KEY_ESCAPE:
606 cleanup(win, panel, files);
607 file[0] = 0;
608 goto done;
609 break;
610 case KEY_F(1):
611 help(BROWSER_HELP, ANYKEY, file_browser_help);
612 break;
613 case '~':
614 strncpy(path, "~", sizeof(path));
615 cleanup(win, panel, files);
616 goto again;
617 break;
618 case CTRL('X'):
619 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
620 == NULL)
621 break;
623 if (tmp[strlen(tmp) - 1] == '/')
624 tmp[strlen(tmp) - 1] = 0;
626 strncpy(path, tmp, sizeof(path));
627 cleanup(win, panel, files);
628 goto again;
629 break;
630 default:
631 break;
635 gotitem:
636 strncpy(file, files[selected].path, sizeof(file));
637 cleanup(win, panel, files);
639 if (stat(file, &st) == -1) {
640 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
641 continue;
644 if (S_ISDIR(st.st_mode)) {
645 p = file + strlen(file) - 2;
647 if (strcmp(p, "..") == 0) {
648 p = file + strlen(file) - 3;
649 *p = 0;
651 if ((p = strrchr(file, '/')) != NULL)
652 file[strlen(file) - strlen(p)] = 0;
655 strncpy(path, file, sizeof(path));
656 goto again;
659 if (S_ISREG(st.st_mode))
660 break;
662 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
665 done:
666 curs_set(cursor);
667 return (*file) ? file : NULL;
670 static int init_country_codes()
672 FILE *fp;
673 char line[LINE_MAX], *s;
674 int cindex = 0;
676 if ((fp = fopen(config.ccfile, "r")) == NULL) {
677 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
678 return 1;
681 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
682 char *tmp;
684 if ((tmp = strsep(&s, " ")) == NULL)
685 continue;
687 s = trim(s);
688 tmp = trim(tmp);
690 if (!s || !tmp)
691 continue;
693 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
694 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
695 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
696 cindex++;
699 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
700 fclose(fp);
702 return 0;
705 char *country_codes(void *arg)
707 WINDOW *win;
708 PANEL *panel;
709 int i = 0, n;
710 int rows, cols;
711 char *tmp = NULL;
712 int len = 0;
713 int total;
714 int selected = 0;
715 int toppos = 0;
717 if (!ccodes) {
718 if (init_country_codes())
719 return NULL;
722 for (i = n = 0; ccodes[i].code && ccodes[i].code[0]; i++) {
723 n = strlen(ccodes[i].code) + strlen(ccodes[i].country);
725 if (len < n)
726 len = n;
729 total = i;
730 cols = len;
732 if (cols < strlen(HELP_PROMPT) + 21)
733 cols = strlen(HELP_PROMPT) + 21;
735 cols += 1;
737 if (cols > COLS)
738 cols = COLS - 4;
740 cols += 2;
741 rows = (i + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : i + 4;
742 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
743 panel = new_panel(win);
744 cbreak();
745 noecho();
746 keypad(win, TRUE);
747 nl();
748 wbkgd(win, CP_MESSAGE_WINDOW);
750 while (1) {
751 int c;
752 char buf[cols - 4];
754 wmove(win, 0, 0);
755 wclrtobot(win);
757 draw_window_title(win, CC_TITLE, cols, CP_MESSAGE_TITLE,
758 CP_MESSAGE_BORDER);
760 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
761 if (i == selected) {
762 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
763 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code,
764 ccodes[i].country);
765 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
766 continue;
769 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code, ccodes[i].country);
772 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR,
773 selected + 1, N_OF_N_STR, total, HELP_PROMPT);
774 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
775 refresh_all();
776 c = wgetch(win);
778 switch (c) {
779 case KEY_F(1):
780 help(CC_KEY_HELP, ANYKEY, cc_help);
781 break;
782 case KEY_HOME:
783 case KEY_END:
784 case KEY_UP:
785 case KEY_DOWN:
786 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
787 break;
788 case '\n':
789 tmp = ccodes[selected].code;
790 goto done;
791 break;
792 case KEY_ESCAPE:
793 tmp = NULL;
794 goto done;
795 break;
796 default:
797 break;
801 done:
802 del_panel(panel);
803 delwin(win);
804 return tmp;
807 static void add_custom_tags(TAG ***t)
809 int i;
810 int total = pgn_tag_total(config.tag);
812 if (!config.tag)
813 return;
815 for (i = 0; i < total; i++)
816 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
818 pgn_tag_sort(*t);
821 TAG **edit_tags(GAME g, BOARD b, int edit)
823 TAG **data = NULL;
824 struct tm tp;
825 int data_index = 0;
826 int len;
827 int selected = 0;
828 int n;
829 int toppos = 0;
831 /* Edit the backup copy, not the original in case the save fails. */
832 len = pgn_tag_total(g.tag);
834 for (n = 0; n < len; n++)
835 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
837 data_index = pgn_tag_total(data);
839 while (1) {
840 WINDOW *win;
841 PANEL *panel;
842 int i;
843 char buf[76] = {0};
844 char *tmp = NULL;
845 int rows, cols;
846 int nlen = 0;
848 data_index = pgn_tag_total(data);
850 for (i = cols = 0, n = 4; i < data_index; i++) {
851 n = strlen(data[i]->name);
853 if (nlen < n)
854 nlen = n;
856 if (data[i]->value)
857 n += strlen(data[i]->value);
858 else
859 n += strlen(UNKNOWN);
861 if (cols < n)
862 cols = n;
865 cols += nlen + 2;
867 if (cols > COLS)
868 cols = COLS - 2;
870 /* +14 for the extra prompt info. */
871 if (cols < strlen(HELP_PROMPT) + 14 + 2)
872 cols = strlen(HELP_PROMPT) + 14 + 2;
874 rows = (data_index + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 :
875 data_index + 4;
877 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
878 panel = new_panel(win);
879 cbreak();
880 noecho();
881 keypad(win, TRUE);
882 nl();
883 wbkgd(win, CP_MESSAGE_WINDOW);
884 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
885 cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
887 if (selected >= data_index - 1)
888 selected = data_index - 1;
890 while (1) {
891 int c;
892 TAG **tmppgn = NULL;
893 char *newtag = NULL;
895 for (i = toppos, c = 2; i < data_index && c < rows - 2; i++, c++) {
896 if (i == selected) {
897 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
898 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
899 cols - nlen - 2 - 2, (data[i]->value &&
900 data[i]->value[0]) ? data[i]->value : UNKNOWN);
901 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
902 continue;
905 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
906 cols - nlen - 2 - 2, (data[i]->value &&
907 data[i]->value[0]) ? data[i]->value : UNKNOWN);
910 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
911 selected + 1, N_OF_N_STR, data_index, HELP_PROMPT);
912 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
913 refresh_all();
914 c = wgetch(win);
916 switch (c) {
917 case CTRL('T'):
918 if (!edit)
919 break;
921 add_custom_tags(&data);
922 selected = data_index - 1;
923 toppos = data_index - (rows - 4);
924 goto cleanup;
925 break;
926 case KEY_F(1):
927 if (edit)
928 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
929 else
930 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
931 break;
932 case CTRL('R'):
933 if (!edit)
934 break;
936 if (selected <= 6) {
937 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
938 goto cleanup;
941 data_index = pgn_tag_total(data);
943 for (i = 0; i < data_index; i++) {
944 if (i == selected)
945 continue;
947 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
950 pgn_tag_free(data);
951 data = NULL;
953 for (i = 0; tmppgn[i]; i++)
954 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
956 pgn_tag_free(tmppgn);
958 if (selected >= data_index)
959 selected = data_index - 1;
961 if (selected > rows - 5)
962 toppos = selected - (rows - 5);
963 else
964 toppos -= (toppos) ? 1 : 0;
966 goto cleanup;
967 break;
968 case CTRL('A'):
969 if (!edit)
970 break;
972 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
973 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
974 == NULL)
975 break;
977 newtag[0] = toupper(newtag[0]);
979 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
980 strlen(PRESS_ENTER)) {
981 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
982 break;
985 for (i = 0; i < data_index; i++) {
986 if (strcasecmp(data[i]->name, newtag) == 0) {
987 selected = i;
988 goto gotitem;
992 pgn_tag_add(&data, newtag, NULL);
993 data_index = pgn_tag_total(data);
994 selected = data_index - 1;
995 set_menu_vars(c, rows - 4, data_index - 1, &selected,
996 &toppos);
997 goto gotitem;
998 break;
999 case KEY_HOME:
1000 case KEY_END:
1001 case KEY_UP:
1002 case KEY_DOWN:
1003 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1004 &toppos);
1005 break;
1006 case CTRL('F'):
1007 if (!edit)
1008 break;
1010 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1011 data_index = pgn_tag_total(data);
1012 selected = data_index - 1;
1013 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1014 &toppos);
1015 goto gotitem;
1016 break;
1017 case '\n':
1018 goto gotitem;
1019 break;
1020 case KEY_ESCAPE:
1021 del_panel(panel);
1022 delwin(win);
1023 goto done;
1024 break;
1025 default:
1026 break;
1030 gotitem:
1031 nlen = strlen(data[selected]->name) + 2;
1032 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1034 if (nlen > MAX_VALUE_WIDTH)
1035 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1036 else
1037 snprintf(buf, sizeof(buf), "%s \"%s\"",
1038 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1039 data[selected]->name);
1041 if (!edit) {
1042 if (!data[selected]->value)
1043 goto cleanup;
1045 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1046 goto cleanup;
1049 if (strcmp(data[selected]->name, "Date") == 0) {
1050 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1051 0, FIELD_TYPE_PGN_DATE);
1053 if (tmp) {
1054 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1055 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1056 goto cleanup;
1059 else
1060 goto cleanup;
1062 else if (strcmp(data[selected]->name, "Site") == 0) {
1063 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1064 country_codes, NULL, CTRL('t'), -1);
1066 if (!tmp)
1067 tmp = "?";
1069 else if (strcmp(data[selected]->name, "Round") == 0) {
1070 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1071 FIELD_TYPE_PGN_ROUND);
1073 if (!tmp) {
1074 if (gtotal > 1)
1075 tmp = "?";
1076 else
1077 tmp = "-";
1080 else if (strcmp(data[selected]->name, "Result") == 0) {
1081 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1082 0, -1);
1084 if (!tmp)
1085 tmp = "*";
1087 else {
1088 tmp = (data[selected]->value) ? data[selected]->value : NULL;
1089 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1092 len = (tmp) ? strlen(tmp) + 1 : 1;
1093 data[selected]->value = Realloc(data[selected]->value, len);
1094 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1096 cleanup:
1097 del_panel(panel);
1098 delwin(win);
1101 done:
1102 if (!edit) {
1103 pgn_tag_free(data);
1104 return NULL;
1107 return data;
1110 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1111 * game index number.
1113 int save_pgn(const char *filename, int isfifo, int saveindex)
1115 FILE *fp;
1116 char *mode = NULL;
1117 int c;
1118 char buf[FILENAME_MAX];
1119 struct stat st;
1120 int i;
1121 char *command = NULL;
1122 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1124 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1125 if (stat(config.savedirectory, &st) == -1) {
1126 if (errno == ENOENT) {
1127 if (mkdir(config.savedirectory, 0755) == -1) {
1128 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1129 strerror(errno));
1130 return 1;
1133 else {
1134 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1135 strerror(errno));
1136 return 1;
1140 stat(config.savedirectory, &st);
1142 if (!S_ISDIR(st.st_mode)) {
1143 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1144 return 1;
1147 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1148 filename = buf;
1151 /* This is a hack to resume an existing game when more than one game is
1152 * available. Also resuming a saved game and a game from history.
1154 // FIXME: may not need this when a FEN tag is supported (by the engine).
1155 if (isfifo)
1156 mode = "w";
1157 else {
1158 if (access(filename, W_OK) == 0) {
1159 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1160 "%s \"%s\"", E_FILEEXISTS, filename);
1162 switch (c) {
1163 case 'a':
1164 if (pgn_is_compressed(filename)) {
1165 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1166 return 1;
1169 mode = "a";
1170 break;
1171 case 'o':
1172 mode = "w+";
1173 break;
1174 default:
1175 return 1;
1178 else
1179 mode = "a";
1182 if (command) {
1183 if ((fp = popen(command, "w")) == NULL) {
1184 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1185 return 1;
1188 else {
1189 if ((fp = fopen(filename, mode)) == NULL) {
1190 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1191 return 1;
1195 if (isfifo)
1196 pgn_write(fp, game[saveindex]);
1197 else {
1198 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1199 pgn_write(fp, game[i]);
1202 if (command)
1203 pclose(fp);
1204 else
1205 fclose(fp);
1207 if (!isfifo && saveindex == -1)
1208 strncpy(loadfile, filename, sizeof(loadfile));
1210 return 0;
1213 char *random_agony(GAME g)
1215 static int n;
1216 FILE *fp;
1217 char line[LINE_MAX];
1219 if (n == -1 || !config.agony || !curses_initialized ||
1220 (g.mode == MODE_HISTORY && !config.historyagony))
1221 return NULL;
1223 if (!agony) {
1224 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1225 n = -1;
1226 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1227 return NULL;
1230 while (!feof(fp)) {
1231 if (fscanf(fp, " %[^\n] ", line) == 1) {
1232 agony = Realloc(agony, (n + 2) * sizeof(char *));
1233 agony[n++] = strdup(trim(line));
1237 agony[n] = NULL;
1238 fclose(fp);
1240 if (agony[0] == NULL || !n) {
1241 n = -1;
1242 return NULL;
1246 return agony[random() % n];
1249 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1251 if (pgn_piece_to_int(piece) == ROOK && col == 7
1252 && row == 7 &&
1253 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1254 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1255 if (mod)
1256 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1257 return 1;
1259 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1260 && row == 7 &&
1261 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1262 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1263 if (mod)
1264 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1265 return 1;
1267 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1268 && row == 0 &&
1269 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1270 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1271 if (mod)
1272 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1273 return 1;
1275 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1276 && row == 0 &&
1277 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1278 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1279 if (mod)
1280 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1281 return 1;
1283 else if (pgn_piece_to_int(piece) == KING && col == 4
1284 && row == 7 &&
1285 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1286 TEST_FLAG(g->flags, GF_WK_CASTLE))
1288 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1289 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1290 if (mod) {
1291 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1292 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1293 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1294 else
1295 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1297 return 1;
1299 else if (pgn_piece_to_int(piece) == KING && col == 4
1300 && row == 0 &&
1301 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1302 TEST_FLAG(g->flags, GF_BK_CASTLE))
1304 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1305 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1306 if (mod) {
1307 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1308 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1309 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1310 else
1311 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1313 return 1;
1316 return 0;
1319 static void draw_board(GAME *g, int details)
1321 int row, col;
1322 int bcol = 0, brow = 0;
1323 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1324 int ncols = 0, offset = 1;
1325 unsigned coords_y = 8;
1327 if (g->mode != MODE_PLAY && g->mode != MODE_EDIT)
1328 update_cursor(*g, g->hindex);
1330 for (row = 0; row < maxy; row++) {
1331 bcol = 0;
1333 for (col = 0; col < maxx; col++) {
1334 int attrwhich = -1;
1335 chtype attrs = 0;
1336 unsigned char piece;
1338 if (row == 0 || row == maxy - 2) {
1339 if (col == 0)
1340 mvwaddch(boardw, row, col,
1341 LINE_GRAPHIC((row) ?
1342 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1343 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1344 else if (col == maxx - 2)
1345 mvwaddch(boardw, row, col,
1346 LINE_GRAPHIC((row) ?
1347 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1348 ACS_URCORNER | CP_BOARD_GRAPHICS));
1349 else if (!(col % 4))
1350 mvwaddch(boardw, row, col,
1351 LINE_GRAPHIC((row) ?
1352 ACS_BTEE | CP_BOARD_GRAPHICS :
1353 ACS_TTEE | CP_BOARD_GRAPHICS));
1354 else {
1355 if (col != maxx - 1)
1356 mvwaddch(boardw, row, col,
1357 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1360 continue;
1363 if ((row % 2) && col == maxx - 1 && coords_y) {
1364 wattron(boardw, CP_BOARD_COORDS);
1365 mvwprintw(boardw, row, col, "%d", coords_y--);
1366 wattroff(boardw, CP_BOARD_COORDS);
1367 continue;
1370 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1371 if (!(row % 2))
1372 mvwaddch(boardw, row, col,
1373 LINE_GRAPHIC((col) ?
1374 ACS_RTEE | CP_BOARD_GRAPHICS :
1375 ACS_LTEE | CP_BOARD_GRAPHICS));
1376 else
1377 mvwaddch(boardw, row, col,
1378 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1380 continue;
1383 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1384 mvwaddch(boardw, row, col,
1385 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1386 continue;
1389 if (!(col % 4) && row != maxy - 1) {
1390 mvwaddch(boardw, row, col,
1391 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1392 continue;
1395 if ((row % 2)) {
1396 if ((col % 4)) {
1397 if (ncols++ == 8) {
1398 offset++;
1399 ncols = 1;
1402 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1403 && (offset % 2)))
1404 attrwhich = BLACK;
1405 else
1406 attrwhich = WHITE;
1408 if (config.validmoves && g->b[brow][bcol].valid) {
1409 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1410 CP_BOARD_MOVES_BLACK;
1412 else
1413 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1414 CP_BOARD_BLACK;
1416 if (row == ROWTOMATRIX(c_row) && col ==
1417 COLTOMATRIX(c_col)) {
1418 attrs = CP_BOARD_CURSOR;
1421 if (row == ROWTOMATRIX(sp.row) &&
1422 col == COLTOMATRIX(sp.col)) {
1423 attrs = CP_BOARD_SELECTED;
1426 if (row == maxy - 1)
1427 attrs = 0;
1429 mvwaddch(boardw, row, col, ' ' | attrs);
1431 if (row == maxy - 1)
1432 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1433 else {
1434 if (details && g->b[row / 2][bcol].enpassant)
1435 piece = 'x';
1436 else
1437 piece = g->b[row / 2][bcol].icon;
1439 if (details && castling_state(g, g->b, brow, bcol,
1440 piece, 0))
1441 attrs |= A_REVERSE;
1443 if (g->side == WHITE && isupper(piece))
1444 attrs |= A_BOLD;
1445 else if (g->side == BLACK && islower(piece))
1446 attrs |= A_BOLD;
1448 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1450 CLEAR_FLAG(attrs, A_BOLD);
1451 CLEAR_FLAG(attrs, A_REVERSE);
1454 waddch(boardw, ' ' | attrs);
1455 col += 2;
1456 bcol++;
1459 else {
1460 if (col != maxx - 1)
1461 mvwaddch(boardw, row, col,
1462 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1466 brow = row / 2;
1470 void invalid_move(int n, const char *m)
1472 if (curses_initialized)
1473 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1474 else
1475 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1478 /* Convert the selected piece to SAN format and validate it. */
1479 static char *board_to_san(GAME *g, BOARD b)
1481 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1482 int piece;
1483 int promo;
1484 struct userdata_s *d = g->data;
1486 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1487 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1489 p = str;
1490 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1492 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1493 (sp.destrow == 1 && g->turn == BLACK))) {
1494 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1496 if (pgn_piece_to_int(promo) == -1)
1497 return NULL;
1499 p = str + strlen(str);
1500 *p++ = toupper(promo);
1501 *p = '\0';
1504 p = str;
1506 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1507 if (pgn_validate_move(g, b, &p)) {
1508 invalid_move(gindex + 1, p);
1509 return NULL;
1512 return p;
1515 if (pgn_validate_only(g, b, &p)) {
1516 invalid_move(gindex + 1, p);
1517 return NULL;
1520 return p;
1523 static int move_to_engine(GAME *g, BOARD b)
1525 char *p;
1526 struct userdata_s *d = g->data;
1528 if ((p = board_to_san(g, b)) == NULL)
1529 return 0;
1531 sp.row = sp.col = sp.icon = 0;
1533 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1534 pgn_history_add(g, p);
1535 pgn_switch_turn(g);
1536 SET_FLAG(g->flags, GF_MODIFIED);
1537 update_all(*g);
1538 return 1;
1541 send_to_engine(g, "%s\n", p);
1542 return 1;
1545 static void update_clock(int n, int *h, int *m, int *s)
1547 *h = n / 3600;
1548 *m = (n % 3600) / 60;
1549 *s = (n % 3600) % 60;
1551 return;
1554 void update_status_window(GAME g)
1556 int i = 0;
1557 char *buf;
1558 char tmp[15], *engine, *mode;
1559 int w;
1560 int h, m, s;
1561 char *p;
1562 int maxy, maxx;
1563 int len;
1564 struct userdata_s *d = g.data;
1566 getmaxyx(statusw, maxy, maxx);
1567 w = maxx - 2 - 8;
1568 len = maxx - 2;
1569 buf = Malloc(len);
1571 *tmp = '\0';
1572 p = tmp;
1574 if (TEST_FLAG(g.flags, GF_DELETE)) {
1575 *p++ = '(';
1576 *p++ = 'x';
1577 i++;
1580 if (TEST_FLAG(g.flags, GF_PERROR)) {
1581 if (!i)
1582 *p++ = '(';
1583 else
1584 *p++ = '/';
1586 *p++ = '!';
1587 i++;
1590 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1591 if (!i)
1592 *p++ = '(';
1593 else
1594 *p++ = '/';
1596 *p++ = '*';
1597 i++;
1600 if (*tmp != '\0')
1601 *p++ = ')';
1603 *p = '\0';
1605 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1606 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1607 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1608 (*tmp) ? tmp : "");
1609 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1611 switch (g.mode) {
1612 case MODE_HISTORY:
1613 mode = MODE_HISTORY_STR;
1614 break;
1615 case MODE_EDIT:
1616 mode = MODE_EDIT_STR;
1617 break;
1618 case MODE_PLAY:
1619 mode = MODE_PLAY_STR;
1620 break;
1621 default:
1622 mode = UNKNOWN;
1623 break;
1626 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1628 if (g.mode == MODE_PLAY) {
1629 if (TEST_FLAG(d->flags, CF_HUMAN))
1630 strncat(buf, " (human/human)", len - 1);
1631 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1632 strncat(buf, " (engine/engine)", len - 1);
1633 else
1634 strncat(buf, " (human/engine)", len - 1);
1637 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1639 if (d->engine) {
1640 switch (d->engine->status) {
1641 case ENGINE_THINKING:
1642 engine = ENGINE_PONDER_STR;
1643 break;
1644 case ENGINE_READY:
1645 engine = ENGINE_READY_STR;
1646 break;
1647 case ENGINE_INITIALIZING:
1648 engine = ENGINE_INITIALIZING_STR;
1649 break;
1650 case ENGINE_OFFLINE:
1651 engine = ENGINE_OFFLINE_STR;
1652 break;
1653 default:
1654 engine = UNKNOWN;
1655 break;
1658 else
1659 engine = ENGINE_OFFLINE_STR;
1661 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1662 wattron(statusw, CP_STATUS_ENGINE);
1663 mvwaddstr(statusw, 5, 9, engine);
1664 wattroff(statusw, CP_STATUS_ENGINE);
1666 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1667 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1669 strncpy(tmp, WHITE_STR, sizeof(tmp));
1670 tmp[0] = toupper(tmp[0]);
1671 update_clock(g.moveclock, &h, &m, &s);
1672 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1673 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1675 strncpy(tmp, BLACK_STR, sizeof(tmp));
1676 tmp[0] = toupper(tmp[0]);
1677 update_clock(g.moveclock, &h, &m, &s);
1678 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1679 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1680 free(buf);
1682 for (i = 1; i < maxx - 4; i++)
1683 mvwprintw(statusw, maxy - 2, i, " ");
1685 if (!status.notify)
1686 status.notify = strdup(GAME_HELP_PROMPT);
1688 wattron(statusw, CP_STATUS_NOTIFY);
1689 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1690 status.notify);
1691 wattroff(statusw, CP_STATUS_NOTIFY);
1694 void update_history_window(GAME g)
1696 char buf[HISTORY_WIDTH - 1];
1697 HISTORY *h = NULL;
1698 int n, total;
1699 int t = pgn_history_total(g.hp);
1701 n = (g.hindex + 1) / 2;
1703 if (t % 2)
1704 total = (t + 1) / 2;
1705 else
1706 total = t / 2;
1708 if (t)
1709 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1710 (movestep == 1) ? HISTORY_PLY_STEP : "");
1711 else
1712 strncpy(buf, UNAVAILABLE, sizeof(buf));
1714 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1715 HISTORY_WIDTH - 13, buf);
1717 h = pgn_history_by_n(g.hp, g.hindex);
1718 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1719 n = 0;
1721 if (h && ((h->comment) || h->nag[0])) {
1722 strncat(buf, " (v", sizeof(buf));
1723 n++;
1726 if (h && h->rav) {
1727 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1728 n++;
1731 if (g.ravlevel) {
1732 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1733 n++;
1736 if (n)
1737 strncat(buf, ")", sizeof(buf));
1739 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1740 HISTORY_WIDTH - 13, buf);
1742 h = pgn_history_by_n(g.hp, game[gindex].hindex - 1);
1743 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1744 n = 0;
1746 if (h && ((h->comment) || h->nag[0])) {
1747 strncat(buf, " (V", sizeof(buf));
1748 n++;
1751 if (h && h->rav) {
1752 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1753 n++;
1756 if (g.ravlevel) {
1757 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1758 n++;
1761 if (n)
1762 strncat(buf, ")", sizeof(buf));
1764 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1765 HISTORY_WIDTH - 13, buf);
1768 void update_tag_window(TAG **t)
1770 int i;
1771 int w = TAG_WIDTH - 10;
1773 for (i = 0; i < 7; i++)
1774 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1777 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1779 int i;
1781 wattron(win, attr);
1783 for (i = 1; i < width - 1; i++)
1784 mvwaddch(win, y, i, ' ');
1786 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1787 wattroff(win, attr);
1790 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1791 chtype battr)
1793 int i;
1795 if (title) {
1796 wattron(win, attr);
1798 for (i = 1; i < width - 1; i++)
1799 mvwaddch(win, 1, i, ' ');
1801 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1802 wattroff(win, attr);
1805 wattron(win, battr);
1806 box(win, ACS_VLINE, ACS_HLINE);
1807 wattroff(win, battr);
1810 void append_enginebuf(char *line)
1812 int i = 0;
1814 if (enginebuf)
1815 for (i = 0; enginebuf[i]; i++);
1817 if (i >= LINES - 3) {
1818 free(enginebuf[0]);
1820 for (i = 0; enginebuf[i+1]; i++)
1821 enginebuf[i] = enginebuf[i+1];
1823 enginebuf[i] = strdup(line);
1825 else {
1826 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1827 enginebuf[i++] = strdup(line);
1828 enginebuf[i] = NULL;
1832 void update_engine_window()
1834 int i;
1836 if (!enginebuf)
1837 return;
1839 wmove(enginew, 0, 0);
1840 wclrtobot(enginew);
1842 if (enginebuf) {
1843 for (i = 0; enginebuf[i]; i++)
1844 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1847 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1848 CP_MESSAGE_BORDER);
1851 void toggle_engine_window()
1853 if (!enginew) {
1854 enginew = newwin(LINES, COLS, 0, 0);
1855 enginep = new_panel(enginew);
1856 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1857 CP_MESSAGE_BORDER);
1858 hide_panel(enginep);
1861 if (panel_hidden(enginep)) {
1862 update_engine_window();
1863 top_panel(enginep);
1864 refresh_all();
1866 else {
1867 hide_panel(enginep);
1868 refresh_all();
1872 void refresh_all()
1874 update_panels();
1875 doupdate();
1878 void update_all(GAME g)
1880 update_status_window(g);
1881 update_history_window(g);
1882 update_tag_window(g.tag);
1883 update_engine_window();
1886 static void game_next_prev(GAME g, int n, int count)
1888 if (gtotal < 2)
1889 return;
1891 if (n == 1) {
1892 if (gindex + count > gtotal - 1) {
1893 if (count != 1)
1894 gindex = gtotal - 1;
1895 else
1896 gindex = 0;
1898 else
1899 gindex += count;
1901 else {
1902 if (gindex - count < 0) {
1903 if (count != 1)
1904 gindex = 0;
1905 else
1906 gindex = gtotal - 1;
1908 else
1909 gindex -= count;
1913 static void delete_game(int which)
1915 GAME *g = NULL;
1916 int gi = 0;
1917 int i;
1919 for (i = 0; i < gtotal; i++) {
1920 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1921 pgn_free(game[i]);
1922 continue;
1925 g = Realloc(g, (gi + 1) * sizeof(GAME));
1926 memcpy(&g[gi], &game[i], sizeof(GAME));
1927 g[gi].tag = game[i].tag;
1928 g[gi].history = game[i].history;
1929 g[gi].hp = game[i].hp;
1930 gi++;
1933 game = g;
1934 gtotal = gi;
1936 if (which != -1) {
1937 if (which + 1 >= gtotal)
1938 gindex = gtotal - 1;
1939 else
1940 gindex = which;
1942 else
1943 gindex = gtotal - 1;
1945 game[gindex].hp = game[gindex].history;
1948 static int find_move_exp(GAME g, const char *str, int init, int which,
1949 int count)
1951 int i;
1952 int ret;
1953 static regex_t r;
1954 static int firstrun = 1;
1955 char errbuf[255];
1956 int incr;
1957 int found;
1959 if (init) {
1960 if (!firstrun)
1961 regfree(&r);
1963 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
1964 regerror(ret, &r, errbuf, sizeof(errbuf));
1965 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1966 return -1;
1969 firstrun = 1;
1972 incr = (which == 0) ? -1 : 1;
1974 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
1975 if (i == g.hindex - 1)
1976 break;
1978 if (i >= pgn_history_total(g.hp))
1979 i = 0;
1980 else if (i < 0)
1981 i = pgn_history_total(g.hp) - 1;
1983 // FIXME RAV
1984 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
1986 if (ret == 0) {
1987 if (count == ++found) {
1988 return i + 1;
1991 else {
1992 if (ret != REG_NOMATCH) {
1993 regerror(ret, &r, errbuf, sizeof(errbuf));
1994 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
1995 return -1;
2000 return -1;
2003 static int toggle_delete_flag(int n)
2005 int i, x;
2007 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2009 for (i = x = 0; i < gtotal; i++) {
2010 if (TEST_FLAG(game[i].flags, GF_DELETE))
2011 x++;
2014 if (x == gtotal) {
2015 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2016 CLEAR_FLAG(game[n].flags, GF_DELETE);
2017 return 1;
2020 return 0;
2023 static void edit_save_tags(GAME *g)
2025 TAG **t;
2027 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2028 return;
2030 pgn_tag_free(g->tag);
2031 g->tag = t;
2032 SET_FLAG(g->flags, GF_MODIFIED);
2033 pgn_tag_sort(g->tag);
2036 static int find_game_exp(char *str, int which, int count)
2038 char *nstr = NULL, *exp = NULL;
2039 regex_t nexp, vexp;
2040 int ret = -1;
2041 int g = 0;
2042 char buf[255], *tmp;
2043 char errbuf[255];
2044 int found = 0;
2045 int incr = (which == 0) ? -(1) : 1;
2047 strncpy(buf, str, sizeof(buf));
2048 tmp = buf;
2050 if (strstr(tmp, ":") != NULL) {
2051 nstr = strsep(&tmp, ":");
2053 if ((ret = regcomp(&nexp, nstr,
2054 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2055 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2056 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2057 ret = g = -1;
2058 goto cleanup;
2062 exp = tmp;
2064 if (exp == NULL)
2065 goto cleanup;
2067 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2068 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2069 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2070 ret = -1;
2071 goto cleanup;
2074 ret = -1;
2076 for (g = gindex + incr, found = 0; ; g += incr) {
2077 int t;
2079 if (g == gindex)
2080 break;
2082 if (g == gtotal)
2083 g = 0;
2084 else if (g < 0)
2085 g = gtotal - 1;
2087 for (t = 0; game[g].tag[t]; t++) {
2088 if (nstr) {
2089 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2090 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2091 if (count == ++found) {
2092 ret = g;
2093 goto cleanup;
2098 else {
2099 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2100 if (count == ++found) {
2101 ret = g;
2102 goto cleanup;
2108 ret = -1;
2111 cleanup:
2112 if (nstr)
2113 regfree(&nexp);
2115 if (g != -1)
2116 regfree(&vexp);
2118 return ret;
2122 * Updates the notification line in the status window then refreshes the
2123 * status window.
2125 void update_status_notify(GAME g, char *fmt, ...)
2127 va_list ap;
2128 #ifdef HAVE_VASPRINTF
2129 char *line;
2130 #else
2131 char line[COLS];
2132 #endif
2134 if (!fmt) {
2135 if (status.notify) {
2136 free(status.notify);
2137 status.notify = NULL;
2139 if (curses_initialized)
2140 update_status_window(g);
2143 return;
2146 va_start(ap, fmt);
2147 #ifdef HAVE_VASPRINTF
2148 vasprintf(&line, fmt, ap);
2149 #else
2150 vsnprintf(line, sizeof(line), fmt, ap);
2151 #endif
2152 va_end(ap);
2154 if (status.notify)
2155 free(status.notify);
2157 status.notify = strdup(line);
2159 #ifdef HAVE_VASPRINTF
2160 free(line);
2161 #endif
2162 if (curses_initialized)
2163 update_status_window(g);
2166 static void switch_side(GAME *g)
2168 g->side = (g->side == WHITE) ? BLACK : WHITE;
2171 int rav_next_prev(GAME *g, BOARD b, int n)
2173 // Next RAV.
2174 if (n) {
2175 if (g->hp[g->hindex]->rav == NULL)
2176 return 1;
2178 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2179 g->rav[g->ravlevel].hp = g->hp;
2180 g->rav[g->ravlevel].flags = g->flags;
2181 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2182 g->rav[g->ravlevel].hindex = g->hindex;
2183 g->hp = g->hp[g->hindex]->rav;
2184 g->hindex = 0;
2185 g->ravlevel++;
2186 return 0;
2189 if (g->ravlevel - 1 < 0)
2190 return 1;
2192 // Previous RAV.
2193 g->ravlevel--;
2194 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2195 free(g->rav[g->ravlevel].fen);
2196 g->hp = g->rav[g->ravlevel].hp;
2197 g->flags = g->rav[g->ravlevel].flags;
2198 g->hindex = g->rav[g->ravlevel].hindex;
2199 return 0;
2202 static void draw_window_decor()
2204 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2205 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2206 wbkgd(boardw, CP_BOARD_WINDOW);
2207 wbkgd(statusw, CP_STATUS_WINDOW);
2208 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2209 CP_STATUS_TITLE, CP_STATUS_BORDER);
2210 wbkgd(tagw, CP_TAG_WINDOW);
2211 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2212 CP_TAG_BORDER);
2213 wbkgd(historyw, CP_HISTORY_WINDOW);
2214 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2215 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2218 static void do_window_resize()
2220 if (LINES < 24 || COLS < 80)
2221 return;
2223 resizeterm(LINES, COLS);
2224 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2225 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2226 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2227 wmove(historyw, 0, 0);
2228 wclrtobot(historyw);
2229 wmove(tagw, 0, 0);
2230 wclrtobot(tagw);
2231 wmove(statusw, 0, 0);
2232 wclrtobot(statusw);
2233 draw_window_decor();
2234 update_all(game[gindex]);
2237 static void historymode_keys(chtype);
2238 static int playmode_keys(chtype c)
2240 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2241 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2242 chtype p;
2243 int w, x;
2244 char *tmp;
2245 struct userdata_s *d = game[gindex].data;
2247 switch (c) {
2248 case 'H':
2249 TOGGLE_FLAG(d->flags, CF_HUMAN);
2251 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2252 pgn_history_total(game[gindex].hp)) {
2253 pgn_tag_add(&game[gindex].tag, "FEN",
2254 pgn_game_to_fen(game[gindex], game[gindex].b));
2255 x = pgn_tag_find(game[gindex].tag, "FEN");
2257 if (start_chess_engine(&game[gindex]) <= 0) {
2258 send_to_engine(&game[gindex], "setboard %s\n",
2259 game[gindex].tag[x]->value);
2260 d->engine->status = ENGINE_READY;
2264 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2266 if (d->engine)
2267 d->engine->status = ENGINE_READY;
2269 update_all(game[gindex]);
2270 break;
2271 case 'E':
2272 if (!d)
2273 break;
2275 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2276 CLEAR_FLAG(d->flags, CF_HUMAN);
2278 if (d->engine && !TEST_FLAG(d->flags, CF_ENGINE_LOOP))
2279 d->engine->status = ENGINE_READY;
2281 update_all(game[gindex]);
2282 break;
2283 case '|':
2284 if (!d->engine)
2285 break;
2287 if (d->engine->status == ENGINE_OFFLINE)
2288 break;
2290 x = d->engine->status;
2292 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2293 send_to_engine(&game[gindex], "%s\n", tmp);
2294 d->engine->status = x;
2295 break;
2296 case '\015':
2297 case '\n':
2298 pushkey = keycount = 0;
2299 update_status_notify(game[gindex], NULL);
2301 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2302 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2303 beep();
2304 break;
2307 if (!sp.icon)
2308 break;
2310 sp.destrow = c_row;
2311 sp.destcol = c_col;
2313 if (editmode) {
2314 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2315 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2316 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
2317 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2318 sp.icon = sp.row = sp.col = 0;
2319 break;
2322 if (move_to_engine(&game[gindex], game[gindex].b)) {
2323 if (config.validmoves)
2324 pgn_reset_valid_moves(game[gindex].b);
2326 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2327 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2328 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2332 break;
2333 case ' ':
2334 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2335 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2336 if (start_chess_engine(&game[gindex])) {
2337 sp.icon = 0;
2338 break;
2341 x = pgn_tag_find(game[gindex].tag, "FEN");
2342 w = pgn_tag_find(game[gindex].tag, "SetUp");
2344 if ((w >= 0 && x >= 0 && atoi(game[gindex].tag[w]->value) == 1)
2345 || (x >= 0 && w == -1)) {
2346 send_to_engine(&game[gindex], "setboard %s\n",
2347 game[gindex].tag[x]->value);
2348 d->engine->status = ENGINE_READY;
2352 if (sp.icon || (!editmode && d->engine &&
2353 d->engine->status == ENGINE_THINKING)) {
2354 beep();
2355 break;
2358 sp.icon = mvwinch(boardw, ROWTOMATRIX(c_row),
2359 COLTOMATRIX(c_col)+1) & A_CHARTEXT;
2361 if (sp.icon == ' ') {
2362 sp.icon = 0;
2363 break;
2366 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2367 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2368 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2369 sp.icon = 0;
2370 break;
2373 sp.row = c_row;
2374 sp.col = c_col;
2376 if (!editmode && config.validmoves)
2377 pgn_get_valid_moves(&game[gindex], game[gindex].b, sp.row,
2378 sp.col);
2380 paused = 0;
2381 break;
2382 case 'w':
2383 send_to_engine(&game[gindex], "\nswitch\n");
2384 switch_side(&game[gindex]);
2385 update_status_window(game[gindex]);
2386 break;
2387 case 'u':
2388 if (!pgn_history_total(game[gindex].hp))
2389 break;
2391 if (d->engine && d->engine->status == ENGINE_READY) {
2392 send_to_engine(&game[gindex], "remove\n");
2393 d->engine->status = ENGINE_READY;
2396 game[gindex].hindex -= 2;
2397 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2398 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2399 pgn_board_update(&game[gindex], game[gindex].b,
2400 game[gindex].hindex);
2401 update_history_window(game[gindex]);
2402 break;
2403 case 'a':
2404 historymode_keys(c);
2405 break;
2406 case 'd':
2407 board_details = (board_details) ? 0 : 1;
2408 break;
2409 case 'p':
2410 paused = (paused) ? 0 : 1;
2411 break;
2412 case 'g':
2413 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2414 start_chess_engine(&game[gindex]);
2416 send_to_engine(&game[gindex], "go\n");
2417 break;
2418 default:
2419 if (!d->engine)
2420 break;
2422 if (config.keys) {
2423 for (x = 0; config.keys[x]; x++) {
2424 if (config.keys[x]->c == c) {
2425 send_to_engine(&game[gindex], "%s\n",
2426 config.keys[x]->str);
2427 d->engine->status = ENGINE_READY;
2428 break;
2432 break;
2435 return 0;
2438 static void editmode_keys(chtype c)
2440 switch (c) {
2441 case '\015':
2442 case '\n':
2443 case ' ':
2444 playmode_keys(c);
2445 break;
2446 case 'd':
2447 if (sp.icon)
2448 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2449 else
2450 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2452 sp.icon = sp.row = sp.col = 0;
2453 break;
2454 case 'w':
2455 pgn_switch_turn(&game[gindex]);
2456 switch_side(&game[gindex]);
2457 update_all(game[gindex]);
2458 break;
2459 case 'c':
2460 castling_state(&game[gindex], game[gindex].b, ROWTOBOARD(c_row),
2461 COLTOBOARD(c_col),
2462 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon, 1);
2463 break;
2464 case 'i':
2465 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2466 GAME_EDIT_TEXT);
2468 if (pgn_piece_to_int(c) == -1)
2469 break;
2471 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = c;
2472 break;
2473 case 'p':
2474 if (c_row == 6 || c_row == 3) {
2475 pgn_reset_enpassant(game[gindex].b);
2476 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].enpassant = 1;
2478 break;
2479 default:
2480 break;
2484 static void historymode_keys(chtype c)
2486 int n, len;
2487 char *tmp, *buf;
2488 static char moveexp[255] = {0};
2490 switch (c) {
2491 case ' ':
2492 movestep = (movestep == 1) ? 2 : 1;
2493 update_history_window(game[gindex]);
2494 break;
2495 case KEY_UP:
2496 pgn_history_next(&game[gindex], game[gindex].b, (keycount > 0) ?
2497 config.jumpcount * keycount * movestep :
2498 config.jumpcount * movestep);
2499 update_all(game[gindex]);
2500 break;
2501 case KEY_DOWN:
2502 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2503 config.jumpcount * keycount * movestep :
2504 config.jumpcount * movestep);
2505 update_all(game[gindex]);
2506 break;
2507 case KEY_LEFT:
2508 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2509 keycount * movestep : movestep);
2510 update_all(game[gindex]);
2511 break;
2512 case KEY_RIGHT:
2513 pgn_history_next(&game[gindex], game[gindex].b, (keycount) ?
2514 keycount * movestep : movestep);
2515 update_all(game[gindex]);
2516 break;
2517 case 'a':
2518 n = game[gindex].hindex;
2520 if (n && game[gindex].hp[n - 1]->move)
2521 n--;
2522 else
2523 break;
2525 buf = Malloc(COLS);
2526 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2527 game[gindex].hp[n]->move);
2529 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2530 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2531 -1);
2532 free(buf);
2534 if (!tmp && (!game[gindex].hp[n]->comment ||
2535 !*game[gindex].hp[n]->comment))
2536 break;
2537 else if (tmp && game[gindex].hp[n]->comment) {
2538 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2539 break;
2542 len = (tmp) ? strlen(tmp) + 1 : 1;
2543 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2544 len);
2545 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2546 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2547 update_all(game[gindex]);
2548 break;
2549 case ']':
2550 case '[':
2551 case '/':
2552 if (pgn_history_total(game[gindex].hp) < 2)
2553 break;
2555 n = 0;
2557 if (!*moveexp || c == '/') {
2558 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2559 break;
2561 strncpy(moveexp, tmp, sizeof(moveexp));
2562 n = 1;
2565 if ((n = find_move_exp(game[gindex], moveexp, n,
2566 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2567 == -1)
2568 break;
2570 game[gindex].hindex = n;
2571 pgn_board_update(&game[gindex], game[gindex].b, game[gindex].hindex);
2572 update_all(game[gindex]);
2573 break;
2574 case 'v':
2575 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2576 break;
2577 case 'V':
2578 if (game[gindex].hindex - 1 >= 0)
2579 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2580 break;
2581 case '-':
2582 case '+':
2583 rav_next_prev(&game[gindex], game[gindex].b, (c == '-') ? 0 : 1);
2584 update_all(game[gindex]);
2585 break;
2586 case 'j':
2587 if (pgn_history_total(game[gindex].hp) < 2)
2588 break;
2590 /* FIXME field validation
2591 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2592 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2593 game[gindex].htotal)) == NULL)
2594 break;
2597 if (!keycount) {
2598 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2599 NULL, NULL, NULL, 0, -1)) == NULL)
2600 break;
2602 if (!isinteger(tmp))
2603 break;
2605 n = atoi(tmp);
2607 else
2608 n = keycount;
2610 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2611 break;
2613 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2614 pgn_board_update(&game[gindex], game[gindex].b,
2615 game[gindex].hindex);
2616 update_all(game[gindex]);
2617 break;
2618 default:
2619 break;
2623 static void cleanup_all_games()
2625 int i;
2627 for (i = 0; i < gtotal; i++) {
2628 struct userdata_s *d;
2630 if (game[i].data) {
2631 stop_engine(&game[i]);
2632 d = game[i].data;
2633 free(game[i].data);
2634 game[i].data = NULL;
2639 void update_loading_window()
2641 if (!loadingw) {
2642 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2643 loadingp = new_panel(loadingw);
2644 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2647 wmove(loadingw, 0, 0);
2648 wclrtobot(loadingw);
2649 wattron(loadingw, CP_MESSAGE_BORDER);
2650 box(loadingw, ACS_VLINE, ACS_HLINE);
2651 wattroff(loadingw, CP_MESSAGE_BORDER);
2652 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2653 11 + strlen(itoa(gtotal))), "Loading... %i", gtotal);
2654 update_panels();
2655 doupdate();
2658 void init_userdata()
2660 int i;
2662 for (i = 0; i < gtotal; i++) {
2663 struct userdata_s *d = NULL;
2665 d = Calloc(1, sizeof(struct userdata_s));
2666 game[i].data = d;
2667 d->n = i;
2671 // Global and other keys.
2672 static int globalkeys(chtype c)
2674 static char gameexp[255] = {0};
2675 FILE *fp;
2676 char *tmp, *p;
2677 int n, i;
2678 char tfile[FILENAME_MAX];
2679 struct userdata_s *d = game[gindex].data;
2681 switch (c) {
2682 case 'W':
2683 toggle_engine_window();
2684 break;
2685 case KEY_F(10):
2686 cmessage("ABOUT", ANYKEY, "%s\n%s with %i colors and %i "
2687 "color pairs\nCopyright 2002-2006 %s", PACKAGE_STRING,
2688 curses_version(), COLORS, COLOR_PAIRS, PACKAGE_BUGREPORT);
2689 break;
2690 case 'h':
2691 if (game[gindex].mode != MODE_HISTORY) {
2692 if (!pgn_history_total(game[gindex].hp) ||
2693 (d->engine && d->engine->status == ENGINE_THINKING))
2694 return 1;
2696 game[gindex].mode = MODE_HISTORY;
2697 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2698 update_all(game[gindex]);
2699 return 1;
2702 // FIXME
2703 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2704 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2705 return 1;
2708 // FIXME Resuming from previous history could append to a RAV.
2709 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2710 if (!pushkey) {
2711 if ((c = message(NULL, YESNO, "%s",
2712 GAME_RESUME_HISTORY_TEXT)) != 'y')
2713 return 1;
2716 else {
2717 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2718 return 1;
2721 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2722 d->engine->status == ENGINE_OFFLINE)) {
2723 if (start_chess_engine(&game[gindex]) < 0)
2724 return 1;
2726 pushkey = 'h';
2727 return 1;
2730 pushkey = 0;
2731 oldhistorytotal = pgn_history_total(game[gindex].hp);
2732 game[gindex].mode = MODE_PLAY;
2733 update_all(game[gindex]);
2734 return 1;
2735 case '>':
2736 case '<':
2737 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2738 keycount : 1);
2740 if (delete_count) {
2741 markend = delete_count;
2742 pushkey = 'x';
2743 delete_count = 0;
2746 if (game[gindex].mode != MODE_EDIT) {
2747 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2749 update_all(game[gindex]);
2750 update_tag_window(game[gindex].tag);
2751 return 1;
2752 case '}':
2753 case '{':
2754 case '?':
2755 if (gtotal < 2)
2756 return 1;
2758 if (!*gameexp || c == '?') {
2759 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2760 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2761 NULL, 0, -1)) == NULL)
2762 return 1;
2764 strncpy(gameexp, tmp, sizeof(gameexp));
2767 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2768 ? keycount : 1)) ==
2770 return 1;
2772 gindex = n;
2774 if (pgn_history_total(game[gindex].hp))
2775 game[gindex].mode = MODE_HISTORY;
2777 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2778 update_all(game[gindex]);
2779 update_tag_window(game[gindex].tag);
2780 return 1;
2781 case 'J':
2782 if (gtotal < 2)
2783 return 1;
2785 /* FIXME field validation
2786 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2787 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2788 == NULL)
2789 return 1;
2792 if (!keycount) {
2793 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2794 NULL, NULL, 0, -1)) == NULL)
2795 return 1;
2797 if (!isinteger(tmp))
2798 return 1;
2800 i = atoi(tmp);
2802 else
2803 i = keycount;
2805 if (--i > gtotal - 1 || i < 0)
2806 return 1;
2808 gindex = i;
2809 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2810 update_all(game[gindex]);
2811 update_tag_window(game[gindex].tag);
2812 return 1;
2813 case 'x':
2814 pushkey = 0;
2816 if (gtotal < 2)
2817 return 1;
2819 if (keycount && !delete_count) {
2820 markstart = gindex;
2821 delete_count = keycount;
2822 update_status_notify(game[gindex], "%s (delete)",
2823 status.notify);
2824 return 1;
2827 if (markstart >= 0 && markend >= 0) {
2828 if (markstart > markend) {
2829 i = markstart;
2830 markstart = markend;
2831 markend = i;
2834 for (i = markstart; i <= markend; i++) {
2835 if (toggle_delete_flag(i))
2836 return 1;
2839 else {
2840 if (toggle_delete_flag(gindex))
2841 return 1;
2844 markstart = markend = -1;
2845 update_status_window(game[gindex]);
2846 return 1;
2847 case 'X':
2848 if (gtotal < 2) {
2849 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2850 return 1;
2853 tmp = NULL;
2855 for (i = n = 0; i < gtotal; i++) {
2856 if (TEST_FLAG(game[i].flags, GF_DELETE))
2857 n++;
2860 if (!n)
2861 tmp = GAME_DELETE_GAME_TEXT;
2862 else {
2863 if (n == gtotal) {
2864 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2865 return 1;
2868 tmp = GAME_DELETE_ALL_TEXT;
2871 if (config.deleteprompt) {
2872 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2873 return 1;
2876 delete_game((!n) ? gindex : -1);
2878 if (pgn_history_total(game[gindex].hp))
2879 game[gindex].mode = MODE_HISTORY;
2881 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2882 update_all(game[gindex]);
2883 update_tag_window(game[gindex].tag);
2884 return 1;
2885 case 'T':
2886 edit_save_tags(&game[gindex]);
2887 update_all(game[gindex]);
2888 update_tag_window(game[gindex].tag);
2889 return 1;
2890 case 't':
2891 edit_tags(game[gindex], game[gindex].b, 0);
2892 return 1;
2893 case 'r':
2894 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2895 BROWSER_PROMPT, file_browser, NULL, '\t',
2896 -1)) == NULL)
2897 return 1;
2899 if ((tmp = word_expand(tmp)) == NULL)
2900 break;
2902 if ((fp = pgn_open(tmp)) == NULL) {
2903 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2904 return 1;
2907 if (pgn_parse(fp))
2908 return 1;
2910 del_panel(loadingp);
2911 delwin(loadingw);
2912 loadingw = NULL;
2913 loadingp = NULL;
2914 init_userdata();
2915 strncpy(loadfile, tmp, sizeof(loadfile));
2917 if (pgn_history_total(game[gindex].hp))
2918 game[gindex].mode = MODE_HISTORY;
2920 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2921 update_all(game[gindex]);
2922 update_tag_window(game[gindex].tag);
2923 return 1;
2924 case 'S':
2925 case 's':
2926 i = -1;
2928 if (gtotal > 1) {
2929 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2930 GAME_SAVE_MULTI_TEXT);
2932 if (n == 'c')
2933 i = gindex;
2934 else if (n == 'a')
2935 i = -1;
2936 else {
2937 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2938 return 1;
2942 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2943 BROWSER_PROMPT, file_browser, NULL,
2944 '\t', -1)) == NULL) {
2945 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2946 return 1;
2949 if ((tmp = word_expand(tmp)) == NULL)
2950 break;
2952 if (pgn_is_compressed(tmp)) {
2953 p = tmp + strlen(tmp) - 1;
2955 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
2956 *(p-3) != '.') {
2957 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2958 tmp = tfile;
2961 else {
2962 if ((p = strchr(tmp, '.')) != NULL) {
2963 if (strcmp(p, ".pgn") != 0) {
2964 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2965 tmp = tfile;
2968 else {
2969 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2970 tmp = tfile;
2974 if (save_pgn(tmp, 0, i)) {
2975 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
2976 return 1;
2979 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
2980 update_all(game[gindex]);
2981 return 1;
2982 case KEY_F(1):
2983 n = 0;
2985 switch (game[gindex].mode) {
2986 case MODE_PLAY:
2987 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
2988 break;
2989 case MODE_HISTORY:
2990 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
2991 break;
2992 case MODE_EDIT:
2993 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
2994 break;
2995 default:
2996 break;
2999 while (c == KEY_F(1)) {
3000 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3001 mainhelp);
3003 switch (c) {
3004 case 'h':
3005 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3006 break;
3007 case 'p':
3008 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3009 break;
3010 case 'e':
3011 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3012 break;
3013 case 'g':
3014 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3015 break;
3016 default:
3017 break;
3021 return 1;
3022 case 'n':
3023 case 'N':
3024 if (c == 'N') {
3025 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3026 return 1;
3029 if (c == 'n') {
3030 pgn_new_game();
3031 add_custom_tags(&game[gindex].tag);
3032 d = Calloc(1, sizeof(struct userdata_s));
3033 game[gindex].data = d;
3035 else {
3036 cleanup_all_games();
3037 pgn_parse(NULL);
3038 add_custom_tags(&game[gindex].tag);
3039 pgn_board_init(game[gindex].b);
3040 d = Calloc(1, sizeof(struct userdata_s));
3041 game[gindex].data = d;
3044 game[gindex].mode = MODE_PLAY;
3045 c_row = (game[gindex].side == WHITE) ? 2 : 7;
3046 c_col = 4;
3047 update_status_notify(game[gindex], NULL);
3048 update_all(game[gindex]);
3049 update_tag_window(game[gindex].tag);
3050 return 1;
3051 case CTRL('L'):
3052 endwin();
3053 keypad(boardw, TRUE);
3054 refresh_all();
3055 return 1;
3056 case KEY_ESCAPE:
3057 sp.icon = sp.row = sp.col = 0;
3058 markend = markstart = 0;
3060 if (keycount) {
3061 keycount = 0;
3062 update_status_notify(game[gindex], NULL);
3065 if (config.validmoves)
3066 pgn_reset_valid_moves(game[gindex].b);
3068 return 1;
3069 case '0' ... '9':
3070 n = c - '0';
3072 if (keycount)
3073 keycount = keycount * 10 + n;
3074 else
3075 keycount = n;
3077 update_status_notify(game[gindex], "Repeat %i", keycount);
3078 return -1;
3079 case KEY_UP:
3080 if (game[gindex].mode == MODE_HISTORY)
3081 return 0;
3083 if (keycount) {
3084 c_row += keycount;
3085 pushkey = '\n';
3087 else
3088 c_row++;
3090 if (c_row > 8)
3091 c_row = 1;
3093 return 1;
3094 case KEY_DOWN:
3095 if (game[gindex].mode == MODE_HISTORY)
3096 return 0;
3098 if (keycount) {
3099 c_row -= keycount;
3100 pushkey = '\n';
3101 update_status_notify(game[gindex], NULL);
3103 else
3104 c_row--;
3106 if (c_row < 1)
3107 c_row = 8;
3109 return 1;
3110 case KEY_LEFT:
3111 if (game[gindex].mode == MODE_HISTORY)
3112 return 0;
3114 if (keycount) {
3115 c_col -= keycount;
3116 pushkey = '\n';
3118 else
3119 c_col--;
3121 if (c_col < 1)
3122 c_col = 8;
3124 return 1;
3125 case KEY_RIGHT:
3126 if (game[gindex].mode == MODE_HISTORY)
3127 return 0;
3129 if (keycount) {
3130 c_col += keycount;
3131 pushkey = '\n';
3133 else
3134 c_col++;
3136 if (c_col > 8)
3137 c_col = 1;
3139 return 1;
3140 case 'e':
3141 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3142 MODE_PLAY)
3143 return 1;
3145 // Don't edit a running game (for now).
3146 if (pgn_history_total(game[gindex].hp))
3147 return 1;
3149 if (game[gindex].mode != MODE_EDIT) {
3150 pgn_board_init_fen(&game[gindex], game[gindex].b, NULL);
3151 board_details++;
3152 game[gindex].mode = MODE_EDIT;
3153 update_all(game[gindex]);
3154 return 1;
3157 board_details--;
3158 pgn_tag_add(&game[gindex].tag, "FEN",
3159 pgn_game_to_fen(game[gindex], game[gindex].b));
3160 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3161 pgn_tag_sort(game[gindex].tag);
3162 game[gindex].mode = MODE_PLAY;
3163 update_all(game[gindex]);
3164 return 1;
3165 case 'Q':
3166 quit = 1;
3167 return 1;
3168 case KEY_RESIZE:
3169 do_window_resize();
3170 return 1;
3171 #ifdef DEBUG
3172 case 'D':
3173 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3174 return 1;
3175 #endif
3176 case 0:
3177 default:
3178 break;
3181 return 0;
3184 void game_loop()
3186 int error_recover = 0;
3188 c_row = 2, c_col = 5;
3189 gindex = gtotal - 1;
3191 if (pgn_history_total(game[gindex].hp))
3192 game[gindex].mode = MODE_HISTORY;
3193 else
3194 game[gindex].mode = MODE_PLAY;
3196 if (game[gindex].mode == MODE_HISTORY) {
3197 pgn_board_update(&game[gindex], game[gindex].b,
3198 pgn_history_total(game[gindex].hp));
3201 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3202 movestep = 2;
3203 paused = 1; //FIXME clock
3204 flushinp();
3205 update_all(game[gindex]);
3206 update_tag_window(game[gindex].tag);
3207 wtimeout(boardw, 70);
3209 while (!quit) {
3210 int c = 0;
3211 int n = 0, i;
3212 char fdbuf[8192] = {0};
3213 int len;
3214 struct timeval tv = {0, 0};
3215 fd_set rfds;
3216 struct userdata_s *d = NULL;
3218 FD_ZERO(&rfds);
3220 for (i = 0; i < gtotal; i++) {
3221 d = game[i].data;
3223 if (d->engine) {
3224 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3225 if (d->engine->fd[ENGINE_IN_FD] > n)
3226 n = d->engine->fd[ENGINE_IN_FD];
3228 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3233 if (n) {
3234 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3235 for (i = 0; i < gtotal; i++) {
3236 d = game[i].data;
3238 if (d->engine) {
3239 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3240 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3241 sizeof(fdbuf));
3243 if (len == -1) {
3244 if (errno != EAGAIN) {
3245 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3246 strerror(errno));
3247 waitpid(d->engine->pid, &n, 0);
3248 free(d->engine);
3249 d->engine = NULL;
3250 break;
3253 else {
3254 if (len) {
3255 parse_engine_output(&game[i], fdbuf);
3256 update_all(game[gindex]);
3263 else {
3264 if (n == -1)
3265 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3266 else {
3267 /* timeout */
3272 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER) && game[gindex].mode
3273 != MODE_HISTORY) {
3274 game[gindex].mode = MODE_HISTORY;
3275 update_all(game[gindex]);
3278 error_recover = 0;
3279 draw_board(&game[gindex], board_details);
3280 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3282 if (!paused) {
3285 refresh_all();
3287 if (pushkey)
3288 c = pushkey;
3289 else {
3290 if ((c = wgetch(boardw)) == ERR)
3291 continue;
3294 if (!keycount && status.notify)
3295 update_status_notify(game[gindex], NULL);
3298 if ((n = globalkeys(c)) == 1) {
3299 keycount = 0;
3300 continue;
3302 else if (n == -1)
3303 continue;
3305 switch (game[gindex].mode) {
3306 case MODE_EDIT:
3307 editmode_keys(c);
3308 break;
3309 case MODE_PLAY:
3310 if (playmode_keys(c))
3311 continue;
3312 break;
3313 case MODE_HISTORY:
3314 historymode_keys(c);
3315 break;
3316 default:
3317 break;
3320 keycount = 0;
3324 void usage(const char *pn, int ret)
3326 fprintf((ret) ? stderr : stdout, "%s",
3327 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3328 " -p Load PGN file.\n"
3329 " -V Validate a game file.\n"
3330 " -S Validate and output a PGN formatted game.\n"
3331 " -R Like -S but write a reduced PGN formatted game.\n"
3332 " -t Also write custom PGN tags from config file.\n"
3333 " -E Stop processing on file parsing error (overrides config).\n"
3334 " -v Version information.\n"
3335 " -h This help text.\n");
3337 exit(ret);
3340 void cleanup_all()
3342 int i;
3344 cleanup_all_games();
3345 pgn_free_all();
3346 del_panel(boardp);
3347 del_panel(historyp);
3348 del_panel(statusp);
3349 del_panel(tagp);
3350 delwin(boardw);
3351 delwin(historyw);
3352 delwin(statusw);
3353 delwin(tagw);
3355 if (enginew) {
3356 del_panel(enginep);
3357 delwin(enginew);
3359 if (enginebuf) {
3360 for (i = 0; enginebuf[i]; i++)
3361 free(enginebuf[i]);
3363 free(enginebuf);
3367 endwin();
3370 void catch_signal(int which)
3372 switch (which) {
3373 case SIGINT:
3374 case SIGPIPE:
3375 if (which == SIGPIPE && quit)
3376 break;
3378 if (which == SIGPIPE)
3379 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3381 cleanup_all();
3382 exit(EXIT_FAILURE);
3383 break;
3384 case SIGSTOP:
3385 savetty();
3386 break;
3387 case SIGCONT:
3388 resetty();
3389 keypad(boardw, TRUE);
3390 curs_set(0);
3391 cbreak();
3392 noecho();
3393 break;
3394 case SIGUSR1:
3395 if (curses_initialized) {
3396 update_loading_window(game[gindex]);
3397 break;
3400 fprintf(stderr, "Loading... %i\r", gtotal);
3401 fflush(stderr);
3402 break;
3403 default:
3404 break;
3408 static void set_defaults()
3410 filetype = NO_FILE;
3411 set_config_defaults();
3414 int main(int argc, char *argv[])
3416 int opt;
3417 struct stat st;
3418 char buf[FILENAME_MAX];
3419 char datadir[FILENAME_MAX];
3420 int ret = EXIT_SUCCESS;
3421 int validate_only = 0, validate_and_write = 0, reduced = 0;
3422 int write_custom_tags = 0;
3423 FILE *fp;
3424 int i;
3426 if ((config.pwd = getpwuid(getuid())) == NULL)
3427 err(EXIT_FAILURE, "getpwuid()");
3429 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3430 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3431 config.ccfile = strdup(buf);
3432 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3433 config.nagfile = strdup(buf);
3434 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3435 config.agonyfile = strdup(buf);
3436 snprintf(buf, sizeof(buf), "%s/config", datadir);
3437 config.configfile = strdup(buf);
3438 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3439 config.fifo = strdup(buf);
3441 if (stat(datadir, &st) == -1) {
3442 if (errno == ENOENT) {
3443 if (mkdir(datadir, 0755) == -1)
3444 err(EXIT_FAILURE, "%s", datadir);
3446 else
3447 err(EXIT_FAILURE, "%s", datadir);
3449 stat(datadir, &st);
3452 if (!S_ISDIR(st.st_mode))
3453 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3455 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3456 if (mkfifo(config.fifo, 0600) == -1)
3457 err(EXIT_FAILURE, "%s", config.fifo);
3460 set_defaults();
3462 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3463 switch (opt) {
3464 case 't':
3465 write_custom_tags = 1;
3466 break;
3467 case 'E':
3468 config.stoponerror = 1;
3469 break;
3470 case 'R':
3471 reduced = 1;
3472 case 'S':
3473 validate_and_write = 1;
3474 case 'V':
3475 validate_only = 1;
3476 break;
3477 case 'v':
3478 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3479 COPYRIGHT);
3480 exit(EXIT_SUCCESS);
3481 case 'p':
3482 filetype = PGN_FILE;
3483 strncpy(loadfile, optarg, sizeof(loadfile));
3484 break;
3485 case 'h':
3486 default:
3487 usage(argv[0], EXIT_SUCCESS);
3491 if ((validate_only || validate_and_write) && !*loadfile)
3492 usage(argv[0], EXIT_FAILURE);
3494 if (access(config.configfile, R_OK) == 0)
3495 parse_rcfile(config.configfile);
3497 signal(SIGPIPE, catch_signal);
3498 signal(SIGCONT, catch_signal);
3499 signal(SIGSTOP, catch_signal);
3500 signal(SIGINT, catch_signal);
3501 signal(SIGUSR1, catch_signal);
3503 srandom(getpid());
3505 switch (filetype) {
3506 case PGN_FILE:
3507 if ((fp = pgn_open(loadfile)) == NULL)
3508 err(EXIT_FAILURE, "%s", loadfile);
3510 ret = pgn_parse(fp);
3511 break;
3512 case FEN_FILE:
3513 //ret = parse_fen_file(loadfile);
3514 break;
3515 case EPD_FILE: // Not implemented.
3516 case NO_FILE:
3517 default:
3518 // No file specified. Empty game.
3519 ret = pgn_parse(NULL);
3520 add_custom_tags(&game[gindex].tag);
3521 break;
3524 if (validate_only || validate_and_write) {
3525 if (validate_and_write) {
3526 for (i = 0; i < gtotal; i++) {
3527 if (write_custom_tags)
3528 add_custom_tags(&game[i].tag);
3530 pgn_write(stdout, game[i]);
3534 pgn_free_all();
3535 exit(ret);
3537 else if (ret)
3538 exit(ret);
3540 init_userdata();
3542 if (initscr() == NULL)
3543 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3544 else
3545 curses_initialized = 1;
3547 if (LINES < 24 || COLS < 80) {
3548 endwin();
3549 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3552 if (has_colors() == TRUE && start_color() == OK)
3553 init_color_pairs();
3555 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3556 boardp = new_panel(boardw);
3557 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3558 COLS - HISTORY_WIDTH);
3559 historyp = new_panel(historyw);
3560 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3561 statusp = new_panel(statusw);
3562 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3563 tagp = new_panel(tagw);
3564 keypad(boardw, TRUE);
3565 // leaveok(boardw, TRUE);
3566 leaveok(tagw, TRUE);
3567 leaveok(statusw, TRUE);
3568 leaveok(historyw, TRUE);
3569 curs_set(0);
3570 cbreak();
3571 noecho();
3572 draw_window_decor();
3573 game_loop();
3574 cleanup_all();
3575 exit(EXIT_SUCCESS);