Fix zombie chess engine processes.
[cboard.git] / src / cboard.c
blob188e7e361dc54d69a302cc5504ea9bd4e213ac70
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <time.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_SYS_WAIT_H
40 #include <sys/wait.h>
41 #endif
43 #ifdef HAVE_WORDEXP_H
44 #include <wordexp.h>
45 #endif
47 #ifdef HAVE_DIRENT_H
48 #include <dirent.h>
49 #endif
51 #ifdef HAVE_MENU_H
52 #include <menu.h>
53 #endif
55 #ifdef HAVE_REGEX_H
56 #include <regex.h>
57 #endif
59 #include "chess.h"
60 #include "conf.h"
61 #include "window.h"
62 #include "colors.h"
63 #include "input.h"
64 #include "misc.h"
65 #include "engine.h"
66 #include "rcfile.h"
67 #include "strings.h"
68 #include "common.h"
69 #include "cboard.h"
71 #ifdef DEBUG
72 #include "debug.h"
73 #endif
75 #ifdef WITH_DMALLOC
76 #include <dmalloc.h>
77 #endif
79 static char *str_etc(const char *str, int maxlen, int rev)
81 int len = strlen(str);
82 static char buf[80], *p = buf;
83 int i;
85 strncpy(buf, str, sizeof(buf));
87 if (len > maxlen) {
88 if (rev) {
89 p = buf;
90 *p++ = '.';
91 *p++ = '.';
92 *p++ = '.';
94 for (i = 0; i < maxlen + 3; i++)
95 *p++ = buf[(len - maxlen) + i + 3];
97 else {
98 p = buf + maxlen - 4;
99 *p++ = '.';
100 *p++ = '.';
101 *p++ = '.';
104 *p = '\0';
107 return buf;
110 /* FIXME castling */
111 void update_cursor(GAME g, int idx)
113 char *p;
114 int len;
115 int t = history_total(g.hp);
118 * If not deincremented then r and c would be the next move.
120 idx--;
122 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
123 c_row = 2, c_col = 5;
124 return;
127 p = g.hp[idx]->move;
128 len = strlen(p);
130 if (*p == 'O') {
131 if (len <= 4)
132 c_col = 7;
133 else
134 c_col = 3;
136 c_row = (g.turn == WHITE) ? 8 : 1;
137 return;
140 p += len;
142 while (!isdigit(*p))
143 p--;
145 c_row = ROWTOINT(*p--);
146 c_col = COLTOINT(*p);
149 static int init_nag()
151 FILE *fp;
152 char line[LINE_MAX];
153 int i = 0;
155 if ((fp = fopen(config.nagfile, "r")) == NULL) {
156 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
157 return 1;
160 while (!feof(fp)) {
161 if (fscanf(fp, " %[^\n] ", line) == 1) {
162 nags = Realloc(nags, (i + 2) * sizeof(struct nag_s));
163 nags[i].line = strdup(line);
164 i++;
168 if (nags)
169 nags[i].line = NULL;
170 return 0;
173 char *history_edit_nag(void *arg)
175 WINDOW *win, *subw;
176 PANEL *panel;
177 ITEM **mitems = NULL;
178 MENU *menu;
179 int i = 0, n;
180 int itemcount = 0;
181 int rows, cols;
182 char *mbuf = NULL;
183 HISTORY *anno = (HISTORY *)arg;
185 if (!nags) {
186 if (init_nag())
187 return NULL;
190 i = 0;
191 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
192 mitems[i++] = new_item(NONE, NULL);
194 for (n = 0; nags[n].line; n++, i++) {
195 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
196 mitems[i] = new_item(nags[n].line, NULL);
199 mitems[i] = NULL;
200 menu = new_menu(mitems);
201 scale_menu(menu, &rows, &cols);
203 win = newwin(rows + 4, cols + 2, CALCPOSY(rows) - 2, CALCPOSX(cols));
204 set_menu_win(menu, win);
205 subw = derwin(win, rows, cols, 2, 1);
206 set_menu_sub(menu, subw);
207 set_menu_fore(menu, A_REVERSE);
208 set_menu_grey(menu, A_NORMAL);
209 set_menu_mark(menu, NULL);
210 set_menu_spacing(menu, 0, 0, 0);
211 menu_opts_off(menu, O_NONCYCLIC|O_SHOWDESC|O_ONEVALUE);
212 post_menu(menu);
213 panel = new_panel(win);
214 cbreak();
215 noecho();
216 keypad(win, TRUE);
217 set_menu_pattern(menu, mbuf);
218 wbkgd(win, CP_MESSAGE_WINDOW);
219 draw_window_title(win, NAG_EDIT_TITLE, cols + 2, CP_HISTORY_TITLE,
220 CP_HISTORY_BORDER);
222 for (i = 0; i < MAX_PGN_NAG; i++) {
223 if (anno->nag[i] && anno->nag[i] <= item_count(menu)) {
224 set_item_value(mitems[anno->nag[i]], TRUE);
225 set_current_item(menu, mitems[anno->nag[i]]);
226 itemcount++;
230 while (1) {
231 int c;
232 char *tmp;
233 char buf[cols - 4];
235 wattron(win, A_REVERSE);
237 for (c = 1; c < (cols + 2) - 1; c++)
238 mvwprintw(win, rows + 2, c, " ");
240 c = item_index(current_item(menu)) + 1;
242 snprintf(buf, sizeof(buf), "Item %i of %i (%i of %i selected) %s", c,
243 item_count(menu), itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
244 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
246 wattroff(win, A_REVERSE);
248 if (!itemcount) {
249 for (i = 0; mitems[i]; i++)
250 set_item_value(mitems[i], FALSE);
252 set_item_value(mitems[0], TRUE);
254 else
255 set_item_value(mitems[0], FALSE);
257 /* This nl() statement needs to be here because NL is recognized
258 * for some reason after the first selection.
260 nl();
261 refresh_all();
262 c = wgetch(win);
264 switch (c) {
265 int found;
267 case KEY_F(1):
268 help(NAG_EDIT_HELP, ANYKEY, naghelp);
269 break;
270 case KEY_RIGHT:
271 if (!itemcount)
272 break;
274 found = 0;
276 for (i = item_index(current_item(menu)) + 1; mitems[i]; i++) {
277 if (item_value(mitems[i]) == TRUE) {
278 found = i;
279 break;
283 if (!found) {
284 for (i = 0; mitems[i]; i++) {
285 if (item_value(mitems[i]) == TRUE) {
286 found = i;
287 break;
292 set_current_item(menu, mitems[found]);
293 break;
294 case KEY_LEFT:
295 if (!itemcount)
296 break;
298 found = 0;
300 for (i = item_index(current_item(menu)) - 1; i > 0; i--) {
301 if (item_value(mitems[i]) == TRUE) {
302 found = i;
303 break;
307 if (!found) {
308 for (i = item_count(menu) - 1; i > 0; i--) {
309 if (item_value(mitems[i]) == TRUE) {
310 found = i;
311 break;
316 set_current_item(menu, mitems[found]);
317 break;
318 case KEY_HOME:
319 menu_driver(menu, REQ_FIRST_ITEM);
320 break;
321 case KEY_END:
322 menu_driver(menu, REQ_LAST_ITEM);
323 break;
324 case KEY_UP:
325 menu_driver(menu, REQ_UP_ITEM);
326 break;
327 case KEY_DOWN:
328 menu_driver(menu, REQ_DOWN_ITEM);
329 break;
330 case KEY_PPAGE:
331 case CTRL('P'):
332 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
333 menu_driver(menu, REQ_FIRST_ITEM);
334 break;
335 case KEY_NPAGE:
336 case CTRL('N'):
337 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
338 menu_driver(menu, REQ_LAST_ITEM);
339 break;
340 case ' ':
341 if (item_index(current_item(menu)) == 0 &&
342 item_value(current_item(menu)) == FALSE) {
343 itemcount = 0;
344 break;
347 if (item_value(current_item(menu)) == TRUE) {
348 set_item_value(current_item(menu), FALSE);
349 itemcount--;
351 else {
352 if (itemcount + 1 > MAX_PGN_NAG)
353 break;
355 set_item_value(current_item(menu), TRUE);
356 itemcount++;
359 SET_FLAG(game[gindex].flags, GF_MODIFIED);
360 break;
361 case '\n':
362 goto gotitem;
363 break;
364 case KEY_ESCAPE:
365 goto done;
366 break;
367 default:
368 tmp = menu_pattern(menu);
370 if (tmp && tmp[strlen(tmp) - 1] != c) {
371 menu_driver(menu, REQ_CLEAR_PATTERN);
372 menu_driver(menu, c);
374 else {
375 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
376 menu_driver(menu, c);
379 break;
383 gotitem:
384 for (i = 0; i < MAX_PGN_NAG; i++)
385 anno->nag[i] = 0;
387 for (i = 0, n = 0; mitems[i] && n < MAX_PGN_NAG; i++) {
388 if (item_value(mitems[i]) == TRUE)
389 anno->nag[n++] = i;
392 done:
393 unpost_menu(menu);
394 free_menu(menu);
396 for (i = 0; mitems[i]; i++)
397 free_item(mitems[i]);
399 free(mitems);
400 del_panel(panel);
401 delwin(subw);
402 delwin(win);
403 return NULL;
406 static void view_nag(void *arg)
408 HISTORY *h = (HISTORY *)arg;
409 char buf[80];
410 char line[LINE_MAX] = {0};
411 int i = 0;
413 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
415 if (!nags) {
416 if (init_nag())
417 return;
420 for (i = 0; i < MAX_PGN_NAG; i++) {
421 if (!h->nag[i])
422 break;
424 strncat(line, nags[h->nag[i] - 1].line, sizeof(line));
425 strncat(line, "\n", sizeof(line));
428 line[strlen(line) - 1] = 0;
429 message(buf, ANYKEY, "%s", line);
432 void view_annotation(HISTORY h)
434 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
435 int nag = 0, comment = 0;
437 if (h.comment && h.comment[0])
438 comment++;
440 if (h.nag[0])
441 nag++;
443 if (!nag && !comment)
444 return;
446 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
448 if (comment)
449 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
450 (nag) ? "Press 'n' to view NAG" : NULL,
451 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
452 (nag) ? 'n' : 0, "%s", h.comment);
453 else
454 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
455 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
458 static void cleanup(WINDOW *win, WINDOW *subw, PANEL *panel, MENU *menu,
459 ITEM **items, struct d_entries *entries)
461 int i;
463 unpost_menu(menu);
464 free_menu(menu);
466 for (i = 0; items[i]; i++)
467 free_item(items[i]);
469 free(items);
471 if (entries) {
472 for (i = 0; entries[i].name; i++) {
473 free(entries[i].name);
474 free(entries[i].fancy);
477 free(entries);
480 del_panel(panel);
481 delwin(subw);
482 delwin(win);
485 static int sort_entries(const void *s1, const void *s2)
487 const struct d_entries *ss1 = s1;
488 const struct d_entries *ss2 = s2;
490 return strcmp(ss1->name, ss2->name);
493 char *browse_directory(void *arg)
495 char *inputstr = (char *)arg;
496 int initkey = (inputstr) ? inputstr[0] : 0;
497 char pattern[FILENAME_MAX];
498 static char path[FILENAME_MAX];
499 static char file[FILENAME_MAX];
500 struct stat st;
501 char *p;
503 if (!*path) {
504 if (config.savedirectory) {
505 if ((p = word_expand(config.savedirectory)) == NULL)
506 return NULL;
508 strncpy(path, p, sizeof(path));
510 if (access(path, R_OK) == -1) {
511 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
512 getcwd(path, sizeof(path));
515 else
516 getcwd(path, sizeof(path));
519 again:
521 * First find directories (including hidden) in the working directory.
522 * Then apply the config.pattern to regular files.
524 if ((p = word_split_append(path, '/', ".* *")) == NULL)
525 return NULL;
527 strncpy(pattern, p, sizeof(pattern));
529 while (1) {
530 WINDOW *win, *subw;
531 PANEL *panel;
532 ITEM **mitems = NULL;
533 MENU *menu;
534 char *tmp = NULL;
535 int rows, cols;
536 int selected = -1;
537 char *mbuf = NULL;
538 int idx = 0;
539 int len = strlen(path);
540 wordexp_t w;
541 int i, n = 0;
542 struct d_entries *entries = NULL;
543 int which = 1;
544 int x = WRDE_NOCMD;
546 new_we:
547 if (wordexp(pattern, &w, x) != 0) {
548 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
549 return NULL;
552 for (i = 0; i < w.we_wordc; i++) {
553 struct tm *tp;
554 char tbuf[16];
556 if (stat(w.we_wordv[i], &st) == -1)
557 continue;
559 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
560 p++;
561 else
562 p = w.we_wordv[i];
564 if (which) {
565 if (!S_ISDIR(st.st_mode))
566 continue;
568 if (p[0] == '.' && p[1] == 0)
569 continue;
571 else {
572 if (S_ISDIR(st.st_mode))
573 continue;
576 len = strlen(p) + 2;
577 entries = Realloc(entries, (n + 2) * sizeof(struct d_entries));
578 entries[n].name = strdup(w.we_wordv[i]);
579 entries[n].fancy = Malloc(len);
580 strncpy(entries[n].fancy, p, len);
582 if (S_ISDIR(st.st_mode))
583 entries[n].fancy[len - 2] = '/';
585 tp = localtime(&st.st_mtime);
586 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
588 snprintf(entries[n].desc, sizeof(entries[n].desc), "%-7i %s",
589 (int)st.st_size, tbuf);
591 memset(&entries[++n], '\0', sizeof(struct d_entries));
594 which--;
596 if (which == 0) {
597 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
598 return NULL;
600 strncpy(pattern, p, sizeof(pattern));
601 x |= WRDE_REUSE;
602 goto new_we;
605 wordfree(&w);
606 qsort(entries, n, sizeof(struct d_entries), sort_entries);
608 for (i = 0; i < n; i++) {
609 mitems = Realloc(mitems, (idx + 2) * sizeof(ITEM));
610 mitems[idx++] = new_item(entries[i].fancy, entries[i].desc);
613 mitems[idx] = NULL;
614 menu = new_menu(mitems);
615 scale_menu(menu, &rows, &cols);
617 if (cols < strlen(path))
618 cols = strlen(path);
620 if (cols < strlen(HELP_PROMPT))
621 cols = strlen(HELP_PROMPT);
623 rows = (LINES / 5) * 4;
624 cols += 2;
626 win = newwin(rows + 4, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
627 set_menu_format(menu, rows, 0);
628 set_menu_win(menu, win);
629 subw = derwin(win, rows, cols - 2, 2, 1);
630 set_menu_sub(menu, subw);
631 set_menu_fore(menu, A_REVERSE);
632 set_menu_grey(menu, A_NORMAL);
633 set_menu_mark(menu, NULL);
634 set_menu_spacing(menu, 2, 0, 0);
635 menu_opts_off(menu, O_NONCYCLIC);
636 post_menu(menu);
637 panel = new_panel(win);
639 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
640 draw_prompt(win, rows + 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
642 cbreak();
643 noecho();
644 keypad(win, TRUE);
645 set_menu_pattern(menu, mbuf);
647 if (isgraph(initkey)) {
648 menu_driver(menu, initkey);
649 initkey = '\0';
652 while (1) {
653 int c;
655 /* This nl() statement needs to be here because NL is recognized
656 * for some reason after the first selection.
658 nl();
659 refresh_all();
660 c = wgetch(win);
662 switch (c) {
663 case CTRL('P'):
664 case KEY_PPAGE:
665 menu_driver(menu, REQ_SCR_UPAGE);
666 break;
667 case ' ':
668 case CTRL('N'):
669 case KEY_NPAGE:
670 menu_driver(menu, REQ_SCR_DPAGE);
671 break;
672 case KEY_UP:
673 menu_driver(menu, REQ_UP_ITEM);
674 break;
675 case KEY_DOWN:
676 menu_driver(menu, REQ_DOWN_ITEM);
677 break;
678 case '\n':
679 selected = item_index(current_item(menu));
680 goto gotitem;
681 break;
682 case KEY_ESCAPE:
683 cleanup(win, subw, panel, menu, mitems, entries);
684 file[0] = 0;
685 goto done;
686 break;
687 case KEY_F(1):
688 help(BROWSER_HELP, ANYKEY, file_browser_help);
689 break;
690 case '~':
691 strncpy(path, "~/", sizeof(path));
692 cleanup(win, subw, panel, menu, mitems, entries);
693 goto again;
694 break;
695 case CTRL('X'):
696 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
697 == NULL)
698 break;
700 strncpy(path, tmp, sizeof(path));
701 cleanup(win, subw, panel, menu, mitems, entries);
702 goto again;
703 break;
704 default:
705 tmp = menu_pattern(menu);
707 if (tmp && tmp[strlen(tmp) - 1] != c) {
708 menu_driver(menu, REQ_CLEAR_PATTERN);
709 menu_driver(menu, c);
711 else {
712 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
713 menu_driver(menu, c);
716 break;
720 gotitem:
721 strncpy(file, entries[selected].name, sizeof(file));
722 cleanup(win, subw, panel, menu, mitems, entries);
724 if (stat(file, &st) == -1) {
725 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
726 continue;
729 if (S_ISDIR(st.st_mode)) {
730 p = file + strlen(file) - 2;
732 if (strcmp(p, "..") == 0) {
733 p = file + strlen(file) - 3;
734 *p = 0;
736 if ((p = strrchr(file, '/')) != NULL)
737 file[strlen(file) - strlen(p)] = 0;
740 strncpy(path, file, sizeof(path));
741 goto again;
744 if (S_ISREG(st.st_mode))
745 break;
747 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
750 done:
751 return (*file) ? file : NULL;
753 static int init_country_codes()
755 FILE *fp;
756 char line[LINE_MAX], *s;
757 int cindex = 0;
759 if ((fp = fopen(config.ccfile, "r")) == NULL) {
760 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
761 return 1;
764 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
765 char *tmp;
767 if ((tmp = strsep(&s, " ")) == NULL)
768 continue;
770 s = trim(s);
771 tmp = trim(tmp);
773 if (!s || !tmp)
774 continue;
776 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
777 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
778 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
779 cindex++;
782 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
783 fclose(fp);
785 return 0;
788 char *country_codes(void *arg)
790 WINDOW *win, *subw;
791 PANEL *panel;
792 ITEM **mitems = NULL;
793 MENU *menu;
794 int i = 0, n;
795 int rows, cols;
796 char *mbuf = NULL;
797 char *tmp = NULL;
799 if (!ccodes) {
800 if (init_country_codes())
801 return NULL;
804 for (n = i = 0; ccodes[n].code[0]; n++, i++) {
805 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
806 mitems[i] = new_item(ccodes[n].country, ccodes[n].code);
809 mitems[i] = NULL;
810 menu = new_menu(mitems);
811 scale_menu(menu, &rows, &cols);
813 if (cols < strlen(HELP_PROMPT) + 21)
814 cols = strlen(HELP_PROMPT) + 21;
816 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
817 set_menu_win(menu, win);
818 subw = derwin(win, rows, cols + 2, 2, 1);
819 set_menu_sub(menu, subw);
820 set_menu_fore(menu, A_REVERSE);
821 set_menu_grey(menu, A_NORMAL);
822 set_menu_mark(menu, NULL);
823 set_menu_spacing(menu, 0, 0, 0);
824 menu_opts_off(menu, O_NONCYCLIC);
825 post_menu(menu);
826 panel = new_panel(win);
827 cbreak();
828 noecho();
829 keypad(win, TRUE);
830 set_menu_pattern(menu, mbuf);
831 wbkgd(win, CP_MESSAGE_WINDOW);
832 draw_window_title(win, CC_TITLE, cols + 4, CP_MESSAGE_TITLE,
833 CP_MESSAGE_BORDER);
835 while (1) {
836 int c;
837 char buf[cols - 4];
839 wattron(win, A_REVERSE);
841 for (c = 1; c < (cols + 2) - 1; c++)
842 mvwprintw(win, rows + 2, c, " ");
844 c = item_index(current_item(menu)) + 1;
846 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR, c,
847 N_OF_N_STR, item_count(menu), HELP_PROMPT);
848 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
850 wattroff(win, A_REVERSE);
852 /* This nl() statement needs to be here because NL is recognized
853 * for some reason after the first selection.
855 nl();
856 refresh_all();
857 c = wgetch(win);
859 switch (c) {
860 case KEY_F(1):
861 help(CC_KEY_HELP, ANYKEY, cc_help);
862 break;
863 case KEY_HOME:
864 menu_driver(menu, REQ_FIRST_ITEM);
865 break;
866 case KEY_END:
867 menu_driver(menu, REQ_LAST_ITEM);
868 break;
869 case KEY_UP:
870 menu_driver(menu, REQ_UP_ITEM);
871 break;
872 case KEY_DOWN:
873 menu_driver(menu, REQ_DOWN_ITEM);
874 break;
875 case KEY_PPAGE:
876 case CTRL('P'):
877 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
878 menu_driver(menu, REQ_FIRST_ITEM);
879 break;
880 case ' ':
881 case KEY_NPAGE:
882 case CTRL('N'):
883 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
884 menu_driver(menu, REQ_LAST_ITEM);
885 break;
886 case '\n':
887 tmp = (char *)item_description(current_item(menu));
888 goto done;
889 break;
890 case KEY_ESCAPE:
891 tmp = NULL;
892 goto done;
893 break;
894 default:
895 tmp = menu_pattern(menu);
897 if (tmp && tmp[strlen(tmp) - 1] != c) {
898 menu_driver(menu, REQ_CLEAR_PATTERN);
899 menu_driver(menu, c);
901 else {
902 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
903 menu_driver(menu, c);
906 break;
910 done:
911 unpost_menu(menu);
912 free_menu(menu);
914 for (i = 0; mitems[i]; i++)
915 free_item(mitems[i]);
917 del_panel(panel);
918 delwin(subw);
919 delwin(win);
920 return tmp;
923 static void add_custom_tags(TAG ***t)
925 int i;
927 if (!config.tag)
928 return;
930 for (i = 0; config.tag[i]; i++)
931 pgn_add_tag(t, config.tag[i]->name, config.tag[i]->value);
933 pgn_sort_tags(*t);
936 TAG **edit_tags(GAME g, BOARD b, int edit)
938 TAG **data = NULL;
939 struct tm tp;
940 unsigned char data_index = 0;
941 int n, lastindex = 0;
942 int len;
944 /* Edit the backup copy, not the original in case the save fails. */
945 for (n = 0; g.tag[n]; n++)
946 pgn_add_tag(&data, g.tag[n]->name, g.tag[n]->value);
948 data_index = pgn_tag_total(data);
950 while (1) {
951 WINDOW *win, *subw;
952 PANEL *panel;
953 ITEM **mitems = NULL;
954 MENU *menu;
955 int i;
956 char buf[76] = {0};
957 char *tmp = NULL;
958 int rows, cols;
959 int selected = -1;
960 char *mbuf = NULL;
961 int nlen = 0, vlen = 0;
963 data_index = pgn_tag_total(data);
965 for (i = 0; i < data_index; i++) {
966 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
968 if (data[i]->value) {
969 nlen = strlen(data[i]->name);
970 vlen = strlen(data[i]->value);
972 /* The +6 is for the menu padding. */
973 mitems[i] = new_item(data[i]->name,
974 (nlen + vlen + 6 >= MAX_VALUE_WIDTH)
975 ? PRESS_ENTER : data[i]->value);
977 else
978 mitems[i] = new_item(data[i]->name, UNKNOWN);
981 mitems[i] = NULL;
982 menu = new_menu(mitems);
983 scale_menu(menu, &rows, &cols);
985 /* +14 for the extra prompt info. */
986 if (cols < strlen(HELP_PROMPT) + 14)
987 cols = strlen(HELP_PROMPT) + 14;
989 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
990 set_menu_win(menu, win);
991 subw = derwin(win, rows, cols + 2, 2, 1);
992 set_menu_sub(menu, subw);
993 set_menu_fore(menu, A_REVERSE);
994 set_menu_grey(menu, A_NORMAL);
995 set_menu_mark(menu, NULL);
996 set_menu_pad(menu, '-');
997 set_menu_spacing(menu, 3, 0, 0);
998 menu_opts_off(menu, O_NONCYCLIC);
999 post_menu(menu);
1000 panel = new_panel(win);
1001 cbreak();
1002 noecho();
1003 nl();
1004 keypad(win, TRUE);
1005 set_menu_pattern(menu, mbuf);
1006 wbkgd(win, CP_MESSAGE_WINDOW);
1007 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1008 cols + 4, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
1010 while (1) {
1011 int c;
1012 TAG **tmppgn = NULL;
1013 char *newtag = NULL;
1015 if (set_current_item(menu, mitems[lastindex]) != E_OK) {
1016 lastindex = item_count(menu) - 1;
1017 continue;
1020 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1021 item_index(current_item(menu)) + 1, N_OF_N_STR,
1022 item_count(menu), HELP_PROMPT);
1023 draw_prompt(win, rows + 2, cols + 4, buf, CP_MESSAGE_PROMPT);
1024 refresh_all();
1025 c = wgetch(win);
1027 switch (c) {
1028 case CTRL('T'):
1029 add_custom_tags(&data);
1030 goto cleanup;
1031 break;
1032 case KEY_F(1):
1033 if (edit)
1034 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1035 else
1036 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1037 break;
1038 case CTRL('R'):
1039 if (!edit)
1040 break;
1042 selected = item_index(current_item(menu));
1044 if (selected <= 6) {
1045 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1046 goto cleanup;
1049 data_index = pgn_tag_total(data);
1051 for (i = 0; i < data_index; i++) {
1052 if (i == selected)
1053 continue;
1055 pgn_add_tag(&tmppgn, data[i]->name, data[i]->value);
1058 pgn_tag_free(data);
1059 data = NULL;
1061 for (i = 0; tmppgn[i]; i++)
1062 pgn_add_tag(&data, tmppgn[i]->name, tmppgn[i]->value);
1064 pgn_tag_free(tmppgn);
1065 goto cleanup;
1066 break;
1067 case CTRL('A'):
1068 if (!edit)
1069 break;
1071 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1072 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1073 == NULL)
1074 break;
1076 newtag[0] = toupper(newtag[0]);
1078 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1079 strlen(PRESS_ENTER)) {
1080 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1081 break;
1084 for (i = 0; i < data_index; i++) {
1085 if (strcasecmp(data[i]->name, newtag) == 0) {
1086 selected = i;
1087 goto gotitem;
1091 pgn_add_tag(&data, newtag, NULL);
1092 data_index = pgn_tag_total(data);
1093 selected = data_index - 1;
1094 goto gotitem;
1095 break;
1096 case KEY_HOME:
1097 menu_driver(menu, REQ_FIRST_ITEM);
1098 break;
1099 case KEY_END:
1100 menu_driver(menu, REQ_LAST_ITEM);
1101 break;
1102 case CTRL('F'):
1103 if (!edit)
1104 break;
1106 pgn_add_tag(&data, "FEN", pgn_game_to_fen(g, b));
1107 data_index = pgn_tag_total(data);
1108 selected = data_index - 1;
1109 goto gotitem;
1110 break;
1111 case KEY_NPAGE:
1112 case CTRL('N'):
1113 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
1114 menu_driver(menu, REQ_LAST_ITEM);
1115 break;
1116 case KEY_PPAGE:
1117 case CTRL('P'):
1118 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
1119 menu_driver(menu, REQ_FIRST_ITEM);
1120 break;
1121 case KEY_UP:
1122 menu_driver(menu, REQ_UP_ITEM);
1123 break;
1124 case KEY_DOWN:
1125 menu_driver(menu, REQ_DOWN_ITEM);
1126 break;
1127 case '\n':
1128 selected = item_index(current_item(menu));
1129 goto gotitem;
1130 break;
1131 case KEY_ESCAPE:
1132 cleanup(win, subw, panel, menu, mitems, NULL);
1133 goto done;
1134 break;
1135 default:
1136 tmp = menu_pattern(menu);
1138 if (tmp && tmp[strlen(tmp) - 1] != c) {
1139 menu_driver(menu, REQ_CLEAR_PATTERN);
1140 menu_driver(menu, c);
1142 else {
1143 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
1144 menu_driver(menu, c);
1147 break;
1150 lastindex = item_index(current_item(menu));
1153 gotitem:
1154 lastindex = selected;
1155 nlen = strlen(data[selected]->name) + 3;
1156 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1158 if (nlen > MAX_VALUE_WIDTH)
1159 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1160 else
1161 snprintf(buf, sizeof(buf), "%s \"%s\"",
1162 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1163 data[selected]->name);
1165 if (!edit) {
1166 if (strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1167 goto cleanup;
1169 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1170 goto cleanup;
1173 if (strcmp(data[selected]->name, "Date") == 0) {
1174 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1175 0, FIELD_TYPE_PGN_DATE);
1177 if (tmp) {
1178 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1179 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1180 goto cleanup;
1183 else
1184 goto cleanup;
1186 else if (strcmp(data[selected]->name, "Site") == 0) {
1187 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1188 country_codes, NULL, CTRL('t'), -1);
1190 if (!tmp)
1191 tmp = "?";
1193 else if (strcmp(data[selected]->name, "Round") == 0) {
1194 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1195 FIELD_TYPE_PGN_ROUND);
1197 if (!tmp) {
1198 if (gtotal > 1)
1199 tmp = "?";
1200 else
1201 tmp = "-";
1204 else if (strcmp(data[selected]->name, "Result") == 0) {
1205 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1206 0, -1);
1208 if (!tmp)
1209 tmp = "*";
1211 else {
1212 if (item_description(mitems[selected]) &&
1213 strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1214 tmp = NULL;
1215 else
1216 tmp = data[selected]->value;
1218 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1221 len = (tmp) ? strlen(tmp) + 1 : 1;
1222 data[selected]->value = Realloc(data[selected]->value, len);
1223 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1225 cleanup:
1226 cleanup(win, subw, panel, menu, mitems, NULL);
1229 done:
1230 if (!edit) {
1231 pgn_tag_free(data);
1232 return NULL;
1235 return data;
1238 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1239 * game index number.
1241 int save_pgn(const char *filename, int isfifo, int saveindex)
1243 FILE *fp;
1244 char *mode = NULL;
1245 int c;
1246 char buf[FILENAME_MAX];
1247 struct stat st;
1248 int i;
1249 char *command = NULL;
1250 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1252 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1253 if (stat(config.savedirectory, &st) == -1) {
1254 if (errno == ENOENT) {
1255 if (mkdir(config.savedirectory, 0755) == -1) {
1256 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1257 strerror(errno));
1258 return 1;
1261 else {
1262 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1263 strerror(errno));
1264 return 1;
1268 stat(config.savedirectory, &st);
1270 if (!S_ISDIR(st.st_mode)) {
1271 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1272 return 1;
1275 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1276 filename = buf;
1279 /* This is a hack to resume an existing game when more than one game is
1280 * available. Also resuming a saved game and a game from history.
1282 // FIXME: may not need this when a FEN tag is supported (by the engine).
1283 if (isfifo)
1284 mode = "w";
1285 else {
1286 if (access(filename, W_OK) == 0) {
1287 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1288 "%s \"%s\"", E_FILEEXISTS, filename);
1290 switch (c) {
1291 case 'a':
1292 if (pgn_is_compressed(filename)) {
1293 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1294 return 1;
1297 mode = "a";
1298 break;
1299 case 'o':
1300 mode = "w+";
1301 break;
1302 default:
1303 return 1;
1306 else
1307 mode = "a";
1310 if (command) {
1311 if ((fp = popen(command, "w")) == NULL) {
1312 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1313 return 1;
1316 else {
1317 if ((fp = fopen(filename, mode)) == NULL) {
1318 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1319 return 1;
1323 if (isfifo)
1324 pgn_write(fp, game[saveindex]);
1325 else {
1326 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1327 pgn_write(fp, game[i]);
1330 if (command)
1331 pclose(fp);
1332 else
1333 fclose(fp);
1335 if (!isfifo && saveindex == -1)
1336 strncpy(loadfile, filename, sizeof(loadfile));
1338 return 0;
1341 char *random_agony(GAME g)
1343 static int n;
1344 FILE *fp;
1345 char line[LINE_MAX];
1347 if (n == -1 || !config.agony || !curses_initialized ||
1348 (g.mode == MODE_HISTORY && !config.historyagony))
1349 return NULL;
1351 if (!agony) {
1352 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1353 n = -1;
1354 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1355 return NULL;
1358 while (!feof(fp)) {
1359 if (fscanf(fp, " %[^\n] ", line) == 1) {
1360 agony = Realloc(agony, (n + 2) * sizeof(char *));
1361 agony[n++] = strdup(trim(line));
1365 agony[n] = NULL;
1366 fclose(fp);
1368 if (agony[0] == NULL || !n) {
1369 n = -1;
1370 return NULL;
1374 return agony[random() % n];
1377 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1379 if (pgn_piece_to_int(piece) == ROOK && col == 7
1380 && row == 7 &&
1381 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1382 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1383 if (mod)
1384 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1385 return 1;
1387 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1388 && row == 7 &&
1389 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1390 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1391 if (mod)
1392 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1393 return 1;
1395 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1396 && row == 0 &&
1397 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1398 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1399 if (mod)
1400 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1401 return 1;
1403 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1404 && row == 0 &&
1405 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1406 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1407 if (mod)
1408 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1409 return 1;
1411 else if (pgn_piece_to_int(piece) == KING && col == 4
1412 && row == 7 &&
1413 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1414 TEST_FLAG(g->flags, GF_WK_CASTLE))
1416 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1417 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1418 if (mod) {
1419 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1420 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1421 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1422 else
1423 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1425 return 1;
1427 else if (pgn_piece_to_int(piece) == KING && col == 4
1428 && row == 0 &&
1429 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1430 TEST_FLAG(g->flags, GF_BK_CASTLE))
1432 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1433 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1434 if (mod) {
1435 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1436 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1437 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1438 else
1439 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1441 return 1;
1444 return 0;
1447 static void draw_board(GAME *g, int details)
1449 int row, col;
1450 int bcol = 0, brow = 0;
1451 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1452 int ncols = 0, offset = 1;
1453 unsigned coords_y = 8;
1455 for (row = 0; row < maxy; row++) {
1456 bcol = 0;
1458 for (col = 0; col < maxx; col++) {
1459 int attrwhich = -1;
1460 chtype attrs = 0;
1461 unsigned char piece;
1463 if (row == 0 || row == maxy - 2) {
1464 if (col == 0)
1465 mvwaddch(boardw, row, col,
1466 LINE_GRAPHIC((row) ?
1467 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1468 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1469 else if (col == maxx - 2)
1470 mvwaddch(boardw, row, col,
1471 LINE_GRAPHIC((row) ?
1472 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1473 ACS_URCORNER | CP_BOARD_GRAPHICS));
1474 else if (!(col % 4))
1475 mvwaddch(boardw, row, col,
1476 LINE_GRAPHIC((row) ?
1477 ACS_BTEE | CP_BOARD_GRAPHICS :
1478 ACS_TTEE | CP_BOARD_GRAPHICS));
1479 else {
1480 if (col != maxx - 1)
1481 mvwaddch(boardw, row, col,
1482 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1485 continue;
1488 if ((row % 2) && col == maxx - 1 && coords_y) {
1489 wattron(boardw, CP_BOARD_COORDS);
1490 mvwprintw(boardw, row, col, "%d", coords_y--);
1491 wattroff(boardw, CP_BOARD_COORDS);
1492 continue;
1495 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1496 if (!(row % 2))
1497 mvwaddch(boardw, row, col,
1498 LINE_GRAPHIC((col) ?
1499 ACS_RTEE | CP_BOARD_GRAPHICS :
1500 ACS_LTEE | CP_BOARD_GRAPHICS));
1501 else
1502 mvwaddch(boardw, row, col,
1503 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1505 continue;
1508 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1509 mvwaddch(boardw, row, col,
1510 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1511 continue;
1514 if (!(col % 4) && row != maxy - 1) {
1515 mvwaddch(boardw, row, col,
1516 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1517 continue;
1520 if ((row % 2)) {
1521 if ((col % 4)) {
1522 if (ncols++ == 8) {
1523 offset++;
1524 ncols = 1;
1527 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1528 && (offset % 2)))
1529 attrwhich = BLACK;
1530 else
1531 attrwhich = WHITE;
1533 if (config.validmoves && g->b[brow][bcol].valid) {
1534 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1535 CP_BOARD_MOVES_BLACK;
1537 else
1538 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1539 CP_BOARD_BLACK;
1541 if (row == ROWTOMATRIX(c_row) && col ==
1542 COLTOMATRIX(c_col)) {
1543 attrs = CP_BOARD_CURSOR;
1546 if (row == ROWTOMATRIX(sp.row) &&
1547 col == COLTOMATRIX(sp.col)) {
1548 attrs = CP_BOARD_SELECTED;
1551 if (row == maxy - 1)
1552 attrs = 0;
1554 mvwaddch(boardw, row, col, ' ' | attrs);
1556 if (row == maxy - 1)
1557 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1558 else {
1559 if (details && g->b[row / 2][bcol].enpassant)
1560 piece = 'x';
1561 else
1562 piece = g->b[row / 2][bcol].icon;
1564 if (details && castling_state(g, g->b, brow, bcol,
1565 piece, 0))
1566 attrs |= A_REVERSE;
1568 if (g->side == WHITE && isupper(piece))
1569 attrs |= A_BOLD;
1570 else if (g->side == BLACK && islower(piece))
1571 attrs |= A_BOLD;
1573 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1575 CLEAR_FLAG(attrs, A_BOLD);
1576 CLEAR_FLAG(attrs, A_REVERSE);
1579 waddch(boardw, ' ' | attrs);
1580 col += 2;
1581 bcol++;
1584 else {
1585 if (col != maxx - 1)
1586 mvwaddch(boardw, row, col,
1587 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1591 brow = row / 2;
1595 void invalid_move(int n, const char *m)
1597 if (curses_initialized)
1598 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1599 else
1600 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1603 /* Convert the selected piece to SAN format and validate it. */
1604 static char *board_to_san(GAME *g, BOARD b)
1606 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1607 int piece;
1608 int promo;
1609 BOARD oldboard;
1611 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1612 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1614 p = str;
1615 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1617 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1618 (sp.destrow == 1 && g->turn == BLACK))) {
1619 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1621 if (pgn_piece_to_int(promo) == -1)
1622 return NULL;
1624 p = str + strlen(str);
1625 *p++ = toupper(promo);
1626 *p = '\0';
1629 memcpy(oldboard, b, sizeof(BOARD));
1631 if ((p = pgn_a2a4tosan(g, b, str)) == NULL) {
1632 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
1633 memcpy(b, oldboard, sizeof(BOARD));
1634 return NULL;
1637 if (pgn_validate_move(g, b, p)) {
1638 invalid_move(gindex + 1, p);
1639 memcpy(b, oldboard, sizeof(BOARD));
1640 return NULL;
1643 return p;
1646 static int move_to_engine(GAME *g, BOARD b)
1648 char *p;
1650 if ((p = board_to_san(g, b)) == NULL)
1651 return 0;
1653 sp.row = sp.col = sp.icon = 0;
1655 if (noengine) {
1656 history_add(g, p);
1657 pgn_switch_turn(g);
1658 SET_FLAG(g->flags, GF_MODIFIED);
1659 update_all(*g);
1660 return 1;
1663 send_to_engine(g, "%s\n", p);
1664 return 1;
1667 static void update_clock(int n, int *h, int *m, int *s)
1669 *h = n / 3600;
1670 *m = (n % 3600) / 60;
1671 *s = (n % 3600) % 60;
1673 return;
1676 void update_status_window(GAME g)
1678 int i = 0;
1679 char *buf;
1680 char tmp[15], *engine, *mode;
1681 int w;
1682 int h, m, s;
1683 char *p;
1684 int maxy, maxx;
1685 int len;
1686 struct userdata_s *d = g.data;
1688 getmaxyx(statusw, maxy, maxx);
1689 w = maxx - 2 - 8;
1690 len = maxx - 2;
1691 buf = Malloc(len);
1693 *tmp = '\0';
1694 p = tmp;
1696 if (TEST_FLAG(g.flags, GF_DELETE)) {
1697 *p++ = '(';
1698 *p++ = 'x';
1699 i++;
1702 if (TEST_FLAG(g.flags, GF_PERROR)) {
1703 if (!i)
1704 *p++ = '(';
1705 else
1706 *p++ = '/';
1708 *p++ = '!';
1709 i++;
1712 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1713 if (!i)
1714 *p++ = '(';
1715 else
1716 *p++ = '/';
1718 *p++ = '*';
1719 i++;
1722 if (*tmp != '\0')
1723 *p++ = ')';
1725 *p = '\0';
1727 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1728 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1729 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1730 (*tmp) ? tmp : "");
1731 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1733 switch (g.mode) {
1734 case MODE_HISTORY:
1735 mode = MODE_HISTORY_STR;
1736 break;
1737 case MODE_EDIT:
1738 mode = MODE_EDIT_STR;
1739 break;
1740 case MODE_PLAY:
1741 mode = MODE_PLAY_STR;
1742 break;
1743 default:
1744 mode = UNKNOWN;
1745 break;
1748 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
1750 if (d->engine) {
1751 switch (d->engine->status) {
1752 case ENGINE_THINKING:
1753 engine = ENGINE_THINKING_STR;
1754 break;
1755 case ENGINE_READY:
1756 engine = ENGINE_READY_STR;
1757 break;
1758 case ENGINE_INITIALIZING:
1759 engine = ENGINE_INITIALIZING_STR;
1760 break;
1761 case ENGINE_OFFLINE:
1762 engine = ENGINE_OFFLINE_STR;
1763 break;
1764 default:
1765 engine = UNKNOWN;
1766 break;
1769 else
1770 engine = ENGINE_OFFLINE_STR;
1772 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1773 wattron(statusw, CP_STATUS_ENGINE);
1775 if (TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
1776 snprintf(buf, len - 1, "%s (loop)", engine);
1777 mvwaddstr(statusw, 5, 9, buf);
1779 else
1780 mvwaddstr(statusw, 5, 9, engine);
1782 wattroff(statusw, CP_STATUS_ENGINE);
1784 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1785 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1787 strncpy(tmp, WHITE_STR, sizeof(tmp));
1788 tmp[0] = toupper(tmp[0]);
1789 update_clock(g.moveclock, &h, &m, &s);
1790 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1791 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1793 strncpy(tmp, BLACK_STR, sizeof(tmp));
1794 tmp[0] = toupper(tmp[0]);
1795 update_clock(g.moveclock, &h, &m, &s);
1796 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1797 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1798 free(buf);
1800 for (i = 1; i < maxx - 4; i++)
1801 mvwprintw(statusw, maxy - 2, i, " ");
1803 if (!status.notify)
1804 status.notify = strdup(GAME_HELP_PROMPT);
1806 wattron(statusw, CP_STATUS_NOTIFY);
1807 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1808 status.notify);
1809 wattroff(statusw, CP_STATUS_NOTIFY);
1812 void update_history_window(GAME g)
1814 char buf[HISTORY_WIDTH - 1];
1815 HISTORY *h = NULL;
1816 int n, total;
1817 int t = history_total(g.hp);
1819 n = (g.hindex + 1) / 2;
1821 if (t % 2)
1822 total = (t + 1) / 2;
1823 else
1824 total = t / 2;
1826 if (t)
1827 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1828 (movestep == 1) ? HISTORY_PLY_STEP : "");
1829 else
1830 strncpy(buf, UNAVAILABLE, sizeof(buf));
1832 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1833 HISTORY_WIDTH - 13, buf);
1835 h = history_by_n(g.hp, g.hindex);
1836 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1837 n = 0;
1839 if (h && ((h->comment) || h->nag[0])) {
1840 strncat(buf, " (v", sizeof(buf));
1841 n++;
1844 if (h && h->rav) {
1845 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1846 n++;
1849 if (g.ravlevel) {
1850 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1851 n++;
1854 if (n)
1855 strncat(buf, ")", sizeof(buf));
1857 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1858 HISTORY_WIDTH - 13, buf);
1860 h = history_by_n(g.hp, game[gindex].hindex - 1);
1861 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1862 n = 0;
1864 if (h && ((h->comment) || h->nag[0])) {
1865 strncat(buf, " (V", sizeof(buf));
1866 n++;
1869 if (h && h->rav) {
1870 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1871 n++;
1874 if (g.ravlevel) {
1875 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1876 n++;
1879 if (n)
1880 strncat(buf, ")", sizeof(buf));
1882 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1883 HISTORY_WIDTH - 13, buf);
1886 void update_tag_window(TAG **t)
1888 int i;
1889 int w = TAG_WIDTH - 10;
1891 for (i = 0; i < 7; i++)
1892 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1895 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1897 int i;
1899 wattron(win, attr);
1901 for (i = 1; i < width - 1; i++)
1902 mvwaddch(win, y, i, ' ');
1904 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1905 wattroff(win, attr);
1908 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1909 chtype battr)
1911 int i;
1913 if (title) {
1914 wattron(win, attr);
1916 for (i = 1; i < width - 1; i++)
1917 mvwaddch(win, 1, i, ' ');
1919 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1920 wattroff(win, attr);
1923 wattron(win, battr);
1924 box(win, ACS_VLINE, ACS_HLINE);
1925 wattroff(win, battr);
1928 void refresh_all()
1930 update_panels();
1931 doupdate();
1934 void update_all(GAME g)
1936 update_status_window(g);
1937 update_history_window(g);
1938 update_tag_window(g.tag);
1941 static void game_next_prev(GAME g, int n, int count)
1943 if (gtotal < 2)
1944 return;
1946 if (n == 1) {
1947 if (gindex + count > gtotal - 1) {
1948 if (count != 1)
1949 gindex = gtotal - 1;
1950 else
1951 gindex = 0;
1953 else
1954 gindex += count;
1956 else {
1957 if (gindex - count < 0) {
1958 if (count != 1)
1959 gindex = 0;
1960 else
1961 gindex = gtotal - 1;
1963 else
1964 gindex -= count;
1968 static void delete_game(int which)
1970 GAME *g = NULL;
1971 int gi = 0;
1972 int i;
1974 for (i = 0; i < gtotal; i++) {
1975 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1976 pgn_free(game[i]);
1977 continue;
1980 g = Realloc(g, (gi + 1) * sizeof(GAME));
1981 memcpy(&g[gi], &game[i], sizeof(GAME));
1982 g[gi].tag = game[i].tag;
1983 g[gi].history = game[i].history;
1984 g[gi].hp = game[i].hp;
1985 gi++;
1988 game = g;
1989 gtotal = gi;
1991 if (which != -1) {
1992 if (which + 1 >= gtotal)
1993 gindex = gtotal - 1;
1994 else
1995 gindex = which;
1997 else
1998 gindex = gtotal - 1;
2000 game[gindex].hp = game[gindex].history;
2003 static int find_move_exp(GAME g, const char *str, int init, int which,
2004 int count)
2006 int i;
2007 int ret;
2008 static regex_t r;
2009 static int firstrun = 1;
2010 char errbuf[255];
2011 int incr;
2012 int found;
2014 if (init) {
2015 if (!firstrun)
2016 regfree(&r);
2018 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2019 regerror(ret, &r, errbuf, sizeof(errbuf));
2020 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2021 return -1;
2024 firstrun = 1;
2027 incr = (which == 0) ? -1 : 1;
2029 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2030 if (i == g.hindex - 1)
2031 break;
2033 if (i >= history_total(g.hp))
2034 i = 0;
2035 else if (i < 0)
2036 i = history_total(g.hp) - 1;
2038 // FIXME RAV
2039 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2041 if (ret == 0) {
2042 if (count == ++found) {
2043 return i + 1;
2046 else {
2047 if (ret != REG_NOMATCH) {
2048 regerror(ret, &r, errbuf, sizeof(errbuf));
2049 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2050 return -1;
2055 return -1;
2058 static int toggle_delete_flag(int n)
2060 int i, x;
2062 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2064 for (i = x = 0; i < gtotal; i++) {
2065 if (TEST_FLAG(game[i].flags, GF_DELETE))
2066 x++;
2069 if (x == gtotal) {
2070 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2071 CLEAR_FLAG(game[n].flags, GF_DELETE);
2072 return 1;
2075 return 0;
2078 static void edit_save_tags(GAME *g)
2080 TAG **t;
2082 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2083 return;
2085 pgn_tag_free(g->tag);
2086 g->tag = t;
2087 SET_FLAG(g->flags, GF_MODIFIED);
2088 pgn_sort_tags(g->tag);
2091 static int find_game_exp(char *str, int which, int count)
2093 char *nstr = NULL, *exp = NULL;
2094 regex_t nexp, vexp;
2095 int ret = -1;
2096 int g = 0;
2097 char buf[255], *tmp;
2098 char errbuf[255];
2099 int found = 0;
2100 int incr = (which == 0) ? -(1) : 1;
2102 strncpy(buf, str, sizeof(buf));
2103 tmp = buf;
2105 if (strstr(tmp, ":") != NULL) {
2106 nstr = strsep(&tmp, ":");
2108 if ((ret = regcomp(&nexp, nstr,
2109 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2110 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2111 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2112 ret = g = -1;
2113 goto cleanup;
2117 exp = tmp;
2119 if (exp == NULL)
2120 goto cleanup;
2122 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2123 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2124 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2125 ret = -1;
2126 goto cleanup;
2129 ret = -1;
2131 for (g = gindex + incr, found = 0; ; g += incr) {
2132 int t;
2134 if (g == gindex)
2135 break;
2137 if (g == gtotal)
2138 g = 0;
2139 else if (g < 0)
2140 g = gtotal - 1;
2142 for (t = 0; game[g].tag[t]; t++) {
2143 if (nstr) {
2144 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2145 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2146 if (count == ++found) {
2147 ret = g;
2148 goto cleanup;
2153 else {
2154 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2155 if (count == ++found) {
2156 ret = g;
2157 goto cleanup;
2163 ret = -1;
2166 cleanup:
2167 if (nstr)
2168 regfree(&nexp);
2170 if (g != -1)
2171 regfree(&vexp);
2173 return ret;
2177 * Updates the notification line in the status window then refreshes the
2178 * status window.
2180 void update_status_notify(GAME g, char *fmt, ...)
2182 va_list ap;
2183 #ifdef HAVE_VASPRINTF
2184 char *line;
2185 #else
2186 char line[COLS];
2187 #endif
2189 if (!fmt) {
2190 if (status.notify) {
2191 free(status.notify);
2192 status.notify = NULL;
2194 if (curses_initialized)
2195 update_status_window(g);
2198 return;
2201 va_start(ap, fmt);
2202 #ifdef HAVE_VASPRINTF
2203 vasprintf(&line, fmt, ap);
2204 #else
2205 vsnprintf(line, sizeof(line), fmt, ap);
2206 #endif
2207 va_end(ap);
2209 if (status.notify)
2210 free(status.notify);
2212 status.notify = strdup(line);
2214 #ifdef HAVE_VASPRINTF
2215 free(line);
2216 #endif
2217 if (curses_initialized)
2218 update_status_window(g);
2221 static void switch_side(GAME *g)
2223 g->side = (g->side == WHITE) ? BLACK : WHITE;
2226 int rav_next_prev(GAME *g, BOARD b, int n)
2228 // Next RAV.
2229 if (n) {
2230 if (g->hp[g->hindex]->rav == NULL)
2231 return 1;
2233 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2234 g->rav[g->ravlevel].hp = g->hp;
2235 g->rav[g->ravlevel].flags = g->flags;
2236 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2237 g->rav[g->ravlevel].hindex = g->hindex;
2238 g->hp = g->hp[g->hindex]->rav;
2239 g->hindex = 0;
2240 g->ravlevel++;
2241 return 0;
2244 if (g->ravlevel - 1 < 0)
2245 return 1;
2247 // Previous RAV.
2248 g->ravlevel--;
2249 pgn_init_fen_board(g, b, g->rav[g->ravlevel].fen);
2250 free(g->rav[g->ravlevel].fen);
2251 g->hp = g->rav[g->ravlevel].hp;
2252 g->flags = g->rav[g->ravlevel].flags;
2253 g->hindex = g->rav[g->ravlevel].hindex;
2254 return 0;
2257 static void draw_window_decor()
2259 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2260 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2261 wbkgd(boardw, CP_BOARD_WINDOW);
2262 wbkgd(statusw, CP_STATUS_WINDOW);
2263 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2264 CP_STATUS_TITLE, CP_STATUS_BORDER);
2265 wbkgd(tagw, CP_TAG_WINDOW);
2266 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2267 CP_TAG_BORDER);
2268 wbkgd(historyw, CP_HISTORY_WINDOW);
2269 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2270 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2273 static void do_window_resize()
2275 if (LINES < 24 || COLS < 80)
2276 return;
2278 resizeterm(LINES, COLS);
2279 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2280 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2281 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2282 wmove(historyw, 0, 0);
2283 wclrtobot(historyw);
2284 wmove(tagw, 0, 0);
2285 wclrtobot(tagw);
2286 wmove(statusw, 0, 0);
2287 wclrtobot(statusw);
2288 draw_window_decor();
2289 update_all(game[gindex]);
2292 static void historymode_keys(int);
2293 static void playmode_keys(int c)
2295 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2296 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2297 chtype p;
2298 int w, x, y, z;
2299 char *tmp;
2300 struct userdata_s *d = game[gindex].data;
2302 switch (c) {
2303 case 'o':
2304 if (!d)
2305 break;
2307 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2308 update_all(game[gindex]);
2309 break;
2310 case '|':
2311 if (!d->engine)
2312 break;
2314 if (d->engine->status == ENGINE_OFFLINE)
2315 break;
2317 x = d->engine->status;
2319 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2320 send_to_engine(&game[gindex], "%s\n", tmp);
2321 d->engine->status = x;
2322 break;
2323 case '\015':
2324 case '\n':
2325 pushkey = keycount = 0;
2326 update_status_notify(game[gindex], NULL);
2328 if (!noengine && (!d->engine ||
2329 d->engine->status == ENGINE_THINKING)) {
2330 beep();
2331 break;
2334 if (!sp.icon)
2335 break;
2337 sp.destrow = c_row;
2338 sp.destcol = c_col;
2340 if (editmode) {
2341 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2342 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2343 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2344 sp.icon = sp.row = sp.col = 0;
2345 break;
2348 if (move_to_engine(&game[gindex], game[gindex].b)) {
2349 if (config.validmoves)
2350 board_reset_valid_moves(game[gindex].b);
2352 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2353 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2354 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2358 break;
2359 case ' ':
2360 if (!noengine && (!d->engine ||
2361 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2362 if (start_chess_engine(&game[gindex]) < 0) {
2363 sp.icon = 0;
2364 break;
2368 if (!editmode)
2369 wtimeout(boardw, 70);
2371 if (sp.icon || (!editmode && d->engine &&
2372 d->engine->status == ENGINE_THINKING)) {
2373 beep();
2374 break;
2377 sp.icon = mvwinch(boardw, ROWTOMATRIX(c_row),
2378 COLTOMATRIX(c_col)+1) & A_CHARTEXT;
2380 if (sp.icon == ' ') {
2381 sp.icon = 0;
2382 break;
2385 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2386 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2387 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2388 sp.icon = 0;
2389 break;
2392 sp.row = c_row;
2393 sp.col = c_col;
2395 if (!editmode && config.validmoves)
2396 board_get_valid_moves(&game[gindex], game[gindex].b,
2397 pgn_piece_to_int(sp.icon), sp.row, sp.col, &w, &x, &y, &z);
2399 paused = 0;
2400 break;
2401 case 'w':
2402 send_to_engine(&game[gindex], "\nswitch\n");
2403 switch_side(&game[gindex]);
2404 update_status_window(game[gindex]);
2405 break;
2406 case 'u':
2407 /* FIXME dies reading FIFO sometimes. */
2408 if (!history_total(game[gindex].hp))
2409 break;
2411 history_previous(&game[gindex], game[gindex].b, (keycount) ? keycount * 2 :
2414 #if 0
2415 if (status.engine == CRAFTY)
2416 SEND_TO_ENGINE("read %s\n", config.fifo);
2417 else
2418 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
2419 #endif
2421 update_history_window(game[gindex]);
2422 break;
2423 case 'a':
2424 historymode_keys(c);
2425 break;
2426 case 'd':
2427 board_details = (board_details) ? 0 : 1;
2428 break;
2429 case 'p':
2430 paused = (paused) ? 0 : 1;
2431 break;
2432 case 'g':
2433 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2434 start_chess_engine(&game[gindex]);
2436 send_to_engine(&game[gindex], "go\n");
2437 break;
2438 default:
2439 break;
2443 static void editmode_keys(int c)
2445 switch (c) {
2446 case '\015':
2447 case '\n':
2448 case ' ':
2449 playmode_keys(c);
2450 break;
2451 case 'd':
2452 if (sp.icon)
2453 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2454 else
2455 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2457 sp.icon = sp.row = sp.col = 0;
2458 break;
2459 case 'w':
2460 pgn_switch_turn(&game[gindex]);
2461 switch_side(&game[gindex]);
2462 update_all(game[gindex]);
2463 break;
2464 case 'c':
2465 castling_state(&game[gindex], game[gindex].b, ROWTOBOARD(c_row),
2466 COLTOBOARD(c_col),
2467 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon, 1);
2468 break;
2469 case 'i':
2470 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2471 GAME_EDIT_TEXT);
2473 if (pgn_piece_to_int(c) == -1)
2474 break;
2476 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = c;
2477 break;
2478 case 'p':
2479 if (c_row == 6 || c_row == 3) {
2480 pgn_reset_enpassant(game[gindex].b);
2481 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].enpassant = 1;
2483 break;
2484 default:
2485 break;
2489 static void historymode_keys(int c)
2491 int n, len;
2492 char *tmp, *buf;
2493 static char moveexp[255] = {0};
2495 switch (c) {
2496 case ' ':
2497 movestep = (movestep == 1) ? 2 : 1;
2498 update_history_window(game[gindex]);
2499 break;
2500 case KEY_UP:
2501 history_next(&game[gindex], game[gindex].b, (keycount > 0) ?
2502 config.jumpcount * keycount * movestep :
2503 config.jumpcount * movestep);
2504 update_cursor(game[gindex], game[gindex].hindex);
2505 update_all(game[gindex]);
2506 break;
2507 case KEY_DOWN:
2508 history_previous(&game[gindex], game[gindex].b, (keycount) ?
2509 config.jumpcount * keycount * movestep :
2510 config.jumpcount * movestep);
2511 update_cursor(game[gindex], game[gindex].hindex);
2512 update_all(game[gindex]);
2513 break;
2514 case KEY_LEFT:
2515 history_previous(&game[gindex], game[gindex].b, (keycount) ?
2516 keycount * movestep : movestep);
2517 update_cursor(game[gindex], game[gindex].hindex);
2518 update_all(game[gindex]);
2519 break;
2520 case KEY_RIGHT:
2521 history_next(&game[gindex], game[gindex].b, (keycount) ?
2522 keycount * movestep : movestep);
2523 update_cursor(game[gindex], game[gindex].hindex);
2524 update_all(game[gindex]);
2525 break;
2526 case 'a':
2527 n = game[gindex].hindex;
2529 if (n && game[gindex].hp[n - 1]->move)
2530 n--;
2531 else
2532 break;
2534 buf = Malloc(COLS);
2535 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2536 game[gindex].hp[n]->move);
2538 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2539 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2540 -1);
2541 free(buf);
2543 if (!tmp && (!game[gindex].hp[n]->comment ||
2544 !*game[gindex].hp[n]->comment))
2545 break;
2546 else if (tmp && game[gindex].hp[n]->comment) {
2547 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2548 break;
2551 len = (tmp) ? strlen(tmp) + 1 : 1;
2552 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2553 len);
2554 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2555 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2556 update_all(game[gindex]);
2557 break;
2558 case ']':
2559 case '[':
2560 case '/':
2561 if (history_total(game[gindex].hp) < 2)
2562 break;
2564 n = 0;
2566 if (!*moveexp || c == '/') {
2567 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2568 break;
2570 strncpy(moveexp, tmp, sizeof(moveexp));
2571 n = 1;
2574 if ((n = find_move_exp(game[gindex], moveexp, n,
2575 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2576 == -1)
2577 break;
2579 game[gindex].hindex = n;
2580 history_update_board(&game[gindex], game[gindex].b, game[gindex].hindex);
2581 update_all(game[gindex]);
2582 update_cursor(game[gindex], game[gindex].hindex);
2583 break;
2584 case 'v':
2585 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2586 break;
2587 case 'V':
2588 if (game[gindex].hindex - 1 >= 0)
2589 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2590 break;
2591 case '-':
2592 case '+':
2593 rav_next_prev(&game[gindex], game[gindex].b, (c == '-') ? 0 : 1);
2594 update_all(game[gindex]);
2595 break;
2596 case 'j':
2597 if (history_total(game[gindex].hp) < 2)
2598 break;
2600 /* FIXME field validation
2601 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2602 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2603 game[gindex].htotal)) == NULL)
2604 break;
2607 if (!keycount) {
2608 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2609 NULL, NULL, NULL, 0, -1)) == NULL)
2610 break;
2612 if (!isinteger(tmp))
2613 break;
2615 n = atoi(tmp);
2617 else
2618 n = keycount;
2620 if (n < 0 || n > (history_total(game[gindex].hp) / 2))
2621 break;
2623 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2624 history_update_board(&game[gindex], game[gindex].b,
2625 game[gindex].hindex);
2626 update_all(game[gindex]);
2627 update_cursor(game[gindex], game[gindex].hindex);
2628 break;
2629 default:
2630 break;
2634 static void cleanup_all_games()
2636 int i;
2638 for (i = 0; i < gtotal; i++) {
2639 struct userdata_s *d;
2641 if (game[i].data) {
2642 stop_engine(&game[i]);
2643 d = game[i].data;
2644 free(game[i].data);
2645 game[i].data = NULL;
2650 // Global and other keys.
2651 static int globalkeys(int c)
2653 static char gameexp[255] = {0};
2654 FILE *fp;
2655 char *tmp, *p;
2656 int n, i;
2657 char tfile[FILENAME_MAX];
2658 struct userdata_s *d = game[gindex].data;
2660 switch (c) {
2661 case 'h':
2662 if (game[gindex].mode != MODE_HISTORY) {
2663 if (!history_total(game[gindex].hp) ||
2664 (d->engine && d->engine->status == ENGINE_THINKING))
2665 return 1;
2667 game[gindex].mode = MODE_HISTORY;
2668 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2669 return 1;
2672 // FIXME
2673 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2674 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2675 return 1;
2678 // FIXME Resuming from previous history could append to a RAV.
2679 if (game[gindex].hindex != history_total(game[gindex].hp)) {
2680 if (!pushkey) {
2681 if ((c = message(NULL, YESNO, "%s",
2682 GAME_RESUME_HISTORY_TEXT)) != 'y')
2683 return 1;
2686 else {
2687 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2688 return 1;
2691 if (!noengine && (!d->engine ||
2692 d->engine->status == ENGINE_OFFLINE)) {
2693 if (start_chess_engine(&game[gindex]) < 0)
2694 return 1;
2696 pushkey = 'h';
2697 return 1;
2700 pushkey = 0;
2701 oldhistorytotal = history_total(game[gindex].hp);
2702 game[gindex].mode = MODE_PLAY;
2703 update_all(game[gindex]);
2704 return 1;
2705 case '>':
2706 case '<':
2707 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2708 keycount : 1);
2710 if (delete_count) {
2711 markend = gindex;
2712 pushkey = 'x';
2713 delete_count = 0;
2716 if (game[gindex].mode != MODE_EDIT) {
2717 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2718 update_cursor(game[gindex], game[gindex].hindex);
2720 update_all(game[gindex]);
2721 update_tag_window(game[gindex].tag);
2722 return 1;
2723 // Not sure whether to keep these.
2724 case '!': c_row = 1; return 1;
2725 case '@': c_row = 2; return 1;
2726 case '#': c_row = 3; return 1;
2727 case '$': c_row = 4; return 1;
2728 case '%': c_row = 5; return 1;
2729 case '^': c_row = 6; return 1;
2730 case '&': c_row = 7; return 1;
2731 case '*': c_row = 8; return 1;
2732 case 'A': c_col = 1; return 1;
2733 case 'B': c_col = 2; return 1;
2734 case 'C': c_col = 3; return 1;
2735 case 'D': c_col = 4; return 1;
2736 case 'E': c_col = 5; return 1;
2737 case 'F': c_col = 6; return 1;
2738 case 'G': c_col = 7; return 1;
2739 case 'H': c_col = 8; return 1;
2740 case '}':
2741 case '{':
2742 case '?':
2743 if (gtotal < 2)
2744 return 1;
2746 if (!*gameexp || c == '?') {
2747 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2748 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2749 NULL, 0, -1)) == NULL)
2750 return 1;
2752 strncpy(gameexp, tmp, sizeof(gameexp));
2755 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2756 ? keycount : 1)) ==
2758 return 1;
2760 gindex = n;
2762 if (history_total(game[gindex].hp))
2763 game[gindex].mode = MODE_HISTORY;
2765 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2766 update_all(game[gindex]);
2767 update_tag_window(game[gindex].tag);
2768 return 1;
2769 case 'J':
2770 if (gtotal < 2)
2771 return 1;
2773 /* FIXME field validation
2774 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2775 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2776 == NULL)
2777 return 1;
2780 if (!keycount) {
2781 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2782 NULL, NULL, 0, -1)) == NULL)
2783 return 1;
2785 if (!isinteger(tmp))
2786 return 1;
2788 i = atoi(tmp);
2790 else
2791 i = keycount;
2793 if (--i > gtotal - 1 || i < 0)
2794 return 1;
2796 gindex = i;
2797 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2798 update_cursor(game[gindex], game[gindex].hindex);
2799 update_all(game[gindex]);
2800 update_tag_window(game[gindex].tag);
2801 return 1;
2802 case 'x':
2803 pushkey = 0;
2805 if (gtotal < 2)
2806 return 1;
2808 if (keycount && !delete_count) {
2809 markstart = gindex;
2810 delete_count = 1;
2811 update_status_notify(game[gindex], "%s (delete)",
2812 status.notify);
2813 return 1;
2816 if (markstart >= 0 && markend >= 0) {
2817 if (markstart > markend) {
2818 i = markstart;
2819 markstart = markend;
2820 markend = i;
2823 for (i = markstart; i <= markend; i++) {
2824 if (toggle_delete_flag(i))
2825 return 1;
2828 else {
2829 if (toggle_delete_flag(gindex))
2830 return 1;
2833 markstart = markend = -1;
2834 update_status_window(game[gindex]);
2835 return 1;
2836 case 'X':
2837 if (gtotal < 2) {
2838 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2839 return 1;
2842 tmp = NULL;
2844 for (i = n = 0; i < gtotal; i++) {
2845 if (TEST_FLAG(game[i].flags, GF_DELETE))
2846 n++;
2849 if (!n)
2850 tmp = GAME_DELETE_GAME_TEXT;
2851 else {
2852 if (n == gtotal) {
2853 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2854 return 1;
2857 tmp = GAME_DELETE_ALL_TEXT;
2860 if (config.deleteprompt) {
2861 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2862 return 1;
2865 delete_game((!n) ? gindex : -1);
2867 if (history_total(game[gindex].hp))
2868 game[gindex].mode = MODE_HISTORY;
2870 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2871 update_all(game[gindex]);
2872 update_tag_window(game[gindex].tag);
2873 return 1;
2874 case 'T':
2875 edit_save_tags(&game[gindex]);
2876 update_all(game[gindex]);
2877 update_tag_window(game[gindex].tag);
2878 return 1;
2879 case 't':
2880 edit_tags(game[gindex], game[gindex].b, 0);
2881 return 1;
2882 case 'r':
2883 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2884 BROWSER_PROMPT, browse_directory, NULL, '\t',
2885 -1)) == NULL)
2886 return 1;
2888 if ((tmp = word_expand(tmp)) == NULL)
2889 break;
2891 if ((fp = pgn_open(tmp)) == NULL) {
2892 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2893 return 1;
2896 if (pgn_parse(fp))
2897 return 1;
2899 strncpy(loadfile, tmp, sizeof(loadfile));
2901 if (history_total(game[gindex].hp))
2902 game[gindex].mode = MODE_HISTORY;
2904 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2905 update_all(game[gindex]);
2906 update_tag_window(game[gindex].tag);
2907 return 1;
2908 case 'S':
2909 case 's':
2910 i = -1;
2912 if (gtotal > 1) {
2913 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2914 GAME_SAVE_MULTI_TEXT);
2916 if (n == 'c')
2917 i = gindex;
2918 else if (n == 'a')
2919 i = -1;
2920 else {
2921 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2922 return 1;
2926 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2927 BROWSER_PROMPT, browse_directory, NULL,
2928 '\t', -1)) == NULL) {
2929 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2930 return 1;
2933 if ((tmp = word_expand(tmp)) == NULL)
2934 break;
2936 if (pgn_is_compressed(tmp)) {
2937 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2938 tmp = tfile;
2940 else {
2941 if ((p = strchr(tmp, '.')) != NULL) {
2942 if (strcmp(p, ".pgn") != 0) {
2943 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2944 tmp = tfile;
2947 else {
2948 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2949 tmp = tfile;
2953 if (save_pgn(tmp, 0, i)) {
2954 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
2955 return 1;
2958 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
2959 update_all(game[gindex]);
2960 return 1;
2961 case KEY_F(1):
2962 n = 0;
2964 while (n != 'q') {
2965 n = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
2966 mainhelp);
2968 switch (n) {
2969 case 'h':
2970 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
2971 return 1;
2972 case 'p':
2973 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
2974 return 1;
2975 case 'e':
2976 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
2977 return 1;
2978 case 'g':
2979 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
2980 return 1;
2981 default:
2982 n = 'q';
2983 return 1;
2987 return 1;
2988 case 'n':
2989 case 'N':
2990 if (c == 'N') {
2991 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
2992 return 1;
2995 if (c == 'n') {
2996 pgn_new_game();
2997 add_custom_tags(&game[gindex].tag);
2998 d = Calloc(1, sizeof(struct userdata_s));
2999 game[gindex].data = d;
3001 else {
3002 cleanup_all_games();
3003 pgn_parse(NULL);
3004 add_custom_tags(&game[gindex].tag);
3005 pgn_init_board(game[gindex].b);
3006 d = Calloc(1, sizeof(struct userdata_s));
3007 game[gindex].data = d;
3010 game[gindex].mode = MODE_PLAY;
3011 c_row = (game[gindex].side == WHITE) ? 2 : 7;
3012 c_col = 4;
3013 update_status_notify(game[gindex], NULL);
3014 update_all(game[gindex]);
3015 update_tag_window(game[gindex].tag);
3016 return 1;
3017 case CTRL('L'):
3018 endwin();
3019 keypad(boardw, TRUE);
3020 refresh_all();
3021 return 1;
3022 case KEY_ESCAPE:
3023 sp.icon = sp.row = sp.col = 0;
3024 markend = markstart = 0;
3026 if (keycount) {
3027 keycount = 0;
3028 update_status_notify(game[gindex], NULL);
3031 if (config.validmoves)
3032 board_reset_valid_moves(game[gindex].b);
3034 return 1;
3035 case '0' ... '9':
3036 n = c - '0';
3038 if (keycount)
3039 keycount = keycount * 10 + n;
3040 else
3041 keycount = n;
3043 update_status_notify(game[gindex], "Repeat %i", keycount);
3044 return -1;
3045 case KEY_UP:
3046 if (game[gindex].mode == MODE_HISTORY)
3047 return 0;
3049 if (keycount) {
3050 c_row += keycount;
3051 pushkey = '\n';
3053 else
3054 c_row++;
3056 if (c_row > 8)
3057 c_row = 1;
3059 return 1;
3060 case KEY_DOWN:
3061 if (game[gindex].mode == MODE_HISTORY)
3062 return 0;
3064 if (keycount) {
3065 c_row -= keycount;
3066 pushkey = '\n';
3067 update_status_notify(game[gindex], NULL);
3069 else
3070 c_row--;
3072 if (c_row < 1)
3073 c_row = 8;
3075 return 1;
3076 case KEY_LEFT:
3077 if (game[gindex].mode == MODE_HISTORY)
3078 return 0;
3080 if (keycount) {
3081 c_col -= keycount;
3082 pushkey = '\n';
3084 else
3085 c_col--;
3087 if (c_col < 1)
3088 c_col = 8;
3090 return 1;
3091 case KEY_RIGHT:
3092 if (game[gindex].mode == MODE_HISTORY)
3093 return 0;
3095 if (keycount) {
3096 c_col += keycount;
3097 pushkey = '\n';
3099 else
3100 c_col++;
3102 if (c_col > 8)
3103 c_col = 1;
3105 return 1;
3106 case 'e':
3107 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3108 MODE_PLAY)
3109 return 1;
3111 // Don't edit a running game (for now).
3112 if (history_total(game[gindex].hp))
3113 return 1;
3115 if (game[gindex].mode != MODE_EDIT) {
3116 pgn_init_fen_board(&game[gindex], game[gindex].b, NULL);
3117 board_details++;
3118 game[gindex].mode = MODE_EDIT;
3119 update_all(game[gindex]);
3120 return 1;
3123 board_details--;
3124 pgn_add_tag(&game[gindex].tag, "FEN",
3125 pgn_game_to_fen(game[gindex], game[gindex].b));
3126 pgn_add_tag(&game[gindex].tag, "SetUp", "1");
3127 pgn_sort_tags(game[gindex].tag);
3128 game[gindex].mode = MODE_PLAY;
3129 update_all(game[gindex]);
3130 return 1;
3131 case 'Q':
3132 quit = 1;
3133 return 1;
3134 case KEY_RESIZE:
3135 do_window_resize();
3136 return 1;
3137 #ifdef DEBUG
3138 case 'O':
3139 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3140 return 1;
3141 #endif
3142 case 0:
3143 default:
3144 break;
3147 return 0;
3150 void game_loop()
3152 int error_recover = 0;
3154 c_row = 2, c_col = 5;
3155 gindex = gtotal - 1;
3157 if (history_total(game[gindex].hp))
3158 game[gindex].mode = MODE_HISTORY;
3159 else
3160 game[gindex].mode = MODE_PLAY;
3162 if (game[gindex].mode == MODE_HISTORY) {
3163 history_update_board(&game[gindex], game[gindex].b,
3164 history_total(game[gindex].hp));
3165 update_cursor(game[gindex], game[gindex].hindex);
3168 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3169 movestep = 2;
3170 paused = 1; //FIXME clock
3171 flushinp();
3172 update_all(game[gindex]);
3173 update_tag_window(game[gindex].tag);
3175 while (!quit) {
3176 int c = 0;
3177 int n = 0, i;
3178 char fdbuf[8192] = {0};
3179 int len;
3180 struct timeval tv = {0, 0};
3181 fd_set rfds;
3182 struct userdata_s *d = NULL;
3184 FD_ZERO(&rfds);
3186 for (i = 0; i < gtotal; i++) {
3187 d = game[i].data;
3189 if (d->engine) {
3190 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3191 if (d->engine->fd[ENGINE_IN_FD] > n)
3192 n = d->engine->fd[ENGINE_IN_FD];
3194 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3199 if (n) {
3200 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3201 for (i = 0; i < gtotal; i++) {
3202 d = game[i].data;
3204 if (d->engine) {
3205 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3206 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3207 sizeof(fdbuf));
3209 if (len == -1) {
3210 if (errno != EAGAIN) {
3211 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3212 strerror(errno));
3213 waitpid(d->engine->pid, &n, 0);
3214 free(d->engine);
3215 d->engine = NULL;
3216 break;
3219 else {
3220 if (len) {
3221 parse_engine_output(&game[i], fdbuf);
3222 update_all(game[gindex]);
3229 else {
3230 if (n == -1)
3231 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3232 else {
3233 /* timeout */
3238 error_recover = 0;
3239 draw_board(&game[gindex], board_details);
3240 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3242 if (!paused) {
3245 refresh_all();
3247 if (pushkey)
3248 c = pushkey;
3249 else {
3250 if ((c = wgetch(boardw)) == ERR)
3251 continue;
3254 if (!keycount && status.notify)
3255 update_status_notify(game[gindex], NULL);
3258 if ((n = globalkeys(c)) == 1) {
3259 keycount = 0;
3260 continue;
3262 else if (n == -1)
3263 continue;
3265 switch (game[gindex].mode) {
3266 case MODE_EDIT:
3267 editmode_keys(c);
3268 break;
3269 case MODE_PLAY:
3270 playmode_keys(c);
3271 break;
3272 case MODE_HISTORY:
3273 historymode_keys(c);
3274 break;
3275 default:
3276 break;
3279 keycount = 0;
3283 void usage(const char *pn, int ret)
3285 fprintf((ret) ? stderr : stdout, "%s",
3286 "Usage: cboard [-hvNE] [-VtRS] [-p <file>]\n"
3287 " -p Load PGN file.\n"
3288 " -V Validate a game file.\n"
3289 " -S Validate and output a PGN formatted game.\n"
3290 " -R Like -S but write a reduced PGN formatted game.\n"
3291 " -t Also write custom PGN tags from config file.\n"
3292 " -N Don't enable the chess engine (two human players).\n"
3293 " -E Stop processing on file parsing error (overrides config).\n"
3294 " -v Version information.\n"
3295 " -h This help text.\n");
3297 exit(ret);
3300 void cleanup_all()
3302 cleanup_all_games();
3303 pgn_free_all();
3304 del_panel(boardp);
3305 del_panel(historyp);
3306 del_panel(statusp);
3307 del_panel(tagp);
3308 delwin(boardw);
3309 delwin(historyw);
3310 delwin(statusw);
3311 delwin(tagw);
3312 endwin();
3315 void catch_signal(int which)
3317 switch (which) {
3318 case SIGINT:
3319 case SIGPIPE:
3320 if (which == SIGPIPE && quit)
3321 break;
3323 if (which == SIGPIPE)
3324 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3326 cleanup_all();
3327 exit(EXIT_FAILURE);
3328 break;
3329 case SIGSTOP:
3330 savetty();
3331 break;
3332 case SIGCONT:
3333 resetty();
3334 keypad(boardw, TRUE);
3335 curs_set(0);
3336 cbreak();
3337 noecho();
3338 break;
3339 default:
3340 break;
3344 static void set_defaults()
3346 filetype = NO_FILE;
3347 set_config_defaults();
3350 int main(int argc, char *argv[])
3352 int opt;
3353 struct stat st;
3354 char buf[FILENAME_MAX];
3355 char datadir[FILENAME_MAX];
3356 int ret = EXIT_SUCCESS;
3357 int validate_only = 0, validate_and_write = 0, reduced = 0;
3358 int write_custom_tags = 0;
3359 FILE *fp;
3360 int i;
3362 if ((config.pwd = getpwuid(getuid())) == NULL)
3363 err(EXIT_FAILURE, "getpwuid()");
3365 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3366 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3367 config.ccfile = strdup(buf);
3368 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3369 config.nagfile = strdup(buf);
3370 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3371 config.agonyfile = strdup(buf);
3372 snprintf(buf, sizeof(buf), "%s/config", datadir);
3373 config.configfile = strdup(buf);
3374 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3375 config.fifo = strdup(buf);
3377 if (stat(datadir, &st) == -1) {
3378 if (errno == ENOENT) {
3379 if (mkdir(datadir, 0755) == -1)
3380 err(EXIT_FAILURE, "%s", datadir);
3382 else
3383 err(EXIT_FAILURE, "%s", datadir);
3385 stat(datadir, &st);
3388 if (!S_ISDIR(st.st_mode))
3389 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3391 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3392 if (mkfifo(config.fifo, 0600) == -1)
3393 err(EXIT_FAILURE, "%s", config.fifo);
3396 set_defaults();
3398 while ((opt = getopt(argc, argv, "ENVtSRhp:v")) != -1) {
3399 switch (opt) {
3400 case 't':
3401 write_custom_tags = 1;
3402 break;
3403 case 'E':
3404 config.stoponerror = 1;
3405 break;
3406 case 'N':
3407 noengine = 1;
3408 break;
3409 case 'R':
3410 reduced = 1;
3411 case 'S':
3412 validate_and_write = 1;
3413 case 'V':
3414 validate_only = 1;
3415 break;
3416 case 'v':
3417 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3418 COPYRIGHT);
3419 exit(EXIT_SUCCESS);
3420 case 'p':
3421 filetype = PGN_FILE;
3422 strncpy(loadfile, optarg, sizeof(loadfile));
3423 break;
3424 case 'h':
3425 default:
3426 usage(argv[0], EXIT_SUCCESS);
3430 if ((validate_only || validate_and_write) && !*loadfile)
3431 usage(argv[0], EXIT_FAILURE);
3433 if (access(config.configfile, R_OK) == 0)
3434 parse_rcfile(config.configfile);
3436 signal(SIGPIPE, catch_signal);
3437 signal(SIGCONT, catch_signal);
3438 signal(SIGSTOP, catch_signal);
3439 signal(SIGINT, catch_signal);
3441 srandom(getpid());
3443 switch (filetype) {
3444 case PGN_FILE:
3445 if ((fp = pgn_open(loadfile)) == NULL)
3446 err(EXIT_FAILURE, "%s", loadfile);
3448 ret = pgn_parse(fp);
3449 break;
3450 case FEN_FILE:
3451 //ret = parse_fen_file(loadfile);
3452 break;
3453 case EPD_FILE: // Not implemented.
3454 case NO_FILE:
3455 default:
3456 // No file specified. Empty game.
3457 ret = pgn_parse(NULL);
3458 add_custom_tags(&game[gindex].tag);
3459 break;
3462 if (validate_only || validate_and_write) {
3463 if (validate_and_write) {
3464 for (i = 0; i < gtotal; i++) {
3465 if (write_custom_tags)
3466 add_custom_tags(&game[i].tag);
3468 pgn_write(stdout, game[i]);
3472 pgn_free_all();
3473 exit(ret);
3475 else if (ret)
3476 exit(ret);
3478 for (i = 0; i < gtotal; i++) {
3479 struct userdata_s *d = NULL;
3481 d = Calloc(1, sizeof(struct userdata_s));
3482 game[i].data = d;
3485 if (initscr() == NULL)
3486 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3487 else
3488 curses_initialized = 1;
3490 if (LINES < 24 || COLS < 80) {
3491 endwin();
3492 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3495 if (has_colors() == TRUE && start_color() == OK)
3496 init_color_pairs();
3498 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3499 boardp = new_panel(boardw);
3500 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3501 COLS - HISTORY_WIDTH);
3502 historyp = new_panel(historyw);
3503 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3504 statusp = new_panel(statusw);
3505 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3506 tagp = new_panel(tagw);
3507 keypad(boardw, TRUE);
3508 // leaveok(boardw, TRUE);
3509 leaveok(tagw, TRUE);
3510 leaveok(statusw, TRUE);
3511 leaveok(historyw, TRUE);
3512 curs_set(0);
3513 cbreak();
3514 noecho();
3515 draw_window_decor();
3516 game_loop();
3517 cleanup_all();
3518 exit(EXIT_SUCCESS);