Fix deleting games with a count.
[cboard.git] / src / cboard.c
blobacb634e7dc8ec6dab6aea49d1f3c30c8c5e84c0c
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <time.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_WORDEXP_H
40 #include <wordexp.h>
41 #endif
43 #ifdef HAVE_DIRENT_H
44 #include <dirent.h>
45 #endif
47 #ifdef HAVE_MENU_H
48 #include <menu.h>
49 #endif
51 #ifdef HAVE_REGEX_H
52 #include <regex.h>
53 #endif
55 #include "chess.h"
56 #include "conf.h"
57 #include "window.h"
58 #include "colors.h"
59 #include "input.h"
60 #include "misc.h"
61 #include "engine.h"
62 #include "rcfile.h"
63 #include "strings.h"
64 #include "common.h"
65 #include "cboard.h"
67 #ifdef DEBUG
68 #include "debug.h"
69 #endif
71 #ifdef WITH_DMALLOC
72 #include <dmalloc.h>
73 #endif
75 static char *str_etc(const char *str, int maxlen, int rev)
77 int len = strlen(str);
78 static char buf[80], *p = buf;
79 int i;
81 strncpy(buf, str, sizeof(buf));
83 if (len > maxlen) {
84 if (rev) {
85 p = buf;
86 *p++ = '.';
87 *p++ = '.';
88 *p++ = '.';
90 for (i = 0; i < maxlen + 3; i++)
91 *p++ = buf[(len - maxlen) + i + 3];
93 else {
94 p = buf + maxlen - 4;
95 *p++ = '.';
96 *p++ = '.';
97 *p++ = '.';
100 *p = '\0';
103 return buf;
106 void update_cursor(GAME g, int idx)
108 char *p;
109 int len;
110 int t = pgn_history_total(g.hp);
113 * If not deincremented then r and c would be the next move.
115 idx--;
117 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
118 c_row = 2, c_col = 5;
119 return;
122 p = g.hp[idx]->move;
123 len = strlen(p);
125 if (*p == 'O') {
126 if (len <= 4)
127 c_col = 7;
128 else
129 c_col = 3;
131 c_row = (g.turn == WHITE) ? 1 : 8;
132 return;
135 p += len;
137 while (!isdigit(*p))
138 p--;
140 c_row = ROWTOINT(*p--);
141 c_col = COLTOINT(*p);
144 static int init_nag()
146 FILE *fp;
147 char line[LINE_MAX];
148 int i = 0;
150 if ((fp = fopen(config.nagfile, "r")) == NULL) {
151 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
152 return 1;
155 while (!feof(fp)) {
156 if (fscanf(fp, " %[^\n] ", line) == 1) {
157 nags = Realloc(nags, (i + 2) * sizeof(struct nag_s));
158 nags[i].line = strdup(line);
159 i++;
163 if (nags)
164 nags[i].line = NULL;
165 return 0;
168 char *history_edit_nag(void *arg)
170 WINDOW *win, *subw;
171 PANEL *panel;
172 ITEM **mitems = NULL;
173 MENU *menu;
174 int i = 0, n;
175 int itemcount = 0;
176 int rows, cols;
177 char *mbuf = NULL;
178 HISTORY *anno = (HISTORY *)arg;
180 if (!nags) {
181 if (init_nag())
182 return NULL;
185 i = 0;
186 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
187 mitems[i++] = new_item(NONE, NULL);
189 for (n = 0; nags[n].line; n++, i++) {
190 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
191 mitems[i] = new_item(nags[n].line, NULL);
194 mitems[i] = NULL;
195 menu = new_menu(mitems);
196 scale_menu(menu, &rows, &cols);
198 win = newwin(rows + 4, cols + 2, CALCPOSY(rows) - 2, CALCPOSX(cols));
199 set_menu_win(menu, win);
200 subw = derwin(win, rows, cols, 2, 1);
201 set_menu_sub(menu, subw);
202 set_menu_fore(menu, A_REVERSE);
203 set_menu_grey(menu, A_NORMAL);
204 set_menu_mark(menu, NULL);
205 set_menu_spacing(menu, 0, 0, 0);
206 menu_opts_off(menu, O_NONCYCLIC|O_SHOWDESC|O_ONEVALUE);
207 post_menu(menu);
208 panel = new_panel(win);
209 cbreak();
210 noecho();
211 keypad(win, TRUE);
212 set_menu_pattern(menu, mbuf);
213 wbkgd(win, CP_MESSAGE_WINDOW);
214 draw_window_title(win, NAG_EDIT_TITLE, cols + 2, CP_HISTORY_TITLE,
215 CP_HISTORY_BORDER);
217 for (i = 0; i < MAX_PGN_NAG; i++) {
218 if (anno->nag[i] && anno->nag[i] <= item_count(menu)) {
219 set_item_value(mitems[anno->nag[i]], TRUE);
220 set_current_item(menu, mitems[anno->nag[i]]);
221 itemcount++;
225 while (1) {
226 int c;
227 char *tmp;
228 char buf[cols - 4];
230 wattron(win, A_REVERSE);
232 for (c = 1; c < (cols + 2) - 1; c++)
233 mvwprintw(win, rows + 2, c, " ");
235 c = item_index(current_item(menu)) + 1;
237 snprintf(buf, sizeof(buf), "Item %i of %i (%i of %i selected) %s", c,
238 item_count(menu), itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
239 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
241 wattroff(win, A_REVERSE);
243 if (!itemcount) {
244 for (i = 0; mitems[i]; i++)
245 set_item_value(mitems[i], FALSE);
247 set_item_value(mitems[0], TRUE);
249 else
250 set_item_value(mitems[0], FALSE);
252 /* This nl() statement needs to be here because NL is recognized
253 * for some reason after the first selection.
255 nl();
256 refresh_all();
257 c = wgetch(win);
259 switch (c) {
260 int found;
262 case KEY_F(1):
263 help(NAG_EDIT_HELP, ANYKEY, naghelp);
264 break;
265 case KEY_RIGHT:
266 if (!itemcount)
267 break;
269 found = 0;
271 for (i = item_index(current_item(menu)) + 1; mitems[i]; i++) {
272 if (item_value(mitems[i]) == TRUE) {
273 found = i;
274 break;
278 if (!found) {
279 for (i = 0; mitems[i]; i++) {
280 if (item_value(mitems[i]) == TRUE) {
281 found = i;
282 break;
287 set_current_item(menu, mitems[found]);
288 break;
289 case KEY_LEFT:
290 if (!itemcount)
291 break;
293 found = 0;
295 for (i = item_index(current_item(menu)) - 1; i > 0; i--) {
296 if (item_value(mitems[i]) == TRUE) {
297 found = i;
298 break;
302 if (!found) {
303 for (i = item_count(menu) - 1; i > 0; i--) {
304 if (item_value(mitems[i]) == TRUE) {
305 found = i;
306 break;
311 set_current_item(menu, mitems[found]);
312 break;
313 case KEY_HOME:
314 menu_driver(menu, REQ_FIRST_ITEM);
315 break;
316 case KEY_END:
317 menu_driver(menu, REQ_LAST_ITEM);
318 break;
319 case KEY_UP:
320 menu_driver(menu, REQ_UP_ITEM);
321 break;
322 case KEY_DOWN:
323 menu_driver(menu, REQ_DOWN_ITEM);
324 break;
325 case KEY_PPAGE:
326 case CTRL('P'):
327 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
328 menu_driver(menu, REQ_FIRST_ITEM);
329 break;
330 case KEY_NPAGE:
331 case CTRL('N'):
332 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
333 menu_driver(menu, REQ_LAST_ITEM);
334 break;
335 case ' ':
336 if (item_index(current_item(menu)) == 0 &&
337 item_value(current_item(menu)) == FALSE) {
338 itemcount = 0;
339 break;
342 if (item_value(current_item(menu)) == TRUE) {
343 set_item_value(current_item(menu), FALSE);
344 itemcount--;
346 else {
347 if (itemcount + 1 > MAX_PGN_NAG)
348 break;
350 set_item_value(current_item(menu), TRUE);
351 itemcount++;
354 SET_FLAG(game[gindex].flags, GF_MODIFIED);
355 break;
356 case '\n':
357 goto gotitem;
358 break;
359 case KEY_ESCAPE:
360 goto done;
361 break;
362 default:
363 tmp = menu_pattern(menu);
365 if (tmp && tmp[strlen(tmp) - 1] != c) {
366 menu_driver(menu, REQ_CLEAR_PATTERN);
367 menu_driver(menu, c);
369 else {
370 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
371 menu_driver(menu, c);
374 break;
378 gotitem:
379 for (i = 0; i < MAX_PGN_NAG; i++)
380 anno->nag[i] = 0;
382 for (i = 0, n = 0; mitems[i] && n < MAX_PGN_NAG; i++) {
383 if (item_value(mitems[i]) == TRUE)
384 anno->nag[n++] = i;
387 done:
388 unpost_menu(menu);
389 free_menu(menu);
391 for (i = 0; mitems[i]; i++)
392 free_item(mitems[i]);
394 free(mitems);
395 del_panel(panel);
396 delwin(subw);
397 delwin(win);
398 return NULL;
401 static void view_nag(void *arg)
403 HISTORY *h = (HISTORY *)arg;
404 char buf[80];
405 char line[LINE_MAX] = {0};
406 int i = 0;
408 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
410 if (!nags) {
411 if (init_nag())
412 return;
415 for (i = 0; i < MAX_PGN_NAG; i++) {
416 if (!h->nag[i])
417 break;
419 strncat(line, nags[h->nag[i] - 1].line, sizeof(line));
420 strncat(line, "\n", sizeof(line));
423 line[strlen(line) - 1] = 0;
424 message(buf, ANYKEY, "%s", line);
427 void view_annotation(HISTORY h)
429 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
430 int nag = 0, comment = 0;
432 if (h.comment && h.comment[0])
433 comment++;
435 if (h.nag[0])
436 nag++;
438 if (!nag && !comment)
439 return;
441 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
443 if (comment)
444 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
445 (nag) ? "Press 'n' to view NAG" : NULL,
446 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
447 (nag) ? 'n' : 0, "%s", h.comment);
448 else
449 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
450 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
453 static void cleanup(WINDOW *win, WINDOW *subw, PANEL *panel, MENU *menu,
454 ITEM **items, struct d_entries *entries)
456 int i;
458 unpost_menu(menu);
459 free_menu(menu);
461 for (i = 0; items[i]; i++)
462 free_item(items[i]);
464 free(items);
466 if (entries) {
467 for (i = 0; entries[i].name; i++) {
468 free(entries[i].name);
469 free(entries[i].fancy);
472 free(entries);
475 del_panel(panel);
476 delwin(subw);
477 delwin(win);
480 static int sort_entries(const void *s1, const void *s2)
482 const struct d_entries *ss1 = s1;
483 const struct d_entries *ss2 = s2;
485 return strcmp(ss1->name, ss2->name);
488 char *browse_directory(void *arg)
490 char *inputstr = (char *)arg;
491 int initkey = (inputstr) ? inputstr[0] : 0;
492 char pattern[FILENAME_MAX];
493 static char path[FILENAME_MAX];
494 static char file[FILENAME_MAX];
495 struct stat st;
496 char *p;
498 if (!*path) {
499 if (config.savedirectory) {
500 if ((p = word_expand(config.savedirectory)) == NULL)
501 return NULL;
503 strncpy(path, p, sizeof(path));
505 if (access(path, R_OK) == -1) {
506 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
507 getcwd(path, sizeof(path));
510 else
511 getcwd(path, sizeof(path));
514 again:
516 * First find directories (including hidden) in the working directory.
517 * Then apply the config.pattern to regular files.
519 if ((p = word_split_append(path, '/', ".* *")) == NULL)
520 return NULL;
522 strncpy(pattern, p, sizeof(pattern));
524 while (1) {
525 WINDOW *win, *subw;
526 PANEL *panel;
527 ITEM **mitems = NULL;
528 MENU *menu;
529 char *tmp = NULL;
530 int rows, cols;
531 int selected = -1;
532 char *mbuf = NULL;
533 int idx = 0;
534 int len = strlen(path);
535 wordexp_t w;
536 int i, n = 0;
537 struct d_entries *entries = NULL;
538 int which = 1;
539 int x = WRDE_NOCMD;
541 new_we:
542 if (wordexp(pattern, &w, x) != 0) {
543 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
544 return NULL;
547 for (i = 0; i < w.we_wordc; i++) {
548 struct tm *tp;
549 char tbuf[16];
551 if (stat(w.we_wordv[i], &st) == -1)
552 continue;
554 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
555 p++;
556 else
557 p = w.we_wordv[i];
559 if (which) {
560 if (!S_ISDIR(st.st_mode))
561 continue;
563 if (p[0] == '.' && p[1] == 0)
564 continue;
566 else {
567 if (S_ISDIR(st.st_mode))
568 continue;
571 len = strlen(p) + 2;
572 entries = Realloc(entries, (n + 2) * sizeof(struct d_entries));
573 entries[n].name = strdup(w.we_wordv[i]);
574 entries[n].fancy = Malloc(len);
575 strncpy(entries[n].fancy, p, len);
577 if (S_ISDIR(st.st_mode))
578 entries[n].fancy[len - 2] = '/';
580 tp = localtime(&st.st_mtime);
581 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
583 snprintf(entries[n].desc, sizeof(entries[n].desc), "%-7i %s",
584 (int)st.st_size, tbuf);
586 memset(&entries[++n], '\0', sizeof(struct d_entries));
589 which--;
591 if (which == 0) {
592 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
593 return NULL;
595 strncpy(pattern, p, sizeof(pattern));
596 x |= WRDE_REUSE;
597 goto new_we;
600 wordfree(&w);
601 qsort(entries, n, sizeof(struct d_entries), sort_entries);
603 for (i = 0; i < n; i++) {
604 mitems = Realloc(mitems, (idx + 2) * sizeof(ITEM));
605 mitems[idx++] = new_item(entries[i].fancy, entries[i].desc);
608 mitems[idx] = NULL;
609 menu = new_menu(mitems);
610 scale_menu(menu, &rows, &cols);
612 if (cols < strlen(path))
613 cols = strlen(path);
615 if (cols < strlen(HELP_PROMPT))
616 cols = strlen(HELP_PROMPT);
618 rows = (LINES / 5) * 4;
619 cols += 2;
621 win = newwin(rows + 4, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
622 set_menu_format(menu, rows, 0);
623 set_menu_win(menu, win);
624 subw = derwin(win, rows, cols - 2, 2, 1);
625 set_menu_sub(menu, subw);
626 set_menu_fore(menu, A_REVERSE);
627 set_menu_grey(menu, A_NORMAL);
628 set_menu_mark(menu, NULL);
629 set_menu_spacing(menu, 2, 0, 0);
630 menu_opts_off(menu, O_NONCYCLIC);
631 post_menu(menu);
632 panel = new_panel(win);
634 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
635 draw_prompt(win, rows + 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
637 cbreak();
638 noecho();
639 keypad(win, TRUE);
640 set_menu_pattern(menu, mbuf);
642 if (isgraph(initkey)) {
643 menu_driver(menu, initkey);
644 initkey = '\0';
647 while (1) {
648 int c;
650 /* This nl() statement needs to be here because NL is recognized
651 * for some reason after the first selection.
653 nl();
654 refresh_all();
655 c = wgetch(win);
657 switch (c) {
658 case CTRL('P'):
659 case KEY_PPAGE:
660 menu_driver(menu, REQ_SCR_UPAGE);
661 break;
662 case ' ':
663 case CTRL('N'):
664 case KEY_NPAGE:
665 menu_driver(menu, REQ_SCR_DPAGE);
666 break;
667 case KEY_UP:
668 menu_driver(menu, REQ_UP_ITEM);
669 break;
670 case KEY_DOWN:
671 menu_driver(menu, REQ_DOWN_ITEM);
672 break;
673 case '\n':
674 selected = item_index(current_item(menu));
675 goto gotitem;
676 break;
677 case KEY_ESCAPE:
678 cleanup(win, subw, panel, menu, mitems, entries);
679 file[0] = 0;
680 goto done;
681 break;
682 case KEY_F(1):
683 help(BROWSER_HELP, ANYKEY, file_browser_help);
684 break;
685 case '~':
686 strncpy(path, "~/", sizeof(path));
687 cleanup(win, subw, panel, menu, mitems, entries);
688 goto again;
689 break;
690 case CTRL('X'):
691 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
692 == NULL)
693 break;
695 strncpy(path, tmp, sizeof(path));
696 cleanup(win, subw, panel, menu, mitems, entries);
697 goto again;
698 break;
699 default:
700 tmp = menu_pattern(menu);
702 if (tmp && tmp[strlen(tmp) - 1] != c) {
703 menu_driver(menu, REQ_CLEAR_PATTERN);
704 menu_driver(menu, c);
706 else {
707 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
708 menu_driver(menu, c);
711 break;
715 gotitem:
716 strncpy(file, entries[selected].name, sizeof(file));
717 cleanup(win, subw, panel, menu, mitems, entries);
719 if (stat(file, &st) == -1) {
720 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
721 continue;
724 if (S_ISDIR(st.st_mode)) {
725 p = file + strlen(file) - 2;
727 if (strcmp(p, "..") == 0) {
728 p = file + strlen(file) - 3;
729 *p = 0;
731 if ((p = strrchr(file, '/')) != NULL)
732 file[strlen(file) - strlen(p)] = 0;
735 strncpy(path, file, sizeof(path));
736 goto again;
739 if (S_ISREG(st.st_mode))
740 break;
742 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
745 done:
746 return (*file) ? file : NULL;
748 static int init_country_codes()
750 FILE *fp;
751 char line[LINE_MAX], *s;
752 int cindex = 0;
754 if ((fp = fopen(config.ccfile, "r")) == NULL) {
755 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
756 return 1;
759 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
760 char *tmp;
762 if ((tmp = strsep(&s, " ")) == NULL)
763 continue;
765 s = trim(s);
766 tmp = trim(tmp);
768 if (!s || !tmp)
769 continue;
771 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
772 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
773 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
774 cindex++;
777 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
778 fclose(fp);
780 return 0;
783 char *country_codes(void *arg)
785 WINDOW *win, *subw;
786 PANEL *panel;
787 ITEM **mitems = NULL;
788 MENU *menu;
789 int i = 0, n;
790 int rows, cols;
791 char *mbuf = NULL;
792 char *tmp = NULL;
794 if (!ccodes) {
795 if (init_country_codes())
796 return NULL;
799 for (n = i = 0; ccodes[n].code[0]; n++, i++) {
800 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
801 mitems[i] = new_item(ccodes[n].country, ccodes[n].code);
804 mitems[i] = NULL;
805 menu = new_menu(mitems);
806 scale_menu(menu, &rows, &cols);
808 if (cols < strlen(HELP_PROMPT) + 21)
809 cols = strlen(HELP_PROMPT) + 21;
811 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
812 set_menu_win(menu, win);
813 subw = derwin(win, rows, cols + 2, 2, 1);
814 set_menu_sub(menu, subw);
815 set_menu_fore(menu, A_REVERSE);
816 set_menu_grey(menu, A_NORMAL);
817 set_menu_mark(menu, NULL);
818 set_menu_spacing(menu, 0, 0, 0);
819 menu_opts_off(menu, O_NONCYCLIC);
820 post_menu(menu);
821 panel = new_panel(win);
822 cbreak();
823 noecho();
824 keypad(win, TRUE);
825 set_menu_pattern(menu, mbuf);
826 wbkgd(win, CP_MESSAGE_WINDOW);
827 draw_window_title(win, CC_TITLE, cols + 4, CP_MESSAGE_TITLE,
828 CP_MESSAGE_BORDER);
830 while (1) {
831 int c;
832 char buf[cols - 4];
834 wattron(win, A_REVERSE);
836 for (c = 1; c < (cols + 2) - 1; c++)
837 mvwprintw(win, rows + 2, c, " ");
839 c = item_index(current_item(menu)) + 1;
841 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR, c,
842 N_OF_N_STR, item_count(menu), HELP_PROMPT);
843 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
845 wattroff(win, A_REVERSE);
847 /* This nl() statement needs to be here because NL is recognized
848 * for some reason after the first selection.
850 nl();
851 refresh_all();
852 c = wgetch(win);
854 switch (c) {
855 case KEY_F(1):
856 help(CC_KEY_HELP, ANYKEY, cc_help);
857 break;
858 case KEY_HOME:
859 menu_driver(menu, REQ_FIRST_ITEM);
860 break;
861 case KEY_END:
862 menu_driver(menu, REQ_LAST_ITEM);
863 break;
864 case KEY_UP:
865 menu_driver(menu, REQ_UP_ITEM);
866 break;
867 case KEY_DOWN:
868 menu_driver(menu, REQ_DOWN_ITEM);
869 break;
870 case KEY_PPAGE:
871 case CTRL('P'):
872 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
873 menu_driver(menu, REQ_FIRST_ITEM);
874 break;
875 case ' ':
876 case KEY_NPAGE:
877 case CTRL('N'):
878 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
879 menu_driver(menu, REQ_LAST_ITEM);
880 break;
881 case '\n':
882 tmp = (char *)item_description(current_item(menu));
883 goto done;
884 break;
885 case KEY_ESCAPE:
886 tmp = NULL;
887 goto done;
888 break;
889 default:
890 tmp = menu_pattern(menu);
892 if (tmp && tmp[strlen(tmp) - 1] != c) {
893 menu_driver(menu, REQ_CLEAR_PATTERN);
894 menu_driver(menu, c);
896 else {
897 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
898 menu_driver(menu, c);
901 break;
905 done:
906 unpost_menu(menu);
907 free_menu(menu);
909 for (i = 0; mitems[i]; i++)
910 free_item(mitems[i]);
912 del_panel(panel);
913 delwin(subw);
914 delwin(win);
915 return tmp;
918 static void add_custom_tags(TAG ***t)
920 int i;
922 if (!config.tag)
923 return;
925 for (i = 0; config.tag[i]; i++)
926 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
928 pgn_tag_sort(*t);
931 TAG **edit_tags(GAME g, BOARD b, int edit)
933 TAG **data = NULL;
934 struct tm tp;
935 unsigned char data_index = 0;
936 int n, lastindex = 0;
937 int len;
939 /* Edit the backup copy, not the original in case the save fails. */
940 for (n = 0; g.tag[n]; n++)
941 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
943 data_index = pgn_tag_total(data);
945 while (1) {
946 WINDOW *win, *subw;
947 PANEL *panel;
948 ITEM **mitems = NULL;
949 MENU *menu;
950 int i;
951 char buf[76] = {0};
952 char *tmp = NULL;
953 int rows, cols;
954 int selected = -1;
955 char *mbuf = NULL;
956 int nlen = 0, vlen = 0;
958 data_index = pgn_tag_total(data);
960 for (i = 0; i < data_index; i++) {
961 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
963 if (data[i]->value) {
964 nlen = strlen(data[i]->name);
965 vlen = strlen(data[i]->value);
967 /* The +6 is for the menu padding. */
968 mitems[i] = new_item(data[i]->name,
969 (nlen + vlen + 6 >= MAX_VALUE_WIDTH)
970 ? PRESS_ENTER : data[i]->value);
972 else
973 mitems[i] = new_item(data[i]->name, UNKNOWN);
976 mitems[i] = NULL;
977 menu = new_menu(mitems);
978 scale_menu(menu, &rows, &cols);
980 /* +14 for the extra prompt info. */
981 if (cols < strlen(HELP_PROMPT) + 14)
982 cols = strlen(HELP_PROMPT) + 14;
984 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
985 set_menu_win(menu, win);
986 subw = derwin(win, rows, cols + 2, 2, 1);
987 set_menu_sub(menu, subw);
988 set_menu_fore(menu, A_REVERSE);
989 set_menu_grey(menu, A_NORMAL);
990 set_menu_mark(menu, NULL);
991 set_menu_pad(menu, '-');
992 set_menu_spacing(menu, 3, 0, 0);
993 menu_opts_off(menu, O_NONCYCLIC);
994 post_menu(menu);
995 panel = new_panel(win);
996 cbreak();
997 noecho();
998 nl();
999 keypad(win, TRUE);
1000 set_menu_pattern(menu, mbuf);
1001 wbkgd(win, CP_MESSAGE_WINDOW);
1002 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1003 cols + 4, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
1005 while (1) {
1006 int c;
1007 TAG **tmppgn = NULL;
1008 char *newtag = NULL;
1010 if (set_current_item(menu, mitems[lastindex]) != E_OK) {
1011 lastindex = item_count(menu) - 1;
1012 continue;
1015 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1016 item_index(current_item(menu)) + 1, N_OF_N_STR,
1017 item_count(menu), HELP_PROMPT);
1018 draw_prompt(win, rows + 2, cols + 4, buf, CP_MESSAGE_PROMPT);
1019 refresh_all();
1020 c = wgetch(win);
1022 switch (c) {
1023 case CTRL('T'):
1024 add_custom_tags(&data);
1025 goto cleanup;
1026 break;
1027 case KEY_F(1):
1028 if (edit)
1029 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1030 else
1031 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1032 break;
1033 case CTRL('R'):
1034 if (!edit)
1035 break;
1037 selected = item_index(current_item(menu));
1039 if (selected <= 6) {
1040 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1041 goto cleanup;
1044 data_index = pgn_tag_total(data);
1046 for (i = 0; i < data_index; i++) {
1047 if (i == selected)
1048 continue;
1050 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
1053 pgn_tag_free(data);
1054 data = NULL;
1056 for (i = 0; tmppgn[i]; i++)
1057 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
1059 pgn_tag_free(tmppgn);
1060 goto cleanup;
1061 break;
1062 case CTRL('A'):
1063 if (!edit)
1064 break;
1066 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1067 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1068 == NULL)
1069 break;
1071 newtag[0] = toupper(newtag[0]);
1073 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1074 strlen(PRESS_ENTER)) {
1075 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1076 break;
1079 for (i = 0; i < data_index; i++) {
1080 if (strcasecmp(data[i]->name, newtag) == 0) {
1081 selected = i;
1082 goto gotitem;
1086 pgn_tag_add(&data, newtag, NULL);
1087 data_index = pgn_tag_total(data);
1088 selected = data_index - 1;
1089 goto gotitem;
1090 break;
1091 case KEY_HOME:
1092 menu_driver(menu, REQ_FIRST_ITEM);
1093 break;
1094 case KEY_END:
1095 menu_driver(menu, REQ_LAST_ITEM);
1096 break;
1097 case CTRL('F'):
1098 if (!edit)
1099 break;
1101 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1102 data_index = pgn_tag_total(data);
1103 selected = data_index - 1;
1104 goto gotitem;
1105 break;
1106 case KEY_NPAGE:
1107 case CTRL('N'):
1108 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
1109 menu_driver(menu, REQ_LAST_ITEM);
1110 break;
1111 case KEY_PPAGE:
1112 case CTRL('P'):
1113 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
1114 menu_driver(menu, REQ_FIRST_ITEM);
1115 break;
1116 case KEY_UP:
1117 menu_driver(menu, REQ_UP_ITEM);
1118 break;
1119 case KEY_DOWN:
1120 menu_driver(menu, REQ_DOWN_ITEM);
1121 break;
1122 case '\n':
1123 selected = item_index(current_item(menu));
1124 goto gotitem;
1125 break;
1126 case KEY_ESCAPE:
1127 cleanup(win, subw, panel, menu, mitems, NULL);
1128 goto done;
1129 break;
1130 default:
1131 tmp = menu_pattern(menu);
1133 if (tmp && tmp[strlen(tmp) - 1] != c) {
1134 menu_driver(menu, REQ_CLEAR_PATTERN);
1135 menu_driver(menu, c);
1137 else {
1138 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
1139 menu_driver(menu, c);
1142 break;
1145 lastindex = item_index(current_item(menu));
1148 gotitem:
1149 lastindex = selected;
1150 nlen = strlen(data[selected]->name) + 3;
1151 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1153 if (nlen > MAX_VALUE_WIDTH)
1154 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1155 else
1156 snprintf(buf, sizeof(buf), "%s \"%s\"",
1157 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1158 data[selected]->name);
1160 if (!edit) {
1161 if (strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1162 goto cleanup;
1164 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1165 goto cleanup;
1168 if (strcmp(data[selected]->name, "Date") == 0) {
1169 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1170 0, FIELD_TYPE_PGN_DATE);
1172 if (tmp) {
1173 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1174 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1175 goto cleanup;
1178 else
1179 goto cleanup;
1181 else if (strcmp(data[selected]->name, "Site") == 0) {
1182 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1183 country_codes, NULL, CTRL('t'), -1);
1185 if (!tmp)
1186 tmp = "?";
1188 else if (strcmp(data[selected]->name, "Round") == 0) {
1189 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1190 FIELD_TYPE_PGN_ROUND);
1192 if (!tmp) {
1193 if (gtotal > 1)
1194 tmp = "?";
1195 else
1196 tmp = "-";
1199 else if (strcmp(data[selected]->name, "Result") == 0) {
1200 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1201 0, -1);
1203 if (!tmp)
1204 tmp = "*";
1206 else {
1207 if (item_description(mitems[selected]) &&
1208 strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1209 tmp = NULL;
1210 else
1211 tmp = data[selected]->value;
1213 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1216 len = (tmp) ? strlen(tmp) + 1 : 1;
1217 data[selected]->value = Realloc(data[selected]->value, len);
1218 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1220 cleanup:
1221 cleanup(win, subw, panel, menu, mitems, NULL);
1224 done:
1225 if (!edit) {
1226 pgn_tag_free(data);
1227 return NULL;
1230 return data;
1233 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1234 * game index number.
1236 int save_pgn(const char *filename, int isfifo, int saveindex)
1238 FILE *fp;
1239 char *mode = NULL;
1240 int c;
1241 char buf[FILENAME_MAX];
1242 struct stat st;
1243 int i;
1244 char *command = NULL;
1245 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1247 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1248 if (stat(config.savedirectory, &st) == -1) {
1249 if (errno == ENOENT) {
1250 if (mkdir(config.savedirectory, 0755) == -1) {
1251 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1252 strerror(errno));
1253 return 1;
1256 else {
1257 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1258 strerror(errno));
1259 return 1;
1263 stat(config.savedirectory, &st);
1265 if (!S_ISDIR(st.st_mode)) {
1266 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1267 return 1;
1270 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1271 filename = buf;
1274 /* This is a hack to resume an existing game when more than one game is
1275 * available. Also resuming a saved game and a game from history.
1277 // FIXME: may not need this when a FEN tag is supported (by the engine).
1278 if (isfifo)
1279 mode = "w";
1280 else {
1281 if (access(filename, W_OK) == 0) {
1282 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1283 "%s \"%s\"", E_FILEEXISTS, filename);
1285 switch (c) {
1286 case 'a':
1287 if (pgn_is_compressed(filename)) {
1288 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1289 return 1;
1292 mode = "a";
1293 break;
1294 case 'o':
1295 mode = "w+";
1296 break;
1297 default:
1298 return 1;
1301 else
1302 mode = "a";
1305 if (command) {
1306 if ((fp = popen(command, "w")) == NULL) {
1307 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1308 return 1;
1311 else {
1312 if ((fp = fopen(filename, mode)) == NULL) {
1313 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1314 return 1;
1318 if (isfifo)
1319 pgn_write(fp, game[saveindex]);
1320 else {
1321 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1322 pgn_write(fp, game[i]);
1325 if (command)
1326 pclose(fp);
1327 else
1328 fclose(fp);
1330 if (!isfifo && saveindex == -1)
1331 strncpy(loadfile, filename, sizeof(loadfile));
1333 return 0;
1336 char *random_agony(GAME g)
1338 static int n;
1339 FILE *fp;
1340 char line[LINE_MAX];
1342 if (n == -1 || !config.agony || !curses_initialized ||
1343 (g.mode == MODE_HISTORY && !config.historyagony))
1344 return NULL;
1346 if (!agony) {
1347 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1348 n = -1;
1349 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1350 return NULL;
1353 while (!feof(fp)) {
1354 if (fscanf(fp, " %[^\n] ", line) == 1) {
1355 agony = Realloc(agony, (n + 2) * sizeof(char *));
1356 agony[n++] = strdup(trim(line));
1360 agony[n] = NULL;
1361 fclose(fp);
1363 if (agony[0] == NULL || !n) {
1364 n = -1;
1365 return NULL;
1369 return agony[random() % n];
1372 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1374 if (pgn_piece_to_int(piece) == ROOK && col == 7
1375 && row == 7 &&
1376 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1377 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1378 if (mod)
1379 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1380 return 1;
1382 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1383 && row == 7 &&
1384 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1385 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1386 if (mod)
1387 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1388 return 1;
1390 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1391 && row == 0 &&
1392 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1393 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1394 if (mod)
1395 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1396 return 1;
1398 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1399 && row == 0 &&
1400 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1401 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1402 if (mod)
1403 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1404 return 1;
1406 else if (pgn_piece_to_int(piece) == KING && col == 4
1407 && row == 7 &&
1408 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1409 TEST_FLAG(g->flags, GF_WK_CASTLE))
1411 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1412 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1413 if (mod) {
1414 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1415 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1416 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1417 else
1418 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1420 return 1;
1422 else if (pgn_piece_to_int(piece) == KING && col == 4
1423 && row == 0 &&
1424 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1425 TEST_FLAG(g->flags, GF_BK_CASTLE))
1427 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1428 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1429 if (mod) {
1430 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1431 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1432 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1433 else
1434 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1436 return 1;
1439 return 0;
1442 static void draw_board(GAME *g, int details)
1444 int row, col;
1445 int bcol = 0, brow = 0;
1446 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1447 int ncols = 0, offset = 1;
1448 unsigned coords_y = 8;
1450 if (g->mode != MODE_PLAY && g->mode != MODE_EDIT)
1451 update_cursor(*g, g->hindex);
1453 for (row = 0; row < maxy; row++) {
1454 bcol = 0;
1456 for (col = 0; col < maxx; col++) {
1457 int attrwhich = -1;
1458 chtype attrs = 0;
1459 unsigned char piece;
1461 if (row == 0 || row == maxy - 2) {
1462 if (col == 0)
1463 mvwaddch(boardw, row, col,
1464 LINE_GRAPHIC((row) ?
1465 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1466 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1467 else if (col == maxx - 2)
1468 mvwaddch(boardw, row, col,
1469 LINE_GRAPHIC((row) ?
1470 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1471 ACS_URCORNER | CP_BOARD_GRAPHICS));
1472 else if (!(col % 4))
1473 mvwaddch(boardw, row, col,
1474 LINE_GRAPHIC((row) ?
1475 ACS_BTEE | CP_BOARD_GRAPHICS :
1476 ACS_TTEE | CP_BOARD_GRAPHICS));
1477 else {
1478 if (col != maxx - 1)
1479 mvwaddch(boardw, row, col,
1480 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1483 continue;
1486 if ((row % 2) && col == maxx - 1 && coords_y) {
1487 wattron(boardw, CP_BOARD_COORDS);
1488 mvwprintw(boardw, row, col, "%d", coords_y--);
1489 wattroff(boardw, CP_BOARD_COORDS);
1490 continue;
1493 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1494 if (!(row % 2))
1495 mvwaddch(boardw, row, col,
1496 LINE_GRAPHIC((col) ?
1497 ACS_RTEE | CP_BOARD_GRAPHICS :
1498 ACS_LTEE | CP_BOARD_GRAPHICS));
1499 else
1500 mvwaddch(boardw, row, col,
1501 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1503 continue;
1506 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1507 mvwaddch(boardw, row, col,
1508 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1509 continue;
1512 if (!(col % 4) && row != maxy - 1) {
1513 mvwaddch(boardw, row, col,
1514 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1515 continue;
1518 if ((row % 2)) {
1519 if ((col % 4)) {
1520 if (ncols++ == 8) {
1521 offset++;
1522 ncols = 1;
1525 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1526 && (offset % 2)))
1527 attrwhich = BLACK;
1528 else
1529 attrwhich = WHITE;
1531 if (config.validmoves && g->b[brow][bcol].valid) {
1532 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1533 CP_BOARD_MOVES_BLACK;
1535 else
1536 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1537 CP_BOARD_BLACK;
1539 if (row == ROWTOMATRIX(c_row) && col ==
1540 COLTOMATRIX(c_col)) {
1541 attrs = CP_BOARD_CURSOR;
1544 if (row == ROWTOMATRIX(sp.row) &&
1545 col == COLTOMATRIX(sp.col)) {
1546 attrs = CP_BOARD_SELECTED;
1549 if (row == maxy - 1)
1550 attrs = 0;
1552 mvwaddch(boardw, row, col, ' ' | attrs);
1554 if (row == maxy - 1)
1555 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1556 else {
1557 if (details && g->b[row / 2][bcol].enpassant)
1558 piece = 'x';
1559 else
1560 piece = g->b[row / 2][bcol].icon;
1562 if (details && castling_state(g, g->b, brow, bcol,
1563 piece, 0))
1564 attrs |= A_REVERSE;
1566 if (g->side == WHITE && isupper(piece))
1567 attrs |= A_BOLD;
1568 else if (g->side == BLACK && islower(piece))
1569 attrs |= A_BOLD;
1571 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1573 CLEAR_FLAG(attrs, A_BOLD);
1574 CLEAR_FLAG(attrs, A_REVERSE);
1577 waddch(boardw, ' ' | attrs);
1578 col += 2;
1579 bcol++;
1582 else {
1583 if (col != maxx - 1)
1584 mvwaddch(boardw, row, col,
1585 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1589 brow = row / 2;
1593 void invalid_move(int n, const char *m)
1595 if (curses_initialized)
1596 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1597 else
1598 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1601 /* Convert the selected piece to SAN format and validate it. */
1602 static char *board_to_san(GAME *g, BOARD b)
1604 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1605 int piece;
1606 int promo;
1607 BOARD oldboard;
1609 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1610 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1612 p = str;
1613 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1615 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1616 (sp.destrow == 1 && g->turn == BLACK))) {
1617 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1619 if (pgn_piece_to_int(promo) == -1)
1620 return NULL;
1622 p = str + strlen(str);
1623 *p++ = toupper(promo);
1624 *p = '\0';
1627 memcpy(oldboard, b, sizeof(BOARD));
1628 p = str;
1630 if (pgn_validate_move(g, b, &p)) {
1631 invalid_move(gindex + 1, p);
1632 memcpy(b, oldboard, sizeof(BOARD));
1633 return NULL;
1636 return p;
1639 static int move_to_engine(GAME *g, BOARD b)
1641 char *p;
1642 struct userdata_s *d = g->data;
1644 if ((p = board_to_san(g, b)) == NULL)
1645 return 0;
1647 sp.row = sp.col = sp.icon = 0;
1649 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1650 pgn_history_add(g, p);
1651 pgn_switch_turn(g);
1652 SET_FLAG(g->flags, GF_MODIFIED);
1653 update_all(*g);
1654 return 1;
1657 send_to_engine(g, "%s\n", p);
1658 return 1;
1661 static void update_clock(int n, int *h, int *m, int *s)
1663 *h = n / 3600;
1664 *m = (n % 3600) / 60;
1665 *s = (n % 3600) % 60;
1667 return;
1670 void update_status_window(GAME g)
1672 int i = 0;
1673 char *buf;
1674 char tmp[15], *engine, *mode;
1675 int w;
1676 int h, m, s;
1677 char *p;
1678 int maxy, maxx;
1679 int len;
1680 struct userdata_s *d = g.data;
1682 getmaxyx(statusw, maxy, maxx);
1683 w = maxx - 2 - 8;
1684 len = maxx - 2;
1685 buf = Malloc(len);
1687 *tmp = '\0';
1688 p = tmp;
1690 if (TEST_FLAG(g.flags, GF_DELETE)) {
1691 *p++ = '(';
1692 *p++ = 'x';
1693 i++;
1696 if (TEST_FLAG(g.flags, GF_PERROR)) {
1697 if (!i)
1698 *p++ = '(';
1699 else
1700 *p++ = '/';
1702 *p++ = '!';
1703 i++;
1706 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1707 if (!i)
1708 *p++ = '(';
1709 else
1710 *p++ = '/';
1712 *p++ = '*';
1713 i++;
1716 if (*tmp != '\0')
1717 *p++ = ')';
1719 *p = '\0';
1721 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1722 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1723 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1724 (*tmp) ? tmp : "");
1725 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1727 switch (g.mode) {
1728 case MODE_HISTORY:
1729 mode = MODE_HISTORY_STR;
1730 break;
1731 case MODE_EDIT:
1732 mode = MODE_EDIT_STR;
1733 break;
1734 case MODE_PLAY:
1735 mode = MODE_PLAY_STR;
1736 break;
1737 default:
1738 mode = UNKNOWN;
1739 break;
1742 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1744 if (g.mode == MODE_PLAY) {
1745 if (TEST_FLAG(d->flags, CF_HUMAN))
1746 strncat(buf, " (human/human)", len - 1);
1747 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1748 strncat(buf, " (engine/engine)", len - 1);
1749 else
1750 strncat(buf, " (human/engine)", len - 1);
1753 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1755 if (d->engine) {
1756 switch (d->engine->status) {
1757 case ENGINE_THINKING:
1758 engine = ENGINE_PONDER_STR;
1759 break;
1760 case ENGINE_READY:
1761 engine = ENGINE_READY_STR;
1762 break;
1763 case ENGINE_INITIALIZING:
1764 engine = ENGINE_INITIALIZING_STR;
1765 break;
1766 case ENGINE_OFFLINE:
1767 engine = ENGINE_OFFLINE_STR;
1768 break;
1769 default:
1770 engine = UNKNOWN;
1771 break;
1774 else
1775 engine = ENGINE_OFFLINE_STR;
1777 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1778 wattron(statusw, CP_STATUS_ENGINE);
1779 mvwaddstr(statusw, 5, 9, engine);
1780 wattroff(statusw, CP_STATUS_ENGINE);
1782 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1783 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1785 strncpy(tmp, WHITE_STR, sizeof(tmp));
1786 tmp[0] = toupper(tmp[0]);
1787 update_clock(g.moveclock, &h, &m, &s);
1788 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1789 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1791 strncpy(tmp, BLACK_STR, sizeof(tmp));
1792 tmp[0] = toupper(tmp[0]);
1793 update_clock(g.moveclock, &h, &m, &s);
1794 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1795 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1796 free(buf);
1798 for (i = 1; i < maxx - 4; i++)
1799 mvwprintw(statusw, maxy - 2, i, " ");
1801 if (!status.notify)
1802 status.notify = strdup(GAME_HELP_PROMPT);
1804 wattron(statusw, CP_STATUS_NOTIFY);
1805 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1806 status.notify);
1807 wattroff(statusw, CP_STATUS_NOTIFY);
1810 void update_history_window(GAME g)
1812 char buf[HISTORY_WIDTH - 1];
1813 HISTORY *h = NULL;
1814 int n, total;
1815 int t = pgn_history_total(g.hp);
1817 n = (g.hindex + 1) / 2;
1819 if (t % 2)
1820 total = (t + 1) / 2;
1821 else
1822 total = t / 2;
1824 if (t)
1825 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1826 (movestep == 1) ? HISTORY_PLY_STEP : "");
1827 else
1828 strncpy(buf, UNAVAILABLE, sizeof(buf));
1830 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1831 HISTORY_WIDTH - 13, buf);
1833 h = pgn_history_by_n(g.hp, g.hindex);
1834 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1835 n = 0;
1837 if (h && ((h->comment) || h->nag[0])) {
1838 strncat(buf, " (v", sizeof(buf));
1839 n++;
1842 if (h && h->rav) {
1843 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1844 n++;
1847 if (g.ravlevel) {
1848 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1849 n++;
1852 if (n)
1853 strncat(buf, ")", sizeof(buf));
1855 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1856 HISTORY_WIDTH - 13, buf);
1858 h = pgn_history_by_n(g.hp, game[gindex].hindex - 1);
1859 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1860 n = 0;
1862 if (h && ((h->comment) || h->nag[0])) {
1863 strncat(buf, " (V", sizeof(buf));
1864 n++;
1867 if (h && h->rav) {
1868 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1869 n++;
1872 if (g.ravlevel) {
1873 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1874 n++;
1877 if (n)
1878 strncat(buf, ")", sizeof(buf));
1880 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1881 HISTORY_WIDTH - 13, buf);
1884 void update_tag_window(TAG **t)
1886 int i;
1887 int w = TAG_WIDTH - 10;
1889 for (i = 0; i < 7; i++)
1890 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1893 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1895 int i;
1897 wattron(win, attr);
1899 for (i = 1; i < width - 1; i++)
1900 mvwaddch(win, y, i, ' ');
1902 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1903 wattroff(win, attr);
1906 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1907 chtype battr)
1909 int i;
1911 if (title) {
1912 wattron(win, attr);
1914 for (i = 1; i < width - 1; i++)
1915 mvwaddch(win, 1, i, ' ');
1917 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1918 wattroff(win, attr);
1921 wattron(win, battr);
1922 box(win, ACS_VLINE, ACS_HLINE);
1923 wattroff(win, battr);
1926 void refresh_all()
1928 update_panels();
1929 doupdate();
1932 void update_all(GAME g)
1934 update_status_window(g);
1935 update_history_window(g);
1936 update_tag_window(g.tag);
1939 static void game_next_prev(GAME g, int n, int count)
1941 if (gtotal < 2)
1942 return;
1944 if (n == 1) {
1945 if (gindex + count > gtotal - 1) {
1946 if (count != 1)
1947 gindex = gtotal - 1;
1948 else
1949 gindex = 0;
1951 else
1952 gindex += count;
1954 else {
1955 if (gindex - count < 0) {
1956 if (count != 1)
1957 gindex = 0;
1958 else
1959 gindex = gtotal - 1;
1961 else
1962 gindex -= count;
1966 static void delete_game(int which)
1968 GAME *g = NULL;
1969 int gi = 0;
1970 int i;
1972 for (i = 0; i < gtotal; i++) {
1973 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1974 pgn_free(game[i]);
1975 continue;
1978 g = Realloc(g, (gi + 1) * sizeof(GAME));
1979 memcpy(&g[gi], &game[i], sizeof(GAME));
1980 g[gi].tag = game[i].tag;
1981 g[gi].history = game[i].history;
1982 g[gi].hp = game[i].hp;
1983 gi++;
1986 game = g;
1987 gtotal = gi;
1989 if (which != -1) {
1990 if (which + 1 >= gtotal)
1991 gindex = gtotal - 1;
1992 else
1993 gindex = which;
1995 else
1996 gindex = gtotal - 1;
1998 game[gindex].hp = game[gindex].history;
2001 static int find_move_exp(GAME g, const char *str, int init, int which,
2002 int count)
2004 int i;
2005 int ret;
2006 static regex_t r;
2007 static int firstrun = 1;
2008 char errbuf[255];
2009 int incr;
2010 int found;
2012 if (init) {
2013 if (!firstrun)
2014 regfree(&r);
2016 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2017 regerror(ret, &r, errbuf, sizeof(errbuf));
2018 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2019 return -1;
2022 firstrun = 1;
2025 incr = (which == 0) ? -1 : 1;
2027 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2028 if (i == g.hindex - 1)
2029 break;
2031 if (i >= pgn_history_total(g.hp))
2032 i = 0;
2033 else if (i < 0)
2034 i = pgn_history_total(g.hp) - 1;
2036 // FIXME RAV
2037 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2039 if (ret == 0) {
2040 if (count == ++found) {
2041 return i + 1;
2044 else {
2045 if (ret != REG_NOMATCH) {
2046 regerror(ret, &r, errbuf, sizeof(errbuf));
2047 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2048 return -1;
2053 return -1;
2056 static int toggle_delete_flag(int n)
2058 int i, x;
2060 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2062 for (i = x = 0; i < gtotal; i++) {
2063 if (TEST_FLAG(game[i].flags, GF_DELETE))
2064 x++;
2067 if (x == gtotal) {
2068 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2069 CLEAR_FLAG(game[n].flags, GF_DELETE);
2070 return 1;
2073 return 0;
2076 static void edit_save_tags(GAME *g)
2078 TAG **t;
2080 if ((t = edit_tags(*g, g->b, 1)) == NULL)
2081 return;
2083 pgn_tag_free(g->tag);
2084 g->tag = t;
2085 SET_FLAG(g->flags, GF_MODIFIED);
2086 pgn_tag_sort(g->tag);
2089 static int find_game_exp(char *str, int which, int count)
2091 char *nstr = NULL, *exp = NULL;
2092 regex_t nexp, vexp;
2093 int ret = -1;
2094 int g = 0;
2095 char buf[255], *tmp;
2096 char errbuf[255];
2097 int found = 0;
2098 int incr = (which == 0) ? -(1) : 1;
2100 strncpy(buf, str, sizeof(buf));
2101 tmp = buf;
2103 if (strstr(tmp, ":") != NULL) {
2104 nstr = strsep(&tmp, ":");
2106 if ((ret = regcomp(&nexp, nstr,
2107 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2108 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2109 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2110 ret = g = -1;
2111 goto cleanup;
2115 exp = tmp;
2117 if (exp == NULL)
2118 goto cleanup;
2120 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2121 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2122 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2123 ret = -1;
2124 goto cleanup;
2127 ret = -1;
2129 for (g = gindex + incr, found = 0; ; g += incr) {
2130 int t;
2132 if (g == gindex)
2133 break;
2135 if (g == gtotal)
2136 g = 0;
2137 else if (g < 0)
2138 g = gtotal - 1;
2140 for (t = 0; game[g].tag[t]; t++) {
2141 if (nstr) {
2142 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2143 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2144 if (count == ++found) {
2145 ret = g;
2146 goto cleanup;
2151 else {
2152 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2153 if (count == ++found) {
2154 ret = g;
2155 goto cleanup;
2161 ret = -1;
2164 cleanup:
2165 if (nstr)
2166 regfree(&nexp);
2168 if (g != -1)
2169 regfree(&vexp);
2171 return ret;
2175 * Updates the notification line in the status window then refreshes the
2176 * status window.
2178 void update_status_notify(GAME g, char *fmt, ...)
2180 va_list ap;
2181 #ifdef HAVE_VASPRINTF
2182 char *line;
2183 #else
2184 char line[COLS];
2185 #endif
2187 if (!fmt) {
2188 if (status.notify) {
2189 free(status.notify);
2190 status.notify = NULL;
2192 if (curses_initialized)
2193 update_status_window(g);
2196 return;
2199 va_start(ap, fmt);
2200 #ifdef HAVE_VASPRINTF
2201 vasprintf(&line, fmt, ap);
2202 #else
2203 vsnprintf(line, sizeof(line), fmt, ap);
2204 #endif
2205 va_end(ap);
2207 if (status.notify)
2208 free(status.notify);
2210 status.notify = strdup(line);
2212 #ifdef HAVE_VASPRINTF
2213 free(line);
2214 #endif
2215 if (curses_initialized)
2216 update_status_window(g);
2219 static void switch_side(GAME *g)
2221 g->side = (g->side == WHITE) ? BLACK : WHITE;
2224 int rav_next_prev(GAME *g, BOARD b, int n)
2226 // Next RAV.
2227 if (n) {
2228 if (g->hp[g->hindex]->rav == NULL)
2229 return 1;
2231 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2232 g->rav[g->ravlevel].hp = g->hp;
2233 g->rav[g->ravlevel].flags = g->flags;
2234 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2235 g->rav[g->ravlevel].hindex = g->hindex;
2236 g->hp = g->hp[g->hindex]->rav;
2237 g->hindex = 0;
2238 g->ravlevel++;
2239 return 0;
2242 if (g->ravlevel - 1 < 0)
2243 return 1;
2245 // Previous RAV.
2246 g->ravlevel--;
2247 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2248 free(g->rav[g->ravlevel].fen);
2249 g->hp = g->rav[g->ravlevel].hp;
2250 g->flags = g->rav[g->ravlevel].flags;
2251 g->hindex = g->rav[g->ravlevel].hindex;
2252 return 0;
2255 static void draw_window_decor()
2257 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2258 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2259 wbkgd(boardw, CP_BOARD_WINDOW);
2260 wbkgd(statusw, CP_STATUS_WINDOW);
2261 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2262 CP_STATUS_TITLE, CP_STATUS_BORDER);
2263 wbkgd(tagw, CP_TAG_WINDOW);
2264 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2265 CP_TAG_BORDER);
2266 wbkgd(historyw, CP_HISTORY_WINDOW);
2267 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2268 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2271 static void do_window_resize()
2273 if (LINES < 24 || COLS < 80)
2274 return;
2276 resizeterm(LINES, COLS);
2277 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2278 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2279 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2280 wmove(historyw, 0, 0);
2281 wclrtobot(historyw);
2282 wmove(tagw, 0, 0);
2283 wclrtobot(tagw);
2284 wmove(statusw, 0, 0);
2285 wclrtobot(statusw);
2286 draw_window_decor();
2287 update_all(game[gindex]);
2290 static void historymode_keys(chtype);
2291 static int playmode_keys(chtype c)
2293 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2294 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2295 chtype p;
2296 int w, x;
2297 char *tmp;
2298 struct userdata_s *d = game[gindex].data;
2300 switch (c) {
2301 case 'U':
2302 TOGGLE_FLAG(d->flags, CF_HUMAN);
2304 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2305 pgn_history_total(game[gindex].hp)) {
2306 pgn_tag_add(&game[gindex].tag, "FEN",
2307 pgn_game_to_fen(game[gindex], game[gindex].b));
2308 x = pgn_tag_find(game[gindex].tag, "FEN");
2310 if (start_chess_engine(&game[gindex]) <= 0) {
2311 send_to_engine(&game[gindex], "setboard %s\n",
2312 game[gindex].tag[x]->value);
2313 d->engine->status = ENGINE_READY;
2317 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2318 update_all(game[gindex]);
2319 break;
2320 case 'o':
2321 if (!d)
2322 break;
2324 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2325 CLEAR_FLAG(d->flags, CF_HUMAN);
2326 update_all(game[gindex]);
2327 break;
2328 case '|':
2329 if (!d->engine)
2330 break;
2332 if (d->engine->status == ENGINE_OFFLINE)
2333 break;
2335 x = d->engine->status;
2337 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2338 send_to_engine(&game[gindex], "%s\n", tmp);
2339 d->engine->status = x;
2340 break;
2341 case '\015':
2342 case '\n':
2343 pushkey = keycount = 0;
2344 update_status_notify(game[gindex], NULL);
2346 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2347 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2348 beep();
2349 break;
2352 if (!sp.icon)
2353 break;
2355 sp.destrow = c_row;
2356 sp.destcol = c_col;
2358 if (editmode) {
2359 p = game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2360 game[gindex].b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2361 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
2362 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2363 sp.icon = sp.row = sp.col = 0;
2364 break;
2367 if (move_to_engine(&game[gindex], game[gindex].b)) {
2368 if (config.validmoves)
2369 pgn_reset_valid_moves(game[gindex].b);
2371 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2372 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2373 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2377 break;
2378 case ' ':
2379 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2380 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2381 if (start_chess_engine(&game[gindex])) {
2382 sp.icon = 0;
2383 break;
2386 x = pgn_tag_find(game[gindex].tag, "FEN");
2387 w = pgn_tag_find(game[gindex].tag, "SetUp");
2389 if ((w >= 0 && x >= 0 && atoi(game[gindex].tag[w]->value) == 1)
2390 || (x >= 0 && w == -1)) {
2391 send_to_engine(&game[gindex], "setboard %s\n",
2392 game[gindex].tag[x]->value);
2393 d->engine->status = ENGINE_READY;
2397 if (sp.icon || (!editmode && d->engine &&
2398 d->engine->status == ENGINE_THINKING)) {
2399 beep();
2400 break;
2403 sp.icon = mvwinch(boardw, ROWTOMATRIX(c_row),
2404 COLTOMATRIX(c_col)+1) & A_CHARTEXT;
2406 if (sp.icon == ' ') {
2407 sp.icon = 0;
2408 break;
2411 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2412 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2413 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2414 sp.icon = 0;
2415 break;
2418 sp.row = c_row;
2419 sp.col = c_col;
2421 if (!editmode && config.validmoves)
2422 pgn_get_valid_moves(&game[gindex], game[gindex].b, sp.row,
2423 sp.col);
2425 paused = 0;
2426 break;
2427 case 'w':
2428 send_to_engine(&game[gindex], "\nswitch\n");
2429 switch_side(&game[gindex]);
2430 update_status_window(game[gindex]);
2431 break;
2432 case 'u':
2433 if (!pgn_history_total(game[gindex].hp))
2434 break;
2436 if (d->engine && d->engine->status == ENGINE_READY) {
2437 send_to_engine(&game[gindex], "remove\n");
2438 d->engine->status = ENGINE_READY;
2441 game[gindex].hindex -= 2;
2442 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2443 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2444 pgn_board_update(&game[gindex], game[gindex].b,
2445 game[gindex].hindex);
2446 update_history_window(game[gindex]);
2447 break;
2448 case 'a':
2449 historymode_keys(c);
2450 break;
2451 case 'd':
2452 board_details = (board_details) ? 0 : 1;
2453 break;
2454 case 'p':
2455 paused = (paused) ? 0 : 1;
2456 break;
2457 case 'g':
2458 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2459 start_chess_engine(&game[gindex]);
2461 send_to_engine(&game[gindex], "go\n");
2462 break;
2463 default:
2464 break;
2467 return 0;
2470 static void editmode_keys(chtype c)
2472 switch (c) {
2473 case '\015':
2474 case '\n':
2475 case ' ':
2476 playmode_keys(c);
2477 break;
2478 case 'd':
2479 if (sp.icon)
2480 game[gindex].b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2481 else
2482 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2484 sp.icon = sp.row = sp.col = 0;
2485 break;
2486 case 'w':
2487 pgn_switch_turn(&game[gindex]);
2488 switch_side(&game[gindex]);
2489 update_all(game[gindex]);
2490 break;
2491 case 'c':
2492 castling_state(&game[gindex], game[gindex].b, ROWTOBOARD(c_row),
2493 COLTOBOARD(c_col),
2494 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon, 1);
2495 break;
2496 case 'i':
2497 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2498 GAME_EDIT_TEXT);
2500 if (pgn_piece_to_int(c) == -1)
2501 break;
2503 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].icon = c;
2504 break;
2505 case 'p':
2506 if (c_row == 6 || c_row == 3) {
2507 pgn_reset_enpassant(game[gindex].b);
2508 game[gindex].b[ROWTOBOARD(c_row)][COLTOBOARD(c_col)].enpassant = 1;
2510 break;
2511 default:
2512 break;
2516 static void historymode_keys(chtype c)
2518 int n, len;
2519 char *tmp, *buf;
2520 static char moveexp[255] = {0};
2522 switch (c) {
2523 case ' ':
2524 movestep = (movestep == 1) ? 2 : 1;
2525 update_history_window(game[gindex]);
2526 break;
2527 case KEY_UP:
2528 pgn_history_next(&game[gindex], game[gindex].b, (keycount > 0) ?
2529 config.jumpcount * keycount * movestep :
2530 config.jumpcount * movestep);
2531 update_all(game[gindex]);
2532 break;
2533 case KEY_DOWN:
2534 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2535 config.jumpcount * keycount * movestep :
2536 config.jumpcount * movestep);
2537 update_all(game[gindex]);
2538 break;
2539 case KEY_LEFT:
2540 pgn_history_prev(&game[gindex], game[gindex].b, (keycount) ?
2541 keycount * movestep : movestep);
2542 update_all(game[gindex]);
2543 break;
2544 case KEY_RIGHT:
2545 pgn_history_next(&game[gindex], game[gindex].b, (keycount) ?
2546 keycount * movestep : movestep);
2547 update_all(game[gindex]);
2548 break;
2549 case 'a':
2550 n = game[gindex].hindex;
2552 if (n && game[gindex].hp[n - 1]->move)
2553 n--;
2554 else
2555 break;
2557 buf = Malloc(COLS);
2558 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2559 game[gindex].hp[n]->move);
2561 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2562 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2563 -1);
2564 free(buf);
2566 if (!tmp && (!game[gindex].hp[n]->comment ||
2567 !*game[gindex].hp[n]->comment))
2568 break;
2569 else if (tmp && game[gindex].hp[n]->comment) {
2570 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2571 break;
2574 len = (tmp) ? strlen(tmp) + 1 : 1;
2575 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2576 len);
2577 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2578 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2579 update_all(game[gindex]);
2580 break;
2581 case ']':
2582 case '[':
2583 case '/':
2584 if (pgn_history_total(game[gindex].hp) < 2)
2585 break;
2587 n = 0;
2589 if (!*moveexp || c == '/') {
2590 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2591 break;
2593 strncpy(moveexp, tmp, sizeof(moveexp));
2594 n = 1;
2597 if ((n = find_move_exp(game[gindex], moveexp, n,
2598 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2599 == -1)
2600 break;
2602 game[gindex].hindex = n;
2603 pgn_board_update(&game[gindex], game[gindex].b, game[gindex].hindex);
2604 update_all(game[gindex]);
2605 break;
2606 case 'v':
2607 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2608 break;
2609 case 'V':
2610 if (game[gindex].hindex - 1 >= 0)
2611 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2612 break;
2613 case '-':
2614 case '+':
2615 rav_next_prev(&game[gindex], game[gindex].b, (c == '-') ? 0 : 1);
2616 update_all(game[gindex]);
2617 break;
2618 case 'j':
2619 if (pgn_history_total(game[gindex].hp) < 2)
2620 break;
2622 /* FIXME field validation
2623 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2624 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2625 game[gindex].htotal)) == NULL)
2626 break;
2629 if (!keycount) {
2630 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2631 NULL, NULL, NULL, 0, -1)) == NULL)
2632 break;
2634 if (!isinteger(tmp))
2635 break;
2637 n = atoi(tmp);
2639 else
2640 n = keycount;
2642 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2643 break;
2645 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2646 pgn_board_update(&game[gindex], game[gindex].b,
2647 game[gindex].hindex);
2648 update_all(game[gindex]);
2649 break;
2650 default:
2651 break;
2655 static void cleanup_all_games()
2657 int i;
2659 for (i = 0; i < gtotal; i++) {
2660 struct userdata_s *d;
2662 if (game[i].data) {
2663 stop_engine(&game[i]);
2664 d = game[i].data;
2665 free(game[i].data);
2666 game[i].data = NULL;
2671 void update_loading_window()
2673 if (!loadingw) {
2674 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2675 loadingp = new_panel(loadingw);
2676 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2679 wmove(loadingw, 0, 0);
2680 wclrtobot(loadingw);
2681 wattron(loadingw, CP_MESSAGE_BORDER);
2682 box(loadingw, ACS_VLINE, ACS_HLINE);
2683 wattroff(loadingw, CP_MESSAGE_BORDER);
2684 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2685 11 + strlen(itoa(gtotal))), "Loading... %i", gtotal);
2686 update_panels();
2687 doupdate();
2690 void init_userdata()
2692 int i;
2694 for (i = 0; i < gtotal; i++) {
2695 struct userdata_s *d = NULL;
2697 d = Calloc(1, sizeof(struct userdata_s));
2698 game[i].data = d;
2702 // Global and other keys.
2703 static int globalkeys(chtype c)
2705 static char gameexp[255] = {0};
2706 FILE *fp;
2707 char *tmp, *p;
2708 int n, i;
2709 char tfile[FILENAME_MAX];
2710 struct userdata_s *d = game[gindex].data;
2712 switch (c) {
2713 case KEY_F(10):
2714 cmessage("ABOUT", ANYKEY, "%s\n%s with %i colors and %i "
2715 "color pairs\nCopyright 2002-2006 %s", PACKAGE_STRING,
2716 curses_version(), COLORS, COLOR_PAIRS, PACKAGE_BUGREPORT);
2717 break;
2718 case 'h':
2719 if (game[gindex].mode != MODE_HISTORY) {
2720 if (!pgn_history_total(game[gindex].hp) ||
2721 (d->engine && d->engine->status == ENGINE_THINKING))
2722 return 1;
2724 game[gindex].mode = MODE_HISTORY;
2725 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2726 update_all(game[gindex]);
2727 return 1;
2730 // FIXME
2731 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2732 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2733 return 1;
2736 // FIXME Resuming from previous history could append to a RAV.
2737 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2738 if (!pushkey) {
2739 if ((c = message(NULL, YESNO, "%s",
2740 GAME_RESUME_HISTORY_TEXT)) != 'y')
2741 return 1;
2744 else {
2745 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2746 return 1;
2749 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2750 d->engine->status == ENGINE_OFFLINE)) {
2751 if (start_chess_engine(&game[gindex]) < 0)
2752 return 1;
2754 pushkey = 'h';
2755 return 1;
2758 pushkey = 0;
2759 oldhistorytotal = pgn_history_total(game[gindex].hp);
2760 game[gindex].mode = MODE_PLAY;
2761 update_all(game[gindex]);
2762 return 1;
2763 case '>':
2764 case '<':
2765 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2766 keycount : 1);
2768 if (delete_count) {
2769 markend = delete_count;
2770 pushkey = 'x';
2771 delete_count = 0;
2774 if (game[gindex].mode != MODE_EDIT) {
2775 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2777 update_all(game[gindex]);
2778 update_tag_window(game[gindex].tag);
2779 return 1;
2780 // Not sure whether to keep these.
2781 case '!': c_row = 1; return 1;
2782 case '@': c_row = 2; return 1;
2783 case '#': c_row = 3; return 1;
2784 case '$': c_row = 4; return 1;
2785 case '%': c_row = 5; return 1;
2786 case '^': c_row = 6; return 1;
2787 case '&': c_row = 7; return 1;
2788 case '*': c_row = 8; return 1;
2789 case 'A': c_col = 1; return 1;
2790 case 'B': c_col = 2; return 1;
2791 case 'C': c_col = 3; return 1;
2792 case 'D': c_col = 4; return 1;
2793 case 'E': c_col = 5; return 1;
2794 case 'F': c_col = 6; return 1;
2795 case 'G': c_col = 7; return 1;
2796 case 'H': c_col = 8; return 1;
2797 case '}':
2798 case '{':
2799 case '?':
2800 if (gtotal < 2)
2801 return 1;
2803 if (!*gameexp || c == '?') {
2804 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2805 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2806 NULL, 0, -1)) == NULL)
2807 return 1;
2809 strncpy(gameexp, tmp, sizeof(gameexp));
2812 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2813 ? keycount : 1)) ==
2815 return 1;
2817 gindex = n;
2819 if (pgn_history_total(game[gindex].hp))
2820 game[gindex].mode = MODE_HISTORY;
2822 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2823 update_all(game[gindex]);
2824 update_tag_window(game[gindex].tag);
2825 return 1;
2826 case 'J':
2827 if (gtotal < 2)
2828 return 1;
2830 /* FIXME field validation
2831 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2832 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2833 == NULL)
2834 return 1;
2837 if (!keycount) {
2838 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2839 NULL, NULL, 0, -1)) == NULL)
2840 return 1;
2842 if (!isinteger(tmp))
2843 return 1;
2845 i = atoi(tmp);
2847 else
2848 i = keycount;
2850 if (--i > gtotal - 1 || i < 0)
2851 return 1;
2853 gindex = i;
2854 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2855 update_all(game[gindex]);
2856 update_tag_window(game[gindex].tag);
2857 return 1;
2858 case 'x':
2859 pushkey = 0;
2861 if (gtotal < 2)
2862 return 1;
2864 if (keycount && !delete_count) {
2865 markstart = gindex;
2866 delete_count = keycount;
2867 update_status_notify(game[gindex], "%s (delete)",
2868 status.notify);
2869 return 1;
2872 if (markstart >= 0 && markend >= 0) {
2873 if (markstart > markend) {
2874 i = markstart;
2875 markstart = markend;
2876 markend = i;
2879 for (i = markstart; i <= markend; i++) {
2880 if (toggle_delete_flag(i))
2881 return 1;
2884 else {
2885 if (toggle_delete_flag(gindex))
2886 return 1;
2889 markstart = markend = -1;
2890 update_status_window(game[gindex]);
2891 return 1;
2892 case 'X':
2893 if (gtotal < 2) {
2894 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2895 return 1;
2898 tmp = NULL;
2900 for (i = n = 0; i < gtotal; i++) {
2901 if (TEST_FLAG(game[i].flags, GF_DELETE))
2902 n++;
2905 if (!n)
2906 tmp = GAME_DELETE_GAME_TEXT;
2907 else {
2908 if (n == gtotal) {
2909 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2910 return 1;
2913 tmp = GAME_DELETE_ALL_TEXT;
2916 if (config.deleteprompt) {
2917 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2918 return 1;
2921 delete_game((!n) ? gindex : -1);
2923 if (pgn_history_total(game[gindex].hp))
2924 game[gindex].mode = MODE_HISTORY;
2926 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2927 update_all(game[gindex]);
2928 update_tag_window(game[gindex].tag);
2929 return 1;
2930 case 'T':
2931 edit_save_tags(&game[gindex]);
2932 update_all(game[gindex]);
2933 update_tag_window(game[gindex].tag);
2934 return 1;
2935 case 't':
2936 edit_tags(game[gindex], game[gindex].b, 0);
2937 return 1;
2938 case 'r':
2939 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2940 BROWSER_PROMPT, browse_directory, NULL, '\t',
2941 -1)) == NULL)
2942 return 1;
2944 if ((tmp = word_expand(tmp)) == NULL)
2945 break;
2947 if ((fp = pgn_open(tmp)) == NULL) {
2948 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
2949 return 1;
2952 if (pgn_parse(fp))
2953 return 1;
2955 del_panel(loadingp);
2956 delwin(loadingw);
2957 loadingw = NULL;
2958 loadingp = NULL;
2959 init_userdata();
2960 strncpy(loadfile, tmp, sizeof(loadfile));
2962 if (pgn_history_total(game[gindex].hp))
2963 game[gindex].mode = MODE_HISTORY;
2965 pgn_board_update(&game[gindex], game[gindex].b, pgn_history_total(game[gindex].hp));
2966 update_all(game[gindex]);
2967 update_tag_window(game[gindex].tag);
2968 return 1;
2969 case 'S':
2970 case 's':
2971 i = -1;
2973 if (gtotal > 1) {
2974 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2975 GAME_SAVE_MULTI_TEXT);
2977 if (n == 'c')
2978 i = gindex;
2979 else if (n == 'a')
2980 i = -1;
2981 else {
2982 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2983 return 1;
2987 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2988 BROWSER_PROMPT, browse_directory, NULL,
2989 '\t', -1)) == NULL) {
2990 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2991 return 1;
2994 if ((tmp = word_expand(tmp)) == NULL)
2995 break;
2997 if (pgn_is_compressed(tmp)) {
2998 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2999 tmp = tfile;
3001 else {
3002 if ((p = strchr(tmp, '.')) != NULL) {
3003 if (strcmp(p, ".pgn") != 0) {
3004 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3005 tmp = tfile;
3008 else {
3009 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3010 tmp = tfile;
3014 if (save_pgn(tmp, 0, i)) {
3015 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3016 return 1;
3019 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3020 update_all(game[gindex]);
3021 return 1;
3022 case KEY_F(1):
3023 n = 0;
3025 switch (game[gindex].mode) {
3026 case MODE_PLAY:
3027 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3028 break;
3029 case MODE_HISTORY:
3030 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3031 break;
3032 case MODE_EDIT:
3033 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3034 break;
3035 default:
3036 break;
3039 while (c == KEY_F(1)) {
3040 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3041 mainhelp);
3043 switch (c) {
3044 case 'h':
3045 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3046 break;
3047 case 'p':
3048 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3049 break;
3050 case 'e':
3051 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3052 break;
3053 case 'g':
3054 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3055 break;
3056 default:
3057 break;
3061 return 1;
3062 case 'n':
3063 case 'N':
3064 if (c == 'N') {
3065 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3066 return 1;
3069 if (c == 'n') {
3070 pgn_new_game();
3071 add_custom_tags(&game[gindex].tag);
3072 d = Calloc(1, sizeof(struct userdata_s));
3073 game[gindex].data = d;
3075 else {
3076 cleanup_all_games();
3077 pgn_parse(NULL);
3078 add_custom_tags(&game[gindex].tag);
3079 pgn_board_init(game[gindex].b);
3080 d = Calloc(1, sizeof(struct userdata_s));
3081 game[gindex].data = d;
3084 game[gindex].mode = MODE_PLAY;
3085 c_row = (game[gindex].side == WHITE) ? 2 : 7;
3086 c_col = 4;
3087 update_status_notify(game[gindex], NULL);
3088 update_all(game[gindex]);
3089 update_tag_window(game[gindex].tag);
3090 return 1;
3091 case CTRL('L'):
3092 endwin();
3093 keypad(boardw, TRUE);
3094 refresh_all();
3095 return 1;
3096 case KEY_ESCAPE:
3097 sp.icon = sp.row = sp.col = 0;
3098 markend = markstart = 0;
3100 if (keycount) {
3101 keycount = 0;
3102 update_status_notify(game[gindex], NULL);
3105 if (config.validmoves)
3106 pgn_reset_valid_moves(game[gindex].b);
3108 return 1;
3109 case '0' ... '9':
3110 n = c - '0';
3112 if (keycount)
3113 keycount = keycount * 10 + n;
3114 else
3115 keycount = n;
3117 update_status_notify(game[gindex], "Repeat %i", keycount);
3118 return -1;
3119 case KEY_UP:
3120 if (game[gindex].mode == MODE_HISTORY)
3121 return 0;
3123 if (keycount) {
3124 c_row += keycount;
3125 pushkey = '\n';
3127 else
3128 c_row++;
3130 if (c_row > 8)
3131 c_row = 1;
3133 return 1;
3134 case KEY_DOWN:
3135 if (game[gindex].mode == MODE_HISTORY)
3136 return 0;
3138 if (keycount) {
3139 c_row -= keycount;
3140 pushkey = '\n';
3141 update_status_notify(game[gindex], NULL);
3143 else
3144 c_row--;
3146 if (c_row < 1)
3147 c_row = 8;
3149 return 1;
3150 case KEY_LEFT:
3151 if (game[gindex].mode == MODE_HISTORY)
3152 return 0;
3154 if (keycount) {
3155 c_col -= keycount;
3156 pushkey = '\n';
3158 else
3159 c_col--;
3161 if (c_col < 1)
3162 c_col = 8;
3164 return 1;
3165 case KEY_RIGHT:
3166 if (game[gindex].mode == MODE_HISTORY)
3167 return 0;
3169 if (keycount) {
3170 c_col += keycount;
3171 pushkey = '\n';
3173 else
3174 c_col++;
3176 if (c_col > 8)
3177 c_col = 1;
3179 return 1;
3180 case 'e':
3181 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3182 MODE_PLAY)
3183 return 1;
3185 // Don't edit a running game (for now).
3186 if (pgn_history_total(game[gindex].hp))
3187 return 1;
3189 if (game[gindex].mode != MODE_EDIT) {
3190 pgn_board_init_fen(&game[gindex], game[gindex].b, NULL);
3191 board_details++;
3192 game[gindex].mode = MODE_EDIT;
3193 update_all(game[gindex]);
3194 return 1;
3197 board_details--;
3198 pgn_tag_add(&game[gindex].tag, "FEN",
3199 pgn_game_to_fen(game[gindex], game[gindex].b));
3200 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3201 pgn_tag_sort(game[gindex].tag);
3202 game[gindex].mode = MODE_PLAY;
3203 update_all(game[gindex]);
3204 return 1;
3205 case 'Q':
3206 quit = 1;
3207 return 1;
3208 case KEY_RESIZE:
3209 do_window_resize();
3210 return 1;
3211 #ifdef DEBUG
3212 case 'O':
3213 message("DEBUG BOARD", ANYKEY, "%s", debug_board(game[gindex].b));
3214 return 1;
3215 #endif
3216 case 0:
3217 default:
3218 break;
3221 return 0;
3224 void game_loop()
3226 int error_recover = 0;
3228 c_row = 2, c_col = 5;
3229 gindex = gtotal - 1;
3231 if (pgn_history_total(game[gindex].hp))
3232 game[gindex].mode = MODE_HISTORY;
3233 else
3234 game[gindex].mode = MODE_PLAY;
3236 if (game[gindex].mode == MODE_HISTORY) {
3237 pgn_board_update(&game[gindex], game[gindex].b,
3238 pgn_history_total(game[gindex].hp));
3241 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3242 movestep = 2;
3243 paused = 1; //FIXME clock
3244 flushinp();
3245 update_all(game[gindex]);
3246 update_tag_window(game[gindex].tag);
3247 wtimeout(boardw, 70);
3249 while (!quit) {
3250 int c = 0;
3251 int n = 0, i;
3252 char fdbuf[8192] = {0};
3253 int len;
3254 struct timeval tv = {0, 0};
3255 fd_set rfds;
3256 struct userdata_s *d = NULL;
3258 FD_ZERO(&rfds);
3260 for (i = 0; i < gtotal; i++) {
3261 d = game[i].data;
3263 if (d->engine) {
3264 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3265 if (d->engine->fd[ENGINE_IN_FD] > n)
3266 n = d->engine->fd[ENGINE_IN_FD];
3268 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3273 if (n) {
3274 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3275 for (i = 0; i < gtotal; i++) {
3276 d = game[i].data;
3278 if (d->engine) {
3279 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3280 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3281 sizeof(fdbuf));
3283 if (len == -1) {
3284 if (errno != EAGAIN) {
3285 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3286 strerror(errno));
3287 waitpid(d->engine->pid, &n, 0);
3288 free(d->engine);
3289 d->engine = NULL;
3290 break;
3293 else {
3294 if (len) {
3295 parse_engine_output(&game[i], fdbuf);
3296 update_all(game[gindex]);
3303 else {
3304 if (n == -1)
3305 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3306 else {
3307 /* timeout */
3312 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER) && game[gindex].mode
3313 != MODE_HISTORY) {
3314 game[gindex].mode = MODE_HISTORY;
3315 update_all(game[gindex]);
3318 error_recover = 0;
3319 draw_board(&game[gindex], board_details);
3320 wmove(boardw, ROWTOMATRIX(c_row), COLTOMATRIX(c_col));
3322 if (!paused) {
3325 refresh_all();
3327 if (pushkey)
3328 c = pushkey;
3329 else {
3330 if ((c = wgetch(boardw)) == ERR)
3331 continue;
3334 if (!keycount && status.notify)
3335 update_status_notify(game[gindex], NULL);
3338 if ((n = globalkeys(c)) == 1) {
3339 keycount = 0;
3340 continue;
3342 else if (n == -1)
3343 continue;
3345 switch (game[gindex].mode) {
3346 case MODE_EDIT:
3347 editmode_keys(c);
3348 break;
3349 case MODE_PLAY:
3350 if (playmode_keys(c))
3351 continue;
3352 break;
3353 case MODE_HISTORY:
3354 historymode_keys(c);
3355 break;
3356 default:
3357 break;
3360 keycount = 0;
3364 void usage(const char *pn, int ret)
3366 fprintf((ret) ? stderr : stdout, "%s",
3367 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3368 " -p Load PGN file.\n"
3369 " -V Validate a game file.\n"
3370 " -S Validate and output a PGN formatted game.\n"
3371 " -R Like -S but write a reduced PGN formatted game.\n"
3372 " -t Also write custom PGN tags from config file.\n"
3373 " -E Stop processing on file parsing error (overrides config).\n"
3374 " -v Version information.\n"
3375 " -h This help text.\n");
3377 exit(ret);
3380 void cleanup_all()
3382 cleanup_all_games();
3383 pgn_free_all();
3384 del_panel(boardp);
3385 del_panel(historyp);
3386 del_panel(statusp);
3387 del_panel(tagp);
3388 delwin(boardw);
3389 delwin(historyw);
3390 delwin(statusw);
3391 delwin(tagw);
3392 endwin();
3395 void catch_signal(int which)
3397 switch (which) {
3398 case SIGINT:
3399 case SIGPIPE:
3400 if (which == SIGPIPE && quit)
3401 break;
3403 if (which == SIGPIPE)
3404 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3406 cleanup_all();
3407 exit(EXIT_FAILURE);
3408 break;
3409 case SIGSTOP:
3410 savetty();
3411 break;
3412 case SIGCONT:
3413 resetty();
3414 keypad(boardw, TRUE);
3415 curs_set(0);
3416 cbreak();
3417 noecho();
3418 break;
3419 case SIGUSR1:
3420 if (curses_initialized) {
3421 update_loading_window(game[gindex]);
3422 break;
3425 fprintf(stderr, "Loading... %i\r", gtotal);
3426 fflush(stderr);
3427 break;
3428 default:
3429 break;
3433 static void set_defaults()
3435 filetype = NO_FILE;
3436 set_config_defaults();
3439 int main(int argc, char *argv[])
3441 int opt;
3442 struct stat st;
3443 char buf[FILENAME_MAX];
3444 char datadir[FILENAME_MAX];
3445 int ret = EXIT_SUCCESS;
3446 int validate_only = 0, validate_and_write = 0, reduced = 0;
3447 int write_custom_tags = 0;
3448 FILE *fp;
3449 int i;
3451 if ((config.pwd = getpwuid(getuid())) == NULL)
3452 err(EXIT_FAILURE, "getpwuid()");
3454 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3455 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3456 config.ccfile = strdup(buf);
3457 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3458 config.nagfile = strdup(buf);
3459 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3460 config.agonyfile = strdup(buf);
3461 snprintf(buf, sizeof(buf), "%s/config", datadir);
3462 config.configfile = strdup(buf);
3463 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3464 config.fifo = strdup(buf);
3466 if (stat(datadir, &st) == -1) {
3467 if (errno == ENOENT) {
3468 if (mkdir(datadir, 0755) == -1)
3469 err(EXIT_FAILURE, "%s", datadir);
3471 else
3472 err(EXIT_FAILURE, "%s", datadir);
3474 stat(datadir, &st);
3477 if (!S_ISDIR(st.st_mode))
3478 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3480 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3481 if (mkfifo(config.fifo, 0600) == -1)
3482 err(EXIT_FAILURE, "%s", config.fifo);
3485 set_defaults();
3487 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3488 switch (opt) {
3489 case 't':
3490 write_custom_tags = 1;
3491 break;
3492 case 'E':
3493 config.stoponerror = 1;
3494 break;
3495 case 'R':
3496 reduced = 1;
3497 case 'S':
3498 validate_and_write = 1;
3499 case 'V':
3500 validate_only = 1;
3501 break;
3502 case 'v':
3503 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3504 COPYRIGHT);
3505 exit(EXIT_SUCCESS);
3506 case 'p':
3507 filetype = PGN_FILE;
3508 strncpy(loadfile, optarg, sizeof(loadfile));
3509 break;
3510 case 'h':
3511 default:
3512 usage(argv[0], EXIT_SUCCESS);
3516 if ((validate_only || validate_and_write) && !*loadfile)
3517 usage(argv[0], EXIT_FAILURE);
3519 if (access(config.configfile, R_OK) == 0)
3520 parse_rcfile(config.configfile);
3522 signal(SIGPIPE, catch_signal);
3523 signal(SIGCONT, catch_signal);
3524 signal(SIGSTOP, catch_signal);
3525 signal(SIGINT, catch_signal);
3526 signal(SIGUSR1, catch_signal);
3528 srandom(getpid());
3530 switch (filetype) {
3531 case PGN_FILE:
3532 if ((fp = pgn_open(loadfile)) == NULL)
3533 err(EXIT_FAILURE, "%s", loadfile);
3535 ret = pgn_parse(fp);
3536 break;
3537 case FEN_FILE:
3538 //ret = parse_fen_file(loadfile);
3539 break;
3540 case EPD_FILE: // Not implemented.
3541 case NO_FILE:
3542 default:
3543 // No file specified. Empty game.
3544 ret = pgn_parse(NULL);
3545 add_custom_tags(&game[gindex].tag);
3546 break;
3549 if (validate_only || validate_and_write) {
3550 if (validate_and_write) {
3551 for (i = 0; i < gtotal; i++) {
3552 if (write_custom_tags)
3553 add_custom_tags(&game[i].tag);
3555 pgn_write(stdout, game[i]);
3559 pgn_free_all();
3560 exit(ret);
3562 else if (ret)
3563 exit(ret);
3565 init_userdata();
3567 if (initscr() == NULL)
3568 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3569 else
3570 curses_initialized = 1;
3572 if (LINES < 24 || COLS < 80) {
3573 endwin();
3574 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3577 if (has_colors() == TRUE && start_color() == OK)
3578 init_color_pairs();
3580 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3581 boardp = new_panel(boardw);
3582 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3583 COLS - HISTORY_WIDTH);
3584 historyp = new_panel(historyw);
3585 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3586 statusp = new_panel(statusw);
3587 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3588 tagp = new_panel(tagw);
3589 keypad(boardw, TRUE);
3590 // leaveok(boardw, TRUE);
3591 leaveok(tagw, TRUE);
3592 leaveok(statusw, TRUE);
3593 leaveok(historyw, TRUE);
3594 curs_set(0);
3595 cbreak();
3596 noecho();
3597 draw_window_decor();
3598 game_loop();
3599 cleanup_all();
3600 exit(EXIT_SUCCESS);