Renamed user_data_s to userdata_s.
[cboard.git] / src / cboard.c
blobc0a7db935851139b951814c552c95a891bbb9a00
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 /* FIXME castling */
107 void update_cursor(GAME g, int idx)
109 char *p;
110 int len;
111 int t = history_total(g.hp);
114 * If not deincremented then r and c would be the next move.
116 idx--;
118 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
119 c_row = 2, c_col = 5;
120 return;
123 p = g.hp[idx]->move;
124 len = strlen(p);
126 if (*p == 'O') {
127 if (len <= 4)
128 c_col = 7;
129 else
130 c_col = 3;
132 c_row = (g.turn == WHITE) ? 8 : 1;
133 return;
136 p += len;
138 while (!isdigit(*p))
139 p--;
141 c_row = ROWTOINT(*p--);
142 c_col = COLTOINT(*p);
145 static int init_nag()
147 FILE *fp;
148 char line[LINE_MAX];
149 int i = 0;
151 if ((fp = fopen(config.nagfile, "r")) == NULL) {
152 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
153 return 1;
156 while (!feof(fp)) {
157 if (fscanf(fp, " %[^\n] ", line) == 1) {
158 nags = Realloc(nags, (i + 2) * sizeof(struct nag_s));
159 nags[i].line = strdup(line);
160 i++;
164 if (nags)
165 nags[i].line = NULL;
166 return 0;
169 char *history_edit_nag(void *arg)
171 WINDOW *win, *subw;
172 PANEL *panel;
173 ITEM **mitems = NULL;
174 MENU *menu;
175 int i = 0, n;
176 int itemcount = 0;
177 int rows, cols;
178 char *mbuf = NULL;
179 HISTORY *anno = (HISTORY *)arg;
181 if (!nags) {
182 if (init_nag())
183 return NULL;
186 i = 0;
187 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
188 mitems[i++] = new_item(NONE, NULL);
190 for (n = 0; nags[n].line; n++, i++) {
191 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
192 mitems[i] = new_item(nags[n].line, NULL);
195 mitems[i] = NULL;
196 menu = new_menu(mitems);
197 scale_menu(menu, &rows, &cols);
199 win = newwin(rows + 4, cols + 2, CALCPOSY(rows) - 2, CALCPOSX(cols));
200 set_menu_win(menu, win);
201 subw = derwin(win, rows, cols, 2, 1);
202 set_menu_sub(menu, subw);
203 set_menu_fore(menu, A_REVERSE);
204 set_menu_grey(menu, A_NORMAL);
205 set_menu_mark(menu, NULL);
206 set_menu_spacing(menu, 0, 0, 0);
207 menu_opts_off(menu, O_NONCYCLIC|O_SHOWDESC|O_ONEVALUE);
208 post_menu(menu);
209 panel = new_panel(win);
210 cbreak();
211 noecho();
212 keypad(win, TRUE);
213 set_menu_pattern(menu, mbuf);
214 wbkgd(win, CP_MESSAGE_WINDOW);
215 draw_window_title(win, NAG_EDIT_TITLE, cols + 2, CP_HISTORY_TITLE,
216 CP_HISTORY_BORDER);
218 for (i = 0; i < MAX_PGN_NAG; i++) {
219 if (anno->nag[i] && anno->nag[i] <= item_count(menu)) {
220 set_item_value(mitems[anno->nag[i]], TRUE);
221 set_current_item(menu, mitems[anno->nag[i]]);
222 itemcount++;
226 while (1) {
227 int c;
228 char *tmp;
229 char buf[cols - 4];
231 wattron(win, A_REVERSE);
233 for (c = 1; c < (cols + 2) - 1; c++)
234 mvwprintw(win, rows + 2, c, " ");
236 c = item_index(current_item(menu)) + 1;
238 snprintf(buf, sizeof(buf), "Item %i of %i (%i of %i selected) %s", c,
239 item_count(menu), itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
240 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
242 wattroff(win, A_REVERSE);
244 if (!itemcount) {
245 for (i = 0; mitems[i]; i++)
246 set_item_value(mitems[i], FALSE);
248 set_item_value(mitems[0], TRUE);
250 else
251 set_item_value(mitems[0], FALSE);
253 /* This nl() statement needs to be here because NL is recognized
254 * for some reason after the first selection.
256 nl();
257 refresh_all();
258 c = wgetch(win);
260 switch (c) {
261 int found;
263 case KEY_F(1):
264 help(NAG_EDIT_HELP, ANYKEY, naghelp);
265 break;
266 case KEY_RIGHT:
267 if (!itemcount)
268 break;
270 found = 0;
272 for (i = item_index(current_item(menu)) + 1; mitems[i]; i++) {
273 if (item_value(mitems[i]) == TRUE) {
274 found = i;
275 break;
279 if (!found) {
280 for (i = 0; mitems[i]; i++) {
281 if (item_value(mitems[i]) == TRUE) {
282 found = i;
283 break;
288 set_current_item(menu, mitems[found]);
289 break;
290 case KEY_LEFT:
291 if (!itemcount)
292 break;
294 found = 0;
296 for (i = item_index(current_item(menu)) - 1; i > 0; i--) {
297 if (item_value(mitems[i]) == TRUE) {
298 found = i;
299 break;
303 if (!found) {
304 for (i = item_count(menu) - 1; i > 0; i--) {
305 if (item_value(mitems[i]) == TRUE) {
306 found = i;
307 break;
312 set_current_item(menu, mitems[found]);
313 break;
314 case KEY_HOME:
315 menu_driver(menu, REQ_FIRST_ITEM);
316 break;
317 case KEY_END:
318 menu_driver(menu, REQ_LAST_ITEM);
319 break;
320 case KEY_UP:
321 menu_driver(menu, REQ_UP_ITEM);
322 break;
323 case KEY_DOWN:
324 menu_driver(menu, REQ_DOWN_ITEM);
325 break;
326 case KEY_PPAGE:
327 case CTRL('P'):
328 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
329 menu_driver(menu, REQ_FIRST_ITEM);
330 break;
331 case KEY_NPAGE:
332 case CTRL('N'):
333 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
334 menu_driver(menu, REQ_LAST_ITEM);
335 break;
336 case ' ':
337 if (item_index(current_item(menu)) == 0 &&
338 item_value(current_item(menu)) == FALSE) {
339 itemcount = 0;
340 break;
343 if (item_value(current_item(menu)) == TRUE) {
344 set_item_value(current_item(menu), FALSE);
345 itemcount--;
347 else {
348 if (itemcount + 1 > MAX_PGN_NAG)
349 break;
351 set_item_value(current_item(menu), TRUE);
352 itemcount++;
355 SET_FLAG(game[gindex].flags, GF_MODIFIED);
356 break;
357 case '\n':
358 goto gotitem;
359 break;
360 case KEY_ESCAPE:
361 goto done;
362 break;
363 default:
364 tmp = menu_pattern(menu);
366 if (tmp && tmp[strlen(tmp) - 1] != c) {
367 menu_driver(menu, REQ_CLEAR_PATTERN);
368 menu_driver(menu, c);
370 else {
371 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
372 menu_driver(menu, c);
375 break;
379 gotitem:
380 for (i = 0; i < MAX_PGN_NAG; i++)
381 anno->nag[i] = 0;
383 for (i = 0, n = 0; mitems[i] && n < MAX_PGN_NAG; i++) {
384 if (item_value(mitems[i]) == TRUE)
385 anno->nag[n++] = i;
388 done:
389 unpost_menu(menu);
390 free_menu(menu);
392 for (i = 0; mitems[i]; i++)
393 free_item(mitems[i]);
395 free(mitems);
396 del_panel(panel);
397 delwin(subw);
398 delwin(win);
399 return NULL;
402 static void view_nag(void *arg)
404 HISTORY *h = (HISTORY *)arg;
405 char buf[80];
406 char line[LINE_MAX] = {0};
407 int i = 0;
409 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
411 if (!nags) {
412 if (init_nag())
413 return;
416 for (i = 0; i < MAX_PGN_NAG; i++) {
417 if (!h->nag[i])
418 break;
420 strncat(line, nags[h->nag[i] - 1].line, sizeof(line));
421 strncat(line, "\n", sizeof(line));
424 line[strlen(line) - 1] = 0;
425 message(buf, ANYKEY, "%s", line);
428 void view_annotation(HISTORY h)
430 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
431 int nag = 0, comment = 0;
433 if (h.comment && h.comment[0])
434 comment++;
436 if (h.nag[0])
437 nag++;
439 if (!nag && !comment)
440 return;
442 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
444 if (comment)
445 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
446 (nag) ? "Press 'n' to view NAG" : NULL,
447 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
448 (nag) ? 'n' : 0, "%s", h.comment);
449 else
450 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
451 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
454 static void cleanup(WINDOW *win, WINDOW *subw, PANEL *panel, MENU *menu,
455 ITEM **items, struct d_entries *entries)
457 int i;
459 unpost_menu(menu);
460 free_menu(menu);
462 for (i = 0; items[i]; i++)
463 free_item(items[i]);
465 free(items);
467 if (entries) {
468 for (i = 0; entries[i].name; i++) {
469 free(entries[i].name);
470 free(entries[i].fancy);
473 free(entries);
476 del_panel(panel);
477 delwin(subw);
478 delwin(win);
481 static int sort_entries(const void *s1, const void *s2)
483 const struct d_entries *ss1 = s1;
484 const struct d_entries *ss2 = s2;
486 return strcmp(ss1->name, ss2->name);
489 char *browse_directory(void *arg)
491 char *inputstr = (char *)arg;
492 int initkey = (inputstr) ? inputstr[0] : 0;
493 char pattern[FILENAME_MAX];
494 static char path[FILENAME_MAX];
495 static char file[FILENAME_MAX];
496 struct stat st;
497 char *p;
499 if (!*path) {
500 if (config.savedirectory) {
501 if ((p = word_expand(config.savedirectory)) == NULL)
502 return NULL;
504 strncpy(path, p, sizeof(path));
506 if (access(path, R_OK) == -1) {
507 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
508 getcwd(path, sizeof(path));
511 else
512 getcwd(path, sizeof(path));
515 again:
517 * First find directories (including hidden) in the working directory.
518 * Then apply the config.pattern to regular files.
520 if ((p = word_split_append(path, '/', ".* *")) == NULL)
521 return NULL;
523 strncpy(pattern, p, sizeof(pattern));
525 while (1) {
526 WINDOW *win, *subw;
527 PANEL *panel;
528 ITEM **mitems = NULL;
529 MENU *menu;
530 char *tmp = NULL;
531 int rows, cols;
532 int selected = -1;
533 char *mbuf = NULL;
534 int idx = 0;
535 int len = strlen(path);
536 wordexp_t w;
537 int i, n = 0;
538 struct d_entries *entries = NULL;
539 int which = 1;
540 int x = WRDE_NOCMD;
542 new_we:
543 if (wordexp(pattern, &w, x) != 0) {
544 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
545 return NULL;
548 for (i = 0; i < w.we_wordc; i++) {
549 struct tm *tp;
550 char tbuf[16];
552 if (stat(w.we_wordv[i], &st) == -1)
553 continue;
555 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
556 p++;
557 else
558 p = w.we_wordv[i];
560 if (which) {
561 if (!S_ISDIR(st.st_mode))
562 continue;
564 if (p[0] == '.' && p[1] == 0)
565 continue;
567 else {
568 if (S_ISDIR(st.st_mode))
569 continue;
572 len = strlen(p) + 2;
573 entries = Realloc(entries, (n + 2) * sizeof(struct d_entries));
574 entries[n].name = strdup(w.we_wordv[i]);
575 entries[n].fancy = Malloc(len);
576 strncpy(entries[n].fancy, p, len);
578 if (S_ISDIR(st.st_mode))
579 entries[n].fancy[len - 2] = '/';
581 tp = localtime(&st.st_mtime);
582 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
584 snprintf(entries[n].desc, sizeof(entries[n].desc), "%-7i %s",
585 (int)st.st_size, tbuf);
587 memset(&entries[++n], '\0', sizeof(struct d_entries));
590 which--;
592 if (which == 0) {
593 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
594 return NULL;
596 strncpy(pattern, p, sizeof(pattern));
597 x |= WRDE_REUSE;
598 goto new_we;
601 wordfree(&w);
602 qsort(entries, n, sizeof(struct d_entries), sort_entries);
604 for (i = 0; i < n; i++) {
605 mitems = Realloc(mitems, (idx + 2) * sizeof(ITEM));
606 mitems[idx++] = new_item(entries[i].fancy, entries[i].desc);
609 mitems[idx] = NULL;
610 menu = new_menu(mitems);
611 scale_menu(menu, &rows, &cols);
613 if (cols < strlen(path))
614 cols = strlen(path);
616 if (cols < strlen(HELP_PROMPT))
617 cols = strlen(HELP_PROMPT);
619 rows = (LINES / 5) * 4;
620 cols += 2;
622 win = newwin(rows + 4, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
623 set_menu_format(menu, rows, 0);
624 set_menu_win(menu, win);
625 subw = derwin(win, rows, cols - 2, 2, 1);
626 set_menu_sub(menu, subw);
627 set_menu_fore(menu, A_REVERSE);
628 set_menu_grey(menu, A_NORMAL);
629 set_menu_mark(menu, NULL);
630 set_menu_spacing(menu, 2, 0, 0);
631 menu_opts_off(menu, O_NONCYCLIC);
632 post_menu(menu);
633 panel = new_panel(win);
635 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
636 draw_prompt(win, rows + 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
638 cbreak();
639 noecho();
640 keypad(win, TRUE);
641 set_menu_pattern(menu, mbuf);
643 if (isgraph(initkey)) {
644 menu_driver(menu, initkey);
645 initkey = '\0';
648 while (1) {
649 int c;
651 /* This nl() statement needs to be here because NL is recognized
652 * for some reason after the first selection.
654 nl();
655 refresh_all();
656 c = wgetch(win);
658 switch (c) {
659 case CTRL('P'):
660 case KEY_PPAGE:
661 menu_driver(menu, REQ_SCR_UPAGE);
662 break;
663 case ' ':
664 case CTRL('N'):
665 case KEY_NPAGE:
666 menu_driver(menu, REQ_SCR_DPAGE);
667 break;
668 case KEY_UP:
669 menu_driver(menu, REQ_UP_ITEM);
670 break;
671 case KEY_DOWN:
672 menu_driver(menu, REQ_DOWN_ITEM);
673 break;
674 case '\n':
675 selected = item_index(current_item(menu));
676 goto gotitem;
677 break;
678 case KEY_ESCAPE:
679 cleanup(win, subw, panel, menu, mitems, entries);
680 file[0] = 0;
681 goto done;
682 break;
683 case KEY_F(1):
684 help(BROWSER_HELP, ANYKEY, file_browser_help);
685 break;
686 case '~':
687 strncpy(path, "~/", sizeof(path));
688 cleanup(win, subw, panel, menu, mitems, entries);
689 goto again;
690 break;
691 case CTRL('X'):
692 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
693 == NULL)
694 break;
696 strncpy(path, tmp, sizeof(path));
697 cleanup(win, subw, panel, menu, mitems, entries);
698 goto again;
699 break;
700 default:
701 tmp = menu_pattern(menu);
703 if (tmp && tmp[strlen(tmp) - 1] != c) {
704 menu_driver(menu, REQ_CLEAR_PATTERN);
705 menu_driver(menu, c);
707 else {
708 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
709 menu_driver(menu, c);
712 break;
716 gotitem:
717 strncpy(file, entries[selected].name, sizeof(file));
718 cleanup(win, subw, panel, menu, mitems, entries);
720 if (stat(file, &st) == -1) {
721 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
722 continue;
725 if (S_ISDIR(st.st_mode)) {
726 p = file + strlen(file) - 2;
728 if (strcmp(p, "..") == 0) {
729 p = file + strlen(file) - 3;
730 *p = 0;
732 if ((p = strrchr(file, '/')) != NULL)
733 file[strlen(file) - strlen(p)] = 0;
736 strncpy(path, file, sizeof(path));
737 goto again;
740 if (S_ISREG(st.st_mode))
741 break;
743 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
746 done:
747 return (*file) ? file : NULL;
749 static int init_country_codes()
751 FILE *fp;
752 char line[LINE_MAX], *s;
753 int cindex = 0;
755 if ((fp = fopen(config.ccfile, "r")) == NULL) {
756 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
757 return 1;
760 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
761 char *tmp;
763 if ((tmp = strsep(&s, " ")) == NULL)
764 continue;
766 s = trim(s);
767 tmp = trim(tmp);
769 if (!s || !tmp)
770 continue;
772 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
773 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
774 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
775 cindex++;
778 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
779 fclose(fp);
781 return 0;
784 char *country_codes(void *arg)
786 WINDOW *win, *subw;
787 PANEL *panel;
788 ITEM **mitems = NULL;
789 MENU *menu;
790 int i = 0, n;
791 int rows, cols;
792 char *mbuf = NULL;
793 char *tmp = NULL;
795 if (!ccodes) {
796 if (init_country_codes())
797 return NULL;
800 for (n = i = 0; ccodes[n].code[0]; n++, i++) {
801 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
802 mitems[i] = new_item(ccodes[n].country, ccodes[n].code);
805 mitems[i] = NULL;
806 menu = new_menu(mitems);
807 scale_menu(menu, &rows, &cols);
809 if (cols < strlen(HELP_PROMPT) + 21)
810 cols = strlen(HELP_PROMPT) + 21;
812 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
813 set_menu_win(menu, win);
814 subw = derwin(win, rows, cols + 2, 2, 1);
815 set_menu_sub(menu, subw);
816 set_menu_fore(menu, A_REVERSE);
817 set_menu_grey(menu, A_NORMAL);
818 set_menu_mark(menu, NULL);
819 set_menu_spacing(menu, 0, 0, 0);
820 menu_opts_off(menu, O_NONCYCLIC);
821 post_menu(menu);
822 panel = new_panel(win);
823 cbreak();
824 noecho();
825 keypad(win, TRUE);
826 set_menu_pattern(menu, mbuf);
827 wbkgd(win, CP_MESSAGE_WINDOW);
828 draw_window_title(win, CC_TITLE, cols + 4, CP_MESSAGE_TITLE,
829 CP_MESSAGE_BORDER);
831 while (1) {
832 int c;
833 char buf[cols - 4];
835 wattron(win, A_REVERSE);
837 for (c = 1; c < (cols + 2) - 1; c++)
838 mvwprintw(win, rows + 2, c, " ");
840 c = item_index(current_item(menu)) + 1;
842 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR, c,
843 N_OF_N_STR, item_count(menu), HELP_PROMPT);
844 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
846 wattroff(win, A_REVERSE);
848 /* This nl() statement needs to be here because NL is recognized
849 * for some reason after the first selection.
851 nl();
852 refresh_all();
853 c = wgetch(win);
855 switch (c) {
856 case KEY_F(1):
857 help(CC_KEY_HELP, ANYKEY, cc_help);
858 break;
859 case KEY_HOME:
860 menu_driver(menu, REQ_FIRST_ITEM);
861 break;
862 case KEY_END:
863 menu_driver(menu, REQ_LAST_ITEM);
864 break;
865 case KEY_UP:
866 menu_driver(menu, REQ_UP_ITEM);
867 break;
868 case KEY_DOWN:
869 menu_driver(menu, REQ_DOWN_ITEM);
870 break;
871 case KEY_PPAGE:
872 case CTRL('P'):
873 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
874 menu_driver(menu, REQ_FIRST_ITEM);
875 break;
876 case ' ':
877 case KEY_NPAGE:
878 case CTRL('N'):
879 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
880 menu_driver(menu, REQ_LAST_ITEM);
881 break;
882 case '\n':
883 tmp = (char *)item_description(current_item(menu));
884 goto done;
885 break;
886 case KEY_ESCAPE:
887 tmp = NULL;
888 goto done;
889 break;
890 default:
891 tmp = menu_pattern(menu);
893 if (tmp && tmp[strlen(tmp) - 1] != c) {
894 menu_driver(menu, REQ_CLEAR_PATTERN);
895 menu_driver(menu, c);
897 else {
898 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
899 menu_driver(menu, c);
902 break;
906 done:
907 unpost_menu(menu);
908 free_menu(menu);
910 for (i = 0; mitems[i]; i++)
911 free_item(mitems[i]);
913 del_panel(panel);
914 delwin(subw);
915 delwin(win);
916 return tmp;
919 static void add_custom_tags(TAG ***t)
921 int i;
923 if (!config.tag)
924 return;
926 for (i = 0; config.tag[i]; i++)
927 pgn_add_tag(t, config.tag[i]->name, config.tag[i]->value);
929 pgn_sort_tags(*t);
932 TAG **edit_tags(GAME g, BOARD b, int edit)
934 TAG **data = NULL;
935 struct tm tp;
936 unsigned char data_index = 0;
937 int n, lastindex = 0;
938 int len;
940 /* Edit the backup copy, not the original in case the save fails. */
941 for (n = 0; g.tag[n]; n++)
942 pgn_add_tag(&data, g.tag[n]->name, g.tag[n]->value);
944 data_index = pgn_tag_total(data);
946 while (1) {
947 WINDOW *win, *subw;
948 PANEL *panel;
949 ITEM **mitems = NULL;
950 MENU *menu;
951 int i;
952 char buf[76] = {0};
953 char *tmp = NULL;
954 int rows, cols;
955 int selected = -1;
956 char *mbuf = NULL;
957 int nlen = 0, vlen = 0;
959 data_index = pgn_tag_total(data);
961 for (i = 0; i < data_index; i++) {
962 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
964 if (data[i]->value) {
965 nlen = strlen(data[i]->name);
966 vlen = strlen(data[i]->value);
968 /* The +6 is for the menu padding. */
969 mitems[i] = new_item(data[i]->name,
970 (nlen + vlen + 6 >= MAX_VALUE_WIDTH)
971 ? PRESS_ENTER : data[i]->value);
973 else
974 mitems[i] = new_item(data[i]->name, UNKNOWN);
977 mitems[i] = NULL;
978 menu = new_menu(mitems);
979 scale_menu(menu, &rows, &cols);
981 /* +14 for the extra prompt info. */
982 if (cols < strlen(HELP_PROMPT) + 14)
983 cols = strlen(HELP_PROMPT) + 14;
985 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
986 set_menu_win(menu, win);
987 subw = derwin(win, rows, cols + 2, 2, 1);
988 set_menu_sub(menu, subw);
989 set_menu_fore(menu, A_REVERSE);
990 set_menu_grey(menu, A_NORMAL);
991 set_menu_mark(menu, NULL);
992 set_menu_pad(menu, '-');
993 set_menu_spacing(menu, 3, 0, 0);
994 menu_opts_off(menu, O_NONCYCLIC);
995 post_menu(menu);
996 panel = new_panel(win);
997 cbreak();
998 noecho();
999 nl();
1000 keypad(win, TRUE);
1001 set_menu_pattern(menu, mbuf);
1002 wbkgd(win, CP_MESSAGE_WINDOW);
1003 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1004 cols + 4, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
1006 while (1) {
1007 int c;
1008 TAG **tmppgn = NULL;
1009 char *newtag = NULL;
1011 if (set_current_item(menu, mitems[lastindex]) != E_OK) {
1012 lastindex = item_count(menu) - 1;
1013 continue;
1016 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1017 item_index(current_item(menu)) + 1, N_OF_N_STR,
1018 item_count(menu), HELP_PROMPT);
1019 draw_prompt(win, rows + 2, cols + 4, buf, CP_MESSAGE_PROMPT);
1020 refresh_all();
1021 c = wgetch(win);
1023 switch (c) {
1024 case CTRL('T'):
1025 add_custom_tags(&data);
1026 goto cleanup;
1027 break;
1028 case KEY_F(1):
1029 if (edit)
1030 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1031 else
1032 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1033 break;
1034 case CTRL('R'):
1035 if (!edit)
1036 break;
1038 selected = item_index(current_item(menu));
1040 if (selected <= 6) {
1041 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1042 goto cleanup;
1045 data_index = pgn_tag_total(data);
1047 for (i = 0; i < data_index; i++) {
1048 if (i == selected)
1049 continue;
1051 pgn_add_tag(&tmppgn, data[i]->name, data[i]->value);
1054 pgn_tag_free(data);
1055 data = NULL;
1057 for (i = 0; tmppgn[i]; i++)
1058 pgn_add_tag(&data, tmppgn[i]->name, tmppgn[i]->value);
1060 pgn_tag_free(tmppgn);
1061 goto cleanup;
1062 break;
1063 case CTRL('A'):
1064 if (!edit)
1065 break;
1067 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1068 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1069 == NULL)
1070 break;
1072 newtag[0] = toupper(newtag[0]);
1074 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1075 strlen(PRESS_ENTER)) {
1076 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1077 break;
1080 for (i = 0; i < data_index; i++) {
1081 if (strcasecmp(data[i]->name, newtag) == 0) {
1082 selected = i;
1083 goto gotitem;
1087 pgn_add_tag(&data, newtag, NULL);
1088 data_index = pgn_tag_total(data);
1089 selected = data_index - 1;
1090 goto gotitem;
1091 break;
1092 case KEY_HOME:
1093 menu_driver(menu, REQ_FIRST_ITEM);
1094 break;
1095 case KEY_END:
1096 menu_driver(menu, REQ_LAST_ITEM);
1097 break;
1098 case CTRL('F'):
1099 if (!edit)
1100 break;
1102 pgn_add_tag(&data, "FEN", pgn_game_to_fen(g, b));
1103 data_index = pgn_tag_total(data);
1104 selected = data_index - 1;
1105 goto gotitem;
1106 break;
1107 case KEY_NPAGE:
1108 case CTRL('N'):
1109 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
1110 menu_driver(menu, REQ_LAST_ITEM);
1111 break;
1112 case KEY_PPAGE:
1113 case CTRL('P'):
1114 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
1115 menu_driver(menu, REQ_FIRST_ITEM);
1116 break;
1117 case KEY_UP:
1118 menu_driver(menu, REQ_UP_ITEM);
1119 break;
1120 case KEY_DOWN:
1121 menu_driver(menu, REQ_DOWN_ITEM);
1122 break;
1123 case '\n':
1124 selected = item_index(current_item(menu));
1125 goto gotitem;
1126 break;
1127 case KEY_ESCAPE:
1128 cleanup(win, subw, panel, menu, mitems, NULL);
1129 goto done;
1130 break;
1131 default:
1132 tmp = menu_pattern(menu);
1134 if (tmp && tmp[strlen(tmp) - 1] != c) {
1135 menu_driver(menu, REQ_CLEAR_PATTERN);
1136 menu_driver(menu, c);
1138 else {
1139 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
1140 menu_driver(menu, c);
1143 break;
1146 lastindex = item_index(current_item(menu));
1149 gotitem:
1150 lastindex = selected;
1151 nlen = strlen(data[selected]->name) + 3;
1152 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1154 if (nlen > MAX_VALUE_WIDTH)
1155 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1156 else
1157 snprintf(buf, sizeof(buf), "%s \"%s\"",
1158 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1159 data[selected]->name);
1161 if (!edit) {
1162 if (strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1163 goto cleanup;
1165 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1166 goto cleanup;
1169 if (strcmp(data[selected]->name, "Date") == 0) {
1170 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1171 0, FIELD_TYPE_PGN_DATE);
1173 if (tmp) {
1174 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1175 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1176 goto cleanup;
1179 else
1180 goto cleanup;
1182 else if (strcmp(data[selected]->name, "Site") == 0) {
1183 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1184 country_codes, NULL, CTRL('t'), -1);
1186 if (!tmp)
1187 tmp = "?";
1189 else if (strcmp(data[selected]->name, "Round") == 0) {
1190 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1191 FIELD_TYPE_PGN_ROUND);
1193 if (!tmp) {
1194 if (gtotal > 1)
1195 tmp = "?";
1196 else
1197 tmp = "-";
1200 else if (strcmp(data[selected]->name, "Result") == 0) {
1201 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1202 0, -1);
1204 if (!tmp)
1205 tmp = "*";
1207 else {
1208 if (item_description(mitems[selected]) &&
1209 strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1210 tmp = NULL;
1211 else
1212 tmp = data[selected]->value;
1214 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1217 len = (tmp) ? strlen(tmp) + 1 : 1;
1218 data[selected]->value = Realloc(data[selected]->value, len);
1219 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1221 cleanup:
1222 cleanup(win, subw, panel, menu, mitems, NULL);
1225 done:
1226 if (!edit) {
1227 pgn_tag_free(data);
1228 return NULL;
1231 return data;
1234 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1235 * game index number.
1237 int save_pgn(const char *filename, int isfifo, int saveindex)
1239 FILE *fp;
1240 char *mode = NULL;
1241 int c;
1242 char buf[FILENAME_MAX];
1243 struct stat st;
1244 int i;
1245 char *command = NULL;
1246 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1248 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1249 if (stat(config.savedirectory, &st) == -1) {
1250 if (errno == ENOENT) {
1251 if (mkdir(config.savedirectory, 0755) == -1) {
1252 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1253 strerror(errno));
1254 return 1;
1257 else {
1258 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1259 strerror(errno));
1260 return 1;
1264 stat(config.savedirectory, &st);
1266 if (!S_ISDIR(st.st_mode)) {
1267 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1268 return 1;
1271 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1272 filename = buf;
1275 /* This is a hack to resume an existing game when more than one game is
1276 * available. Also resuming a saved game and a game from history.
1278 // FIXME: may not need this when a FEN tag is supported (by the engine).
1279 if (isfifo)
1280 mode = "w";
1281 else {
1282 if (access(filename, W_OK) == 0) {
1283 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1284 "%s \"%s\"", E_FILEEXISTS, filename);
1286 switch (c) {
1287 case 'a':
1288 if (pgn_is_compressed(filename)) {
1289 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1290 return 1;
1293 mode = "a";
1294 break;
1295 case 'o':
1296 mode = "w+";
1297 break;
1298 default:
1299 return 1;
1302 else
1303 mode = "a";
1306 if (command) {
1307 if ((fp = popen(command, "w")) == NULL) {
1308 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1309 return 1;
1312 else {
1313 if ((fp = fopen(filename, mode)) == NULL) {
1314 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1315 return 1;
1319 if (isfifo)
1320 pgn_write(fp, game[saveindex]);
1321 else {
1322 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1323 pgn_write(fp, game[i]);
1326 if (command)
1327 pclose(fp);
1328 else
1329 fclose(fp);
1331 if (!isfifo && saveindex == -1)
1332 strncpy(loadfile, filename, sizeof(loadfile));
1334 return 0;
1337 char *random_agony(GAME g)
1339 static int n;
1340 FILE *fp;
1341 char line[LINE_MAX];
1343 if (n == -1 || !config.agony || !curses_initialized ||
1344 (g.mode == MODE_HISTORY && !config.historyagony))
1345 return NULL;
1347 if (!agony) {
1348 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1349 n = -1;
1350 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1351 return NULL;
1354 while (!feof(fp)) {
1355 if (fscanf(fp, " %[^\n] ", line) == 1) {
1356 agony = Realloc(agony, (n + 2) * sizeof(char *));
1357 agony[n++] = strdup(trim(line));
1361 agony[n] = NULL;
1362 fclose(fp);
1364 if (agony[0] == NULL || !n) {
1365 n = -1;
1366 return NULL;
1370 return agony[random() % n];
1373 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1375 if (pgn_piece_to_int(piece) == ROOK && col == 7
1376 && row == 7 &&
1377 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1378 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1379 if (mod)
1380 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1381 return 1;
1383 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1384 && row == 7 &&
1385 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1386 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1387 if (mod)
1388 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1389 return 1;
1391 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1392 && row == 0 &&
1393 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1394 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1395 if (mod)
1396 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1397 return 1;
1399 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1400 && row == 0 &&
1401 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1402 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1403 if (mod)
1404 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1405 return 1;
1407 else if (pgn_piece_to_int(piece) == KING && col == 4
1408 && row == 7 &&
1409 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1410 TEST_FLAG(g->flags, GF_WK_CASTLE))
1412 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1413 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1414 if (mod) {
1415 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1416 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1417 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1418 else
1419 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1421 return 1;
1423 else if (pgn_piece_to_int(piece) == KING && col == 4
1424 && row == 0 &&
1425 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1426 TEST_FLAG(g->flags, GF_BK_CASTLE))
1428 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1429 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1430 if (mod) {
1431 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1432 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1433 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1434 else
1435 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1437 return 1;
1440 return 0;
1443 static void draw_board(GAME *g, int details)
1445 int row, col;
1446 int bcol = 0, brow = 0;
1447 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1448 int ncols = 0, offset = 1;
1449 unsigned coords_y = 8;
1451 for (row = 0; row < maxy; row++) {
1452 bcol = 0;
1454 for (col = 0; col < maxx; col++) {
1455 int attrwhich = -1;
1456 chtype attrs = 0;
1457 unsigned char piece;
1459 if (row == 0 || row == maxy - 2) {
1460 if (col == 0)
1461 mvwaddch(boardw, row, col,
1462 LINE_GRAPHIC((row) ?
1463 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1464 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1465 else if (col == maxx - 2)
1466 mvwaddch(boardw, row, col,
1467 LINE_GRAPHIC((row) ?
1468 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1469 ACS_URCORNER | CP_BOARD_GRAPHICS));
1470 else if (!(col % 4))
1471 mvwaddch(boardw, row, col,
1472 LINE_GRAPHIC((row) ?
1473 ACS_BTEE | CP_BOARD_GRAPHICS :
1474 ACS_TTEE | CP_BOARD_GRAPHICS));
1475 else {
1476 if (col != maxx - 1)
1477 mvwaddch(boardw, row, col,
1478 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1481 continue;
1484 if ((row % 2) && col == maxx - 1 && coords_y) {
1485 wattron(boardw, CP_BOARD_COORDS);
1486 mvwprintw(boardw, row, col, "%d", coords_y--);
1487 wattroff(boardw, CP_BOARD_COORDS);
1488 continue;
1491 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1492 if (!(row % 2))
1493 mvwaddch(boardw, row, col,
1494 LINE_GRAPHIC((col) ?
1495 ACS_RTEE | CP_BOARD_GRAPHICS :
1496 ACS_LTEE | CP_BOARD_GRAPHICS));
1497 else
1498 mvwaddch(boardw, row, col,
1499 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1501 continue;
1504 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1505 mvwaddch(boardw, row, col,
1506 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1507 continue;
1510 if (!(col % 4) && row != maxy - 1) {
1511 mvwaddch(boardw, row, col,
1512 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1513 continue;
1516 if ((row % 2)) {
1517 if ((col % 4)) {
1518 if (ncols++ == 8) {
1519 offset++;
1520 ncols = 1;
1523 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1524 && (offset % 2)))
1525 attrwhich = BLACK;
1526 else
1527 attrwhich = WHITE;
1529 if (config.validmoves && g->b[brow][bcol].valid) {
1530 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1531 CP_BOARD_MOVES_BLACK;
1533 else
1534 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1535 CP_BOARD_BLACK;
1537 if (row == ROWTOMATRIX(c_row) && col ==
1538 COLTOMATRIX(c_col)) {
1539 attrs = CP_BOARD_CURSOR;
1542 if (row == ROWTOMATRIX(sp.row) &&
1543 col == COLTOMATRIX(sp.col)) {
1544 attrs = CP_BOARD_SELECTED;
1547 if (row == maxy - 1)
1548 attrs = 0;
1550 mvwaddch(boardw, row, col, ' ' | attrs);
1552 if (row == maxy - 1)
1553 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1554 else {
1555 if (details && g->b[row / 2][bcol].enpassant)
1556 piece = 'x';
1557 else
1558 piece = g->b[row / 2][bcol].icon;
1560 if (details && castling_state(g, g->b, brow, bcol,
1561 piece, 0))
1562 attrs |= A_REVERSE;
1564 if (g->side == WHITE && isupper(piece))
1565 attrs |= A_BOLD;
1566 else if (g->side == BLACK && islower(piece))
1567 attrs |= A_BOLD;
1569 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1571 CLEAR_FLAG(attrs, A_BOLD);
1572 CLEAR_FLAG(attrs, A_REVERSE);
1575 waddch(boardw, ' ' | attrs);
1576 col += 2;
1577 bcol++;
1580 else {
1581 if (col != maxx - 1)
1582 mvwaddch(boardw, row, col,
1583 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1587 brow = row / 2;
1591 void invalid_move(int n, const char *m)
1593 if (curses_initialized)
1594 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1595 else
1596 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1599 /* Convert the selected piece to SAN format and validate it. */
1600 static char *board_to_san(GAME *g, BOARD b)
1602 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1603 int piece;
1604 int promo;
1605 BOARD oldboard;
1607 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1608 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1610 p = str;
1611 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1613 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1614 (sp.destrow == 1 && g->turn == BLACK))) {
1615 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1617 if (pgn_piece_to_int(promo) == -1)
1618 return NULL;
1620 p = str + strlen(str);
1621 *p++ = toupper(promo);
1622 *p = '\0';
1625 memcpy(oldboard, b, sizeof(BOARD));
1627 if ((p = pgn_a2a4tosan(g, b, str)) == NULL) {
1628 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
1629 memcpy(b, oldboard, sizeof(BOARD));
1630 return NULL;
1633 if (pgn_validate_move(g, b, p)) {
1634 invalid_move(gindex + 1, p);
1635 memcpy(b, oldboard, sizeof(BOARD));
1636 return NULL;
1639 return p;
1642 static int move_to_engine(GAME *g, BOARD b)
1644 char *p;
1646 if ((p = board_to_san(g, b)) == NULL)
1647 return 0;
1649 sp.row = sp.col = sp.icon = 0;
1651 if (noengine) {
1652 history_add(g, p);
1653 pgn_switch_turn(g);
1654 SET_FLAG(g->flags, GF_MODIFIED);
1655 update_all(*g);
1656 return 1;
1659 send_to_engine(g, "%s\n", p);
1660 return 1;
1663 static void update_clock(int n, int *h, int *m, int *s)
1665 *h = n / 3600;
1666 *m = (n % 3600) / 60;
1667 *s = (n % 3600) % 60;
1669 return;
1672 void update_status_window(GAME g)
1674 int i = 0;
1675 char *buf;
1676 char tmp[15], *engine, *mode;
1677 int w;
1678 int h, m, s;
1679 char *p;
1680 int maxy, maxx;
1681 int len;
1682 struct userdata_s *d = g.data;
1684 getmaxyx(statusw, maxy, maxx);
1685 w = maxx - 2 - 8;
1686 len = maxx - 2;
1687 buf = Malloc(len);
1689 *tmp = '\0';
1690 p = tmp;
1692 if (TEST_FLAG(g.flags, GF_DELETE)) {
1693 *p++ = '(';
1694 *p++ = 'x';
1695 i++;
1698 if (TEST_FLAG(g.flags, GF_PERROR)) {
1699 if (!i)
1700 *p++ = '(';
1701 else
1702 *p++ = '/';
1704 *p++ = '!';
1705 i++;
1708 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1709 if (!i)
1710 *p++ = '(';
1711 else
1712 *p++ = '/';
1714 *p++ = '*';
1715 i++;
1718 if (*tmp != '\0')
1719 *p++ = ')';
1721 *p = '\0';
1723 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1724 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1725 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1726 (*tmp) ? tmp : "");
1727 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1729 switch (g.mode) {
1730 case MODE_HISTORY:
1731 mode = MODE_HISTORY_STR;
1732 break;
1733 case MODE_EDIT:
1734 mode = MODE_EDIT_STR;
1735 break;
1736 case MODE_PLAY:
1737 mode = MODE_PLAY_STR;
1738 break;
1739 default:
1740 mode = UNKNOWN;
1741 break;
1744 snprintf(buf, len - 1, "%*s %s%s", 7, STATUS_MODE_STR, mode,
1745 (d && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) ? " (loop)" : "");
1746 mvwprintw(statusw, 4, 1, "%-*s", w, buf);
1748 if (d->engine) {
1749 switch (d->engine->status) {
1750 case ENGINE_THINKING:
1751 engine = ENGINE_THINKING_STR;
1752 break;
1753 case ENGINE_READY:
1754 engine = ENGINE_READY_STR;
1755 break;
1756 case ENGINE_INITIALIZING:
1757 engine = ENGINE_INITIALIZING_STR;
1758 break;
1759 case ENGINE_OFFLINE:
1760 engine = ENGINE_OFFLINE_STR;
1761 break;
1762 default:
1763 engine = UNKNOWN;
1764 break;
1767 else
1768 engine = ENGINE_OFFLINE_STR;
1770 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1771 wattron(statusw, CP_STATUS_ENGINE);
1772 mvwaddstr(statusw, 5, 9, engine);
1773 wattroff(statusw, CP_STATUS_ENGINE);
1775 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1776 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1778 strncpy(tmp, WHITE_STR, sizeof(tmp));
1779 tmp[0] = toupper(tmp[0]);
1780 update_clock(g.moveclock, &h, &m, &s);
1781 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1782 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1784 strncpy(tmp, BLACK_STR, sizeof(tmp));
1785 tmp[0] = toupper(tmp[0]);
1786 update_clock(g.moveclock, &h, &m, &s);
1787 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1788 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1789 free(buf);
1791 for (i = 1; i < maxx - 4; i++)
1792 mvwprintw(statusw, maxy - 2, i, " ");
1794 if (!status.notify)
1795 status.notify = strdup(GAME_HELP_PROMPT);
1797 wattron(statusw, CP_STATUS_NOTIFY);
1798 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1799 status.notify);
1800 wattroff(statusw, CP_STATUS_NOTIFY);
1803 void update_history_window(GAME g)
1805 char buf[HISTORY_WIDTH - 1];
1806 HISTORY *h = NULL;
1807 int n, total;
1808 int t = history_total(g.hp);
1810 n = (g.hindex + 1) / 2;
1812 if (t % 2)
1813 total = (t + 1) / 2;
1814 else
1815 total = t / 2;
1817 if (t)
1818 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1819 (movestep == 1) ? HISTORY_PLY_STEP : "");
1820 else
1821 strncpy(buf, UNAVAILABLE, sizeof(buf));
1823 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1824 HISTORY_WIDTH - 13, buf);
1826 h = history_by_n(g.hp, g.hindex);
1827 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1828 n = 0;
1830 if (h && ((h->comment) || h->nag[0])) {
1831 strncat(buf, " (v", sizeof(buf));
1832 n++;
1835 if (h && h->rav) {
1836 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1837 n++;
1840 if (g.ravlevel) {
1841 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1842 n++;
1845 if (n)
1846 strncat(buf, ")", sizeof(buf));
1848 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1849 HISTORY_WIDTH - 13, buf);
1851 h = history_by_n(g.hp, game[gindex].hindex - 1);
1852 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1853 n = 0;
1855 if (h && ((h->comment) || h->nag[0])) {
1856 strncat(buf, " (V", sizeof(buf));
1857 n++;
1860 if (h && h->rav) {
1861 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1862 n++;
1865 if (g.ravlevel) {
1866 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1867 n++;
1870 if (n)
1871 strncat(buf, ")", sizeof(buf));
1873 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1874 HISTORY_WIDTH - 13, buf);
1877 void update_tag_window(TAG **t)
1879 int i;
1880 int w = TAG_WIDTH - 10;
1882 for (i = 0; i < 7; i++)
1883 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1886 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1888 int i;
1890 wattron(win, attr);
1892 for (i = 1; i < width - 1; i++)
1893 mvwaddch(win, y, i, ' ');
1895 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1896 wattroff(win, attr);
1899 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1900 chtype battr)
1902 int i;
1904 if (title) {
1905 wattron(win, attr);
1907 for (i = 1; i < width - 1; i++)
1908 mvwaddch(win, 1, i, ' ');
1910 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1911 wattroff(win, attr);
1914 wattron(win, battr);
1915 box(win, ACS_VLINE, ACS_HLINE);
1916 wattroff(win, battr);
1919 void refresh_all()
1921 update_panels();
1922 doupdate();
1925 void update_all(GAME g)
1927 update_status_window(g);
1928 update_history_window(g);
1929 update_tag_window(g.tag);
1932 static void game_next_prev(GAME g, int n, int count)
1934 if (gtotal < 2)
1935 return;
1937 if (n == 1) {
1938 if (gindex + count > gtotal - 1) {
1939 if (count != 1)
1940 gindex = gtotal - 1;
1941 else
1942 gindex = 0;
1944 else
1945 gindex += count;
1947 else {
1948 if (gindex - count < 0) {
1949 if (count != 1)
1950 gindex = 0;
1951 else
1952 gindex = gtotal - 1;
1954 else
1955 gindex -= count;
1959 static void delete_game(int which)
1961 GAME *g = NULL;
1962 int gi = 0;
1963 int i;
1965 for (i = 0; i < gtotal; i++) {
1966 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1967 pgn_free(game[i]);
1968 continue;
1971 g = Realloc(g, (gi + 1) * sizeof(GAME));
1972 memcpy(&g[gi], &game[i], sizeof(GAME));
1973 g[gi].tag = game[i].tag;
1974 g[gi].history = game[i].history;
1975 g[gi].hp = game[i].hp;
1976 gi++;
1979 game = g;
1980 gtotal = gi;
1982 if (which != -1) {
1983 if (which + 1 >= gtotal)
1984 gindex = gtotal - 1;
1985 else
1986 gindex = which;
1988 else
1989 gindex = gtotal - 1;
1991 game[gindex].hp = game[gindex].history;
1994 static int find_move_exp(GAME g, const char *str, int init, int which,
1995 int count)
1997 int i;
1998 int ret;
1999 static regex_t r;
2000 static int firstrun = 1;
2001 char errbuf[255];
2002 int incr;
2003 int found;
2005 if (init) {
2006 if (!firstrun)
2007 regfree(&r);
2009 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2010 regerror(ret, &r, errbuf, sizeof(errbuf));
2011 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2012 return -1;
2015 firstrun = 1;
2018 incr = (which == 0) ? -1 : 1;
2020 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2021 if (i == g.hindex - 1)
2022 break;
2024 if (i >= history_total(g.hp))
2025 i = 0;
2026 else if (i < 0)
2027 i = history_total(g.hp) - 1;
2029 // FIXME RAV
2030 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2032 if (ret == 0) {
2033 if (count == ++found) {
2034 return i + 1;
2037 else {
2038 if (ret != REG_NOMATCH) {
2039 regerror(ret, &r, errbuf, sizeof(errbuf));
2040 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2041 return -1;
2046 return -1;
2049 static int toggle_delete_flag(int n)
2051 int i, x;
2053 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2055 for (i = x = 0; i < gtotal; i++) {
2056 if (TEST_FLAG(game[i].flags, GF_DELETE))
2057 x++;
2060 if (x == gtotal) {
2061 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2062 CLEAR_FLAG(game[n].flags, GF_DELETE);
2063 return 1;
2066 return 0;
2069 static void edit_save_tags(GAME *g)
2071 TAG **t;
2073 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2074 return;
2076 pgn_tag_free(g->tag);
2077 g->tag = t;
2078 SET_FLAG(g->flags, GF_MODIFIED);
2079 pgn_sort_tags(g->tag);
2082 static int find_game_exp(char *str, int which, int count)
2084 char *nstr = NULL, *exp = NULL;
2085 regex_t nexp, vexp;
2086 int ret = -1;
2087 int g = 0;
2088 char buf[255], *tmp;
2089 char errbuf[255];
2090 int found = 0;
2091 int incr = (which == 0) ? -(1) : 1;
2093 strncpy(buf, str, sizeof(buf));
2094 tmp = buf;
2096 if (strstr(tmp, ":") != NULL) {
2097 nstr = strsep(&tmp, ":");
2099 if ((ret = regcomp(&nexp, nstr,
2100 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2101 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2102 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2103 ret = g = -1;
2104 goto cleanup;
2108 exp = tmp;
2110 if (exp == NULL)
2111 goto cleanup;
2113 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2114 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2115 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2116 ret = -1;
2117 goto cleanup;
2120 ret = -1;
2122 for (g = gindex + incr, found = 0; ; g += incr) {
2123 int t;
2125 if (g == gindex)
2126 break;
2128 if (g == gtotal)
2129 g = 0;
2130 else if (g < 0)
2131 g = gtotal - 1;
2133 for (t = 0; game[g].tag[t]; t++) {
2134 if (nstr) {
2135 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2136 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2137 if (count == ++found) {
2138 ret = g;
2139 goto cleanup;
2144 else {
2145 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2146 if (count == ++found) {
2147 ret = g;
2148 goto cleanup;
2154 ret = -1;
2157 cleanup:
2158 if (nstr)
2159 regfree(&nexp);
2161 if (g != -1)
2162 regfree(&vexp);
2164 return ret;
2168 * Updates the notification line in the status window then refreshes the
2169 * status window.
2171 void update_status_notify(GAME g, char *fmt, ...)
2173 va_list ap;
2174 #ifdef HAVE_VASPRINTF
2175 char *line;
2176 #else
2177 char line[COLS];
2178 #endif
2180 if (!fmt) {
2181 if (status.notify) {
2182 free(status.notify);
2183 status.notify = NULL;
2185 if (curses_initialized)
2186 update_status_window(g);
2189 return;
2192 va_start(ap, fmt);
2193 #ifdef HAVE_VASPRINTF
2194 vasprintf(&line, fmt, ap);
2195 #else
2196 vsnprintf(line, sizeof(line), fmt, ap);
2197 #endif
2198 va_end(ap);
2200 if (status.notify)
2201 free(status.notify);
2203 status.notify = strdup(line);
2205 #ifdef HAVE_VASPRINTF
2206 free(line);
2207 #endif
2208 if (curses_initialized)
2209 update_status_window(g);
2212 static void switch_side(GAME *g)
2214 g->side = (g->side == WHITE) ? BLACK : WHITE;
2217 int rav_next_prev(GAME *g, BOARD b, int n)
2219 // Next RAV.
2220 if (n) {
2221 if (g->hp[g->hindex]->rav == NULL)
2222 return 1;
2224 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2225 g->rav[g->ravlevel].hp = g->hp;
2226 g->rav[g->ravlevel].flags = g->flags;
2227 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2228 g->rav[g->ravlevel].hindex = g->hindex;
2229 g->hp = g->hp[g->hindex]->rav;
2230 g->hindex = 0;
2231 g->ravlevel++;
2232 return 0;
2235 if (g->ravlevel - 1 < 0)
2236 return 1;
2238 // Previous RAV.
2239 g->ravlevel--;
2240 pgn_init_fen_board(g, b, g->rav[g->ravlevel].fen);
2241 free(g->rav[g->ravlevel].fen);
2242 g->hp = g->rav[g->ravlevel].hp;
2243 g->flags = g->rav[g->ravlevel].flags;
2244 g->hindex = g->rav[g->ravlevel].hindex;
2245 return 0;
2248 static void draw_window_decor()
2250 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2251 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2252 wbkgd(boardw, CP_BOARD_WINDOW);
2253 wbkgd(statusw, CP_STATUS_WINDOW);
2254 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2255 CP_STATUS_TITLE, CP_STATUS_BORDER);
2256 wbkgd(tagw, CP_TAG_WINDOW);
2257 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2258 CP_TAG_BORDER);
2259 wbkgd(historyw, CP_HISTORY_WINDOW);
2260 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2261 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2264 static void do_window_resize()
2266 if (LINES < 24 || COLS < 80)
2267 return;
2269 resizeterm(LINES, COLS);
2270 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2271 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2272 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2273 wmove(historyw, 0, 0);
2274 wclrtobot(historyw);
2275 wmove(tagw, 0, 0);
2276 wclrtobot(tagw);
2277 wmove(statusw, 0, 0);
2278 wclrtobot(statusw);
2279 draw_window_decor();
2280 update_all(game[gindex]);
2283 static void historymode_keys(int);
2284 static void playmode_keys(int c)
2286 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2287 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2288 chtype p;
2289 int w, x, y, z;
2290 char *tmp;
2291 struct userdata_s *d = game[gindex].data;
2293 switch (c) {
2294 case 'o':
2295 if (!d)
2296 break;
2298 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2299 update_all(game[gindex]);
2300 break;
2301 case '|':
2302 if (!d->engine)
2303 break;
2305 if (d->engine->status == ENGINE_OFFLINE)
2306 break;
2308 x = d->engine->status;
2310 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2311 send_to_engine(&game[gindex], "%s\n", tmp);
2312 d->engine->status = x;
2313 break;
2314 case '\015':
2315 case '\n':
2316 pushkey = keycount = 0;
2317 update_status_notify(game[gindex], NULL);
2319 if (!noengine && (!d->engine ||
2320 d->engine->status == ENGINE_THINKING)) {
2321 beep();
2322 break;
2325 if (!sp.icon)
2326 break;
2328 sp.destrow = c_row;
2329 sp.destcol = c_col;
2331 if (editmode) {
2332 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2333 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2334 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2335 sp.icon = sp.row = sp.col = 0;
2336 break;
2339 if (move_to_engine(&game[gindex], game[gindex].b)) {
2340 if (config.validmoves)
2341 board_reset_valid_moves(game[gindex].b);
2343 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2344 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2345 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2349 break;
2350 case ' ':
2351 if (!noengine && (!d->engine ||
2352 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2353 if (start_chess_engine(&game[gindex]) < 0) {
2354 sp.icon = 0;
2355 break;
2359 if (!editmode)
2360 wtimeout(boardw, 70);
2362 if (sp.icon || (!editmode && d->engine &&
2363 d->engine->status == ENGINE_THINKING)) {
2364 beep();
2365 break;
2368 sp.icon = mvwinch(boardw, ROWTOMATRIX(c_row),
2369 COLTOMATRIX(c_col)+1) & A_CHARTEXT;
2371 if (sp.icon == ' ') {
2372 sp.icon = 0;
2373 break;
2376 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2377 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2378 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2379 sp.icon = 0;
2380 break;
2383 sp.row = c_row;
2384 sp.col = c_col;
2386 if (!editmode && config.validmoves)
2387 board_get_valid_moves(&game[gindex], game[gindex].b,
2388 pgn_piece_to_int(sp.icon), sp.row, sp.col, &w, &x, &y, &z);
2390 paused = 0;
2391 break;
2392 case 'w':
2393 send_to_engine(&game[gindex], "\nswitch\n");
2394 switch_side(&game[gindex]);
2395 update_status_window(game[gindex]);
2396 break;
2397 case 'u':
2398 /* FIXME dies reading FIFO sometimes. */
2399 if (!history_total(game[gindex].hp))
2400 break;
2402 history_previous(&game[gindex], game[gindex].b, (keycount) ? keycount * 2 :
2405 #if 0
2406 if (status.engine == CRAFTY)
2407 SEND_TO_ENGINE("read %s\n", config.fifo);
2408 else
2409 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
2410 #endif
2412 update_history_window(game[gindex]);
2413 break;
2414 case 'a':
2415 historymode_keys(c);
2416 break;
2417 case 'd':
2418 board_details = (board_details) ? 0 : 1;
2419 break;
2420 case 'p':
2421 paused = (paused) ? 0 : 1;
2422 break;
2423 case 'g':
2424 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2425 start_chess_engine(&game[gindex]);
2427 send_to_engine(&game[gindex], "go\n");
2428 break;
2429 default:
2430 break;
2434 static void editmode_keys(int 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(int 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 history_next(&game[gindex], game[gindex].b, (keycount > 0) ?
2493 config.jumpcount * keycount * movestep :
2494 config.jumpcount * movestep);
2495 update_cursor(game[gindex], game[gindex].hindex);
2496 update_all(game[gindex]);
2497 break;
2498 case KEY_DOWN:
2499 history_previous(&game[gindex], game[gindex].b, (keycount) ?
2500 config.jumpcount * keycount * movestep :
2501 config.jumpcount * movestep);
2502 update_cursor(game[gindex], game[gindex].hindex);
2503 update_all(game[gindex]);
2504 break;
2505 case KEY_LEFT:
2506 history_previous(&game[gindex], game[gindex].b, (keycount) ?
2507 keycount * movestep : movestep);
2508 update_cursor(game[gindex], game[gindex].hindex);
2509 update_all(game[gindex]);
2510 break;
2511 case KEY_RIGHT:
2512 history_next(&game[gindex], game[gindex].b, (keycount) ?
2513 keycount * movestep : movestep);
2514 update_cursor(game[gindex], game[gindex].hindex);
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 (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 history_update_board(&game[gindex], game[gindex].b, game[gindex].hindex);
2572 update_all(game[gindex]);
2573 update_cursor(game[gindex], game[gindex].hindex);
2574 break;
2575 case 'v':
2576 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2577 break;
2578 case 'V':
2579 if (game[gindex].hindex - 1 >= 0)
2580 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2581 break;
2582 case '-':
2583 case '+':
2584 rav_next_prev(&game[gindex], game[gindex].b, (c == '-') ? 0 : 1);
2585 update_all(game[gindex]);
2586 break;
2587 case 'j':
2588 if (history_total(game[gindex].hp) < 2)
2589 break;
2591 /* FIXME field validation
2592 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2593 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2594 game[gindex].htotal)) == NULL)
2595 break;
2598 if (!keycount) {
2599 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2600 NULL, NULL, NULL, 0, -1)) == NULL)
2601 break;
2603 if (!isinteger(tmp))
2604 break;
2606 n = atoi(tmp);
2608 else
2609 n = keycount;
2611 if (n < 0 || n > (history_total(game[gindex].hp) / 2))
2612 break;
2614 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2615 history_update_board(&game[gindex], game[gindex].b,
2616 game[gindex].hindex);
2617 update_all(game[gindex]);
2618 update_cursor(game[gindex], game[gindex].hindex);
2619 break;
2620 default:
2621 break;
2625 static void cleanup_all_games()
2627 int i;
2629 for (i = 0; i < gtotal; i++) {
2630 struct userdata_s *d;
2632 if (game[i].data) {
2633 stop_engine(&game[i]);
2634 d = game[i].data;
2635 free(game[i].data);
2636 game[i].data = NULL;
2641 // Global and other keys.
2642 static int globalkeys(int c)
2644 static char gameexp[255] = {0};
2645 FILE *fp;
2646 char *tmp, *p;
2647 int n, i;
2648 char tfile[FILENAME_MAX];
2649 struct userdata_s *d = game[gindex].data;
2651 switch (c) {
2652 case 'h':
2653 if (game[gindex].mode != MODE_HISTORY) {
2654 if (!history_total(game[gindex].hp) ||
2655 (d->engine && d->engine->status == ENGINE_THINKING))
2656 return 1;
2658 game[gindex].mode = MODE_HISTORY;
2659 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2660 return 1;
2663 // FIXME
2664 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2665 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2666 return 1;
2669 // FIXME Resuming from previous history could append to a RAV.
2670 if (game[gindex].hindex != history_total(game[gindex].hp)) {
2671 if (!pushkey) {
2672 if ((c = message(NULL, YESNO, "%s",
2673 GAME_RESUME_HISTORY_TEXT)) != 'y')
2674 return 1;
2677 else {
2678 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2679 return 1;
2682 if (!noengine && (!d->engine ||
2683 d->engine->status == ENGINE_OFFLINE)) {
2684 if (start_chess_engine(&game[gindex]) < 0)
2685 return 1;
2687 pushkey = 'h';
2688 return 1;
2691 pushkey = 0;
2692 oldhistorytotal = history_total(game[gindex].hp);
2693 game[gindex].mode = MODE_PLAY;
2694 update_all(game[gindex]);
2695 return 1;
2696 case '>':
2697 case '<':
2698 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2699 keycount : 1);
2701 if (delete_count) {
2702 markend = gindex;
2703 pushkey = 'x';
2704 delete_count = 0;
2707 if (game[gindex].mode != MODE_EDIT) {
2708 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2709 update_cursor(game[gindex], game[gindex].hindex);
2711 update_all(game[gindex]);
2712 update_tag_window(game[gindex].tag);
2713 return 1;
2714 // Not sure whether to keep these.
2715 case '!': c_row = 1; return 1;
2716 case '@': c_row = 2; return 1;
2717 case '#': c_row = 3; return 1;
2718 case '$': c_row = 4; return 1;
2719 case '%': c_row = 5; return 1;
2720 case '^': c_row = 6; return 1;
2721 case '&': c_row = 7; return 1;
2722 case '*': c_row = 8; return 1;
2723 case 'A': c_col = 1; return 1;
2724 case 'B': c_col = 2; return 1;
2725 case 'C': c_col = 3; return 1;
2726 case 'D': c_col = 4; return 1;
2727 case 'E': c_col = 5; return 1;
2728 case 'F': c_col = 6; return 1;
2729 case 'G': c_col = 7; return 1;
2730 case 'H': c_col = 8; return 1;
2731 case '}':
2732 case '{':
2733 case '?':
2734 if (gtotal < 2)
2735 return 1;
2737 if (!*gameexp || c == '?') {
2738 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2739 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2740 NULL, 0, -1)) == NULL)
2741 return 1;
2743 strncpy(gameexp, tmp, sizeof(gameexp));
2746 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2747 ? keycount : 1)) ==
2749 return 1;
2751 gindex = n;
2753 if (history_total(game[gindex].hp))
2754 game[gindex].mode = MODE_HISTORY;
2756 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2757 update_all(game[gindex]);
2758 update_tag_window(game[gindex].tag);
2759 return 1;
2760 case 'J':
2761 if (gtotal < 2)
2762 return 1;
2764 /* FIXME field validation
2765 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2766 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2767 == NULL)
2768 return 1;
2771 if (!keycount) {
2772 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2773 NULL, NULL, 0, -1)) == NULL)
2774 return 1;
2776 if (!isinteger(tmp))
2777 return 1;
2779 i = atoi(tmp);
2781 else
2782 i = keycount;
2784 if (--i > gtotal - 1 || i < 0)
2785 return 1;
2787 gindex = i;
2788 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2789 update_cursor(game[gindex], game[gindex].hindex);
2790 update_all(game[gindex]);
2791 update_tag_window(game[gindex].tag);
2792 return 1;
2793 case 'x':
2794 pushkey = 0;
2796 if (gtotal < 2)
2797 return 1;
2799 if (keycount && !delete_count) {
2800 markstart = gindex;
2801 delete_count = 1;
2802 update_status_notify(game[gindex], "%s (delete)",
2803 status.notify);
2804 return 1;
2807 if (markstart >= 0 && markend >= 0) {
2808 if (markstart > markend) {
2809 i = markstart;
2810 markstart = markend;
2811 markend = i;
2814 for (i = markstart; i <= markend; i++) {
2815 if (toggle_delete_flag(i))
2816 return 1;
2819 else {
2820 if (toggle_delete_flag(gindex))
2821 return 1;
2824 markstart = markend = -1;
2825 update_status_window(game[gindex]);
2826 return 1;
2827 case 'X':
2828 if (gtotal < 2) {
2829 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2830 return 1;
2833 tmp = NULL;
2835 for (i = n = 0; i < gtotal; i++) {
2836 if (TEST_FLAG(game[i].flags, GF_DELETE))
2837 n++;
2840 if (!n)
2841 tmp = GAME_DELETE_GAME_TEXT;
2842 else {
2843 if (n == gtotal) {
2844 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2845 return 1;
2848 tmp = GAME_DELETE_ALL_TEXT;
2851 if (config.deleteprompt) {
2852 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2853 return 1;
2856 delete_game((!n) ? gindex : -1);
2858 if (history_total(game[gindex].hp))
2859 game[gindex].mode = MODE_HISTORY;
2861 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2862 update_all(game[gindex]);
2863 update_tag_window(game[gindex].tag);
2864 return 1;
2865 case 'T':
2866 edit_save_tags(&game[gindex]);
2867 update_all(game[gindex]);
2868 update_tag_window(game[gindex].tag);
2869 return 1;
2870 case 't':
2871 edit_tags(game[gindex], game[gindex].b, 0);
2872 return 1;
2873 case 'r':
2874 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2875 BROWSER_PROMPT, browse_directory, NULL, '\t',
2876 -1)) == NULL)
2877 return 1;
2879 if ((tmp = word_expand(tmp)) == NULL)
2880 break;
2882 if ((fp = pgn_open(tmp)) == NULL) {
2883 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2884 return 1;
2887 if (pgn_parse(fp))
2888 return 1;
2890 strncpy(loadfile, tmp, sizeof(loadfile));
2892 if (history_total(game[gindex].hp))
2893 game[gindex].mode = MODE_HISTORY;
2895 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2896 update_all(game[gindex]);
2897 update_tag_window(game[gindex].tag);
2898 return 1;
2899 case 'S':
2900 case 's':
2901 i = -1;
2903 if (gtotal > 1) {
2904 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2905 GAME_SAVE_MULTI_TEXT);
2907 if (n == 'c')
2908 i = gindex;
2909 else if (n == 'a')
2910 i = -1;
2911 else {
2912 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2913 return 1;
2917 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2918 BROWSER_PROMPT, browse_directory, NULL,
2919 '\t', -1)) == NULL) {
2920 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2921 return 1;
2924 if ((tmp = word_expand(tmp)) == NULL)
2925 break;
2927 if (pgn_is_compressed(tmp)) {
2928 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2929 tmp = tfile;
2931 else {
2932 if ((p = strchr(tmp, '.')) != NULL) {
2933 if (strcmp(p, ".pgn") != 0) {
2934 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2935 tmp = tfile;
2938 else {
2939 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2940 tmp = tfile;
2944 if (save_pgn(tmp, 0, i)) {
2945 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
2946 return 1;
2949 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
2950 update_all(game[gindex]);
2951 return 1;
2952 case KEY_F(1):
2953 n = 0;
2955 while (n != 'q') {
2956 n = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
2957 mainhelp);
2959 switch (n) {
2960 case 'h':
2961 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
2962 return 1;
2963 case 'p':
2964 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
2965 return 1;
2966 case 'e':
2967 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
2968 return 1;
2969 case 'g':
2970 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
2971 return 1;
2972 default:
2973 n = 'q';
2974 return 1;
2978 return 1;
2979 case 'n':
2980 case 'N':
2981 if (c == 'N') {
2982 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
2983 return 1;
2986 if (c == 'n') {
2987 pgn_new_game();
2988 add_custom_tags(&game[gindex].tag);
2989 d = Calloc(1, sizeof(struct userdata_s));
2990 game[gindex].data = d;
2992 else {
2993 cleanup_all_games();
2994 pgn_parse(NULL);
2995 add_custom_tags(&game[gindex].tag);
2996 pgn_init_board(game[gindex].b);
2997 d = Calloc(1, sizeof(struct userdata_s));
2998 game[gindex].data = d;
3001 game[gindex].mode = MODE_PLAY;
3002 c_row = (game[gindex].side == WHITE) ? 2 : 7;
3003 c_col = 4;
3004 update_status_notify(game[gindex], NULL);
3005 update_all(game[gindex]);
3006 update_tag_window(game[gindex].tag);
3007 return 1;
3008 case CTRL('L'):
3009 endwin();
3010 keypad(boardw, TRUE);
3011 refresh_all();
3012 return 1;
3013 case KEY_ESCAPE:
3014 sp.icon = sp.row = sp.col = 0;
3015 markend = markstart = 0;
3017 if (keycount) {
3018 keycount = 0;
3019 update_status_notify(game[gindex], NULL);
3022 if (config.validmoves)
3023 board_reset_valid_moves(game[gindex].b);
3025 return 1;
3026 case '0' ... '9':
3027 n = c - '0';
3029 if (keycount)
3030 keycount = keycount * 10 + n;
3031 else
3032 keycount = n;
3034 update_status_notify(game[gindex], "Repeat %i", keycount);
3035 return -1;
3036 case KEY_UP:
3037 if (game[gindex].mode == MODE_HISTORY)
3038 return 0;
3040 if (keycount) {
3041 c_row += keycount;
3042 pushkey = '\n';
3044 else
3045 c_row++;
3047 if (c_row > 8)
3048 c_row = 1;
3050 return 1;
3051 case KEY_DOWN:
3052 if (game[gindex].mode == MODE_HISTORY)
3053 return 0;
3055 if (keycount) {
3056 c_row -= keycount;
3057 pushkey = '\n';
3058 update_status_notify(game[gindex], NULL);
3060 else
3061 c_row--;
3063 if (c_row < 1)
3064 c_row = 8;
3066 return 1;
3067 case KEY_LEFT:
3068 if (game[gindex].mode == MODE_HISTORY)
3069 return 0;
3071 if (keycount) {
3072 c_col -= keycount;
3073 pushkey = '\n';
3075 else
3076 c_col--;
3078 if (c_col < 1)
3079 c_col = 8;
3081 return 1;
3082 case KEY_RIGHT:
3083 if (game[gindex].mode == MODE_HISTORY)
3084 return 0;
3086 if (keycount) {
3087 c_col += keycount;
3088 pushkey = '\n';
3090 else
3091 c_col++;
3093 if (c_col > 8)
3094 c_col = 1;
3096 return 1;
3097 case 'e':
3098 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3099 MODE_PLAY)
3100 return 1;
3102 // Don't edit a running game (for now).
3103 if (history_total(game[gindex].hp))
3104 return 1;
3106 if (game[gindex].mode != MODE_EDIT) {
3107 pgn_init_fen_board(&game[gindex], game[gindex].b, NULL);
3108 board_details++;
3109 game[gindex].mode = MODE_EDIT;
3110 update_all(game[gindex]);
3111 return 1;
3114 board_details--;
3115 pgn_add_tag(&game[gindex].tag, "FEN",
3116 pgn_game_to_fen(game[gindex], game[gindex].b));
3117 pgn_add_tag(&game[gindex].tag, "SetUp", "1");
3118 pgn_sort_tags(game[gindex].tag);
3119 game[gindex].mode = MODE_PLAY;
3120 update_all(game[gindex]);
3121 return 1;
3122 case 'Q':
3123 quit = 1;
3124 return 1;
3125 case KEY_RESIZE:
3126 do_window_resize();
3127 return 1;
3128 #ifdef DEBUG
3129 case 'O':
3130 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3131 return 1;
3132 #endif
3133 case 0:
3134 default:
3135 break;
3138 return 0;
3141 void game_loop()
3143 int error_recover = 0;
3145 c_row = 2, c_col = 5;
3146 gindex = gtotal - 1;
3148 if (history_total(game[gindex].hp))
3149 game[gindex].mode = MODE_HISTORY;
3150 else
3151 game[gindex].mode = MODE_PLAY;
3153 if (game[gindex].mode == MODE_HISTORY) {
3154 history_update_board(&game[gindex], game[gindex].b,
3155 history_total(game[gindex].hp));
3156 update_cursor(game[gindex], game[gindex].hindex);
3159 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3160 movestep = 2;
3161 paused = 1; //FIXME clock
3162 flushinp();
3163 update_all(game[gindex]);
3164 update_tag_window(game[gindex].tag);
3166 while (!quit) {
3167 int c = 0;
3168 int n = 0, i;
3169 char fdbuf[8192] = {0};
3170 int len;
3171 struct timeval tv = {0, 0};
3172 fd_set rfds;
3173 struct userdata_s *d = NULL;
3175 FD_ZERO(&rfds);
3177 for (i = 0; i < gtotal; i++) {
3178 d = game[i].data;
3180 if (d->engine) {
3181 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3182 if (d->engine->fd[ENGINE_IN_FD] > n)
3183 n = d->engine->fd[ENGINE_IN_FD];
3185 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3190 if (n) {
3191 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3192 for (i = 0; i < gtotal; i++) {
3193 d = game[i].data;
3195 if (d->engine) {
3196 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3197 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3198 sizeof(fdbuf));
3200 if (len == -1) {
3201 if (errno != EAGAIN) {
3202 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3203 strerror(errno));
3204 free(d->engine);
3205 d->engine = NULL;
3206 break;
3209 else {
3210 if (len) {
3211 parse_engine_output(&game[gindex], fdbuf);
3212 update_all(game[gindex]);
3219 else {
3220 if (n == -1)
3221 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3222 else {
3223 /* timeout */
3228 error_recover = 0;
3229 draw_board(&game[gindex], board_details);
3230 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3232 if (!paused) {
3235 refresh_all();
3237 if (pushkey)
3238 c = pushkey;
3239 else {
3240 if ((c = wgetch(boardw)) == ERR)
3241 continue;
3244 if (!keycount && status.notify)
3245 update_status_notify(game[gindex], NULL);
3248 if ((n = globalkeys(c)) == 1) {
3249 keycount = 0;
3250 continue;
3252 else if (n == -1)
3253 continue;
3255 switch (game[gindex].mode) {
3256 case MODE_EDIT:
3257 editmode_keys(c);
3258 break;
3259 case MODE_PLAY:
3260 playmode_keys(c);
3261 break;
3262 case MODE_HISTORY:
3263 historymode_keys(c);
3264 break;
3265 default:
3266 break;
3269 keycount = 0;
3273 void usage(const char *pn, int ret)
3275 fprintf((ret) ? stderr : stdout, "%s",
3276 "Usage: cboard [-hvNE] [-VtRS] [-p <file>]\n"
3277 " -p Load PGN file.\n"
3278 " -V Validate a game file.\n"
3279 " -S Validate and output a PGN formatted game.\n"
3280 " -R Like -S but write a reduced PGN formatted game.\n"
3281 " -t Also write custom PGN tags from config file.\n"
3282 " -N Don't enable the chess engine (two human players).\n"
3283 " -E Stop processing on file parsing error (overrides config).\n"
3284 " -v Version information.\n"
3285 " -h This help text.\n");
3287 exit(ret);
3290 void cleanup_all()
3292 cleanup_all_games();
3293 pgn_free_all();
3294 del_panel(boardp);
3295 del_panel(historyp);
3296 del_panel(statusp);
3297 del_panel(tagp);
3298 delwin(boardw);
3299 delwin(historyw);
3300 delwin(statusw);
3301 delwin(tagw);
3302 endwin();
3305 void catch_signal(int which)
3307 switch (which) {
3308 case SIGINT:
3309 case SIGPIPE:
3310 if (which == SIGPIPE && quit)
3311 break;
3313 if (which == SIGPIPE)
3314 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3316 cleanup_all();
3317 exit(EXIT_FAILURE);
3318 break;
3319 case SIGSTOP:
3320 savetty();
3321 break;
3322 case SIGCONT:
3323 resetty();
3324 keypad(boardw, TRUE);
3325 curs_set(0);
3326 cbreak();
3327 noecho();
3328 break;
3329 default:
3330 break;
3334 static void set_defaults()
3336 filetype = NO_FILE;
3337 set_config_defaults();
3340 int main(int argc, char *argv[])
3342 int opt;
3343 struct stat st;
3344 char buf[FILENAME_MAX];
3345 char datadir[FILENAME_MAX];
3346 int ret = EXIT_SUCCESS;
3347 int validate_only = 0, validate_and_write = 0, reduced = 0;
3348 int write_custom_tags = 0;
3349 FILE *fp;
3350 int i;
3352 if ((config.pwd = getpwuid(getuid())) == NULL)
3353 err(EXIT_FAILURE, "getpwuid()");
3355 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3356 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3357 config.ccfile = strdup(buf);
3358 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3359 config.nagfile = strdup(buf);
3360 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3361 config.agonyfile = strdup(buf);
3362 snprintf(buf, sizeof(buf), "%s/config", datadir);
3363 config.configfile = strdup(buf);
3364 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3365 config.fifo = strdup(buf);
3367 if (stat(datadir, &st) == -1) {
3368 if (errno == ENOENT) {
3369 if (mkdir(datadir, 0755) == -1)
3370 err(EXIT_FAILURE, "%s", datadir);
3372 else
3373 err(EXIT_FAILURE, "%s", datadir);
3375 stat(datadir, &st);
3378 if (!S_ISDIR(st.st_mode))
3379 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3381 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3382 if (mkfifo(config.fifo, 0600) == -1)
3383 err(EXIT_FAILURE, "%s", config.fifo);
3386 set_defaults();
3388 while ((opt = getopt(argc, argv, "ENVtSRhp:v")) != -1) {
3389 switch (opt) {
3390 case 't':
3391 write_custom_tags = 1;
3392 break;
3393 case 'E':
3394 config.stoponerror = 1;
3395 break;
3396 case 'N':
3397 noengine = 1;
3398 break;
3399 case 'R':
3400 reduced = 1;
3401 case 'S':
3402 validate_and_write = 1;
3403 case 'V':
3404 validate_only = 1;
3405 break;
3406 case 'v':
3407 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3408 COPYRIGHT);
3409 exit(EXIT_SUCCESS);
3410 case 'p':
3411 filetype = PGN_FILE;
3412 strncpy(loadfile, optarg, sizeof(loadfile));
3413 break;
3414 case 'h':
3415 default:
3416 usage(argv[0], EXIT_SUCCESS);
3420 if ((validate_only || validate_and_write) && !*loadfile)
3421 usage(argv[0], EXIT_FAILURE);
3423 if (access(config.configfile, R_OK) == 0)
3424 parse_rcfile(config.configfile);
3426 signal(SIGPIPE, catch_signal);
3427 signal(SIGCONT, catch_signal);
3428 signal(SIGSTOP, catch_signal);
3429 signal(SIGINT, catch_signal);
3431 srandom(getpid());
3433 switch (filetype) {
3434 case PGN_FILE:
3435 if ((fp = pgn_open(loadfile)) == NULL)
3436 err(EXIT_FAILURE, "%s", loadfile);
3438 ret = pgn_parse(fp);
3439 break;
3440 case FEN_FILE:
3441 //ret = parse_fen_file(loadfile);
3442 break;
3443 case EPD_FILE: // Not implemented.
3444 case NO_FILE:
3445 default:
3446 // No file specified. Empty game.
3447 ret = pgn_parse(NULL);
3448 add_custom_tags(&game[gindex].tag);
3449 break;
3452 if (validate_only || validate_and_write) {
3453 if (validate_and_write) {
3454 for (i = 0; i < gtotal; i++) {
3455 if (write_custom_tags)
3456 add_custom_tags(&game[i].tag);
3458 pgn_write(stdout, game[i]);
3462 pgn_free_all();
3463 exit(ret);
3465 else if (ret)
3466 exit(ret);
3468 for (i = 0; i < gtotal; i++) {
3469 struct userdata_s *d = NULL;
3471 d = Calloc(1, sizeof(struct userdata_s));
3472 game[i].data = d;
3475 if (initscr() == NULL)
3476 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3477 else
3478 curses_initialized = 1;
3480 if (LINES < 24 || COLS < 80) {
3481 endwin();
3482 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3485 if (has_colors() == TRUE && start_color() == OK)
3486 init_color_pairs();
3488 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3489 boardp = new_panel(boardw);
3490 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3491 COLS - HISTORY_WIDTH);
3492 historyp = new_panel(historyw);
3493 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3494 statusp = new_panel(statusw);
3495 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3496 tagp = new_panel(tagw);
3497 keypad(boardw, TRUE);
3498 // leaveok(boardw, TRUE);
3499 leaveok(tagw, TRUE);
3500 leaveok(statusw, TRUE);
3501 leaveok(historyw, TRUE);
3502 curs_set(0);
3503 cbreak();
3504 noecho();
3505 draw_window_decor();
3506 game_loop();
3507 cleanup_all();
3508 exit(EXIT_SUCCESS);