Fix engine status.
[cboard.git] / src / cboard.c
blobe71faaae54a0985c57f196d9d329d916215d3c83
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 update_panels();
258 doupdate();
260 c = wgetch(win);
262 switch (c) {
263 int found;
265 case KEY_F(1):
266 help(NAG_EDIT_HELP, ANYKEY, naghelp);
267 break;
268 case KEY_RIGHT:
269 if (!itemcount)
270 break;
272 found = 0;
274 for (i = item_index(current_item(menu)) + 1; mitems[i]; i++) {
275 if (item_value(mitems[i]) == TRUE) {
276 found = i;
277 break;
281 if (!found) {
282 for (i = 0; mitems[i]; i++) {
283 if (item_value(mitems[i]) == TRUE) {
284 found = i;
285 break;
290 set_current_item(menu, mitems[found]);
291 break;
292 case KEY_LEFT:
293 if (!itemcount)
294 break;
296 found = 0;
298 for (i = item_index(current_item(menu)) - 1; i > 0; i--) {
299 if (item_value(mitems[i]) == TRUE) {
300 found = i;
301 break;
305 if (!found) {
306 for (i = item_count(menu) - 1; i > 0; i--) {
307 if (item_value(mitems[i]) == TRUE) {
308 found = i;
309 break;
314 set_current_item(menu, mitems[found]);
315 break;
316 case KEY_HOME:
317 menu_driver(menu, REQ_FIRST_ITEM);
318 break;
319 case KEY_END:
320 menu_driver(menu, REQ_LAST_ITEM);
321 break;
322 case KEY_UP:
323 menu_driver(menu, REQ_UP_ITEM);
324 break;
325 case KEY_DOWN:
326 menu_driver(menu, REQ_DOWN_ITEM);
327 break;
328 case KEY_PPAGE:
329 case CTRL('P'):
330 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
331 menu_driver(menu, REQ_FIRST_ITEM);
332 break;
333 case KEY_NPAGE:
334 case CTRL('N'):
335 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
336 menu_driver(menu, REQ_LAST_ITEM);
337 break;
338 case ' ':
339 if (item_index(current_item(menu)) == 0 &&
340 item_value(current_item(menu)) == FALSE) {
341 itemcount = 0;
342 break;
345 if (item_value(current_item(menu)) == TRUE) {
346 set_item_value(current_item(menu), FALSE);
347 itemcount--;
349 else {
350 if (itemcount + 1 > MAX_PGN_NAG)
351 break;
353 set_item_value(current_item(menu), TRUE);
354 itemcount++;
357 SET_FLAG(game[gindex].flags, GF_MODIFIED);
358 break;
359 case '\n':
360 goto gotitem;
361 break;
362 case KEY_ESCAPE:
363 goto done;
364 break;
365 default:
366 tmp = menu_pattern(menu);
368 if (tmp && tmp[strlen(tmp) - 1] != c) {
369 menu_driver(menu, REQ_CLEAR_PATTERN);
370 menu_driver(menu, c);
372 else {
373 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
374 menu_driver(menu, c);
377 break;
381 gotitem:
382 for (i = 0; i < MAX_PGN_NAG; i++)
383 anno->nag[i] = 0;
385 for (i = 0, n = 0; mitems[i] && n < MAX_PGN_NAG; i++) {
386 if (item_value(mitems[i]) == TRUE)
387 anno->nag[n++] = i;
390 done:
391 unpost_menu(menu);
392 free_menu(menu);
394 for (i = 0; mitems[i]; i++)
395 free_item(mitems[i]);
397 free(mitems);
398 del_panel(panel);
399 delwin(subw);
400 delwin(win);
401 return NULL;
404 static void view_nag(void *arg)
406 HISTORY *h = (HISTORY *)arg;
407 char buf[80];
408 char line[LINE_MAX] = {0};
409 int i = 0;
411 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
413 if (!nags) {
414 if (init_nag())
415 return;
418 for (i = 0; i < MAX_PGN_NAG; i++) {
419 if (!h->nag[i])
420 break;
422 strncat(line, nags[h->nag[i] - 1].line, sizeof(line));
423 strncat(line, "\n", sizeof(line));
426 line[strlen(line) - 1] = 0;
427 message(buf, ANYKEY, "%s", line);
430 void view_annotation(HISTORY h)
432 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
433 int nag = 0, comment = 0;
435 if (h.comment && h.comment[0])
436 comment++;
438 if (h.nag[0])
439 nag++;
441 if (!nag && !comment)
442 return;
444 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
446 if (comment)
447 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
448 (nag) ? "Press 'n' to view NAG" : NULL,
449 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
450 (nag) ? 'n' : 0, "%s", h.comment);
451 else
452 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
453 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
456 static void cleanup(WINDOW *win, WINDOW *subw, PANEL *panel, MENU *menu,
457 ITEM **items, struct d_entries *entries)
459 int i;
461 unpost_menu(menu);
462 free_menu(menu);
464 for (i = 0; items[i]; i++)
465 free_item(items[i]);
467 free(items);
469 if (entries) {
470 for (i = 0; entries[i].name; i++) {
471 free(entries[i].name);
472 free(entries[i].fancy);
475 free(entries);
478 del_panel(panel);
479 delwin(subw);
480 delwin(win);
483 static int sort_entries(const void *s1, const void *s2)
485 const struct d_entries *ss1 = s1;
486 const struct d_entries *ss2 = s2;
488 return strcmp(ss1->name, ss2->name);
491 char *browse_directory(void *arg)
493 char *inputstr = (char *)arg;
494 int initkey = (inputstr) ? inputstr[0] : 0;
495 char pattern[FILENAME_MAX];
496 static char path[FILENAME_MAX];
497 static char file[FILENAME_MAX];
498 struct stat st;
499 char *p;
501 if (!*path) {
502 if (config.savedirectory) {
503 if ((p = word_expand(config.savedirectory)) == NULL)
504 return NULL;
506 strncpy(path, p, sizeof(path));
508 if (access(path, R_OK) == -1) {
509 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
510 getcwd(path, sizeof(path));
513 else
514 getcwd(path, sizeof(path));
517 again:
519 * First find directories (including hidden) in the working directory.
520 * Then apply the config.pattern to regular files.
522 if ((p = word_split_append(path, '/', ".* *")) == NULL)
523 return NULL;
525 strncpy(pattern, p, sizeof(pattern));
527 while (1) {
528 WINDOW *win, *subw;
529 PANEL *panel;
530 ITEM **mitems = NULL;
531 MENU *menu;
532 char *tmp = NULL;
533 int rows, cols;
534 int selected = -1;
535 char *mbuf = NULL;
536 int idx = 0;
537 int len = strlen(path);
538 wordexp_t w;
539 int i, n = 0;
540 struct d_entries *entries = NULL;
541 int which = 1;
542 int x = WRDE_NOCMD;
544 new_we:
545 if (wordexp(pattern, &w, x) != 0) {
546 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
547 return NULL;
550 for (i = 0; i < w.we_wordc; i++) {
551 struct tm *tp;
552 char tbuf[16];
554 if (stat(w.we_wordv[i], &st) == -1)
555 continue;
557 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
558 p++;
559 else
560 p = w.we_wordv[i];
562 if (which) {
563 if (!S_ISDIR(st.st_mode))
564 continue;
566 if (p[0] == '.' && p[1] == 0)
567 continue;
569 else {
570 if (S_ISDIR(st.st_mode))
571 continue;
574 len = strlen(p) + 2;
575 entries = Realloc(entries, (n + 2) * sizeof(struct d_entries));
576 entries[n].name = strdup(w.we_wordv[i]);
577 entries[n].fancy = Malloc(len);
578 strncpy(entries[n].fancy, p, len);
580 if (S_ISDIR(st.st_mode))
581 entries[n].fancy[len - 2] = '/';
583 tp = localtime(&st.st_mtime);
584 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
586 snprintf(entries[n].desc, sizeof(entries[n].desc), "%-7i %s",
587 (int)st.st_size, tbuf);
589 memset(&entries[++n], '\0', sizeof(struct d_entries));
592 which--;
594 if (which == 0) {
595 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
596 return NULL;
598 strncpy(pattern, p, sizeof(pattern));
599 x |= WRDE_REUSE;
600 goto new_we;
603 wordfree(&w);
604 qsort(entries, n, sizeof(struct d_entries), sort_entries);
606 for (i = 0; i < n; i++) {
607 mitems = Realloc(mitems, (idx + 2) * sizeof(ITEM));
608 mitems[idx++] = new_item(entries[i].fancy, entries[i].desc);
611 mitems[idx] = NULL;
612 menu = new_menu(mitems);
613 scale_menu(menu, &rows, &cols);
615 if (cols < strlen(path))
616 cols = strlen(path);
618 if (cols < strlen(HELP_PROMPT))
619 cols = strlen(HELP_PROMPT);
621 rows = (LINES / 5) * 4;
622 cols += 2;
624 win = newwin(rows + 4, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
625 set_menu_format(menu, rows, 0);
626 set_menu_win(menu, win);
627 subw = derwin(win, rows, cols - 2, 2, 1);
628 set_menu_sub(menu, subw);
629 set_menu_fore(menu, A_REVERSE);
630 set_menu_grey(menu, A_NORMAL);
631 set_menu_mark(menu, NULL);
632 set_menu_spacing(menu, 2, 0, 0);
633 menu_opts_off(menu, O_NONCYCLIC);
634 post_menu(menu);
635 panel = new_panel(win);
637 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
638 draw_prompt(win, rows + 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
640 cbreak();
641 noecho();
642 keypad(win, TRUE);
643 set_menu_pattern(menu, mbuf);
645 if (isgraph(initkey)) {
646 menu_driver(menu, initkey);
647 initkey = '\0';
650 while (1) {
651 int c;
653 /* This nl() statement needs to be here because NL is recognized
654 * for some reason after the first selection.
656 nl();
657 update_panels();
658 doupdate();
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 update_panels();
857 doupdate();
859 c = wgetch(win);
861 switch (c) {
862 case KEY_F(1):
863 help(CC_KEY_HELP, ANYKEY, cc_help);
864 break;
865 case KEY_HOME:
866 menu_driver(menu, REQ_FIRST_ITEM);
867 break;
868 case KEY_END:
869 menu_driver(menu, REQ_LAST_ITEM);
870 break;
871 case KEY_UP:
872 menu_driver(menu, REQ_UP_ITEM);
873 break;
874 case KEY_DOWN:
875 menu_driver(menu, REQ_DOWN_ITEM);
876 break;
877 case KEY_PPAGE:
878 case CTRL('P'):
879 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
880 menu_driver(menu, REQ_FIRST_ITEM);
881 break;
882 case ' ':
883 case KEY_NPAGE:
884 case CTRL('N'):
885 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
886 menu_driver(menu, REQ_LAST_ITEM);
887 break;
888 case '\n':
889 tmp = (char *)item_description(current_item(menu));
890 goto done;
891 break;
892 case KEY_ESCAPE:
893 tmp = NULL;
894 goto done;
895 break;
896 default:
897 tmp = menu_pattern(menu);
899 if (tmp && tmp[strlen(tmp) - 1] != c) {
900 menu_driver(menu, REQ_CLEAR_PATTERN);
901 menu_driver(menu, c);
903 else {
904 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
905 menu_driver(menu, c);
908 break;
912 done:
913 unpost_menu(menu);
914 free_menu(menu);
916 for (i = 0; mitems[i]; i++)
917 free_item(mitems[i]);
919 del_panel(panel);
920 delwin(subw);
921 delwin(win);
922 return tmp;
925 static void add_custom_tags(TAG ***t)
927 int i;
929 if (!config.tag)
930 return;
932 for (i = 0; config.tag[i]; i++)
933 pgn_add_tag(t, config.tag[i]->name, config.tag[i]->value);
935 pgn_sort_tags(*t);
938 TAG **edit_tags(GAME g, BOARD b, int edit)
940 TAG **data = NULL;
941 struct tm tp;
942 unsigned char data_index = 0;
943 int n, lastindex = 0;
944 int len;
946 /* Edit the backup copy, not the original in case the save fails. */
947 for (n = 0; g.tag[n]; n++)
948 pgn_add_tag(&data, g.tag[n]->name, g.tag[n]->value);
950 data_index = pgn_tag_total(data);
952 while (1) {
953 WINDOW *win, *subw;
954 PANEL *panel;
955 ITEM **mitems = NULL;
956 MENU *menu;
957 int i;
958 char buf[76] = {0};
959 char *tmp = NULL;
960 int rows, cols;
961 int selected = -1;
962 char *mbuf = NULL;
963 int nlen = 0, vlen = 0;
965 data_index = pgn_tag_total(data);
967 for (i = 0; i < data_index; i++) {
968 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
970 if (data[i]->value) {
971 nlen = strlen(data[i]->name);
972 vlen = strlen(data[i]->value);
974 /* The +6 is for the menu padding. */
975 mitems[i] = new_item(data[i]->name,
976 (nlen + vlen + 6 >= MAX_VALUE_WIDTH)
977 ? PRESS_ENTER : data[i]->value);
979 else
980 mitems[i] = new_item(data[i]->name, UNKNOWN);
983 mitems[i] = NULL;
984 menu = new_menu(mitems);
985 scale_menu(menu, &rows, &cols);
987 /* +14 for the extra prompt info. */
988 if (cols < strlen(HELP_PROMPT) + 14)
989 cols = strlen(HELP_PROMPT) + 14;
991 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
992 set_menu_win(menu, win);
993 subw = derwin(win, rows, cols + 2, 2, 1);
994 set_menu_sub(menu, subw);
995 set_menu_fore(menu, A_REVERSE);
996 set_menu_grey(menu, A_NORMAL);
997 set_menu_mark(menu, NULL);
998 set_menu_pad(menu, '-');
999 set_menu_spacing(menu, 3, 0, 0);
1000 menu_opts_off(menu, O_NONCYCLIC);
1001 post_menu(menu);
1002 panel = new_panel(win);
1003 cbreak();
1004 noecho();
1005 nl();
1006 keypad(win, TRUE);
1007 set_menu_pattern(menu, mbuf);
1008 wbkgd(win, CP_MESSAGE_WINDOW);
1009 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1010 cols + 4, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
1012 while (1) {
1013 int c;
1014 TAG **tmppgn = NULL;
1015 char *newtag = NULL;
1017 if (set_current_item(menu, mitems[lastindex]) != E_OK) {
1018 lastindex = item_count(menu) - 1;
1019 continue;
1022 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1023 item_index(current_item(menu)) + 1, N_OF_N_STR,
1024 item_count(menu), HELP_PROMPT);
1025 draw_prompt(win, rows + 2, cols + 4, buf, CP_MESSAGE_PROMPT);
1027 update_panels();
1028 doupdate();
1030 c = wgetch(win);
1032 switch (c) {
1033 case CTRL('T'):
1034 add_custom_tags(&data);
1035 goto cleanup;
1036 break;
1037 case KEY_F(1):
1038 if (edit)
1039 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1040 else
1041 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1042 break;
1043 case CTRL('R'):
1044 if (!edit)
1045 break;
1047 selected = item_index(current_item(menu));
1049 if (selected <= 6) {
1050 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1051 goto cleanup;
1054 data_index = pgn_tag_total(data);
1056 for (i = 0; i < data_index; i++) {
1057 if (i == selected)
1058 continue;
1060 pgn_add_tag(&tmppgn, data[i]->name, data[i]->value);
1063 pgn_tag_free(data);
1064 data = NULL;
1066 for (i = 0; tmppgn[i]; i++)
1067 pgn_add_tag(&data, tmppgn[i]->name, tmppgn[i]->value);
1069 pgn_tag_free(tmppgn);
1070 goto cleanup;
1071 break;
1072 case CTRL('A'):
1073 if (!edit)
1074 break;
1076 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1077 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1078 == NULL)
1079 break;
1081 newtag[0] = toupper(newtag[0]);
1083 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1084 strlen(PRESS_ENTER)) {
1085 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1086 break;
1089 for (i = 0; i < data_index; i++) {
1090 if (strcasecmp(data[i]->name, newtag) == 0) {
1091 selected = i;
1092 goto gotitem;
1096 pgn_add_tag(&data, newtag, NULL);
1097 data_index = pgn_tag_total(data);
1098 selected = data_index - 1;
1099 goto gotitem;
1100 break;
1101 case KEY_HOME:
1102 menu_driver(menu, REQ_FIRST_ITEM);
1103 break;
1104 case KEY_END:
1105 menu_driver(menu, REQ_LAST_ITEM);
1106 break;
1107 case CTRL('F'):
1108 if (!edit)
1109 break;
1111 pgn_add_tag(&data, "FEN", pgn_game_to_fen(g, b));
1112 data_index = pgn_tag_total(data);
1113 selected = data_index - 1;
1114 goto gotitem;
1115 break;
1116 case KEY_NPAGE:
1117 case CTRL('N'):
1118 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
1119 menu_driver(menu, REQ_LAST_ITEM);
1120 break;
1121 case KEY_PPAGE:
1122 case CTRL('P'):
1123 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
1124 menu_driver(menu, REQ_FIRST_ITEM);
1125 break;
1126 case KEY_UP:
1127 menu_driver(menu, REQ_UP_ITEM);
1128 break;
1129 case KEY_DOWN:
1130 menu_driver(menu, REQ_DOWN_ITEM);
1131 break;
1132 case '\n':
1133 selected = item_index(current_item(menu));
1134 goto gotitem;
1135 break;
1136 case KEY_ESCAPE:
1137 cleanup(win, subw, panel, menu, mitems, NULL);
1138 goto done;
1139 break;
1140 default:
1141 tmp = menu_pattern(menu);
1143 if (tmp && tmp[strlen(tmp) - 1] != c) {
1144 menu_driver(menu, REQ_CLEAR_PATTERN);
1145 menu_driver(menu, c);
1147 else {
1148 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
1149 menu_driver(menu, c);
1152 break;
1155 lastindex = item_index(current_item(menu));
1158 gotitem:
1159 lastindex = selected;
1160 nlen = strlen(data[selected]->name) + 3;
1161 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1163 if (nlen > MAX_VALUE_WIDTH)
1164 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1165 else
1166 snprintf(buf, sizeof(buf), "%s \"%s\"",
1167 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1168 data[selected]->name);
1170 if (!edit) {
1171 if (strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1172 goto cleanup;
1174 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1175 goto cleanup;
1178 if (strcmp(data[selected]->name, "Date") == 0) {
1179 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1180 0, FIELD_TYPE_PGN_DATE);
1182 if (tmp) {
1183 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1184 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1185 goto cleanup;
1188 else
1189 goto cleanup;
1191 else if (strcmp(data[selected]->name, "Site") == 0) {
1192 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1193 country_codes, NULL, CTRL('t'), -1);
1195 if (!tmp)
1196 tmp = "?";
1198 else if (strcmp(data[selected]->name, "Round") == 0) {
1199 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1200 FIELD_TYPE_PGN_ROUND);
1202 if (!tmp) {
1203 if (gtotal > 1)
1204 tmp = "?";
1205 else
1206 tmp = "-";
1209 else if (strcmp(data[selected]->name, "Result") == 0) {
1210 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1211 0, -1);
1213 if (!tmp)
1214 tmp = "*";
1216 else {
1217 if (item_description(mitems[selected]) &&
1218 strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1219 tmp = NULL;
1220 else
1221 tmp = data[selected]->value;
1223 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1226 len = (tmp) ? strlen(tmp) + 1 : 1;
1227 data[selected]->value = Realloc(data[selected]->value, len);
1228 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1230 cleanup:
1231 cleanup(win, subw, panel, menu, mitems, NULL);
1234 done:
1235 if (!edit) {
1236 pgn_tag_free(data);
1237 return NULL;
1240 return data;
1243 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1244 * game index number.
1246 int save_pgn(const char *filename, int isfifo, int saveindex)
1248 FILE *fp;
1249 char *mode = NULL;
1250 int c;
1251 char buf[FILENAME_MAX];
1252 struct stat st;
1253 int i;
1254 char *command = NULL;
1255 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1257 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1258 if (stat(config.savedirectory, &st) == -1) {
1259 if (errno == ENOENT) {
1260 if (mkdir(config.savedirectory, 0755) == -1) {
1261 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1262 strerror(errno));
1263 return 1;
1266 else {
1267 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1268 strerror(errno));
1269 return 1;
1273 stat(config.savedirectory, &st);
1275 if (!S_ISDIR(st.st_mode)) {
1276 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1277 return 1;
1280 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1281 filename = buf;
1284 /* This is a hack to resume an existing game when more than one game is
1285 * available. Also resuming a saved game and a game from history.
1287 // FIXME: may not need this when a FEN tag is supported (by the engine).
1288 if (isfifo)
1289 mode = "w";
1290 else {
1291 if (access(filename, W_OK) == 0) {
1292 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1293 "%s \"%s\"", E_FILEEXISTS, filename);
1295 switch (c) {
1296 case 'a':
1297 if (pgn_is_compressed(filename)) {
1298 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1299 return 1;
1302 mode = "a";
1303 break;
1304 case 'o':
1305 mode = "w+";
1306 break;
1307 default:
1308 return 1;
1311 else
1312 mode = "a";
1315 if (command) {
1316 if ((fp = popen(command, "w")) == NULL) {
1317 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1318 return 1;
1321 else {
1322 if ((fp = fopen(filename, mode)) == NULL) {
1323 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1324 return 1;
1328 if (isfifo)
1329 pgn_write(fp, game[saveindex]);
1330 else {
1331 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1332 pgn_write(fp, game[i]);
1335 if (command)
1336 pclose(fp);
1337 else
1338 fclose(fp);
1340 if (!isfifo && saveindex == -1)
1341 strncpy(loadfile, filename, sizeof(loadfile));
1343 return 0;
1346 char *random_agony(GAME g)
1348 static int n;
1349 FILE *fp;
1350 char line[LINE_MAX];
1352 if (n == -1 || !config.agony || !curses_initialized ||
1353 (g.mode == MODE_HISTORY && !config.historyagony))
1354 return NULL;
1356 if (!agony) {
1357 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1358 n = -1;
1359 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1360 return NULL;
1363 while (!feof(fp)) {
1364 if (fscanf(fp, " %[^\n] ", line) == 1) {
1365 agony = Realloc(agony, (n + 2) * sizeof(char *));
1366 agony[n++] = strdup(trim(line));
1370 agony[n] = NULL;
1371 fclose(fp);
1373 if (agony[0] == NULL || !n) {
1374 n = -1;
1375 return NULL;
1379 return agony[random() % n];
1382 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1384 if (pgn_piece_to_int(piece) == ROOK && col == 7
1385 && row == 7 &&
1386 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1387 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1388 if (mod)
1389 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1390 return 1;
1392 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1393 && row == 7 &&
1394 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1395 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1396 if (mod)
1397 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1398 return 1;
1400 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1401 && row == 0 &&
1402 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1403 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1404 if (mod)
1405 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1406 return 1;
1408 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1409 && row == 0 &&
1410 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1411 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1412 if (mod)
1413 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1414 return 1;
1416 else if (pgn_piece_to_int(piece) == KING && col == 4
1417 && row == 7 &&
1418 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1419 TEST_FLAG(g->flags, GF_WK_CASTLE))
1421 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1422 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1423 if (mod) {
1424 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1425 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1426 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1427 else
1428 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1430 return 1;
1432 else if (pgn_piece_to_int(piece) == KING && col == 4
1433 && row == 0 &&
1434 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1435 TEST_FLAG(g->flags, GF_BK_CASTLE))
1437 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1438 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1439 if (mod) {
1440 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1441 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1442 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1443 else
1444 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1446 return 1;
1449 return 0;
1452 static void draw_board(GAME *g, int details)
1454 int row, col;
1455 int bcol = 0, brow = 0;
1456 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1457 int ncols = 0, offset = 1;
1458 unsigned coords_y = 8;
1460 for (row = 0; row < maxy; row++) {
1461 bcol = 0;
1463 for (col = 0; col < maxx; col++) {
1464 int attrwhich = -1;
1465 chtype attrs = 0;
1466 unsigned char piece;
1468 if (row == 0 || row == maxy - 2) {
1469 if (col == 0)
1470 mvwaddch(boardw, row, col,
1471 LINE_GRAPHIC((row) ?
1472 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1473 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1474 else if (col == maxx - 2)
1475 mvwaddch(boardw, row, col,
1476 LINE_GRAPHIC((row) ?
1477 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1478 ACS_URCORNER | CP_BOARD_GRAPHICS));
1479 else if (!(col % 4))
1480 mvwaddch(boardw, row, col,
1481 LINE_GRAPHIC((row) ?
1482 ACS_BTEE | CP_BOARD_GRAPHICS :
1483 ACS_TTEE | CP_BOARD_GRAPHICS));
1484 else {
1485 if (col != maxx - 1)
1486 mvwaddch(boardw, row, col,
1487 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1490 continue;
1493 if ((row % 2) && col == maxx - 1 && coords_y) {
1494 wattron(boardw, CP_BOARD_COORDS);
1495 mvwprintw(boardw, row, col, "%d", coords_y--);
1496 wattroff(boardw, CP_BOARD_COORDS);
1497 continue;
1500 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1501 if (!(row % 2))
1502 mvwaddch(boardw, row, col,
1503 LINE_GRAPHIC((col) ?
1504 ACS_RTEE | CP_BOARD_GRAPHICS :
1505 ACS_LTEE | CP_BOARD_GRAPHICS));
1506 else
1507 mvwaddch(boardw, row, col,
1508 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1510 continue;
1513 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1514 mvwaddch(boardw, row, col,
1515 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1516 continue;
1519 if (!(col % 4) && row != maxy - 1) {
1520 mvwaddch(boardw, row, col,
1521 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1522 continue;
1525 if ((row % 2)) {
1526 if ((col % 4)) {
1527 if (ncols++ == 8) {
1528 offset++;
1529 ncols = 1;
1532 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1533 && (offset % 2)))
1534 attrwhich = BLACK;
1535 else
1536 attrwhich = WHITE;
1538 if (config.validmoves && g->b[brow][bcol].valid) {
1539 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1540 CP_BOARD_MOVES_BLACK;
1542 else
1543 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1544 CP_BOARD_BLACK;
1546 if (row == ROWTOMATRIX(c_row) && col ==
1547 COLTOMATRIX(c_col)) {
1548 attrs = CP_BOARD_CURSOR;
1551 if (row == ROWTOMATRIX(sp.row) &&
1552 col == COLTOMATRIX(sp.col)) {
1553 attrs = CP_BOARD_SELECTED;
1556 if (row == maxy - 1)
1557 attrs = 0;
1559 mvwaddch(boardw, row, col, ' ' | attrs);
1561 if (row == maxy - 1)
1562 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1563 else {
1564 if (details && g->b[row / 2][bcol].enpassant)
1565 piece = 'x';
1566 else
1567 piece = g->b[row / 2][bcol].icon;
1569 if (details && castling_state(g, g->b, brow, bcol,
1570 piece, 0))
1571 attrs |= A_REVERSE;
1573 if (g->side == WHITE && isupper(piece))
1574 attrs |= A_BOLD;
1575 else if (g->side == BLACK && islower(piece))
1576 attrs |= A_BOLD;
1578 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1580 CLEAR_FLAG(attrs, A_BOLD);
1581 CLEAR_FLAG(attrs, A_REVERSE);
1584 waddch(boardw, ' ' | attrs);
1585 col += 2;
1586 bcol++;
1589 else {
1590 if (col != maxx - 1)
1591 mvwaddch(boardw, row, col,
1592 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1596 brow = row / 2;
1600 void invalid_move(int n, const char *m)
1602 if (curses_initialized)
1603 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1604 else
1605 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1608 /* Convert the selected piece to SAN format and validate it. */
1609 static char *board_to_san(GAME *g, BOARD b)
1611 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1612 int piece;
1613 int promo;
1614 BOARD oldboard;
1616 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1617 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1619 p = str;
1620 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1622 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1623 (sp.destrow == 1 && g->turn == BLACK))) {
1624 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1626 if (pgn_piece_to_int(promo) == -1)
1627 return NULL;
1629 p = str + strlen(str);
1630 *p++ = toupper(promo);
1631 *p = '\0';
1634 memcpy(oldboard, b, sizeof(BOARD));
1636 if ((p = pgn_a2a4tosan(g, b, str)) == NULL) {
1637 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
1638 memcpy(b, oldboard, sizeof(BOARD));
1639 return NULL;
1642 if (pgn_validate_move(g, b, p)) {
1643 invalid_move(gindex + 1, p);
1644 memcpy(b, oldboard, sizeof(BOARD));
1645 return NULL;
1648 return p;
1651 static int move_to_engine(GAME *g, BOARD b)
1653 char *p;
1655 if ((p = board_to_san(g, b)) == NULL)
1656 return 0;
1658 sp.row = sp.col = sp.icon = 0;
1660 if (noengine) {
1661 history_add(g, p);
1662 pgn_switch_turn(g);
1663 SET_FLAG(g->flags, GF_MODIFIED);
1664 update_all(*g);
1665 return 1;
1668 send_to_engine(g, "%s\n", p);
1669 return 1;
1672 static void update_clock(int n, int *h, int *m, int *s)
1674 *h = n / 3600;
1675 *m = (n % 3600) / 60;
1676 *s = (n % 3600) % 60;
1678 return;
1681 void update_status_window(GAME g)
1683 int i = 0;
1684 char *buf;
1685 char tmp[15], *engine, *mode;
1686 int w;
1687 int h, m, s;
1688 char *p;
1689 int maxy, maxx;
1690 int len;
1691 struct user_data_s *d = g.data;
1693 getmaxyx(statusw, maxy, maxx);
1694 w = maxx - 2 - 8;
1695 len = maxx - 2;
1696 buf = Malloc(len);
1698 *tmp = '\0';
1699 p = tmp;
1701 if (TEST_FLAG(g.flags, GF_DELETE)) {
1702 *p++ = '(';
1703 *p++ = 'x';
1704 i++;
1707 if (TEST_FLAG(g.flags, GF_PERROR)) {
1708 if (!i)
1709 *p++ = '(';
1710 else
1711 *p++ = '/';
1713 *p++ = '!';
1714 i++;
1717 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1718 if (!i)
1719 *p++ = '(';
1720 else
1721 *p++ = '/';
1723 *p++ = '*';
1724 i++;
1727 if (*tmp != '\0')
1728 *p++ = ')';
1730 *p = '\0';
1732 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1733 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1734 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1735 (*tmp) ? tmp : "");
1736 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1738 switch (g.mode) {
1739 case MODE_HISTORY:
1740 mode = MODE_HISTORY_STR;
1741 break;
1742 case MODE_EDIT:
1743 mode = MODE_EDIT_STR;
1744 break;
1745 case MODE_PLAY:
1746 mode = MODE_PLAY_STR;
1747 break;
1748 default:
1749 mode = UNKNOWN;
1750 break;
1753 snprintf(buf, len - 1, "%*s %s%s", 7, STATUS_MODE_STR, mode,
1754 (d && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) ? " (loop)" : "");
1755 mvwprintw(statusw, 4, 1, "%-*s", w, buf);
1757 if (d) {
1758 switch (d->status) {
1759 case ENGINE_THINKING:
1760 engine = ENGINE_THINKING_STR;
1761 break;
1762 case ENGINE_READY:
1763 engine = ENGINE_READY_STR;
1764 break;
1765 case ENGINE_INITIALIZING:
1766 engine = ENGINE_INITIALIZING_STR;
1767 break;
1768 case ENGINE_OFFLINE:
1769 engine = ENGINE_OFFLINE_STR;
1770 break;
1771 default:
1772 engine = UNKNOWN;
1773 break;
1776 else
1777 engine = ENGINE_OFFLINE_STR;
1779 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1780 wattron(statusw, CP_STATUS_ENGINE);
1781 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 update_all(GAME g)
1930 update_status_window(g);
1931 update_history_window(g);
1932 update_tag_window(g.tag);
1935 static void game_next_prev(GAME g, int n, int count)
1937 if (gtotal < 2)
1938 return;
1940 if (n == 1) {
1941 if (gindex + count > gtotal - 1) {
1942 if (count != 1)
1943 gindex = gtotal - 1;
1944 else
1945 gindex = 0;
1947 else
1948 gindex += count;
1950 else {
1951 if (gindex - count < 0) {
1952 if (count != 1)
1953 gindex = 0;
1954 else
1955 gindex = gtotal - 1;
1957 else
1958 gindex -= count;
1962 static void delete_game(int which)
1964 GAME *g = NULL;
1965 int gi = 0;
1966 int i;
1968 for (i = 0; i < gtotal; i++) {
1969 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1970 pgn_free(game[i]);
1971 continue;
1974 g = Realloc(g, (gi + 1) * sizeof(GAME));
1975 memcpy(&g[gi], &game[i], sizeof(GAME));
1976 g[gi].tag = game[i].tag;
1977 g[gi].history = game[i].history;
1978 g[gi].hp = game[i].hp;
1979 gi++;
1982 game = g;
1983 gtotal = gi;
1985 if (which != -1) {
1986 if (which + 1 >= gtotal)
1987 gindex = gtotal - 1;
1988 else
1989 gindex = which;
1991 else
1992 gindex = gtotal - 1;
1994 game[gindex].hp = game[gindex].history;
1997 static int find_move_exp(GAME g, const char *str, int init, int which,
1998 int count)
2000 int i;
2001 int ret;
2002 static regex_t r;
2003 static int firstrun = 1;
2004 char errbuf[255];
2005 int incr;
2006 int found;
2008 if (init) {
2009 if (!firstrun)
2010 regfree(&r);
2012 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2013 regerror(ret, &r, errbuf, sizeof(errbuf));
2014 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2015 return -1;
2018 firstrun = 1;
2021 incr = (which == 0) ? -1 : 1;
2023 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2024 if (i == g.hindex - 1)
2025 break;
2027 if (i >= history_total(g.hp))
2028 i = 0;
2029 else if (i < 0)
2030 i = history_total(g.hp) - 1;
2032 // FIXME RAV
2033 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2035 if (ret == 0) {
2036 if (count == ++found) {
2037 return i + 1;
2040 else {
2041 if (ret != REG_NOMATCH) {
2042 regerror(ret, &r, errbuf, sizeof(errbuf));
2043 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2044 return -1;
2049 return -1;
2052 static int toggle_delete_flag(int n)
2054 int i, x;
2056 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2058 for (i = x = 0; i < gtotal; i++) {
2059 if (TEST_FLAG(game[i].flags, GF_DELETE))
2060 x++;
2063 if (x == gtotal) {
2064 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2065 CLEAR_FLAG(game[n].flags, GF_DELETE);
2066 return 1;
2069 return 0;
2072 static void edit_save_tags(GAME *g)
2074 TAG **t;
2076 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2077 return;
2079 pgn_tag_free(g->tag);
2080 g->tag = t;
2081 SET_FLAG(g->flags, GF_MODIFIED);
2082 pgn_sort_tags(g->tag);
2085 static int find_game_exp(char *str, int which, int count)
2087 char *nstr = NULL, *exp = NULL;
2088 regex_t nexp, vexp;
2089 int ret = -1;
2090 int g = 0;
2091 char buf[255], *tmp;
2092 char errbuf[255];
2093 int found = 0;
2094 int incr = (which == 0) ? -(1) : 1;
2096 strncpy(buf, str, sizeof(buf));
2097 tmp = buf;
2099 if (strstr(tmp, ":") != NULL) {
2100 nstr = strsep(&tmp, ":");
2102 if ((ret = regcomp(&nexp, nstr,
2103 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2104 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2105 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2106 ret = g = -1;
2107 goto cleanup;
2111 exp = tmp;
2113 if (exp == NULL)
2114 goto cleanup;
2116 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2117 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2118 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2119 ret = -1;
2120 goto cleanup;
2123 ret = -1;
2125 for (g = gindex + incr, found = 0; ; g += incr) {
2126 int t;
2128 if (g == gindex)
2129 break;
2131 if (g == gtotal)
2132 g = 0;
2133 else if (g < 0)
2134 g = gtotal - 1;
2136 for (t = 0; game[g].tag[t]; t++) {
2137 if (nstr) {
2138 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2139 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2140 if (count == ++found) {
2141 ret = g;
2142 goto cleanup;
2147 else {
2148 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2149 if (count == ++found) {
2150 ret = g;
2151 goto cleanup;
2157 ret = -1;
2160 cleanup:
2161 if (nstr)
2162 regfree(&nexp);
2164 if (g != -1)
2165 regfree(&vexp);
2167 return ret;
2171 * Updates the notification line in the status window then refreshes the
2172 * status window.
2174 void update_status_notify(GAME g, char *fmt, ...)
2176 va_list ap;
2177 #ifdef HAVE_VASPRINTF
2178 char *line;
2179 #else
2180 char line[COLS];
2181 #endif
2183 if (!fmt) {
2184 if (status.notify) {
2185 free(status.notify);
2186 status.notify = NULL;
2188 if (curses_initialized)
2189 update_status_window(g);
2192 return;
2195 va_start(ap, fmt);
2196 #ifdef HAVE_VASPRINTF
2197 vasprintf(&line, fmt, ap);
2198 #else
2199 vsnprintf(line, sizeof(line), fmt, ap);
2200 #endif
2201 va_end(ap);
2203 if (status.notify)
2204 free(status.notify);
2206 status.notify = strdup(line);
2208 #ifdef HAVE_VASPRINTF
2209 free(line);
2210 #endif
2211 if (curses_initialized)
2212 update_status_window(g);
2215 static void switch_side(GAME *g)
2217 g->side = (g->side == WHITE) ? BLACK : WHITE;
2220 int rav_next_prev(GAME *g, BOARD b, int n)
2222 // Next RAV.
2223 if (n) {
2224 if (g->hp[g->hindex]->rav == NULL)
2225 return 1;
2227 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2228 g->rav[g->ravlevel].hp = g->hp;
2229 g->rav[g->ravlevel].flags = g->flags;
2230 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2231 g->rav[g->ravlevel].hindex = g->hindex;
2232 g->hp = g->hp[g->hindex]->rav;
2233 g->hindex = 0;
2234 g->ravlevel++;
2235 return 0;
2238 if (g->ravlevel - 1 < 0)
2239 return 1;
2241 // Previous RAV.
2242 g->ravlevel--;
2243 pgn_init_fen_board(g, b, g->rav[g->ravlevel].fen);
2244 free(g->rav[g->ravlevel].fen);
2245 g->hp = g->rav[g->ravlevel].hp;
2246 g->flags = g->rav[g->ravlevel].flags;
2247 g->hindex = g->rav[g->ravlevel].hindex;
2248 return 0;
2251 static void draw_window_decor()
2253 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2254 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2255 wbkgd(boardw, CP_BOARD_WINDOW);
2256 wbkgd(statusw, CP_STATUS_WINDOW);
2257 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2258 CP_STATUS_TITLE, CP_STATUS_BORDER);
2259 wbkgd(tagw, CP_TAG_WINDOW);
2260 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2261 CP_TAG_BORDER);
2262 wbkgd(historyw, CP_HISTORY_WINDOW);
2263 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2264 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2267 static void do_window_resize()
2269 if (LINES < 24 || COLS < 80)
2270 return;
2272 resizeterm(LINES, COLS);
2273 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2274 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2275 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2276 wmove(historyw, 0, 0);
2277 wclrtobot(historyw);
2278 wmove(tagw, 0, 0);
2279 wclrtobot(tagw);
2280 wmove(statusw, 0, 0);
2281 wclrtobot(statusw);
2282 draw_window_decor();
2283 update_all(game[gindex]);
2286 static void historymode_keys(int);
2287 static void playmode_keys(int c)
2289 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2290 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2291 chtype p;
2292 int w, x, y, z;
2293 char *tmp;
2294 struct user_data_s *d = game[gindex].data;
2296 switch (c) {
2297 case 'o':
2298 if (!d)
2299 break;
2301 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2302 update_all(game[gindex]);
2303 break;
2304 case '|':
2305 if (!d)
2306 break;
2308 if (d->status == ENGINE_OFFLINE)
2309 break;
2311 x = d->status;
2313 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2314 send_to_engine(&game[gindex], "%s\n", tmp);
2315 d->status = x;
2316 break;
2317 case '\015':
2318 case '\n':
2319 pushkey = keycount = 0;
2320 update_status_notify(game[gindex], NULL);
2322 if (!noengine && (!d || d->status == ENGINE_THINKING)) {
2323 beep();
2324 break;
2327 if (!sp.icon)
2328 break;
2330 sp.destrow = c_row;
2331 sp.destcol = c_col;
2333 if (editmode) {
2334 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2335 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2336 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2337 sp.icon = sp.row = sp.col = 0;
2338 break;
2341 if (move_to_engine(&game[gindex], game[gindex].b)) {
2342 if (config.validmoves)
2343 board_reset_valid_moves(game[gindex].b);
2345 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2346 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2347 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2351 break;
2352 case ' ':
2353 if (!noengine && (!d || d->status == ENGINE_OFFLINE) && !editmode) {
2354 if (start_chess_engine(&game[gindex]) < 0) {
2355 sp.icon = 0;
2356 break;
2360 if (!editmode)
2361 wtimeout(boardw, 70);
2363 if (sp.icon || (!editmode && d && d->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 || d->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 // Global and other keys.
2626 static int globalkeys(int c)
2628 static char gameexp[255] = {0};
2629 FILE *fp;
2630 char *tmp, *p;
2631 int n, i;
2632 char tfile[FILENAME_MAX];
2633 struct user_data_s *d = game[gindex].data;
2635 switch (c) {
2636 case 'h':
2637 if (game[gindex].mode != MODE_HISTORY) {
2638 if (!history_total(game[gindex].hp) ||
2639 (d && d->status == ENGINE_THINKING))
2640 return 1;
2642 game[gindex].mode = MODE_HISTORY;
2643 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2644 return 1;
2647 // FIXME
2648 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2649 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2650 return 1;
2653 // FIXME Resuming from previous history could append to a RAV.
2654 if (game[gindex].hindex != history_total(game[gindex].hp)) {
2655 if (!pushkey) {
2656 if ((c = message(NULL, YESNO, "%s",
2657 GAME_RESUME_HISTORY_TEXT)) != 'y')
2658 return 1;
2661 else {
2662 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2663 return 1;
2666 if (!noengine && (!d || d->status == ENGINE_OFFLINE)) {
2667 if (start_chess_engine(&game[gindex]) < 0)
2668 return 1;
2670 pushkey = 'h';
2671 return 1;
2674 pushkey = 0;
2675 oldhistorytotal = history_total(game[gindex].hp);
2676 game[gindex].mode = MODE_PLAY;
2677 update_all(game[gindex]);
2678 return 1;
2679 case '>':
2680 case '<':
2681 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2682 keycount : 1);
2684 if (delete_count) {
2685 markend = gindex;
2686 pushkey = 'x';
2687 delete_count = 0;
2690 if (game[gindex].mode != MODE_EDIT) {
2691 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2692 update_cursor(game[gindex], game[gindex].hindex);
2694 update_all(game[gindex]);
2695 update_tag_window(game[gindex].tag);
2696 return 1;
2697 // Not sure whether to keep these.
2698 case '!': c_row = 1; return 1;
2699 case '@': c_row = 2; return 1;
2700 case '#': c_row = 3; return 1;
2701 case '$': c_row = 4; return 1;
2702 case '%': c_row = 5; return 1;
2703 case '^': c_row = 6; return 1;
2704 case '&': c_row = 7; return 1;
2705 case '*': c_row = 8; return 1;
2706 case 'A': c_col = 1; return 1;
2707 case 'B': c_col = 2; return 1;
2708 case 'C': c_col = 3; return 1;
2709 case 'D': c_col = 4; return 1;
2710 case 'E': c_col = 5; return 1;
2711 case 'F': c_col = 6; return 1;
2712 case 'G': c_col = 7; return 1;
2713 case 'H': c_col = 8; return 1;
2714 case '}':
2715 case '{':
2716 case '?':
2717 if (gtotal < 2)
2718 return 1;
2720 if (!*gameexp || c == '?') {
2721 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2722 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2723 NULL, 0, -1)) == NULL)
2724 return 1;
2726 strncpy(gameexp, tmp, sizeof(gameexp));
2729 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2730 ? keycount : 1)) ==
2732 return 1;
2734 gindex = n;
2736 if (history_total(game[gindex].hp))
2737 game[gindex].mode = MODE_HISTORY;
2739 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2740 update_all(game[gindex]);
2741 update_tag_window(game[gindex].tag);
2742 return 1;
2743 case 'J':
2744 if (gtotal < 2)
2745 return 1;
2747 /* FIXME field validation
2748 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2749 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2750 == NULL)
2751 return 1;
2754 if (!keycount) {
2755 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2756 NULL, NULL, 0, -1)) == NULL)
2757 return 1;
2759 if (!isinteger(tmp))
2760 return 1;
2762 i = atoi(tmp);
2764 else
2765 i = keycount;
2767 if (--i > gtotal - 1 || i < 0)
2768 return 1;
2770 gindex = i;
2771 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2772 update_cursor(game[gindex], game[gindex].hindex);
2773 update_all(game[gindex]);
2774 update_tag_window(game[gindex].tag);
2775 return 1;
2776 case 'x':
2777 pushkey = 0;
2779 if (gtotal < 2)
2780 return 1;
2782 if (keycount && !delete_count) {
2783 markstart = gindex;
2784 delete_count = 1;
2785 update_status_notify(game[gindex], "%s (delete)",
2786 status.notify);
2787 return 1;
2790 if (markstart >= 0 && markend >= 0) {
2791 if (markstart > markend) {
2792 i = markstart;
2793 markstart = markend;
2794 markend = i;
2797 for (i = markstart; i <= markend; i++) {
2798 if (toggle_delete_flag(i))
2799 return 1;
2802 else {
2803 if (toggle_delete_flag(gindex))
2804 return 1;
2807 markstart = markend = -1;
2808 update_status_window(game[gindex]);
2809 return 1;
2810 case 'X':
2811 if (gtotal < 2) {
2812 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2813 return 1;
2816 tmp = NULL;
2818 for (i = n = 0; i < gtotal; i++) {
2819 if (TEST_FLAG(game[i].flags, GF_DELETE))
2820 n++;
2823 if (!n)
2824 tmp = GAME_DELETE_GAME_TEXT;
2825 else {
2826 if (n == gtotal) {
2827 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2828 return 1;
2831 tmp = GAME_DELETE_ALL_TEXT;
2834 if (config.deleteprompt) {
2835 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2836 return 1;
2839 delete_game((!n) ? gindex : -1);
2841 if (history_total(game[gindex].hp))
2842 game[gindex].mode = MODE_HISTORY;
2844 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2845 update_all(game[gindex]);
2846 update_tag_window(game[gindex].tag);
2847 return 1;
2848 case 'T':
2849 edit_save_tags(&game[gindex]);
2850 update_all(game[gindex]);
2851 update_tag_window(game[gindex].tag);
2852 return 1;
2853 case 't':
2854 edit_tags(game[gindex], game[gindex].b, 0);
2855 return 1;
2856 case 'r':
2857 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2858 BROWSER_PROMPT, browse_directory, NULL, '\t',
2859 -1)) == NULL)
2860 return 1;
2862 if ((tmp = word_expand(tmp)) == NULL)
2863 break;
2865 if ((fp = pgn_open(tmp)) == NULL) {
2866 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2867 return 1;
2870 if (pgn_parse(fp))
2871 return 1;
2873 strncpy(loadfile, tmp, sizeof(loadfile));
2875 if (history_total(game[gindex].hp))
2876 game[gindex].mode = MODE_HISTORY;
2878 history_update_board(&game[gindex], game[gindex].b, history_total(game[gindex].hp));
2879 update_all(game[gindex]);
2880 update_tag_window(game[gindex].tag);
2881 return 1;
2882 case 'S':
2883 case 's':
2884 i = -1;
2886 if (gtotal > 1) {
2887 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2888 GAME_SAVE_MULTI_TEXT);
2890 if (n == 'c')
2891 i = gindex;
2892 else if (n == 'a')
2893 i = -1;
2894 else {
2895 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2896 return 1;
2900 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2901 BROWSER_PROMPT, browse_directory, NULL,
2902 '\t', -1)) == NULL) {
2903 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2904 return 1;
2907 if ((tmp = word_expand(tmp)) == NULL)
2908 break;
2910 if (pgn_is_compressed(tmp)) {
2911 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2912 tmp = tfile;
2914 else {
2915 if ((p = strchr(tmp, '.')) != NULL) {
2916 if (strcmp(p, ".pgn") != 0) {
2917 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2918 tmp = tfile;
2921 else {
2922 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2923 tmp = tfile;
2927 if (save_pgn(tmp, 0, i)) {
2928 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
2929 return 1;
2932 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
2933 update_all(game[gindex]);
2934 return 1;
2935 case KEY_F(1):
2936 n = 0;
2938 while (n != 'q') {
2939 n = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
2940 mainhelp);
2942 switch (n) {
2943 case 'h':
2944 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
2945 return 1;
2946 case 'p':
2947 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
2948 return 1;
2949 case 'e':
2950 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
2951 return 1;
2952 case 'g':
2953 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
2954 return 1;
2955 default:
2956 n = 'q';
2957 return 1;
2961 return 1;
2962 case 'n':
2963 case 'N':
2964 if (c == 'N') {
2965 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
2966 return 1;
2969 if (c == 'n') {
2970 pgn_new_game();
2971 add_custom_tags(&game[gindex].tag);
2973 else {
2974 pgn_parse(NULL);
2975 add_custom_tags(&game[gindex].tag);
2976 pgn_init_board(game[gindex].b);
2979 game[gindex].mode = MODE_PLAY;
2980 c_row = (game[gindex].side == WHITE) ? 2 : 7;
2981 c_col = 4;
2983 if (!noengine && (!d || d->status == ENGINE_OFFLINE)) {
2984 if (start_chess_engine(&game[gindex]) < 0)
2985 return 1;
2988 send_to_engine(&game[gindex], "\nnew\n");
2989 update_status_notify(game[gindex], NULL);
2990 update_all(game[gindex]);
2991 update_tag_window(game[gindex].tag);
2992 return 1;
2993 case CTRL('L'):
2994 endwin();
2995 keypad(boardw, TRUE);
2996 update_panels();
2997 doupdate();
2998 return 1;
2999 case KEY_ESCAPE:
3000 sp.icon = sp.row = sp.col = 0;
3001 markend = markstart = 0;
3003 if (keycount) {
3004 keycount = 0;
3005 update_status_notify(game[gindex], NULL);
3008 if (config.validmoves)
3009 board_reset_valid_moves(game[gindex].b);
3011 return 1;
3012 case '0' ... '9':
3013 n = c - '0';
3015 if (keycount)
3016 keycount = keycount * 10 + n;
3017 else
3018 keycount = n;
3020 update_status_notify(game[gindex], "Repeat %i", keycount);
3021 return -1;
3022 case KEY_UP:
3023 if (game[gindex].mode == MODE_HISTORY)
3024 return 0;
3026 if (keycount) {
3027 c_row += keycount;
3028 pushkey = '\n';
3030 else
3031 c_row++;
3033 if (c_row > 8)
3034 c_row = 1;
3036 return 1;
3037 case KEY_DOWN:
3038 if (game[gindex].mode == MODE_HISTORY)
3039 return 0;
3041 if (keycount) {
3042 c_row -= keycount;
3043 pushkey = '\n';
3044 update_status_notify(game[gindex], NULL);
3046 else
3047 c_row--;
3049 if (c_row < 1)
3050 c_row = 8;
3052 return 1;
3053 case KEY_LEFT:
3054 if (game[gindex].mode == MODE_HISTORY)
3055 return 0;
3057 if (keycount) {
3058 c_col -= keycount;
3059 pushkey = '\n';
3061 else
3062 c_col--;
3064 if (c_col < 1)
3065 c_col = 8;
3067 return 1;
3068 case KEY_RIGHT:
3069 if (game[gindex].mode == MODE_HISTORY)
3070 return 0;
3072 if (keycount) {
3073 c_col += keycount;
3074 pushkey = '\n';
3076 else
3077 c_col++;
3079 if (c_col > 8)
3080 c_col = 1;
3082 return 1;
3083 case 'e':
3084 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3085 MODE_PLAY)
3086 return 1;
3088 // Don't edit a running game (for now).
3089 if (history_total(game[gindex].hp))
3090 return 1;
3092 if (game[gindex].mode != MODE_EDIT) {
3093 pgn_init_fen_board(&game[gindex], game[gindex].b, NULL);
3094 board_details++;
3095 game[gindex].mode = MODE_EDIT;
3096 update_all(game[gindex]);
3097 return 1;
3100 board_details--;
3101 pgn_add_tag(&game[gindex].tag, "FEN",
3102 pgn_game_to_fen(game[gindex], game[gindex].b));
3103 pgn_add_tag(&game[gindex].tag, "SetUp", "1");
3104 pgn_sort_tags(game[gindex].tag);
3105 game[gindex].mode = MODE_PLAY;
3106 update_all(game[gindex]);
3107 return 1;
3108 case 'Q':
3109 quit = 1;
3110 return 1;
3111 case KEY_RESIZE:
3112 do_window_resize();
3113 return 1;
3114 #ifdef DEBUG
3115 case 'O':
3116 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3117 return 1;
3118 #endif
3119 case 0:
3120 default:
3121 break;
3124 return 0;
3127 void game_loop()
3129 int error_recover = 0;
3131 c_row = 2, c_col = 5;
3132 gindex = gtotal - 1;
3134 if (history_total(game[gindex].hp))
3135 game[gindex].mode = MODE_HISTORY;
3136 else
3137 game[gindex].mode = MODE_PLAY;
3139 if (game[gindex].mode == MODE_HISTORY) {
3140 history_update_board(&game[gindex], game[gindex].b,
3141 history_total(game[gindex].hp));
3142 update_cursor(game[gindex], game[gindex].hindex);
3145 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3146 movestep = 2;
3147 paused = 1; //FIXME clock
3148 flushinp();
3149 update_all(game[gindex]);
3150 update_tag_window(game[gindex].tag);
3152 while (!quit) {
3153 int c = 0;
3154 int n = 0, i;
3155 char fdbuf[8192] = {0};
3156 int len;
3157 struct timeval tv = {0, 0};
3158 fd_set rfds, wfds;
3159 struct user_data_s *d = NULL;
3161 FD_ZERO(&rfds);
3162 FD_ZERO(&wfds);
3164 for (i = 0; i < gtotal; i++) {
3165 if (game[i].data) {
3166 d = game[i].data;
3168 if (d->fd[ENGINE_IN_FD] > 2) {
3169 if (d->fd[ENGINE_IN_FD] > n)
3170 n = d->fd[ENGINE_IN_FD];
3172 FD_SET(d->fd[ENGINE_IN_FD], &rfds);
3175 if (d->fd[ICS_FD] > 2) {
3176 if (d->fd[ICS_FD] > n)
3177 n = d->fd[ICS_FD];
3179 FD_SET(d->fd[ICS_FD], &rfds);
3180 FD_SET(d->fd[ICS_FD], &wfds);
3185 if (n) {
3186 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3187 for (i = 0; i < gtotal; i++) {
3188 if (game[i].data) {
3189 d = game[i].data;
3191 if (FD_ISSET(d->fd[ENGINE_IN_FD], &rfds)) {
3192 len = read(d->fd[ENGINE_IN_FD], fdbuf,
3193 sizeof(fdbuf));
3195 if (len == -1) {
3196 if (errno != EAGAIN) {
3197 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
3198 ++error_recover, strerror(errno));
3199 continue;
3202 else {
3203 if (len) {
3204 parse_engine_output(&game[gindex], fdbuf);
3205 update_all(game[gindex]);
3212 else {
3213 if (n == -1)
3214 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3215 else {
3216 /* timeout */
3221 error_recover = 0;
3222 draw_board(&game[gindex], board_details);
3223 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3225 if (!paused) {
3228 update_panels();
3229 doupdate();
3231 if (pushkey)
3232 c = pushkey;
3233 else {
3234 if ((c = wgetch(boardw)) == ERR)
3235 continue;
3238 if (!keycount && status.notify)
3239 update_status_notify(game[gindex], NULL);
3242 if ((n = globalkeys(c)) == 1) {
3243 keycount = 0;
3244 continue;
3246 else if (n == -1)
3247 continue;
3249 switch (game[gindex].mode) {
3250 case MODE_EDIT:
3251 editmode_keys(c);
3252 break;
3253 case MODE_PLAY:
3254 playmode_keys(c);
3255 break;
3256 case MODE_HISTORY:
3257 historymode_keys(c);
3258 break;
3259 default:
3260 break;
3263 keycount = 0;
3267 void usage(const char *pn, int ret)
3269 fprintf((ret) ? stderr : stdout, "%s",
3270 "Usage: cboard [-hvNE] [-VtRS] [-p <file>]\n"
3271 " -p Load PGN file.\n"
3272 " -V Validate a game file.\n"
3273 " -S Validate and output a PGN formatted game.\n"
3274 " -R Like -S but write a reduced PGN formatted game.\n"
3275 " -t Also write custom PGN tags from config file.\n"
3276 " -N Don't enable the chess engine (two human players).\n"
3277 " -E Stop processing on file parsing error (overrides config).\n"
3278 " -v Version information.\n"
3279 " -h This help text.\n");
3281 exit(ret);
3284 static void cleanup_all_games()
3286 int i;
3288 for (i = 0; i < gtotal; i++) {
3289 struct user_data_s *d;
3291 if (game[i].data) {
3292 stop_engine(&game[i]);
3293 d = game[i].data;
3295 if (d->fd[ICS_FD] > 2)
3296 close(d->fd[ICS_FD]);
3298 free(game[i].data);
3303 void cleanup_all()
3305 cleanup_all_games();
3306 pgn_free_all();
3307 del_panel(boardp);
3308 del_panel(historyp);
3309 del_panel(statusp);
3310 del_panel(tagp);
3311 delwin(boardw);
3312 delwin(historyw);
3313 delwin(statusw);
3314 delwin(tagw);
3315 endwin();
3318 void catch_signal(int which)
3320 switch (which) {
3321 case SIGINT:
3322 case SIGPIPE:
3323 if (which == SIGPIPE && quit)
3324 break;
3326 if (which == SIGPIPE)
3327 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3329 cleanup_all();
3330 exit(EXIT_FAILURE);
3331 break;
3332 case SIGSTOP:
3333 savetty();
3334 break;
3335 case SIGCONT:
3336 resetty();
3337 keypad(boardw, TRUE);
3338 curs_set(0);
3339 cbreak();
3340 noecho();
3341 break;
3342 default:
3343 break;
3347 static void set_defaults()
3349 filetype = NO_FILE;
3350 set_config_defaults();
3353 int main(int argc, char *argv[])
3355 int opt;
3356 struct stat st;
3357 char buf[FILENAME_MAX];
3358 char datadir[FILENAME_MAX];
3359 int ret = EXIT_SUCCESS;
3360 int validate_only = 0, validate_and_write = 0, reduced = 0;
3361 int write_custom_tags = 0;
3362 FILE *fp;
3364 if ((config.pwd = getpwuid(getuid())) == NULL)
3365 err(EXIT_FAILURE, "getpwuid()");
3367 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3368 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3369 config.ccfile = strdup(buf);
3370 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3371 config.nagfile = strdup(buf);
3372 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3373 config.agonyfile = strdup(buf);
3374 snprintf(buf, sizeof(buf), "%s/config", datadir);
3375 config.configfile = strdup(buf);
3376 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3377 config.fifo = strdup(buf);
3379 if (stat(datadir, &st) == -1) {
3380 if (errno == ENOENT) {
3381 if (mkdir(datadir, 0755) == -1)
3382 err(EXIT_FAILURE, "%s", datadir);
3384 else
3385 err(EXIT_FAILURE, "%s", datadir);
3387 stat(datadir, &st);
3390 if (!S_ISDIR(st.st_mode))
3391 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3393 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3394 if (mkfifo(config.fifo, 0600) == -1)
3395 err(EXIT_FAILURE, "%s", config.fifo);
3398 set_defaults();
3400 while ((opt = getopt(argc, argv, "ENVtSRhp:v")) != -1) {
3401 switch (opt) {
3402 case 't':
3403 write_custom_tags = 1;
3404 break;
3405 case 'E':
3406 config.stoponerror = 1;
3407 break;
3408 case 'N':
3409 noengine = 1;
3410 break;
3411 case 'R':
3412 reduced = 1;
3413 case 'S':
3414 validate_and_write = 1;
3415 case 'V':
3416 validate_only = 1;
3417 break;
3418 case 'v':
3419 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3420 COPYRIGHT);
3421 exit(EXIT_SUCCESS);
3422 case 'p':
3423 filetype = PGN_FILE;
3424 strncpy(loadfile, optarg, sizeof(loadfile));
3425 break;
3426 case 'h':
3427 default:
3428 usage(argv[0], EXIT_SUCCESS);
3432 if ((validate_only || validate_and_write) && !*loadfile)
3433 usage(argv[0], EXIT_FAILURE);
3435 if (access(config.configfile, R_OK) == 0)
3436 parse_rcfile(config.configfile);
3438 signal(SIGPIPE, catch_signal);
3439 signal(SIGCONT, catch_signal);
3440 signal(SIGSTOP, catch_signal);
3441 signal(SIGINT, catch_signal);
3443 srandom(getpid());
3445 switch (filetype) {
3446 case PGN_FILE:
3447 if ((fp = pgn_open(loadfile)) == NULL)
3448 err(EXIT_FAILURE, "%s", loadfile);
3450 ret = pgn_parse(fp);
3451 break;
3452 case FEN_FILE:
3453 //ret = parse_fen_file(loadfile);
3454 break;
3455 case EPD_FILE: // Not implemented.
3456 case NO_FILE:
3457 default:
3458 // No file specified. Empty game.
3459 ret = pgn_parse(NULL);
3460 add_custom_tags(&game[gindex].tag);
3461 break;
3464 if (validate_only || validate_and_write) {
3465 if (validate_and_write) {
3466 int i;
3468 for (i = 0; i < gtotal; i++) {
3469 if (write_custom_tags)
3470 add_custom_tags(&game[i].tag);
3472 pgn_write(stdout, game[i]);
3476 pgn_free_all();
3477 exit(ret);
3479 else if (ret)
3480 exit(ret);
3482 if (initscr() == NULL)
3483 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3484 else
3485 curses_initialized = 1;
3487 if (LINES < 24 || COLS < 80) {
3488 endwin();
3489 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3492 if (has_colors() == TRUE && start_color() == OK)
3493 init_color_pairs();
3495 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3496 boardp = new_panel(boardw);
3497 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3498 COLS - HISTORY_WIDTH);
3499 historyp = new_panel(historyw);
3500 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3501 statusp = new_panel(statusw);
3502 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3503 tagp = new_panel(tagw);
3504 keypad(boardw, TRUE);
3505 // leaveok(boardw, TRUE);
3506 leaveok(tagw, TRUE);
3507 leaveok(statusw, TRUE);
3508 leaveok(historyw, TRUE);
3509 curs_set(0);
3510 cbreak();
3511 noecho();
3512 draw_window_decor();
3513 game_loop();
3514 cleanup_all();
3515 exit(EXIT_SUCCESS);