Don't check for libmenu or menu.h during ./configure.
[cboard.git] / src / cboard.c
bloba32abfe61cfb4cc2600560e22ca7ff86394a8fe6
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_REGEX_H
48 #include <regex.h>
49 #endif
51 #include "chess.h"
52 #include "conf.h"
53 #include "window.h"
54 #include "colors.h"
55 #include "input.h"
56 #include "misc.h"
57 #include "engine.h"
58 #include "rcfile.h"
59 #include "strings.h"
60 #include "common.h"
61 #include "cboard.h"
63 #ifdef DEBUG
64 #include "debug.h"
65 #endif
67 #ifdef WITH_DMALLOC
68 #include <dmalloc.h>
69 #endif
71 static char *str_etc(const char *str, int maxlen, int rev)
73 int len = strlen(str);
74 static char buf[80], *p = buf;
75 int i;
77 strncpy(buf, str, sizeof(buf));
79 if (len > maxlen) {
80 if (rev) {
81 p = buf;
82 *p++ = '.';
83 *p++ = '.';
84 *p++ = '.';
86 for (i = 0; i < maxlen + 3; i++)
87 *p++ = buf[(len - maxlen) + i + 3];
89 else {
90 p = buf + maxlen - 4;
91 *p++ = '.';
92 *p++ = '.';
93 *p++ = '.';
96 *p = '\0';
99 return buf;
102 void update_cursor(GAME g, int idx)
104 char *p;
105 int len;
106 int t = pgn_history_total(g.hp);
109 * If not deincremented then r and c would be the next move.
111 idx--;
113 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
114 c_row = 2, c_col = 5;
115 return;
118 p = g.hp[idx]->move;
119 len = strlen(p);
121 if (*p == 'O') {
122 if (len <= 4)
123 c_col = 7;
124 else
125 c_col = 3;
127 c_row = (g.turn == WHITE) ? 1 : 8;
128 return;
131 p += len;
133 while (!isdigit(*p))
134 p--;
136 c_row = ROWTOINT(*p--);
137 c_col = COLTOINT(*p);
140 static int init_nag()
142 FILE *fp;
143 char line[LINE_MAX];
144 int i = 0;
146 if ((fp = fopen(config.nagfile, "r")) == NULL) {
147 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
148 return 1;
151 nags = Realloc(nags, 2 * sizeof(char *));
152 nags[0] = NULL;
153 i++;
155 while (!feof(fp)) {
156 if (fscanf(fp, " %[^\n] ", line) == 1) {
157 nags = Realloc(nags, (i + 2) * sizeof(char *));
158 nags[i++] = strdup(line);
162 return 0;
165 void set_menu_vars(int c, int rows, int items, int *item, int *top)
167 int selected = *item;
168 int toppos = *top;
170 switch (c) {
171 case KEY_HOME:
172 selected = toppos = 0;
173 break;
174 case KEY_END:
175 selected = items;
176 toppos = items - rows + 1;
177 break;
178 case KEY_UP:
179 if (selected - 1 < 0) {
180 selected = items;
182 toppos = selected - rows + 1;
184 else {
185 selected--;
187 if (toppos && selected <= toppos)
188 toppos = selected;
190 break;
191 case KEY_DOWN:
192 if (selected + 1 > items )
193 selected = toppos = 0;
194 else {
195 selected++;
197 if (selected - toppos >= rows)
198 toppos++;
200 break;
201 default:
202 toppos = (items > rows) ? items - rows + 1 : 0;
203 break;
206 *item = selected;
207 *top = toppos;
210 int test_nag_selected(unsigned char nag[], int s)
212 int i;
214 for (i = 0; i < MAX_PGN_NAG; i++) {
215 if (nag[i] == s)
216 return i;
219 return -1;
222 char *history_edit_nag(void *arg)
224 WINDOW *win;
225 PANEL *panel;
226 int i = 0, n;
227 int itemcount = 0;
228 int rows, cols;
229 HISTORY *anno = (HISTORY *)arg;
230 int selected = 0;
231 int toppos = 0;
232 int len = 0;
233 int total = 0;
234 unsigned char nag[MAX_PGN_NAG] = {0};
236 if (!nags) {
237 if (init_nag())
238 return NULL;
241 for (i = 1, n = 0; nags[i]; i++) {
242 n = strlen(nags[i]);
244 if (len < n)
245 len = n;
248 total = i;
249 cols = len + 2;
250 rows = (total + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : total + 4;
252 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
253 panel = new_panel(win);
254 cbreak();
255 noecho();
256 keypad(win, TRUE);
257 nl();
258 wbkgd(win, CP_MESSAGE_WINDOW);
259 memcpy(&nag, &anno->nag, sizeof(nag));
261 for (i = 0; i < MAX_PGN_NAG; i++) {
262 if (nag[i])
263 itemcount++;
266 while (1) {
267 int c;
268 char buf[cols - 4];
270 wmove(win, 0, 0);
271 wclrtobot(win);
272 draw_window_title(win, NAG_EDIT_TITLE, cols, CP_HISTORY_TITLE,
273 CP_HISTORY_BORDER);
275 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
276 if ((i == selected && i != 0)|| test_nag_selected(nag, i) != -1) {
277 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
278 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
279 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
280 continue;
283 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
286 snprintf(buf, sizeof(buf), "NAG %i of %i (%i of %i selected) %s",
287 selected + 1, total, itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
288 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
290 nl();
291 refresh_all();
292 c = wgetch(win);
294 switch (c) {
295 int found;
297 case KEY_F(1):
298 help(NAG_EDIT_HELP, ANYKEY, naghelp);
299 break;
300 case KEY_HOME:
301 case KEY_END:
302 case KEY_UP:
303 case KEY_DOWN:
304 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
305 break;
306 case ' ':
307 if (selected == 0) {
308 for (i = 0; i < MAX_PGN_NAG; i++)
309 nag[i] = 0;
311 itemcount = 0;
312 break;
315 if ((found = test_nag_selected(nag, selected)) != -1) {
316 nag[found] = 0;
317 itemcount--;
319 else {
320 if (itemcount + 1 > MAX_PGN_NAG)
321 break;
323 for (i = 0; i < MAX_PGN_NAG; i++) {
324 if (nag[i] == 0) {
325 nag[i] = selected;
326 break;
330 itemcount++;
333 break;
334 case '\n':
335 goto done;
336 break;
337 case KEY_ESCAPE:
338 goto done;
339 break;
340 default:
341 break;
345 done:
346 memcpy(&anno->nag, &nag, sizeof(nag));
347 del_panel(panel);
348 delwin(win);
349 return NULL;
352 static void view_nag(void *arg)
354 HISTORY *h = (HISTORY *)arg;
355 char buf[80];
356 char line[LINE_MAX] = {0};
357 int i = 0;
359 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
361 if (!nags) {
362 if (init_nag())
363 return;
366 for (i = 0; i < MAX_PGN_NAG; i++) {
367 if (!h->nag[i])
368 break;
370 strncat(line, nags[h->nag[i]], sizeof(line));
371 strncat(line, "\n", sizeof(line));
374 line[strlen(line) - 1] = 0;
375 message(buf, ANYKEY, "%s", line);
378 void view_annotation(HISTORY h)
380 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
381 int nag = 0, comment = 0;
383 if (h.comment && h.comment[0])
384 comment++;
386 if (h.nag[0])
387 nag++;
389 if (!nag && !comment)
390 return;
392 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
394 if (comment)
395 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
396 (nag) ? "Press 'n' to view NAG" : NULL,
397 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
398 (nag) ? 'n' : 0, "%s", h.comment);
399 else
400 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
401 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
404 static void cleanup(WINDOW *win, PANEL *panel, struct file_s *files)
406 int i;
408 if (files) {
409 for (i = 0; files[i].name; i++) {
410 free(files[i].path);
411 free(files[i].name);
414 free(files);
417 del_panel(panel);
418 delwin(win);
421 static int sort_files(const void *a, const void *b)
423 const struct file_s *aa = a;
424 const struct file_s *bb = b;
426 return strcmp(aa->name, bb->name);
429 char *file_browser(void *arg)
431 char pattern[FILENAME_MAX];
432 static char path[FILENAME_MAX];
433 static char file[FILENAME_MAX];
434 struct stat st;
435 char *p;
436 int cursor = curs_set(0);
438 if (!*path) {
439 if (config.savedirectory) {
440 if ((p = word_expand(config.savedirectory)) == NULL)
441 return NULL;
443 strncpy(path, p, sizeof(path));
445 if (access(path, R_OK) == -1) {
446 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
447 getcwd(path, sizeof(path));
450 else
451 getcwd(path, sizeof(path));
454 again:
456 * First find directories (including hidden) in the working directory.
457 * Then apply the config.pattern to regular files.
459 if ((p = word_split_append(path, '/', ".* *")) == NULL)
460 return NULL;
462 strncpy(pattern, p, sizeof(pattern));
464 while (1) {
465 WINDOW *win;
466 PANEL *panel;
467 char *tmp = NULL;
468 int rows, cols = 0;
469 int selected = 0;
470 int toppos = 0;
471 int len = strlen(path);
472 wordexp_t w;
473 int i, n = 0;
474 struct file_s *files = NULL;
475 int which = 1;
476 int x = WRDE_NOCMD;
477 int nlen = 0;
479 new_we:
480 if (wordexp(pattern, &w, x) != 0) {
481 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
482 return NULL;
485 for (i = 0; i < w.we_wordc; i++) {
486 struct tm *tp;
487 char tbuf[16];
488 char sbuf[64];
490 if (stat(w.we_wordv[i], &st) == -1)
491 continue;
493 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
494 p++;
495 else
496 p = w.we_wordv[i];
498 if (which) {
499 if (!S_ISDIR(st.st_mode))
500 continue;
502 if (p[0] == '.' && p[1] == 0)
503 continue;
505 else {
506 if (S_ISDIR(st.st_mode))
507 continue;
510 len = strlen(p) + 2;
511 files = Realloc(files, (n + 2) * sizeof(struct file_s));
512 files[n].path = strdup(w.we_wordv[i]);
513 files[n].name = Malloc(len);
514 strncpy(files[n].name, p, len);
516 if (S_ISDIR(st.st_mode))
517 files[n].name[len - 2] = '/';
519 tp = localtime(&st.st_mtime);
520 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
521 snprintf(sbuf, sizeof(sbuf), "%i %s", (int)st.st_size, tbuf);
522 files[n].st = strdup(sbuf);
523 memset(&files[++n], '\0', sizeof(struct file_s));
526 which--;
528 if (which == 0) {
529 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
530 return NULL;
532 strncpy(pattern, p, sizeof(pattern));
533 x |= WRDE_REUSE;
534 goto new_we;
537 wordfree(&w);
538 qsort(files, n, sizeof(struct file_s), sort_files);
540 for (i = x = nlen = 0; i < n; i++) {
541 if (strlen(files[i].name) > nlen)
542 nlen = strlen(files[i].name);
544 if (x < nlen + strlen(files[i].st))
545 x = nlen + strlen(files[i].st);
548 cols = x + 1;
550 if (cols < strlen(path))
551 cols = strlen(path);
553 if (cols < strlen(HELP_PROMPT))
554 cols = strlen(HELP_PROMPT);
556 if (cols > COLS)
557 cols = COLS - 4;
559 cols += 2;
560 rows = (n + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : n + 4;
562 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
563 wbkgd(win, CP_MESSAGE_WINDOW);
564 panel = new_panel(win);
565 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
566 draw_prompt(win, rows - 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
567 cbreak();
568 noecho();
569 keypad(win, TRUE);
570 nl();
572 while (1) {
573 int c;
575 for (i = toppos, c = 2; i < n && c < rows - 2; i++, c++) {
576 if (i == selected) {
577 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
578 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
579 cols - nlen - 2 - 2, files[i].st);
580 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
581 continue;
584 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
585 cols - nlen - 2 - 2, files[i].st);
588 refresh_all();
589 c = wgetch(win);
591 switch (c) {
592 case KEY_HOME:
593 case KEY_END:
594 case KEY_UP:
595 case KEY_DOWN:
596 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
597 break;
598 case '\n':
599 goto gotitem;
600 break;
601 case KEY_ESCAPE:
602 cleanup(win, panel, files);
603 file[0] = 0;
604 goto done;
605 break;
606 case KEY_F(1):
607 help(BROWSER_HELP, ANYKEY, file_browser_help);
608 break;
609 case '~':
610 strncpy(path, "~", sizeof(path));
611 cleanup(win, panel, files);
612 goto again;
613 break;
614 case CTRL('X'):
615 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
616 == NULL)
617 break;
619 if (tmp[strlen(tmp) - 1] == '/')
620 tmp[strlen(tmp) - 1] = 0;
622 strncpy(path, tmp, sizeof(path));
623 cleanup(win, panel, files);
624 goto again;
625 break;
626 default:
627 break;
631 gotitem:
632 strncpy(file, files[selected].path, sizeof(file));
633 cleanup(win, panel, files);
635 if (stat(file, &st) == -1) {
636 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
637 continue;
640 if (S_ISDIR(st.st_mode)) {
641 p = file + strlen(file) - 2;
643 if (strcmp(p, "..") == 0) {
644 p = file + strlen(file) - 3;
645 *p = 0;
647 if ((p = strrchr(file, '/')) != NULL)
648 file[strlen(file) - strlen(p)] = 0;
651 strncpy(path, file, sizeof(path));
652 goto again;
655 if (S_ISREG(st.st_mode))
656 break;
658 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
661 done:
662 curs_set(cursor);
663 return (*file) ? file : NULL;
666 static int init_country_codes()
668 FILE *fp;
669 char line[LINE_MAX], *s;
670 int cindex = 0;
672 if ((fp = fopen(config.ccfile, "r")) == NULL) {
673 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
674 return 1;
677 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
678 char *tmp;
680 if ((tmp = strsep(&s, " ")) == NULL)
681 continue;
683 s = trim(s);
684 tmp = trim(tmp);
686 if (!s || !tmp)
687 continue;
689 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
690 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
691 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
692 cindex++;
695 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
696 fclose(fp);
698 return 0;
701 char *country_codes(void *arg)
703 WINDOW *win;
704 PANEL *panel;
705 int i = 0, n;
706 int rows, cols;
707 char *tmp = NULL;
708 int len = 0;
709 int total;
710 int selected = 0;
711 int toppos = 0;
713 if (!ccodes) {
714 if (init_country_codes())
715 return NULL;
718 for (i = n = 0; ccodes[i].code && ccodes[i].code[0]; i++) {
719 n = strlen(ccodes[i].code) + strlen(ccodes[i].country);
721 if (len < n)
722 len = n;
725 total = i;
726 cols = len;
728 if (cols < strlen(HELP_PROMPT) + 21)
729 cols = strlen(HELP_PROMPT) + 21;
731 cols += 1;
733 if (cols > COLS)
734 cols = COLS - 4;
736 cols += 2;
737 rows = (i + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : i + 4;
738 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
739 panel = new_panel(win);
740 cbreak();
741 noecho();
742 keypad(win, TRUE);
743 nl();
744 wbkgd(win, CP_MESSAGE_WINDOW);
746 while (1) {
747 int c;
748 char buf[cols - 4];
750 wmove(win, 0, 0);
751 wclrtobot(win);
753 draw_window_title(win, CC_TITLE, cols, CP_MESSAGE_TITLE,
754 CP_MESSAGE_BORDER);
756 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
757 if (i == selected) {
758 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
759 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code,
760 ccodes[i].country);
761 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
762 continue;
765 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code, ccodes[i].country);
768 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR,
769 selected + 1, N_OF_N_STR, total, HELP_PROMPT);
770 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
771 refresh_all();
772 c = wgetch(win);
774 switch (c) {
775 case KEY_F(1):
776 help(CC_KEY_HELP, ANYKEY, cc_help);
777 break;
778 case KEY_HOME:
779 case KEY_END:
780 case KEY_UP:
781 case KEY_DOWN:
782 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
783 break;
784 case '\n':
785 tmp = ccodes[selected].code;
786 goto done;
787 break;
788 case KEY_ESCAPE:
789 tmp = NULL;
790 goto done;
791 break;
792 default:
793 break;
797 done:
798 del_panel(panel);
799 delwin(win);
800 return tmp;
803 static void add_custom_tags(TAG ***t)
805 int i;
806 int total = pgn_tag_total(config.tag);
808 if (!config.tag)
809 return;
811 for (i = 0; i < total; i++)
812 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
814 pgn_tag_sort(*t);
817 TAG **edit_tags(GAME g, BOARD b, int edit)
819 TAG **data = NULL;
820 struct tm tp;
821 int data_index = 0;
822 int len;
823 int selected = 0;
824 int n;
825 int toppos = 0;
827 /* Edit the backup copy, not the original in case the save fails. */
828 len = pgn_tag_total(g.tag);
830 for (n = 0; n < len; n++)
831 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
833 data_index = pgn_tag_total(data);
835 while (1) {
836 WINDOW *win;
837 PANEL *panel;
838 int i;
839 char buf[76] = {0};
840 char *tmp = NULL;
841 int rows, cols;
842 int nlen = 0;
844 data_index = pgn_tag_total(data);
846 for (i = cols = 0, n = 4; i < data_index; i++) {
847 n = strlen(data[i]->name);
849 if (nlen < n)
850 nlen = n;
852 if (data[i]->value)
853 n += strlen(data[i]->value);
854 else
855 n += strlen(UNKNOWN);
857 if (cols < n)
858 cols = n;
861 cols += nlen + 2;
863 if (cols > COLS)
864 cols = COLS - 2;
866 /* +14 for the extra prompt info. */
867 if (cols < strlen(HELP_PROMPT) + 14 + 2)
868 cols = strlen(HELP_PROMPT) + 14 + 2;
870 rows = (data_index + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 :
871 data_index + 4;
873 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
874 panel = new_panel(win);
875 cbreak();
876 noecho();
877 keypad(win, TRUE);
878 nl();
879 wbkgd(win, CP_MESSAGE_WINDOW);
880 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
881 cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
883 if (selected >= data_index - 1)
884 selected = data_index - 1;
886 while (1) {
887 int c;
888 TAG **tmppgn = NULL;
889 char *newtag = NULL;
891 for (i = toppos, c = 2; i < data_index && c < rows - 2; i++, c++) {
892 if (i == selected) {
893 wattron(win, CP_MESSAGE_WINDOW | A_REVERSE);
894 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
895 cols - nlen - 2 - 2, (data[i]->value &&
896 data[i]->value[0]) ? data[i]->value : UNKNOWN);
897 wattroff(win, CP_MESSAGE_WINDOW | A_REVERSE);
898 continue;
901 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
902 cols - nlen - 2 - 2, (data[i]->value &&
903 data[i]->value[0]) ? data[i]->value : UNKNOWN);
906 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
907 selected + 1, N_OF_N_STR, data_index, HELP_PROMPT);
908 draw_prompt(win, rows - 2, cols, buf, CP_MESSAGE_PROMPT);
909 refresh_all();
910 c = wgetch(win);
912 switch (c) {
913 case CTRL('T'):
914 if (!edit)
915 break;
917 add_custom_tags(&data);
918 selected = data_index - 1;
919 toppos = data_index - (rows - 4);
920 goto cleanup;
921 break;
922 case KEY_F(1):
923 if (edit)
924 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
925 else
926 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
927 break;
928 case CTRL('R'):
929 if (!edit)
930 break;
932 if (selected <= 6) {
933 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
934 goto cleanup;
937 data_index = pgn_tag_total(data);
939 for (i = 0; i < data_index; i++) {
940 if (i == selected)
941 continue;
943 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
946 pgn_tag_free(data);
947 data = NULL;
949 for (i = 0; tmppgn[i]; i++)
950 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
952 pgn_tag_free(tmppgn);
954 if (selected >= data_index)
955 selected = data_index - 1;
957 if (selected > rows - 5)
958 toppos = selected - (rows - 5);
959 else
960 toppos -= (toppos) ? 1 : 0;
962 goto cleanup;
963 break;
964 case CTRL('A'):
965 if (!edit)
966 break;
968 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
969 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
970 == NULL)
971 break;
973 newtag[0] = toupper(newtag[0]);
975 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
976 strlen(PRESS_ENTER)) {
977 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
978 break;
981 for (i = 0; i < data_index; i++) {
982 if (strcasecmp(data[i]->name, newtag) == 0) {
983 selected = i;
984 goto gotitem;
988 pgn_tag_add(&data, newtag, NULL);
989 data_index = pgn_tag_total(data);
990 selected = data_index - 1;
991 set_menu_vars(c, rows - 4, data_index - 1, &selected,
992 &toppos);
993 goto gotitem;
994 break;
995 case KEY_HOME:
996 case KEY_END:
997 case KEY_UP:
998 case KEY_DOWN:
999 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1000 &toppos);
1001 break;
1002 case CTRL('F'):
1003 if (!edit)
1004 break;
1006 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1007 data_index = pgn_tag_total(data);
1008 selected = data_index - 1;
1009 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1010 &toppos);
1011 goto gotitem;
1012 break;
1013 case '\n':
1014 goto gotitem;
1015 break;
1016 case KEY_ESCAPE:
1017 del_panel(panel);
1018 delwin(win);
1019 goto done;
1020 break;
1021 default:
1022 break;
1026 gotitem:
1027 nlen = strlen(data[selected]->name) + 2;
1028 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1030 if (nlen > MAX_VALUE_WIDTH)
1031 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1032 else
1033 snprintf(buf, sizeof(buf), "%s \"%s\"",
1034 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1035 data[selected]->name);
1037 if (!edit) {
1038 if (!data[selected]->value)
1039 goto cleanup;
1041 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1042 goto cleanup;
1045 if (strcmp(data[selected]->name, "Date") == 0) {
1046 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1047 0, FIELD_TYPE_PGN_DATE);
1049 if (tmp) {
1050 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1051 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1052 goto cleanup;
1055 else
1056 goto cleanup;
1058 else if (strcmp(data[selected]->name, "Site") == 0) {
1059 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1060 country_codes, NULL, CTRL('t'), -1);
1062 if (!tmp)
1063 tmp = "?";
1065 else if (strcmp(data[selected]->name, "Round") == 0) {
1066 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1067 FIELD_TYPE_PGN_ROUND);
1069 if (!tmp) {
1070 if (gtotal > 1)
1071 tmp = "?";
1072 else
1073 tmp = "-";
1076 else if (strcmp(data[selected]->name, "Result") == 0) {
1077 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1078 0, -1);
1080 if (!tmp)
1081 tmp = "*";
1083 else {
1084 tmp = (data[selected]->value) ? data[selected]->value : NULL;
1085 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1088 len = (tmp) ? strlen(tmp) + 1 : 1;
1089 data[selected]->value = Realloc(data[selected]->value, len);
1090 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1092 cleanup:
1093 del_panel(panel);
1094 delwin(win);
1097 done:
1098 if (!edit) {
1099 pgn_tag_free(data);
1100 return NULL;
1103 return data;
1106 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1107 * game index number.
1109 int save_pgn(const char *filename, int isfifo, int saveindex)
1111 FILE *fp;
1112 char *mode = NULL;
1113 int c;
1114 char buf[FILENAME_MAX];
1115 struct stat st;
1116 int i;
1117 char *command = NULL;
1118 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1120 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1121 if (stat(config.savedirectory, &st) == -1) {
1122 if (errno == ENOENT) {
1123 if (mkdir(config.savedirectory, 0755) == -1) {
1124 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1125 strerror(errno));
1126 return 1;
1129 else {
1130 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1131 strerror(errno));
1132 return 1;
1136 stat(config.savedirectory, &st);
1138 if (!S_ISDIR(st.st_mode)) {
1139 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1140 return 1;
1143 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1144 filename = buf;
1147 /* This is a hack to resume an existing game when more than one game is
1148 * available. Also resuming a saved game and a game from history.
1150 // FIXME: may not need this when a FEN tag is supported (by the engine).
1151 if (isfifo)
1152 mode = "w";
1153 else {
1154 if (access(filename, W_OK) == 0) {
1155 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1156 "%s \"%s\"", E_FILEEXISTS, filename);
1158 switch (c) {
1159 case 'a':
1160 if (pgn_is_compressed(filename)) {
1161 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1162 return 1;
1165 mode = "a";
1166 break;
1167 case 'o':
1168 mode = "w+";
1169 break;
1170 default:
1171 return 1;
1174 else
1175 mode = "a";
1178 if (command) {
1179 if ((fp = popen(command, "w")) == NULL) {
1180 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1181 return 1;
1184 else {
1185 if ((fp = fopen(filename, mode)) == NULL) {
1186 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1187 return 1;
1191 if (isfifo)
1192 pgn_write(fp, game[saveindex]);
1193 else {
1194 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1195 pgn_write(fp, game[i]);
1198 if (command)
1199 pclose(fp);
1200 else
1201 fclose(fp);
1203 if (!isfifo && saveindex == -1)
1204 strncpy(loadfile, filename, sizeof(loadfile));
1206 return 0;
1209 char *random_agony(GAME g)
1211 static int n;
1212 FILE *fp;
1213 char line[LINE_MAX];
1215 if (n == -1 || !config.agony || !curses_initialized ||
1216 (g.mode == MODE_HISTORY && !config.historyagony))
1217 return NULL;
1219 if (!agony) {
1220 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1221 n = -1;
1222 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1223 return NULL;
1226 while (!feof(fp)) {
1227 if (fscanf(fp, " %[^\n] ", line) == 1) {
1228 agony = Realloc(agony, (n + 2) * sizeof(char *));
1229 agony[n++] = strdup(trim(line));
1233 agony[n] = NULL;
1234 fclose(fp);
1236 if (agony[0] == NULL || !n) {
1237 n = -1;
1238 return NULL;
1242 return agony[random() % n];
1245 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1247 if (pgn_piece_to_int(piece) == ROOK && col == 7
1248 && row == 7 &&
1249 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1250 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1251 if (mod)
1252 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1253 return 1;
1255 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1256 && row == 7 &&
1257 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1258 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1259 if (mod)
1260 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1261 return 1;
1263 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1264 && row == 0 &&
1265 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1266 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1267 if (mod)
1268 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1269 return 1;
1271 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1272 && row == 0 &&
1273 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1274 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1275 if (mod)
1276 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1277 return 1;
1279 else if (pgn_piece_to_int(piece) == KING && col == 4
1280 && row == 7 &&
1281 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1282 TEST_FLAG(g->flags, GF_WK_CASTLE))
1284 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1285 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1286 if (mod) {
1287 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1288 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1289 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1290 else
1291 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1293 return 1;
1295 else if (pgn_piece_to_int(piece) == KING && col == 4
1296 && row == 0 &&
1297 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1298 TEST_FLAG(g->flags, GF_BK_CASTLE))
1300 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1301 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1302 if (mod) {
1303 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1304 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1305 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1306 else
1307 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1309 return 1;
1312 return 0;
1315 static void draw_board(GAME *g, int details)
1317 int row, col;
1318 int bcol = 0, brow = 0;
1319 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1320 int ncols = 0, offset = 1;
1321 unsigned coords_y = 8;
1323 if (g->mode != MODE_PLAY && g->mode != MODE_EDIT)
1324 update_cursor(*g, g->hindex);
1326 for (row = 0; row < maxy; row++) {
1327 bcol = 0;
1329 for (col = 0; col < maxx; col++) {
1330 int attrwhich = -1;
1331 chtype attrs = 0;
1332 unsigned char piece;
1334 if (row == 0 || row == maxy - 2) {
1335 if (col == 0)
1336 mvwaddch(boardw, row, col,
1337 LINE_GRAPHIC((row) ?
1338 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1339 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1340 else if (col == maxx - 2)
1341 mvwaddch(boardw, row, col,
1342 LINE_GRAPHIC((row) ?
1343 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1344 ACS_URCORNER | CP_BOARD_GRAPHICS));
1345 else if (!(col % 4))
1346 mvwaddch(boardw, row, col,
1347 LINE_GRAPHIC((row) ?
1348 ACS_BTEE | CP_BOARD_GRAPHICS :
1349 ACS_TTEE | CP_BOARD_GRAPHICS));
1350 else {
1351 if (col != maxx - 1)
1352 mvwaddch(boardw, row, col,
1353 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1356 continue;
1359 if ((row % 2) && col == maxx - 1 && coords_y) {
1360 wattron(boardw, CP_BOARD_COORDS);
1361 mvwprintw(boardw, row, col, "%d", coords_y--);
1362 wattroff(boardw, CP_BOARD_COORDS);
1363 continue;
1366 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1367 if (!(row % 2))
1368 mvwaddch(boardw, row, col,
1369 LINE_GRAPHIC((col) ?
1370 ACS_RTEE | CP_BOARD_GRAPHICS :
1371 ACS_LTEE | CP_BOARD_GRAPHICS));
1372 else
1373 mvwaddch(boardw, row, col,
1374 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1376 continue;
1379 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1380 mvwaddch(boardw, row, col,
1381 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1382 continue;
1385 if (!(col % 4) && row != maxy - 1) {
1386 mvwaddch(boardw, row, col,
1387 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1388 continue;
1391 if ((row % 2)) {
1392 if ((col % 4)) {
1393 if (ncols++ == 8) {
1394 offset++;
1395 ncols = 1;
1398 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1399 && (offset % 2)))
1400 attrwhich = BLACK;
1401 else
1402 attrwhich = WHITE;
1404 if (config.validmoves && g->b[brow][bcol].valid) {
1405 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1406 CP_BOARD_MOVES_BLACK;
1408 else
1409 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1410 CP_BOARD_BLACK;
1412 if (row == ROWTOMATRIX(c_row) && col ==
1413 COLTOMATRIX(c_col)) {
1414 attrs = CP_BOARD_CURSOR;
1417 if (row == ROWTOMATRIX(sp.row) &&
1418 col == COLTOMATRIX(sp.col)) {
1419 attrs = CP_BOARD_SELECTED;
1422 if (row == maxy - 1)
1423 attrs = 0;
1425 mvwaddch(boardw, row, col, ' ' | attrs);
1427 if (row == maxy - 1)
1428 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1429 else {
1430 if (details && g->b[row / 2][bcol].enpassant)
1431 piece = 'x';
1432 else
1433 piece = g->b[row / 2][bcol].icon;
1435 if (details && castling_state(g, g->b, brow, bcol,
1436 piece, 0))
1437 attrs |= A_REVERSE;
1439 if (g->side == WHITE && isupper(piece))
1440 attrs |= A_BOLD;
1441 else if (g->side == BLACK && islower(piece))
1442 attrs |= A_BOLD;
1444 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1446 CLEAR_FLAG(attrs, A_BOLD);
1447 CLEAR_FLAG(attrs, A_REVERSE);
1450 waddch(boardw, ' ' | attrs);
1451 col += 2;
1452 bcol++;
1455 else {
1456 if (col != maxx - 1)
1457 mvwaddch(boardw, row, col,
1458 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1462 brow = row / 2;
1466 void invalid_move(int n, const char *m)
1468 if (curses_initialized)
1469 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1470 else
1471 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1474 /* Convert the selected piece to SAN format and validate it. */
1475 static char *board_to_san(GAME *g, BOARD b)
1477 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1478 int piece;
1479 int promo;
1480 struct userdata_s *d = g->data;
1482 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1483 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1485 p = str;
1486 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1488 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1489 (sp.destrow == 1 && g->turn == BLACK))) {
1490 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1492 if (pgn_piece_to_int(promo) == -1)
1493 return NULL;
1495 p = str + strlen(str);
1496 *p++ = toupper(promo);
1497 *p = '\0';
1500 p = str;
1502 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1503 if (pgn_validate_move(g, b, &p)) {
1504 invalid_move(gindex + 1, p);
1505 return NULL;
1508 return p;
1511 if (pgn_validate_only(g, b, &p)) {
1512 invalid_move(gindex + 1, p);
1513 return NULL;
1516 return p;
1519 static int move_to_engine(GAME *g, BOARD b)
1521 char *p;
1522 struct userdata_s *d = g->data;
1524 if ((p = board_to_san(g, b)) == NULL)
1525 return 0;
1527 sp.row = sp.col = sp.icon = 0;
1529 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1530 pgn_history_add(g, p);
1531 pgn_switch_turn(g);
1532 SET_FLAG(g->flags, GF_MODIFIED);
1533 update_all(*g);
1534 return 1;
1537 send_to_engine(g, "%s\n", p);
1538 return 1;
1541 static void update_clock(int n, int *h, int *m, int *s)
1543 *h = n / 3600;
1544 *m = (n % 3600) / 60;
1545 *s = (n % 3600) % 60;
1547 return;
1550 void update_status_window(GAME g)
1552 int i = 0;
1553 char *buf;
1554 char tmp[15], *engine, *mode;
1555 int w;
1556 int h, m, s;
1557 char *p;
1558 int maxy, maxx;
1559 int len;
1560 struct userdata_s *d = g.data;
1562 getmaxyx(statusw, maxy, maxx);
1563 w = maxx - 2 - 8;
1564 len = maxx - 2;
1565 buf = Malloc(len);
1567 *tmp = '\0';
1568 p = tmp;
1570 if (TEST_FLAG(g.flags, GF_DELETE)) {
1571 *p++ = '(';
1572 *p++ = 'x';
1573 i++;
1576 if (TEST_FLAG(g.flags, GF_PERROR)) {
1577 if (!i)
1578 *p++ = '(';
1579 else
1580 *p++ = '/';
1582 *p++ = '!';
1583 i++;
1586 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1587 if (!i)
1588 *p++ = '(';
1589 else
1590 *p++ = '/';
1592 *p++ = '*';
1593 i++;
1596 if (*tmp != '\0')
1597 *p++ = ')';
1599 *p = '\0';
1601 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1602 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1603 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1604 (*tmp) ? tmp : "");
1605 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1607 switch (g.mode) {
1608 case MODE_HISTORY:
1609 mode = MODE_HISTORY_STR;
1610 break;
1611 case MODE_EDIT:
1612 mode = MODE_EDIT_STR;
1613 break;
1614 case MODE_PLAY:
1615 mode = MODE_PLAY_STR;
1616 break;
1617 default:
1618 mode = UNKNOWN;
1619 break;
1622 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1624 if (g.mode == MODE_PLAY) {
1625 if (TEST_FLAG(d->flags, CF_HUMAN))
1626 strncat(buf, " (human/human)", len - 1);
1627 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1628 strncat(buf, " (engine/engine)", len - 1);
1629 else
1630 strncat(buf, " (human/engine)", len - 1);
1633 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1635 if (d->engine) {
1636 switch (d->engine->status) {
1637 case ENGINE_THINKING:
1638 engine = ENGINE_PONDER_STR;
1639 break;
1640 case ENGINE_READY:
1641 engine = ENGINE_READY_STR;
1642 break;
1643 case ENGINE_INITIALIZING:
1644 engine = ENGINE_INITIALIZING_STR;
1645 break;
1646 case ENGINE_OFFLINE:
1647 engine = ENGINE_OFFLINE_STR;
1648 break;
1649 default:
1650 engine = UNKNOWN;
1651 break;
1654 else
1655 engine = ENGINE_OFFLINE_STR;
1657 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1658 wattron(statusw, CP_STATUS_ENGINE);
1659 mvwaddstr(statusw, 5, 9, engine);
1660 wattroff(statusw, CP_STATUS_ENGINE);
1662 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1663 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1665 strncpy(tmp, WHITE_STR, sizeof(tmp));
1666 tmp[0] = toupper(tmp[0]);
1667 update_clock(g.moveclock, &h, &m, &s);
1668 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1669 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1671 strncpy(tmp, BLACK_STR, sizeof(tmp));
1672 tmp[0] = toupper(tmp[0]);
1673 update_clock(g.moveclock, &h, &m, &s);
1674 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1675 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1676 free(buf);
1678 for (i = 1; i < maxx - 4; i++)
1679 mvwprintw(statusw, maxy - 2, i, " ");
1681 if (!status.notify)
1682 status.notify = strdup(GAME_HELP_PROMPT);
1684 wattron(statusw, CP_STATUS_NOTIFY);
1685 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1686 status.notify);
1687 wattroff(statusw, CP_STATUS_NOTIFY);
1690 void update_history_window(GAME g)
1692 char buf[HISTORY_WIDTH - 1];
1693 HISTORY *h = NULL;
1694 int n, total;
1695 int t = pgn_history_total(g.hp);
1697 n = (g.hindex + 1) / 2;
1699 if (t % 2)
1700 total = (t + 1) / 2;
1701 else
1702 total = t / 2;
1704 if (t)
1705 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1706 (movestep == 1) ? HISTORY_PLY_STEP : "");
1707 else
1708 strncpy(buf, UNAVAILABLE, sizeof(buf));
1710 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1711 HISTORY_WIDTH - 13, buf);
1713 h = pgn_history_by_n(g.hp, g.hindex);
1714 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1715 n = 0;
1717 if (h && ((h->comment) || h->nag[0])) {
1718 strncat(buf, " (v", sizeof(buf));
1719 n++;
1722 if (h && h->rav) {
1723 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1724 n++;
1727 if (g.ravlevel) {
1728 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1729 n++;
1732 if (n)
1733 strncat(buf, ")", sizeof(buf));
1735 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1736 HISTORY_WIDTH - 13, buf);
1738 h = pgn_history_by_n(g.hp, game[gindex].hindex - 1);
1739 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1740 n = 0;
1742 if (h && ((h->comment) || h->nag[0])) {
1743 strncat(buf, " (V", sizeof(buf));
1744 n++;
1747 if (h && h->rav) {
1748 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1749 n++;
1752 if (g.ravlevel) {
1753 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1754 n++;
1757 if (n)
1758 strncat(buf, ")", sizeof(buf));
1760 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1761 HISTORY_WIDTH - 13, buf);
1764 void update_tag_window(TAG **t)
1766 int i;
1767 int w = TAG_WIDTH - 10;
1769 for (i = 0; i < 7; i++)
1770 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1773 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1775 int i;
1777 wattron(win, attr);
1779 for (i = 1; i < width - 1; i++)
1780 mvwaddch(win, y, i, ' ');
1782 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1783 wattroff(win, attr);
1786 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1787 chtype battr)
1789 int i;
1791 if (title) {
1792 wattron(win, attr);
1794 for (i = 1; i < width - 1; i++)
1795 mvwaddch(win, 1, i, ' ');
1797 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1798 wattroff(win, attr);
1801 wattron(win, battr);
1802 box(win, ACS_VLINE, ACS_HLINE);
1803 wattroff(win, battr);
1806 void append_enginebuf(char *line)
1808 int i = 0;
1810 if (enginebuf)
1811 for (i = 0; enginebuf[i]; i++);
1813 if (i >= LINES - 3) {
1814 free(enginebuf[0]);
1816 for (i = 0; enginebuf[i+1]; i++)
1817 enginebuf[i] = enginebuf[i+1];
1819 enginebuf[i] = strdup(line);
1821 else {
1822 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1823 enginebuf[i++] = strdup(line);
1824 enginebuf[i] = NULL;
1828 void update_engine_window()
1830 int i;
1832 if (!enginebuf)
1833 return;
1835 wmove(enginew, 0, 0);
1836 wclrtobot(enginew);
1838 if (enginebuf) {
1839 for (i = 0; enginebuf[i]; i++)
1840 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1843 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1844 CP_MESSAGE_BORDER);
1847 void toggle_engine_window()
1849 if (!enginew) {
1850 enginew = newwin(LINES, COLS, 0, 0);
1851 enginep = new_panel(enginew);
1852 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1853 CP_MESSAGE_BORDER);
1854 hide_panel(enginep);
1857 if (panel_hidden(enginep)) {
1858 update_engine_window();
1859 top_panel(enginep);
1860 refresh_all();
1862 else {
1863 hide_panel(enginep);
1864 refresh_all();
1868 void refresh_all()
1870 update_panels();
1871 doupdate();
1874 void update_all(GAME g)
1876 update_status_window(g);
1877 update_history_window(g);
1878 update_tag_window(g.tag);
1879 update_engine_window();
1882 static void game_next_prev(GAME g, int n, int count)
1884 if (gtotal < 2)
1885 return;
1887 if (n == 1) {
1888 if (gindex + count > gtotal - 1) {
1889 if (count != 1)
1890 gindex = gtotal - 1;
1891 else
1892 gindex = 0;
1894 else
1895 gindex += count;
1897 else {
1898 if (gindex - count < 0) {
1899 if (count != 1)
1900 gindex = 0;
1901 else
1902 gindex = gtotal - 1;
1904 else
1905 gindex -= count;
1909 static void delete_game(int which)
1911 GAME *g = NULL;
1912 int gi = 0;
1913 int i;
1915 for (i = 0; i < gtotal; i++) {
1916 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1917 pgn_free(game[i]);
1918 continue;
1921 g = Realloc(g, (gi + 1) * sizeof(GAME));
1922 memcpy(&g[gi], &game[i], sizeof(GAME));
1923 g[gi].tag = game[i].tag;
1924 g[gi].history = game[i].history;
1925 g[gi].hp = game[i].hp;
1926 gi++;
1929 game = g;
1930 gtotal = gi;
1932 if (which != -1) {
1933 if (which + 1 >= gtotal)
1934 gindex = gtotal - 1;
1935 else
1936 gindex = which;
1938 else
1939 gindex = gtotal - 1;
1941 game[gindex].hp = game[gindex].history;
1944 static int find_move_exp(GAME g, const char *str, int init, int which,
1945 int count)
1947 int i;
1948 int ret;
1949 static regex_t r;
1950 static int firstrun = 1;
1951 char errbuf[255];
1952 int incr;
1953 int found;
1955 if (init) {
1956 if (!firstrun)
1957 regfree(&r);
1959 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
1960 regerror(ret, &r, errbuf, sizeof(errbuf));
1961 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
1962 return -1;
1965 firstrun = 1;
1968 incr = (which == 0) ? -1 : 1;
1970 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
1971 if (i == g.hindex - 1)
1972 break;
1974 if (i >= pgn_history_total(g.hp))
1975 i = 0;
1976 else if (i < 0)
1977 i = pgn_history_total(g.hp) - 1;
1979 // FIXME RAV
1980 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
1982 if (ret == 0) {
1983 if (count == ++found) {
1984 return i + 1;
1987 else {
1988 if (ret != REG_NOMATCH) {
1989 regerror(ret, &r, errbuf, sizeof(errbuf));
1990 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
1991 return -1;
1996 return -1;
1999 static int toggle_delete_flag(int n)
2001 int i, x;
2003 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2005 for (i = x = 0; i < gtotal; i++) {
2006 if (TEST_FLAG(game[i].flags, GF_DELETE))
2007 x++;
2010 if (x == gtotal) {
2011 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2012 CLEAR_FLAG(game[n].flags, GF_DELETE);
2013 return 1;
2016 return 0;
2019 static void edit_save_tags(GAME *g)
2021 TAG **t;
2023 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2024 return;
2026 pgn_tag_free(g->tag);
2027 g->tag = t;
2028 SET_FLAG(g->flags, GF_MODIFIED);
2029 pgn_tag_sort(g->tag);
2032 static int find_game_exp(char *str, int which, int count)
2034 char *nstr = NULL, *exp = NULL;
2035 regex_t nexp, vexp;
2036 int ret = -1;
2037 int g = 0;
2038 char buf[255], *tmp;
2039 char errbuf[255];
2040 int found = 0;
2041 int incr = (which == 0) ? -(1) : 1;
2043 strncpy(buf, str, sizeof(buf));
2044 tmp = buf;
2046 if (strstr(tmp, ":") != NULL) {
2047 nstr = strsep(&tmp, ":");
2049 if ((ret = regcomp(&nexp, nstr,
2050 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2051 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2052 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2053 ret = g = -1;
2054 goto cleanup;
2058 exp = tmp;
2060 if (exp == NULL)
2061 goto cleanup;
2063 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2064 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2065 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2066 ret = -1;
2067 goto cleanup;
2070 ret = -1;
2072 for (g = gindex + incr, found = 0; ; g += incr) {
2073 int t;
2075 if (g == gindex)
2076 break;
2078 if (g == gtotal)
2079 g = 0;
2080 else if (g < 0)
2081 g = gtotal - 1;
2083 for (t = 0; game[g].tag[t]; t++) {
2084 if (nstr) {
2085 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2086 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2087 if (count == ++found) {
2088 ret = g;
2089 goto cleanup;
2094 else {
2095 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2096 if (count == ++found) {
2097 ret = g;
2098 goto cleanup;
2104 ret = -1;
2107 cleanup:
2108 if (nstr)
2109 regfree(&nexp);
2111 if (g != -1)
2112 regfree(&vexp);
2114 return ret;
2118 * Updates the notification line in the status window then refreshes the
2119 * status window.
2121 void update_status_notify(GAME g, char *fmt, ...)
2123 va_list ap;
2124 #ifdef HAVE_VASPRINTF
2125 char *line;
2126 #else
2127 char line[COLS];
2128 #endif
2130 if (!fmt) {
2131 if (status.notify) {
2132 free(status.notify);
2133 status.notify = NULL;
2135 if (curses_initialized)
2136 update_status_window(g);
2139 return;
2142 va_start(ap, fmt);
2143 #ifdef HAVE_VASPRINTF
2144 vasprintf(&line, fmt, ap);
2145 #else
2146 vsnprintf(line, sizeof(line), fmt, ap);
2147 #endif
2148 va_end(ap);
2150 if (status.notify)
2151 free(status.notify);
2153 status.notify = strdup(line);
2155 #ifdef HAVE_VASPRINTF
2156 free(line);
2157 #endif
2158 if (curses_initialized)
2159 update_status_window(g);
2162 static void switch_side(GAME *g)
2164 g->side = (g->side == WHITE) ? BLACK : WHITE;
2167 int rav_next_prev(GAME *g, BOARD b, int n)
2169 // Next RAV.
2170 if (n) {
2171 if (g->hp[g->hindex]->rav == NULL)
2172 return 1;
2174 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2175 g->rav[g->ravlevel].hp = g->hp;
2176 g->rav[g->ravlevel].flags = g->flags;
2177 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2178 g->rav[g->ravlevel].hindex = g->hindex;
2179 g->hp = g->hp[g->hindex]->rav;
2180 g->hindex = 0;
2181 g->ravlevel++;
2182 return 0;
2185 if (g->ravlevel - 1 < 0)
2186 return 1;
2188 // Previous RAV.
2189 g->ravlevel--;
2190 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2191 free(g->rav[g->ravlevel].fen);
2192 g->hp = g->rav[g->ravlevel].hp;
2193 g->flags = g->rav[g->ravlevel].flags;
2194 g->hindex = g->rav[g->ravlevel].hindex;
2195 return 0;
2198 static void draw_window_decor()
2200 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2201 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2202 wbkgd(boardw, CP_BOARD_WINDOW);
2203 wbkgd(statusw, CP_STATUS_WINDOW);
2204 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2205 CP_STATUS_TITLE, CP_STATUS_BORDER);
2206 wbkgd(tagw, CP_TAG_WINDOW);
2207 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2208 CP_TAG_BORDER);
2209 wbkgd(historyw, CP_HISTORY_WINDOW);
2210 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2211 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2214 static void do_window_resize()
2216 if (LINES < 24 || COLS < 80)
2217 return;
2219 resizeterm(LINES, COLS);
2220 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2221 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2222 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2223 wmove(historyw, 0, 0);
2224 wclrtobot(historyw);
2225 wmove(tagw, 0, 0);
2226 wclrtobot(tagw);
2227 wmove(statusw, 0, 0);
2228 wclrtobot(statusw);
2229 draw_window_decor();
2230 update_all(game[gindex]);
2233 static void historymode_keys(chtype);
2234 static int playmode_keys(chtype c)
2236 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2237 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2238 chtype p;
2239 int w, x;
2240 char *tmp;
2241 struct userdata_s *d = game[gindex].data;
2243 switch (c) {
2244 case 'H':
2245 TOGGLE_FLAG(d->flags, CF_HUMAN);
2247 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2248 pgn_history_total(game[gindex].hp)) {
2249 pgn_tag_add(&game[gindex].tag, "FEN",
2250 pgn_game_to_fen(game[gindex], game[gindex].b));
2251 x = pgn_tag_find(game[gindex].tag, "FEN");
2253 if (start_chess_engine(&game[gindex]) <= 0) {
2254 send_to_engine(&game[gindex], "setboard %s\n",
2255 game[gindex].tag[x]->value);
2256 d->engine->status = ENGINE_READY;
2260 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2262 if (d->engine)
2263 d->engine->status = ENGINE_READY;
2265 update_all(game[gindex]);
2266 break;
2267 case 'E':
2268 if (!d)
2269 break;
2271 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2272 CLEAR_FLAG(d->flags, CF_HUMAN);
2274 if (d->engine && !TEST_FLAG(d->flags, CF_ENGINE_LOOP))
2275 d->engine->status = ENGINE_READY;
2277 update_all(game[gindex]);
2278 break;
2279 case '|':
2280 if (!d->engine)
2281 break;
2283 if (d->engine->status == ENGINE_OFFLINE)
2284 break;
2286 x = d->engine->status;
2288 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2289 send_to_engine(&game[gindex], "%s\n", tmp);
2290 d->engine->status = x;
2291 break;
2292 case '\015':
2293 case '\n':
2294 pushkey = keycount = 0;
2295 update_status_notify(game[gindex], NULL);
2297 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2298 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2299 beep();
2300 break;
2303 if (!sp.icon)
2304 break;
2306 sp.destrow = c_row;
2307 sp.destcol = c_col;
2309 if (editmode) {
2310 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2311 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2312 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
2313 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2314 sp.icon = sp.row = sp.col = 0;
2315 break;
2318 if (move_to_engine(&game[gindex], game[gindex].b)) {
2319 if (config.validmoves)
2320 pgn_reset_valid_moves(game[gindex].b);
2322 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2323 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2324 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2328 break;
2329 case ' ':
2330 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2331 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2332 if (start_chess_engine(&game[gindex])) {
2333 sp.icon = 0;
2334 break;
2337 x = pgn_tag_find(game[gindex].tag, "FEN");
2338 w = pgn_tag_find(game[gindex].tag, "SetUp");
2340 if ((w >= 0 && x >= 0 && atoi(game[gindex].tag[w]->value) == 1)
2341 || (x >= 0 && w == -1)) {
2342 send_to_engine(&game[gindex], "setboard %s\n",
2343 game[gindex].tag[x]->value);
2344 d->engine->status = ENGINE_READY;
2348 if (sp.icon || (!editmode && d->engine &&
2349 d->engine->status == ENGINE_THINKING)) {
2350 beep();
2351 break;
2354 sp.icon = mvwinch(boardw, ROWTOMATRIX(c_row),
2355 COLTOMATRIX(c_col)+1) & A_CHARTEXT;
2357 if (sp.icon == ' ') {
2358 sp.icon = 0;
2359 break;
2362 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2363 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2364 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2365 sp.icon = 0;
2366 break;
2369 sp.row = c_row;
2370 sp.col = c_col;
2372 if (!editmode && config.validmoves)
2373 pgn_get_valid_moves(&game[gindex], game[gindex].b, sp.row,
2374 sp.col);
2376 paused = 0;
2377 break;
2378 case 'w':
2379 send_to_engine(&game[gindex], "\nswitch\n");
2380 switch_side(&game[gindex]);
2381 update_status_window(game[gindex]);
2382 break;
2383 case 'u':
2384 if (!pgn_history_total(game[gindex].hp))
2385 break;
2387 if (d->engine && d->engine->status == ENGINE_READY) {
2388 send_to_engine(&game[gindex], "remove\n");
2389 d->engine->status = ENGINE_READY;
2392 game[gindex].hindex -= 2;
2393 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2394 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2395 pgn_board_update(&game[gindex], game[gindex].b,
2396 game[gindex].hindex);
2397 update_history_window(game[gindex]);
2398 break;
2399 case 'a':
2400 historymode_keys(c);
2401 break;
2402 case 'd':
2403 board_details = (board_details) ? 0 : 1;
2404 break;
2405 case 'p':
2406 paused = (paused) ? 0 : 1;
2407 break;
2408 case 'g':
2409 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2410 start_chess_engine(&game[gindex]);
2412 send_to_engine(&game[gindex], "go\n");
2413 break;
2414 default:
2415 if (!d->engine)
2416 break;
2418 if (config.keys) {
2419 for (x = 0; config.keys[x]; x++) {
2420 if (config.keys[x]->c == c) {
2421 send_to_engine(&game[gindex], "%s\n",
2422 config.keys[x]->str);
2423 d->engine->status = ENGINE_READY;
2424 break;
2428 break;
2431 return 0;
2434 static void editmode_keys(chtype c)
2436 switch (c) {
2437 case '\015':
2438 case '\n':
2439 case ' ':
2440 playmode_keys(c);
2441 break;
2442 case 'd':
2443 if (sp.icon)
2444 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2445 else
2446 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2448 sp.icon = sp.row = sp.col = 0;
2449 break;
2450 case 'w':
2451 pgn_switch_turn(&game[gindex]);
2452 switch_side(&game[gindex]);
2453 update_all(game[gindex]);
2454 break;
2455 case 'c':
2456 castling_state(&game[gindex], game[gindex].b, ROWTOBOARD(c_row),
2457 COLTOBOARD(c_col),
2458 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon, 1);
2459 break;
2460 case 'i':
2461 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2462 GAME_EDIT_TEXT);
2464 if (pgn_piece_to_int(c) == -1)
2465 break;
2467 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = c;
2468 break;
2469 case 'p':
2470 if (c_row == 6 || c_row == 3) {
2471 pgn_reset_enpassant(game[gindex].b);
2472 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].enpassant = 1;
2474 break;
2475 default:
2476 break;
2480 static void historymode_keys(chtype c)
2482 int n, len;
2483 char *tmp, *buf;
2484 static char moveexp[255] = {0};
2486 switch (c) {
2487 case ' ':
2488 movestep = (movestep == 1) ? 2 : 1;
2489 update_history_window(game[gindex]);
2490 break;
2491 case KEY_UP:
2492 pgn_history_next(&game[gindex], game[gindex].b, (keycount > 0) ?
2493 config.jumpcount * keycount * movestep :
2494 config.jumpcount * movestep);
2495 update_all(game[gindex]);
2496 break;
2497 case KEY_DOWN:
2498 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2499 config.jumpcount * keycount * movestep :
2500 config.jumpcount * movestep);
2501 update_all(game[gindex]);
2502 break;
2503 case KEY_LEFT:
2504 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2505 keycount * movestep : movestep);
2506 update_all(game[gindex]);
2507 break;
2508 case KEY_RIGHT:
2509 pgn_history_next(&game[gindex], game[gindex].b, (keycount) ?
2510 keycount * movestep : movestep);
2511 update_all(game[gindex]);
2512 break;
2513 case 'a':
2514 n = game[gindex].hindex;
2516 if (n && game[gindex].hp[n - 1]->move)
2517 n--;
2518 else
2519 break;
2521 buf = Malloc(COLS);
2522 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2523 game[gindex].hp[n]->move);
2525 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2526 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2527 -1);
2528 free(buf);
2530 if (!tmp && (!game[gindex].hp[n]->comment ||
2531 !*game[gindex].hp[n]->comment))
2532 break;
2533 else if (tmp && game[gindex].hp[n]->comment) {
2534 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2535 break;
2538 len = (tmp) ? strlen(tmp) + 1 : 1;
2539 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2540 len);
2541 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2542 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2543 update_all(game[gindex]);
2544 break;
2545 case ']':
2546 case '[':
2547 case '/':
2548 if (pgn_history_total(game[gindex].hp) < 2)
2549 break;
2551 n = 0;
2553 if (!*moveexp || c == '/') {
2554 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2555 break;
2557 strncpy(moveexp, tmp, sizeof(moveexp));
2558 n = 1;
2561 if ((n = find_move_exp(game[gindex], moveexp, n,
2562 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2563 == -1)
2564 break;
2566 game[gindex].hindex = n;
2567 pgn_board_update(&game[gindex], game[gindex].b, game[gindex].hindex);
2568 update_all(game[gindex]);
2569 break;
2570 case 'v':
2571 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2572 break;
2573 case 'V':
2574 if (game[gindex].hindex - 1 >= 0)
2575 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2576 break;
2577 case '-':
2578 case '+':
2579 rav_next_prev(&game[gindex], game[gindex].b, (c == '-') ? 0 : 1);
2580 update_all(game[gindex]);
2581 break;
2582 case 'j':
2583 if (pgn_history_total(game[gindex].hp) < 2)
2584 break;
2586 /* FIXME field validation
2587 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2588 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2589 game[gindex].htotal)) == NULL)
2590 break;
2593 if (!keycount) {
2594 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2595 NULL, NULL, NULL, 0, -1)) == NULL)
2596 break;
2598 if (!isinteger(tmp))
2599 break;
2601 n = atoi(tmp);
2603 else
2604 n = keycount;
2606 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2607 break;
2609 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2610 pgn_board_update(&game[gindex], game[gindex].b,
2611 game[gindex].hindex);
2612 update_all(game[gindex]);
2613 break;
2614 default:
2615 break;
2619 static void cleanup_all_games()
2621 int i;
2623 for (i = 0; i < gtotal; i++) {
2624 struct userdata_s *d;
2626 if (game[i].data) {
2627 stop_engine(&game[i]);
2628 d = game[i].data;
2629 free(game[i].data);
2630 game[i].data = NULL;
2635 void update_loading_window()
2637 if (!loadingw) {
2638 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2639 loadingp = new_panel(loadingw);
2640 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2643 wmove(loadingw, 0, 0);
2644 wclrtobot(loadingw);
2645 wattron(loadingw, CP_MESSAGE_BORDER);
2646 box(loadingw, ACS_VLINE, ACS_HLINE);
2647 wattroff(loadingw, CP_MESSAGE_BORDER);
2648 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2649 11 + strlen(itoa(gtotal))), "Loading... %i", gtotal);
2650 update_panels();
2651 doupdate();
2654 void init_userdata()
2656 int i;
2658 for (i = 0; i < gtotal; i++) {
2659 struct userdata_s *d = NULL;
2661 d = Calloc(1, sizeof(struct userdata_s));
2662 game[i].data = d;
2663 d->n = i;
2667 // Global and other keys.
2668 static int globalkeys(chtype c)
2670 static char gameexp[255] = {0};
2671 FILE *fp;
2672 char *tmp, *p;
2673 int n, i;
2674 char tfile[FILENAME_MAX];
2675 struct userdata_s *d = game[gindex].data;
2677 switch (c) {
2678 case 'W':
2679 toggle_engine_window();
2680 break;
2681 case KEY_F(10):
2682 cmessage("ABOUT", ANYKEY, "%s\n%s with %i colors and %i "
2683 "color pairs\nCopyright 2002-2006 %s", PACKAGE_STRING,
2684 curses_version(), COLORS, COLOR_PAIRS, PACKAGE_BUGREPORT);
2685 break;
2686 case 'h':
2687 if (game[gindex].mode != MODE_HISTORY) {
2688 if (!pgn_history_total(game[gindex].hp) ||
2689 (d->engine && d->engine->status == ENGINE_THINKING))
2690 return 1;
2692 game[gindex].mode = MODE_HISTORY;
2693 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2694 update_all(game[gindex]);
2695 return 1;
2698 // FIXME
2699 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2700 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2701 return 1;
2704 // FIXME Resuming from previous history could append to a RAV.
2705 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2706 if (!pushkey) {
2707 if ((c = message(NULL, YESNO, "%s",
2708 GAME_RESUME_HISTORY_TEXT)) != 'y')
2709 return 1;
2712 else {
2713 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2714 return 1;
2717 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2718 d->engine->status == ENGINE_OFFLINE)) {
2719 if (start_chess_engine(&game[gindex]) < 0)
2720 return 1;
2722 pushkey = 'h';
2723 return 1;
2726 pushkey = 0;
2727 oldhistorytotal = pgn_history_total(game[gindex].hp);
2728 game[gindex].mode = MODE_PLAY;
2729 update_all(game[gindex]);
2730 return 1;
2731 case '>':
2732 case '<':
2733 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2734 keycount : 1);
2736 if (delete_count) {
2737 markend = delete_count;
2738 pushkey = 'x';
2739 delete_count = 0;
2742 if (game[gindex].mode != MODE_EDIT) {
2743 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2745 update_all(game[gindex]);
2746 update_tag_window(game[gindex].tag);
2747 return 1;
2748 case '}':
2749 case '{':
2750 case '?':
2751 if (gtotal < 2)
2752 return 1;
2754 if (!*gameexp || c == '?') {
2755 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2756 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2757 NULL, 0, -1)) == NULL)
2758 return 1;
2760 strncpy(gameexp, tmp, sizeof(gameexp));
2763 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2764 ? keycount : 1)) ==
2766 return 1;
2768 gindex = n;
2770 if (pgn_history_total(game[gindex].hp))
2771 game[gindex].mode = MODE_HISTORY;
2773 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2774 update_all(game[gindex]);
2775 update_tag_window(game[gindex].tag);
2776 return 1;
2777 case 'J':
2778 if (gtotal < 2)
2779 return 1;
2781 /* FIXME field validation
2782 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2783 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2784 == NULL)
2785 return 1;
2788 if (!keycount) {
2789 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2790 NULL, NULL, 0, -1)) == NULL)
2791 return 1;
2793 if (!isinteger(tmp))
2794 return 1;
2796 i = atoi(tmp);
2798 else
2799 i = keycount;
2801 if (--i > gtotal - 1 || i < 0)
2802 return 1;
2804 gindex = i;
2805 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2806 update_all(game[gindex]);
2807 update_tag_window(game[gindex].tag);
2808 return 1;
2809 case 'x':
2810 pushkey = 0;
2812 if (gtotal < 2)
2813 return 1;
2815 if (keycount && !delete_count) {
2816 markstart = gindex;
2817 delete_count = keycount;
2818 update_status_notify(game[gindex], "%s (delete)",
2819 status.notify);
2820 return 1;
2823 if (markstart >= 0 && markend >= 0) {
2824 if (markstart > markend) {
2825 i = markstart;
2826 markstart = markend;
2827 markend = i;
2830 for (i = markstart; i <= markend; i++) {
2831 if (toggle_delete_flag(i))
2832 return 1;
2835 else {
2836 if (toggle_delete_flag(gindex))
2837 return 1;
2840 markstart = markend = -1;
2841 update_status_window(game[gindex]);
2842 return 1;
2843 case 'X':
2844 if (gtotal < 2) {
2845 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2846 return 1;
2849 tmp = NULL;
2851 for (i = n = 0; i < gtotal; i++) {
2852 if (TEST_FLAG(game[i].flags, GF_DELETE))
2853 n++;
2856 if (!n)
2857 tmp = GAME_DELETE_GAME_TEXT;
2858 else {
2859 if (n == gtotal) {
2860 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2861 return 1;
2864 tmp = GAME_DELETE_ALL_TEXT;
2867 if (config.deleteprompt) {
2868 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2869 return 1;
2872 delete_game((!n) ? gindex : -1);
2874 if (pgn_history_total(game[gindex].hp))
2875 game[gindex].mode = MODE_HISTORY;
2877 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2878 update_all(game[gindex]);
2879 update_tag_window(game[gindex].tag);
2880 return 1;
2881 case 'T':
2882 edit_save_tags(&game[gindex]);
2883 update_all(game[gindex]);
2884 update_tag_window(game[gindex].tag);
2885 return 1;
2886 case 't':
2887 edit_tags(game[gindex], game[gindex].b, 0);
2888 return 1;
2889 case 'r':
2890 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2891 BROWSER_PROMPT, file_browser, NULL, '\t',
2892 -1)) == NULL)
2893 return 1;
2895 if ((tmp = word_expand(tmp)) == NULL)
2896 break;
2898 if ((fp = pgn_open(tmp)) == NULL) {
2899 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2900 return 1;
2903 if (pgn_parse(fp))
2904 return 1;
2906 del_panel(loadingp);
2907 delwin(loadingw);
2908 loadingw = NULL;
2909 loadingp = NULL;
2910 init_userdata();
2911 strncpy(loadfile, tmp, sizeof(loadfile));
2913 if (pgn_history_total(game[gindex].hp))
2914 game[gindex].mode = MODE_HISTORY;
2916 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2917 update_all(game[gindex]);
2918 update_tag_window(game[gindex].tag);
2919 return 1;
2920 case 'S':
2921 case 's':
2922 i = -1;
2924 if (gtotal > 1) {
2925 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2926 GAME_SAVE_MULTI_TEXT);
2928 if (n == 'c')
2929 i = gindex;
2930 else if (n == 'a')
2931 i = -1;
2932 else {
2933 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2934 return 1;
2938 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2939 BROWSER_PROMPT, file_browser, NULL,
2940 '\t', -1)) == NULL) {
2941 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2942 return 1;
2945 if ((tmp = word_expand(tmp)) == NULL)
2946 break;
2948 if (pgn_is_compressed(tmp)) {
2949 p = tmp + strlen(tmp) - 1;
2951 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
2952 *(p-3) != '.') {
2953 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2954 tmp = tfile;
2957 else {
2958 if ((p = strchr(tmp, '.')) != NULL) {
2959 if (strcmp(p, ".pgn") != 0) {
2960 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2961 tmp = tfile;
2964 else {
2965 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2966 tmp = tfile;
2970 if (save_pgn(tmp, 0, i)) {
2971 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
2972 return 1;
2975 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
2976 update_all(game[gindex]);
2977 return 1;
2978 case KEY_F(1):
2979 n = 0;
2981 switch (game[gindex].mode) {
2982 case MODE_PLAY:
2983 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
2984 break;
2985 case MODE_HISTORY:
2986 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
2987 break;
2988 case MODE_EDIT:
2989 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
2990 break;
2991 default:
2992 break;
2995 while (c == KEY_F(1)) {
2996 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
2997 mainhelp);
2999 switch (c) {
3000 case 'h':
3001 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3002 break;
3003 case 'p':
3004 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3005 break;
3006 case 'e':
3007 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3008 break;
3009 case 'g':
3010 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3011 break;
3012 default:
3013 break;
3017 return 1;
3018 case 'n':
3019 case 'N':
3020 if (c == 'N') {
3021 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3022 return 1;
3025 if (c == 'n') {
3026 pgn_new_game();
3027 add_custom_tags(&game[gindex].tag);
3028 d = Calloc(1, sizeof(struct userdata_s));
3029 game[gindex].data = d;
3031 else {
3032 cleanup_all_games();
3033 pgn_parse(NULL);
3034 add_custom_tags(&game[gindex].tag);
3035 pgn_board_init(game[gindex].b);
3036 d = Calloc(1, sizeof(struct userdata_s));
3037 game[gindex].data = d;
3040 game[gindex].mode = MODE_PLAY;
3041 c_row = (game[gindex].side == WHITE) ? 2 : 7;
3042 c_col = 4;
3043 update_status_notify(game[gindex], NULL);
3044 update_all(game[gindex]);
3045 update_tag_window(game[gindex].tag);
3046 return 1;
3047 case CTRL('L'):
3048 endwin();
3049 keypad(boardw, TRUE);
3050 refresh_all();
3051 return 1;
3052 case KEY_ESCAPE:
3053 sp.icon = sp.row = sp.col = 0;
3054 markend = markstart = 0;
3056 if (keycount) {
3057 keycount = 0;
3058 update_status_notify(game[gindex], NULL);
3061 if (config.validmoves)
3062 pgn_reset_valid_moves(game[gindex].b);
3064 return 1;
3065 case '0' ... '9':
3066 n = c - '0';
3068 if (keycount)
3069 keycount = keycount * 10 + n;
3070 else
3071 keycount = n;
3073 update_status_notify(game[gindex], "Repeat %i", keycount);
3074 return -1;
3075 case KEY_UP:
3076 if (game[gindex].mode == MODE_HISTORY)
3077 return 0;
3079 if (keycount) {
3080 c_row += keycount;
3081 pushkey = '\n';
3083 else
3084 c_row++;
3086 if (c_row > 8)
3087 c_row = 1;
3089 return 1;
3090 case KEY_DOWN:
3091 if (game[gindex].mode == MODE_HISTORY)
3092 return 0;
3094 if (keycount) {
3095 c_row -= keycount;
3096 pushkey = '\n';
3097 update_status_notify(game[gindex], NULL);
3099 else
3100 c_row--;
3102 if (c_row < 1)
3103 c_row = 8;
3105 return 1;
3106 case KEY_LEFT:
3107 if (game[gindex].mode == MODE_HISTORY)
3108 return 0;
3110 if (keycount) {
3111 c_col -= keycount;
3112 pushkey = '\n';
3114 else
3115 c_col--;
3117 if (c_col < 1)
3118 c_col = 8;
3120 return 1;
3121 case KEY_RIGHT:
3122 if (game[gindex].mode == MODE_HISTORY)
3123 return 0;
3125 if (keycount) {
3126 c_col += keycount;
3127 pushkey = '\n';
3129 else
3130 c_col++;
3132 if (c_col > 8)
3133 c_col = 1;
3135 return 1;
3136 case 'e':
3137 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3138 MODE_PLAY)
3139 return 1;
3141 // Don't edit a running game (for now).
3142 if (pgn_history_total(game[gindex].hp))
3143 return 1;
3145 if (game[gindex].mode != MODE_EDIT) {
3146 pgn_board_init_fen(&game[gindex], game[gindex].b, NULL);
3147 board_details++;
3148 game[gindex].mode = MODE_EDIT;
3149 update_all(game[gindex]);
3150 return 1;
3153 board_details--;
3154 pgn_tag_add(&game[gindex].tag, "FEN",
3155 pgn_game_to_fen(game[gindex], game[gindex].b));
3156 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3157 pgn_tag_sort(game[gindex].tag);
3158 game[gindex].mode = MODE_PLAY;
3159 update_all(game[gindex]);
3160 return 1;
3161 case 'Q':
3162 quit = 1;
3163 return 1;
3164 case KEY_RESIZE:
3165 do_window_resize();
3166 return 1;
3167 #ifdef DEBUG
3168 case 'D':
3169 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3170 return 1;
3171 #endif
3172 case 0:
3173 default:
3174 break;
3177 return 0;
3180 void game_loop()
3182 int error_recover = 0;
3184 c_row = 2, c_col = 5;
3185 gindex = gtotal - 1;
3187 if (pgn_history_total(game[gindex].hp))
3188 game[gindex].mode = MODE_HISTORY;
3189 else
3190 game[gindex].mode = MODE_PLAY;
3192 if (game[gindex].mode == MODE_HISTORY) {
3193 pgn_board_update(&game[gindex], game[gindex].b,
3194 pgn_history_total(game[gindex].hp));
3197 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3198 movestep = 2;
3199 paused = 1; //FIXME clock
3200 flushinp();
3201 update_all(game[gindex]);
3202 update_tag_window(game[gindex].tag);
3203 wtimeout(boardw, 70);
3205 while (!quit) {
3206 int c = 0;
3207 int n = 0, i;
3208 char fdbuf[8192] = {0};
3209 int len;
3210 struct timeval tv = {0, 0};
3211 fd_set rfds;
3212 struct userdata_s *d = NULL;
3214 FD_ZERO(&rfds);
3216 for (i = 0; i < gtotal; i++) {
3217 d = game[i].data;
3219 if (d->engine) {
3220 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3221 if (d->engine->fd[ENGINE_IN_FD] > n)
3222 n = d->engine->fd[ENGINE_IN_FD];
3224 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3229 if (n) {
3230 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3231 for (i = 0; i < gtotal; i++) {
3232 d = game[i].data;
3234 if (d->engine) {
3235 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3236 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3237 sizeof(fdbuf));
3239 if (len == -1) {
3240 if (errno != EAGAIN) {
3241 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3242 strerror(errno));
3243 waitpid(d->engine->pid, &n, 0);
3244 free(d->engine);
3245 d->engine = NULL;
3246 break;
3249 else {
3250 if (len) {
3251 parse_engine_output(&game[i], fdbuf);
3252 update_all(game[gindex]);
3259 else {
3260 if (n == -1)
3261 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3262 else {
3263 /* timeout */
3268 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER) && game[gindex].mode
3269 != MODE_HISTORY) {
3270 game[gindex].mode = MODE_HISTORY;
3271 update_all(game[gindex]);
3274 error_recover = 0;
3275 draw_board(&game[gindex], board_details);
3276 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3278 if (!paused) {
3281 refresh_all();
3283 if (pushkey)
3284 c = pushkey;
3285 else {
3286 if ((c = wgetch(boardw)) == ERR)
3287 continue;
3290 if (!keycount && status.notify)
3291 update_status_notify(game[gindex], NULL);
3294 if ((n = globalkeys(c)) == 1) {
3295 keycount = 0;
3296 continue;
3298 else if (n == -1)
3299 continue;
3301 switch (game[gindex].mode) {
3302 case MODE_EDIT:
3303 editmode_keys(c);
3304 break;
3305 case MODE_PLAY:
3306 if (playmode_keys(c))
3307 continue;
3308 break;
3309 case MODE_HISTORY:
3310 historymode_keys(c);
3311 break;
3312 default:
3313 break;
3316 keycount = 0;
3320 void usage(const char *pn, int ret)
3322 fprintf((ret) ? stderr : stdout, "%s",
3323 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3324 " -p Load PGN file.\n"
3325 " -V Validate a game file.\n"
3326 " -S Validate and output a PGN formatted game.\n"
3327 " -R Like -S but write a reduced PGN formatted game.\n"
3328 " -t Also write custom PGN tags from config file.\n"
3329 " -E Stop processing on file parsing error (overrides config).\n"
3330 " -v Version information.\n"
3331 " -h This help text.\n");
3333 exit(ret);
3336 void cleanup_all()
3338 int i;
3340 cleanup_all_games();
3341 pgn_free_all();
3342 del_panel(boardp);
3343 del_panel(historyp);
3344 del_panel(statusp);
3345 del_panel(tagp);
3346 delwin(boardw);
3347 delwin(historyw);
3348 delwin(statusw);
3349 delwin(tagw);
3351 if (enginew) {
3352 del_panel(enginep);
3353 delwin(enginew);
3355 if (enginebuf) {
3356 for (i = 0; enginebuf[i]; i++)
3357 free(enginebuf[i]);
3359 free(enginebuf);
3363 endwin();
3366 void catch_signal(int which)
3368 switch (which) {
3369 case SIGINT:
3370 case SIGPIPE:
3371 if (which == SIGPIPE && quit)
3372 break;
3374 if (which == SIGPIPE)
3375 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3377 cleanup_all();
3378 exit(EXIT_FAILURE);
3379 break;
3380 case SIGSTOP:
3381 savetty();
3382 break;
3383 case SIGCONT:
3384 resetty();
3385 keypad(boardw, TRUE);
3386 curs_set(0);
3387 cbreak();
3388 noecho();
3389 break;
3390 case SIGUSR1:
3391 if (curses_initialized) {
3392 update_loading_window(game[gindex]);
3393 break;
3396 fprintf(stderr, "Loading... %i\r", gtotal);
3397 fflush(stderr);
3398 break;
3399 default:
3400 break;
3404 static void set_defaults()
3406 filetype = NO_FILE;
3407 set_config_defaults();
3410 int main(int argc, char *argv[])
3412 int opt;
3413 struct stat st;
3414 char buf[FILENAME_MAX];
3415 char datadir[FILENAME_MAX];
3416 int ret = EXIT_SUCCESS;
3417 int validate_only = 0, validate_and_write = 0, reduced = 0;
3418 int write_custom_tags = 0;
3419 FILE *fp;
3420 int i;
3422 if ((config.pwd = getpwuid(getuid())) == NULL)
3423 err(EXIT_FAILURE, "getpwuid()");
3425 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3426 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3427 config.ccfile = strdup(buf);
3428 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3429 config.nagfile = strdup(buf);
3430 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3431 config.agonyfile = strdup(buf);
3432 snprintf(buf, sizeof(buf), "%s/config", datadir);
3433 config.configfile = strdup(buf);
3434 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3435 config.fifo = strdup(buf);
3437 if (stat(datadir, &st) == -1) {
3438 if (errno == ENOENT) {
3439 if (mkdir(datadir, 0755) == -1)
3440 err(EXIT_FAILURE, "%s", datadir);
3442 else
3443 err(EXIT_FAILURE, "%s", datadir);
3445 stat(datadir, &st);
3448 if (!S_ISDIR(st.st_mode))
3449 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3451 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3452 if (mkfifo(config.fifo, 0600) == -1)
3453 err(EXIT_FAILURE, "%s", config.fifo);
3456 set_defaults();
3458 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3459 switch (opt) {
3460 case 't':
3461 write_custom_tags = 1;
3462 break;
3463 case 'E':
3464 config.stoponerror = 1;
3465 break;
3466 case 'R':
3467 reduced = 1;
3468 case 'S':
3469 validate_and_write = 1;
3470 case 'V':
3471 validate_only = 1;
3472 break;
3473 case 'v':
3474 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3475 COPYRIGHT);
3476 exit(EXIT_SUCCESS);
3477 case 'p':
3478 filetype = PGN_FILE;
3479 strncpy(loadfile, optarg, sizeof(loadfile));
3480 break;
3481 case 'h':
3482 default:
3483 usage(argv[0], EXIT_SUCCESS);
3487 if ((validate_only || validate_and_write) && !*loadfile)
3488 usage(argv[0], EXIT_FAILURE);
3490 if (access(config.configfile, R_OK) == 0)
3491 parse_rcfile(config.configfile);
3493 signal(SIGPIPE, catch_signal);
3494 signal(SIGCONT, catch_signal);
3495 signal(SIGSTOP, catch_signal);
3496 signal(SIGINT, catch_signal);
3497 signal(SIGUSR1, catch_signal);
3499 srandom(getpid());
3501 switch (filetype) {
3502 case PGN_FILE:
3503 if ((fp = pgn_open(loadfile)) == NULL)
3504 err(EXIT_FAILURE, "%s", loadfile);
3506 ret = pgn_parse(fp);
3507 break;
3508 case FEN_FILE:
3509 //ret = parse_fen_file(loadfile);
3510 break;
3511 case EPD_FILE: // Not implemented.
3512 case NO_FILE:
3513 default:
3514 // No file specified. Empty game.
3515 ret = pgn_parse(NULL);
3516 add_custom_tags(&game[gindex].tag);
3517 break;
3520 if (validate_only || validate_and_write) {
3521 if (validate_and_write) {
3522 for (i = 0; i < gtotal; i++) {
3523 if (write_custom_tags)
3524 add_custom_tags(&game[i].tag);
3526 pgn_write(stdout, game[i]);
3530 pgn_free_all();
3531 exit(ret);
3533 else if (ret)
3534 exit(ret);
3536 init_userdata();
3538 if (initscr() == NULL)
3539 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3540 else
3541 curses_initialized = 1;
3543 if (LINES < 24 || COLS < 80) {
3544 endwin();
3545 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3548 if (has_colors() == TRUE && start_color() == OK)
3549 init_color_pairs();
3551 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3552 boardp = new_panel(boardw);
3553 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3554 COLS - HISTORY_WIDTH);
3555 historyp = new_panel(historyw);
3556 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3557 statusp = new_panel(statusw);
3558 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3559 tagp = new_panel(tagw);
3560 keypad(boardw, TRUE);
3561 // leaveok(boardw, TRUE);
3562 leaveok(tagw, TRUE);
3563 leaveok(statusw, TRUE);
3564 leaveok(historyw, TRUE);
3565 curs_set(0);
3566 cbreak();
3567 noecho();
3568 draw_window_decor();
3569 game_loop();
3570 cleanup_all();
3571 exit(EXIT_SUCCESS);