Added window resizing for xterm. Needs at least an 80x24 terminal.
[cboard.git] / src / cboard.c
bloba5b1a4fd1e260a52a105dae311ad0c89c6d4e4cb
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_DIRENT_H
40 #include <dirent.h>
41 #endif
43 #ifdef HAVE_MENU_H
44 #include <menu.h>
45 #endif
47 #ifdef HAVE_REGEX_H
48 #include <regex.h>
49 #endif
51 #include "chess.h"
52 #include "conf.h"
53 #include "window.h"
54 #include "colors.h"
55 #include "input.h"
56 #include "misc.h"
57 #include "engine.h"
58 #include "rcfile.h"
59 #include "strings.h"
60 #include "cboard.h"
62 #ifdef DEBUG
63 #include "debug.h"
64 #endif
66 #ifdef WITH_DMALLOC
67 #include <dmalloc.h>
68 #endif
70 static char *str_etc(const char *str, int maxlen, int rev)
72 int len = strlen(str);
73 static char buf[80], *p = buf;
74 int i;
76 strncpy(buf, str, sizeof(buf));
78 if (len > maxlen) {
79 if (rev) {
80 p = buf;
81 *p++ = '.';
82 *p++ = '.';
83 *p++ = '.';
85 for (i = 0; i < maxlen + 3; i++)
86 *p++ = buf[(len - maxlen) + i + 3];
88 else {
89 p = buf + maxlen - 4;
90 *p++ = '.';
91 *p++ = '.';
92 *p++ = '.';
95 *p = '\0';
98 return buf;
101 static char *real_filename(char *path)
103 char *tmp;
104 static char buf[FILENAME_MAX];
105 int slash = 0;
107 if (!path[0])
108 return NULL;
110 strncpy(buf, path, sizeof(buf));
111 tmp = buf;
113 if (tmp[strlen(tmp) - 1] == '/') {
114 tmp[strlen(tmp) - 1] = 0;
115 slash = 1;
118 if ((tmp = strrchr(tmp, '/')) == NULL)
119 return path;
121 if (slash)
122 buf[strlen(tmp)] = '/';
124 return ++tmp;
127 /* FIXME castling */
128 static void update_cursor(GAME g, int idx, int *r, int *c)
130 char *p;
131 int len;
132 int t = history_total(g.hp);
135 * If not deincremented then r and c would be the next move.
137 idx--;
139 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
140 *r = *c = 0;
141 return;
144 p = g.hp[idx]->move;
145 len = strlen(p);
147 if (*p == 'O') {
148 if (len <= 4)
149 *c = 7;
150 else
151 *c = 3;
153 *r = (g.turn == WHITE) ? 8 : 1;
154 return;
157 p += len;
159 while (!isdigit(*p))
160 p--;
162 *r = ROWTOINT(*p--);
163 *c = COLTOINT(*p);
166 static int init_nag()
168 FILE *fp;
169 char line[LINE_MAX];
170 int i = 0;
172 if ((fp = fopen(config.nagfile, "r")) == NULL) {
173 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
174 return 1;
177 while (!feof(fp)) {
178 if (fscanf(fp, " %[^\n] ", line) == 1) {
179 nags = Realloc(nags, (i + 2) * sizeof(struct nag_s));
180 nags[i].line = strdup(line);
181 i++;
185 if (nags)
186 nags[i].line = NULL;
187 return 0;
190 char *history_edit_nag(void *arg)
192 WINDOW *win, *subw;
193 PANEL *panel;
194 ITEM **mitems = NULL;
195 MENU *menu;
196 int i = 0, n;
197 int itemcount = 0;
198 int rows, cols;
199 char *mbuf = NULL;
200 HISTORY *anno = (HISTORY *)arg;
202 if (!nags) {
203 if (init_nag())
204 return NULL;
207 i = 0;
208 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
209 mitems[i++] = new_item(NONE, NULL);
211 for (n = 0; nags[n].line; n++, i++) {
212 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
213 mitems[i] = new_item(nags[n].line, NULL);
216 mitems[i] = NULL;
217 menu = new_menu(mitems);
218 scale_menu(menu, &rows, &cols);
220 win = newwin(rows + 4, cols + 2, CALCPOSY(rows) - 2, CALCPOSX(cols));
221 set_menu_win(menu, win);
222 subw = derwin(win, rows, cols, 2, 1);
223 set_menu_sub(menu, subw);
224 set_menu_fore(menu, A_REVERSE);
225 set_menu_grey(menu, A_NORMAL);
226 set_menu_mark(menu, NULL);
227 set_menu_spacing(menu, 0, 0, 0);
228 menu_opts_off(menu, O_NONCYCLIC|O_SHOWDESC|O_ONEVALUE);
229 post_menu(menu);
230 panel = new_panel(win);
231 cbreak();
232 noecho();
233 keypad(win, TRUE);
234 set_menu_pattern(menu, mbuf);
235 wbkgd(win, CP_MESSAGE_WINDOW);
236 draw_window_title(win, NAG_EDIT_TITLE, cols + 2, CP_HISTORY_TITLE,
237 CP_HISTORY_BORDER);
239 for (i = 0; i < MAX_PGN_NAG; i++) {
240 if (anno->nag[i] && anno->nag[i] <= item_count(menu)) {
241 set_item_value(mitems[anno->nag[i]], TRUE);
242 set_current_item(menu, mitems[anno->nag[i]]);
243 itemcount++;
247 while (1) {
248 int c;
249 char *tmp;
250 char buf[cols - 4];
252 wattron(win, A_REVERSE);
254 for (c = 1; c < (cols + 2) - 1; c++)
255 mvwprintw(win, rows + 2, c, " ");
257 c = item_index(current_item(menu)) + 1;
259 snprintf(buf, sizeof(buf), "Item %i of %i (%i of %i selected) %s", c,
260 item_count(menu), itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
261 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
263 wattroff(win, A_REVERSE);
265 if (!itemcount) {
266 for (i = 0; mitems[i]; i++)
267 set_item_value(mitems[i], FALSE);
269 set_item_value(mitems[0], TRUE);
271 else
272 set_item_value(mitems[0], FALSE);
274 /* This nl() statement needs to be here because NL is recognized
275 * for some reason after the first selection.
277 nl();
278 update_panels();
279 doupdate();
281 c = wgetch(win);
283 switch (c) {
284 int found;
286 case CTRL('G'):
287 help(NAG_EDIT_HELP, ANYKEY, naghelp);
288 break;
289 case KEY_RIGHT:
290 if (!itemcount)
291 break;
293 found = 0;
295 for (i = item_index(current_item(menu)) + 1; mitems[i]; i++) {
296 if (item_value(mitems[i]) == TRUE) {
297 found = i;
298 break;
302 if (!found) {
303 for (i = 0; mitems[i]; 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_LEFT:
314 if (!itemcount)
315 break;
317 found = 0;
319 for (i = item_index(current_item(menu)) - 1; i > 0; i--) {
320 if (item_value(mitems[i]) == TRUE) {
321 found = i;
322 break;
326 if (!found) {
327 for (i = item_count(menu) - 1; i > 0; i--) {
328 if (item_value(mitems[i]) == TRUE) {
329 found = i;
330 break;
335 set_current_item(menu, mitems[found]);
336 break;
337 case KEY_HOME:
338 menu_driver(menu, REQ_FIRST_ITEM);
339 break;
340 case KEY_END:
341 menu_driver(menu, REQ_LAST_ITEM);
342 break;
343 case KEY_UP:
344 menu_driver(menu, REQ_UP_ITEM);
345 break;
346 case KEY_DOWN:
347 menu_driver(menu, REQ_DOWN_ITEM);
348 break;
349 case KEY_PPAGE:
350 case CTRL('P'):
351 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
352 menu_driver(menu, REQ_FIRST_ITEM);
353 break;
354 case KEY_NPAGE:
355 case CTRL('N'):
356 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
357 menu_driver(menu, REQ_LAST_ITEM);
358 break;
359 case ' ':
360 if (item_index(current_item(menu)) == 0 &&
361 item_value(current_item(menu)) == FALSE) {
362 itemcount = 0;
363 break;
366 if (item_value(current_item(menu)) == TRUE) {
367 set_item_value(current_item(menu), FALSE);
368 itemcount--;
370 else {
371 if (itemcount + 1 > MAX_PGN_NAG)
372 break;
374 set_item_value(current_item(menu), TRUE);
375 itemcount++;
378 SET_FLAG(game[gindex].flags, GF_MODIFIED);
379 break;
380 case '\n':
381 goto gotitem;
382 break;
383 case KEY_ESCAPE:
384 goto done;
385 break;
386 default:
387 tmp = menu_pattern(menu);
389 if (tmp && tmp[strlen(tmp) - 1] != c) {
390 menu_driver(menu, REQ_CLEAR_PATTERN);
391 menu_driver(menu, c);
393 else {
394 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
395 menu_driver(menu, c);
398 break;
402 gotitem:
403 for (i = 0; i < MAX_PGN_NAG; i++)
404 anno->nag[i] = 0;
406 for (i = 0, n = 0; mitems[i] && n < MAX_PGN_NAG; i++) {
407 if (item_value(mitems[i]) == TRUE)
408 anno->nag[n++] = i;
411 done:
412 unpost_menu(menu);
413 free_menu(menu);
415 for (i = 0; mitems[i]; i++)
416 free_item(mitems[i]);
418 free(mitems);
419 del_panel(panel);
420 delwin(subw);
421 delwin(win);
422 return NULL;
425 static void view_nag(void *arg)
427 HISTORY *h = (HISTORY *)arg;
428 char buf[80];
429 char line[LINE_MAX] = {0};
430 int i = 0;
432 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
434 if (!nags) {
435 if (init_nag())
436 return;
439 for (i = 0; i < MAX_PGN_NAG; i++) {
440 if (!h->nag[i])
441 break;
443 strncat(line, nags[h->nag[i] - 1].line, sizeof(line));
444 strncat(line, "\n", sizeof(line));
447 line[strlen(line) - 1] = 0;
448 message(buf, ANYKEY, "%s", line);
451 void view_annotation(HISTORY h)
453 char buf[MAX_SAN_MOVE_LEN + strlen(ANNOTATION_VIEW_TITLE) + 4];
454 int nag = 0, comment = 0;
456 if (h.comment && h.comment[0])
457 comment++;
459 if (h.nag[0])
460 nag++;
462 if (!nag && !comment)
463 return;
465 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
467 if (comment)
468 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
469 (nag) ? "Press 'n' to view NAG" : NULL,
470 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
471 (nag) ? 'n' : 0, "%s", h.comment);
472 else
473 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
474 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
477 static void cleanup(WINDOW *win, WINDOW *subw, PANEL *panel, MENU *menu,
478 ITEM **items, struct d_entries *entries)
480 int i;
482 unpost_menu(menu);
483 free_menu(menu);
485 for (i = 0; items[i]; i++)
486 free_item(items[i]);
488 free(items);
490 if (entries) {
491 for (i = 0; entries[i].name; i++) {
492 free(entries[i].name);
493 free(entries[i].fancy);
496 free(entries);
499 del_panel(panel);
500 delwin(subw);
501 delwin(win);
504 static int sort_entries(const void *s1, const void *s2)
506 const struct d_entries *ss1 = s1;
507 const struct d_entries *ss2 = s2;
509 return strcmp(ss1->name, ss2->name);
512 static struct d_entries *get_directory_entries(const char *path)
514 DIR *dp;
515 struct dirent *entry;
516 struct d_entries *entries = NULL;
517 int n = 0;
519 if ((dp = opendir(path)) == NULL)
520 return NULL;
522 while ((entry = readdir(dp)) != NULL) {
523 struct stat st;
524 int len;
525 char tbuf[64 + 1] = {0}; //FIXME
526 struct tm *tp;
527 char buf[FILENAME_MAX];
528 char *tmp;
529 size_t size;
531 if (entry->d_name[0] == '.' && entry->d_name[1] == 0)
532 continue;
534 snprintf(buf, sizeof(buf), "%s/%s", path, entry->d_name);
536 if (stat(buf, &st) == -1)
537 continue;
539 size = st.st_size ;
540 entries = Realloc(entries, (n + 2) * sizeof(struct d_entries));
541 entries[n].name = strdup(buf);
542 tmp = real_filename(buf);
543 len = strlen(tmp) + 2;
544 entries[n].fancy = Malloc(len);
545 strncpy(entries[n].fancy, tmp, len);
547 if (S_ISDIR(st.st_mode))
548 entries[n].fancy[len - 2] = '/';
550 tp = localtime(&st.st_mtime);
551 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
553 snprintf(entries[n].desc, sizeof(entries[n].desc), "%-7i %s",
554 size, tbuf);
556 memset(&entries[++n], '\0', sizeof(struct d_entries));
559 closedir(dp);
560 qsort(entries, n, sizeof(struct d_entries), sort_entries);
561 return entries;
564 char *browse_directory(void *arg)
566 int i;
567 char path[FILENAME_MAX] = {0};
568 static char file[FILENAME_MAX];
569 char *oldwd = getcwd(NULL, 0);
570 DIR *dp;
571 char *inputstr = (char *)arg;
572 int initkey = (inputstr) ? inputstr[0] : 0;
574 if (config.savedirectory) {
575 if ((dp = opendir(config.savedirectory)) == NULL) {
576 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
577 strerror(errno));
578 getcwd(path, sizeof(path));
580 else {
581 closedir(dp);
582 strncpy(path, config.savedirectory, sizeof(path));
585 else
586 getcwd(path, sizeof(path));
588 again:
589 while (1) {
590 WINDOW *win, *subw;
591 PANEL *panel;
592 ITEM **mitems = NULL;
593 MENU *menu;
594 char *tmp = NULL;
595 int rows, cols;
596 int selected = -1;
597 char *mbuf = NULL;
598 struct d_entries *entries = NULL;
599 struct stat st;
600 int idx = 0;
601 int len = strlen(path);
603 /* /some/path/blah/../ */
604 if (path[len - 1] == '.' && path[len - 2] == '.' &&
605 path[len - 3] == '/') {
606 tmp = path;
607 tmp += strlen(path) - 5;
609 /* /some/path/ */
610 while (*--tmp != '/')
611 *tmp = '\0';
613 if (!*path) {
614 path[0] = '/';
615 path[1] = '\0';
619 if (path[1] && path[strlen(path) - 1] == '/')
620 path[strlen(path) - 1] = '\0';
622 if ((entries = get_directory_entries(path)) == NULL) {
623 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
624 return NULL;
627 for (i = 0; entries[i].name; i++) {
628 mitems = Realloc(mitems, (idx + 2) * sizeof(ITEM));
629 mitems[idx++] = new_item(entries[i].fancy, entries[i].desc);
632 mitems[idx] = NULL;
633 menu = new_menu(mitems);
634 scale_menu(menu, &rows, &cols);
636 if (cols < strlen(path))
637 cols = strlen(path);
639 if (cols < strlen(HELP_PROMPT))
640 cols = strlen(HELP_PROMPT);
642 rows = (LINES / 5) * 4;
643 cols += 2;
645 win = newwin(rows + 4, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
646 set_menu_format(menu, rows, 0);
647 set_menu_win(menu, win);
648 subw = derwin(win, rows, cols - 2, 2, 1);
649 set_menu_sub(menu, subw);
650 set_menu_fore(menu, A_REVERSE);
651 set_menu_grey(menu, A_NORMAL);
652 set_menu_mark(menu, NULL);
653 set_menu_spacing(menu, 2, 0, 0);
654 menu_opts_off(menu, O_NONCYCLIC);
655 post_menu(menu);
656 panel = new_panel(win);
658 draw_window_title(win, path, cols, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
659 draw_prompt(win, rows + 2, cols, HELP_PROMPT, CP_MESSAGE_PROMPT);
661 cbreak();
662 noecho();
663 keypad(win, TRUE);
664 set_menu_pattern(menu, mbuf);
666 if (isgraph(initkey)) {
667 menu_driver(menu, initkey);
668 initkey = '\0';
671 while (1) {
672 int c;
674 /* This nl() statement needs to be here because NL is recognized
675 * for some reason after the first selection.
677 nl();
678 update_panels();
679 doupdate();
681 c = wgetch(win);
683 switch (c) {
684 case CTRL('P'):
685 case KEY_PPAGE:
686 menu_driver(menu, REQ_SCR_UPAGE);
687 break;
688 case ' ':
689 case CTRL('N'):
690 case KEY_NPAGE:
691 menu_driver(menu, REQ_SCR_DPAGE);
692 break;
693 case KEY_UP:
694 menu_driver(menu, REQ_UP_ITEM);
695 break;
696 case KEY_DOWN:
697 menu_driver(menu, REQ_DOWN_ITEM);
698 break;
699 case '\n':
700 selected = item_index(current_item(menu));
701 goto gotitem;
702 break;
703 case KEY_ESCAPE:
704 cleanup(win, subw, panel, menu, mitems, entries);
705 file[0] = '\0';
706 goto done;
707 break;
708 case CTRL('G'):
709 help(BROWSER_HELP, ANYKEY, file_browser_help);
710 break;
711 case '~':
712 if ((tmp = getenv("HOME")) == NULL) {
713 cmessage(ERROR, ANYKEY, "%s", E_HOME_ENV);
714 break;
717 strncpy(path, tmp, sizeof(path));
718 cleanup(win, subw, panel, menu, mitems, entries);
719 goto again;
720 break;
721 case CTRL('X'):
722 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
723 == NULL)
724 break;
726 tmp = tilde_expand(tmp);
727 strncpy(path, tmp, sizeof(path));
728 cleanup(win, subw, panel, menu, mitems, entries);
729 goto again;
730 break;
731 default:
732 tmp = menu_pattern(menu);
734 if (tmp && tmp[strlen(tmp) - 1] != c) {
735 menu_driver(menu, REQ_CLEAR_PATTERN);
736 menu_driver(menu, c);
738 else {
739 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
740 menu_driver(menu, c);
743 break;
747 gotitem:
748 snprintf(file, sizeof(file), "%s", entries[selected].name);
750 if (stat(file, &st) == -1) {
751 cmessage(ERROR, ANYKEY, "%s", strerror(errno));
752 cleanup(win, subw, panel, menu, mitems, entries);
753 continue;
756 cleanup(win, subw, panel, menu, mitems, entries);
758 if (S_ISDIR(st.st_mode)) {
759 strncpy(path, file, sizeof(path));
760 continue;
763 if (S_ISREG(st.st_mode))
764 break;
766 cmessage(ERROR, ANYKEY, "%s", E_NOTAREGFILE);
769 done:
770 chdir(oldwd);
771 free(oldwd);
772 return (*file) ? file : NULL;
774 static int init_country_codes()
776 FILE *fp;
777 char line[LINE_MAX], *s;
778 int cindex = 0;
780 if ((fp = fopen(config.ccfile, "r")) == NULL) {
781 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
782 return 1;
785 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
786 char *tmp;
788 if ((tmp = strsep(&s, " ")) == NULL)
789 continue;
791 s = trim(s);
792 tmp = trim(tmp);
794 if (!s || !tmp)
795 continue;
797 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
798 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
799 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
800 cindex++;
803 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
804 fclose(fp);
806 return 0;
809 char *country_codes(void *arg)
811 WINDOW *win, *subw;
812 PANEL *panel;
813 ITEM **mitems = NULL;
814 MENU *menu;
815 int i = 0, n;
816 int rows, cols;
817 char *mbuf = NULL;
818 char *tmp = NULL;
820 if (!ccodes) {
821 if (init_country_codes())
822 return NULL;
825 for (n = i = 0; ccodes[n].code[0]; n++, i++) {
826 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
827 mitems[i] = new_item(ccodes[n].country, ccodes[n].code);
830 mitems[i] = NULL;
831 menu = new_menu(mitems);
832 scale_menu(menu, &rows, &cols);
834 if (cols < strlen(HELP_PROMPT) + 21)
835 cols = strlen(HELP_PROMPT) + 21;
837 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
838 set_menu_win(menu, win);
839 subw = derwin(win, rows, cols + 2, 2, 1);
840 set_menu_sub(menu, subw);
841 set_menu_fore(menu, A_REVERSE);
842 set_menu_grey(menu, A_NORMAL);
843 set_menu_mark(menu, NULL);
844 set_menu_spacing(menu, 0, 0, 0);
845 menu_opts_off(menu, O_NONCYCLIC);
846 post_menu(menu);
847 panel = new_panel(win);
848 cbreak();
849 noecho();
850 keypad(win, TRUE);
851 set_menu_pattern(menu, mbuf);
852 wbkgd(win, CP_MESSAGE_WINDOW);
853 draw_window_title(win, CC_TITLE, cols + 4, CP_MESSAGE_TITLE,
854 CP_MESSAGE_BORDER);
856 while (1) {
857 int c;
858 char buf[cols - 4];
860 wattron(win, A_REVERSE);
862 for (c = 1; c < (cols + 2) - 1; c++)
863 mvwprintw(win, rows + 2, c, " ");
865 c = item_index(current_item(menu)) + 1;
867 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR, c,
868 N_OF_N_STR, item_count(menu), HELP_PROMPT);
869 draw_prompt(win, rows + 2, cols + 2, buf, CP_MESSAGE_PROMPT);
871 wattroff(win, A_REVERSE);
873 /* This nl() statement needs to be here because NL is recognized
874 * for some reason after the first selection.
876 nl();
877 update_panels();
878 doupdate();
880 c = wgetch(win);
882 switch (c) {
883 case CTRL('G'):
884 help(CC_KEY_HELP, ANYKEY, cc_help);
885 break;
886 case KEY_HOME:
887 menu_driver(menu, REQ_FIRST_ITEM);
888 break;
889 case KEY_END:
890 menu_driver(menu, REQ_LAST_ITEM);
891 break;
892 case KEY_UP:
893 menu_driver(menu, REQ_UP_ITEM);
894 break;
895 case KEY_DOWN:
896 menu_driver(menu, REQ_DOWN_ITEM);
897 break;
898 case KEY_PPAGE:
899 case CTRL('P'):
900 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
901 menu_driver(menu, REQ_FIRST_ITEM);
902 break;
903 case ' ':
904 case KEY_NPAGE:
905 case CTRL('N'):
906 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
907 menu_driver(menu, REQ_LAST_ITEM);
908 break;
909 case '\n':
910 tmp = (char *)item_description(current_item(menu));
911 goto done;
912 break;
913 case KEY_ESCAPE:
914 tmp = NULL;
915 goto done;
916 break;
917 default:
918 tmp = menu_pattern(menu);
920 if (tmp && tmp[strlen(tmp) - 1] != c) {
921 menu_driver(menu, REQ_CLEAR_PATTERN);
922 menu_driver(menu, c);
924 else {
925 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
926 menu_driver(menu, c);
929 break;
933 done:
934 unpost_menu(menu);
935 free_menu(menu);
937 for (i = 0; mitems[i]; i++)
938 free_item(mitems[i]);
940 del_panel(panel);
941 delwin(subw);
942 delwin(win);
943 return tmp;
946 static void add_custom_tags(TAG **t, unsigned char *n)
948 int i;
950 for (i = 0; i < config.tindex; i++)
951 pgn_add_tag(&(*t), &(*n), config.tag[i].name,
952 config.tag[i].value);
955 TAG *edit_tags(GAME g, BOARD b, int edit)
957 TAG *data = NULL;
958 struct tm tp;
959 unsigned char data_index = 0;
960 int n, lastindex = 0;
961 int len;
963 /* Edit the backup copy, not the original in case the save fails. */
964 for (n = 0; n < g.tindex; n++)
965 pgn_add_tag(&data, &data_index, g.tag[n].name, g.tag[n].value);
967 while (1) {
968 WINDOW *win, *subw;
969 PANEL *panel;
970 ITEM **mitems = NULL;
971 MENU *menu;
972 int i;
973 char buf[76] = {0};
974 char *tmp = NULL;
975 int rows, cols;
976 int selected = -1;
977 char *mbuf = NULL;
978 int nlen = 0, vlen = 0;
980 for (i = 0; i < data_index; i++) {
981 mitems = Realloc(mitems, (i + 2) * sizeof(ITEM));
983 if (data[i].value[0]) {
984 nlen = strlen(data[i].name);
985 vlen = strlen(data[i].value);
987 /* The +6 is for the menu padding. */
988 mitems[i] = new_item(data[i].name,
989 (nlen + vlen + 6 >= MAX_VALUE_WIDTH)
990 ? PRESS_ENTER : data[i].value);
992 else
993 mitems[i] = new_item(data[i].name, UNKNOWN);
996 mitems[i] = NULL;
997 menu = new_menu(mitems);
998 scale_menu(menu, &rows, &cols);
1000 /* +14 for the extra prompt info. */
1001 if (cols < strlen(HELP_PROMPT) + 14)
1002 cols = strlen(HELP_PROMPT) + 14;
1004 win = newwin(rows + 4, cols + 4, CALCPOSY(rows) - 2, CALCPOSX(cols));
1005 set_menu_win(menu, win);
1006 subw = derwin(win, rows, cols + 2, 2, 1);
1007 set_menu_sub(menu, subw);
1008 set_menu_fore(menu, A_REVERSE);
1009 set_menu_grey(menu, A_NORMAL);
1010 set_menu_mark(menu, NULL);
1011 set_menu_pad(menu, '-');
1012 set_menu_spacing(menu, 3, 0, 0);
1013 menu_opts_off(menu, O_NONCYCLIC);
1014 post_menu(menu);
1015 panel = new_panel(win);
1016 cbreak();
1017 noecho();
1018 nl();
1019 keypad(win, TRUE);
1020 set_menu_pattern(menu, mbuf);
1021 wbkgd(win, CP_MESSAGE_WINDOW);
1022 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1023 cols + 4, CP_MESSAGE_TITLE, CP_MESSAGE_BORDER);
1025 while (1) {
1026 int c;
1027 TAG *tmppgn = NULL;
1028 char *newtag = NULL;
1029 unsigned char tpgn_index = 0;
1031 if (set_current_item(menu, mitems[lastindex]) != E_OK) {
1032 lastindex = item_count(menu) - 1;
1033 continue;
1036 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1037 item_index(current_item(menu)) + 1, N_OF_N_STR,
1038 item_count(menu), HELP_PROMPT);
1039 draw_prompt(win, rows + 2, cols + 4, buf, CP_MESSAGE_PROMPT);
1041 update_panels();
1042 doupdate();
1044 c = wgetch(win);
1046 switch (c) {
1047 case CTRL('T'):
1048 add_custom_tags(&data, &data_index);
1049 goto cleanup;
1050 break;
1051 case CTRL('G'):
1052 if (edit)
1053 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1054 else
1055 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1056 break;
1057 case CTRL('R'):
1058 if (!edit)
1059 break;
1061 selected = item_index(current_item(menu));
1063 if (selected <= 6) {
1064 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1065 goto cleanup;
1068 for (i = 0; i < data_index; i++) {
1069 if (i == selected)
1070 continue;
1072 pgn_add_tag(&tmppgn, &tpgn_index, data[i].name,
1073 data[i].value);
1076 pgn_tag_free(data, data_index);
1077 data = NULL;
1079 for (i = data_index = 0; i < tpgn_index; i++) {
1080 pgn_add_tag(&data, &data_index, tmppgn[i].name,
1081 tmppgn[i].value);
1084 pgn_tag_free(tmppgn, tpgn_index);
1085 goto cleanup;
1086 break;
1087 case CTRL('A'):
1088 if (!edit)
1089 break;
1091 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1092 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1093 == NULL)
1094 break;
1096 newtag[0] = toupper(newtag[0]);
1098 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1099 strlen(PRESS_ENTER)) {
1100 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1101 break;
1104 for (i = 0; i < data_index; i++) {
1105 if (strcasecmp(data[i].name, newtag) == 0) {
1106 selected = i;
1107 goto gotitem;
1111 pgn_add_tag(&data, &data_index, newtag, NULL);
1113 selected = data_index - 1;
1114 goto gotitem;
1115 break;
1116 case KEY_HOME:
1117 menu_driver(menu, REQ_FIRST_ITEM);
1118 break;
1119 case KEY_END:
1120 menu_driver(menu, REQ_LAST_ITEM);
1121 break;
1122 case CTRL('F'):
1123 if (!edit)
1124 break;
1126 pgn_add_tag(&data, &data_index, "FEN", pgn_game_to_fen(g, b));
1127 selected = data_index - 1;
1128 goto gotitem;
1129 break;
1130 case KEY_NPAGE:
1131 case CTRL('N'):
1132 if (menu_driver(menu, REQ_SCR_DPAGE) == E_REQUEST_DENIED)
1133 menu_driver(menu, REQ_LAST_ITEM);
1134 break;
1135 case KEY_PPAGE:
1136 case CTRL('P'):
1137 if (menu_driver(menu, REQ_SCR_UPAGE) == E_REQUEST_DENIED)
1138 menu_driver(menu, REQ_FIRST_ITEM);
1139 break;
1140 case KEY_UP:
1141 menu_driver(menu, REQ_UP_ITEM);
1142 break;
1143 case KEY_DOWN:
1144 menu_driver(menu, REQ_DOWN_ITEM);
1145 break;
1146 case '\n':
1147 selected = item_index(current_item(menu));
1148 goto gotitem;
1149 break;
1150 case KEY_ESCAPE:
1151 cleanup(win, subw, panel, menu, mitems, NULL);
1152 goto done;
1153 break;
1154 default:
1155 tmp = menu_pattern(menu);
1157 if (tmp && tmp[strlen(tmp) - 1] != c) {
1158 menu_driver(menu, REQ_CLEAR_PATTERN);
1159 menu_driver(menu, c);
1161 else {
1162 if (menu_driver(menu, REQ_NEXT_MATCH) == E_NO_MATCH)
1163 menu_driver(menu, c);
1166 break;
1169 lastindex = item_index(current_item(menu));
1172 gotitem:
1173 lastindex = selected;
1174 nlen = strlen(data[selected].name) + 3;
1175 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1177 if (nlen > MAX_VALUE_WIDTH)
1178 snprintf(buf, sizeof(buf), "%s", data[selected].name);
1179 else
1180 snprintf(buf, sizeof(buf), "%s \"%s\"",
1181 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1182 data[selected].name);
1184 if (!edit) {
1185 if (strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1186 goto cleanup;
1188 cmessage(buf, ANYKEY, "%s", data[selected].value);
1189 goto cleanup;
1192 if (strcmp(data[selected].name, "Date") == 0) {
1193 tmp = get_input(buf, data[selected].value, 0, 0, NULL, NULL, NULL,
1194 0, FIELD_TYPE_PGN_DATE);
1196 if (tmp) {
1197 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1198 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1199 goto cleanup;
1202 else
1203 goto cleanup;
1205 else if (strcmp(data[selected].name, "Site") == 0) {
1206 tmp = get_input(buf, data[selected].value, 1, 1, CC_PROMPT,
1207 country_codes, NULL, CTRL('t'), -1);
1209 if (!tmp)
1210 tmp = "?";
1212 else if (strcmp(data[selected].name, "Round") == 0) {
1213 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1214 FIELD_TYPE_PGN_ROUND);
1216 if (!tmp) {
1217 if (gtotal > 1)
1218 tmp = "?";
1219 else
1220 tmp = "-";
1223 else if (strcmp(data[selected].name, "Result") == 0) {
1224 tmp = get_input(buf, data[selected].value, 1, 1, NULL, NULL, NULL,
1225 0, -1);
1227 if (!tmp)
1228 tmp = "*";
1230 else {
1231 if (item_description(mitems[selected]) &&
1232 strcmp(item_description(mitems[selected]), UNKNOWN) == 0)
1233 tmp = NULL;
1234 else
1235 tmp = data[selected].value;
1237 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1240 len = (tmp) ? strlen(tmp) + 1 : 1;
1241 data[selected].value = Realloc(data[selected].value, len);
1242 strncpy(data[selected].value, (tmp) ? tmp : "", len);
1244 cleanup:
1245 cleanup(win, subw, panel, menu, mitems, NULL);
1248 done:
1249 if (!edit) {
1250 pgn_tag_free(data, data_index);
1251 return NULL;
1254 return data;
1257 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1258 * game index number.
1260 int save_pgn(const char *filename, int isfifo, int saveindex)
1262 FILE *fp;
1263 char *mode = NULL;
1264 int c;
1265 char buf[FILENAME_MAX];
1266 struct stat st;
1267 int i;
1268 char *command = NULL;
1269 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1271 if (filename[0] != '/' && config.savedirectory && !isfifo) {
1272 if (stat(config.savedirectory, &st) == -1) {
1273 if (errno == ENOENT) {
1274 if (mkdir(config.savedirectory, 0755) == -1) {
1275 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1276 strerror(errno));
1277 return 1;
1280 else {
1281 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1282 strerror(errno));
1283 return 1;
1287 stat(config.savedirectory, &st);
1289 if (!S_ISDIR(st.st_mode)) {
1290 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1291 return 1;
1294 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1295 filename = buf;
1298 if (!isfifo)
1299 command = compression_cmd(filename, 0);
1301 /* This is a hack to resume an existing game when more than one game is
1302 * available. Also resuming a saved game and a game from history.
1304 // FIXME: may not need this when a FEN tag is supported (by the engine).
1305 if (isfifo)
1306 mode = "w";
1307 else {
1308 if (access(filename, W_OK) == 0) {
1309 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1310 "%s \"%s\"", E_FILEEXISTS, filename);
1312 switch (c) {
1313 case 'a':
1314 if (command) {
1315 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1316 return 1;
1319 mode = "a";
1320 break;
1321 case 'o':
1322 mode = "w+";
1323 break;
1324 default:
1325 return 1;
1328 else
1329 mode = "a";
1332 if (command) {
1333 if ((fp = popen(command, "w")) == NULL) {
1334 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1335 return 1;
1338 else {
1339 if ((fp = fopen(filename, mode)) == NULL) {
1340 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1341 return 1;
1345 if (isfifo)
1346 pgn_write(fp, game[saveindex], isfifo);
1347 else {
1348 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1349 pgn_write(fp, game[i], isfifo);
1352 if (command)
1353 pclose(fp);
1354 else
1355 fclose(fp);
1357 if (!isfifo && saveindex == -1)
1358 strncpy(loadfile, filename, sizeof(loadfile));
1360 return 0;
1363 char *random_agony(GAME g)
1365 static int n;
1366 FILE *fp;
1367 char line[LINE_MAX];
1369 if (n == -1 || !config.agony || !curses_initialized ||
1370 (g.mode == MODE_HISTORY && !config.historyagony))
1371 return NULL;
1373 if (!agony) {
1374 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1375 n = -1;
1376 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1377 return NULL;
1380 while (!feof(fp)) {
1381 if (fscanf(fp, " %[^\n] ", line) == 1) {
1382 agony = Realloc(agony, (n + 2) * sizeof(char *));
1383 agony[n++] = strdup(trim(line));
1387 agony[n] = NULL;
1388 fclose(fp);
1390 if (agony[0] == NULL || !n) {
1391 n = -1;
1392 return NULL;
1396 return agony[random() % n];
1399 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1401 if (pgn_piece_to_int(piece) == ROOK && col == 7
1402 && row == 7 &&
1403 (TEST_FLAG((*g).flags, GF_WK_CASTLE) || mod) &&
1404 pgn_piece_to_int(board[7][4].icon) == KING && isupper(piece)) {
1405 if (mod)
1406 TOGGLE_FLAG((*g).flags, GF_WK_CASTLE);
1407 return 1;
1409 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1410 && row == 7 &&
1411 (TEST_FLAG((*g).flags, GF_WQ_CASTLE) || mod) &&
1412 pgn_piece_to_int(board[7][4].icon) == KING && isupper(piece)) {
1413 if (mod)
1414 TOGGLE_FLAG((*g).flags, GF_WQ_CASTLE);
1415 return 1;
1417 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1418 && row == 0 &&
1419 (TEST_FLAG((*g).flags, GF_BK_CASTLE) || mod) &&
1420 pgn_piece_to_int(board[0][4].icon) == KING && islower(piece)) {
1421 if (mod)
1422 TOGGLE_FLAG((*g).flags, GF_BK_CASTLE);
1423 return 1;
1425 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1426 && row == 0 &&
1427 (TEST_FLAG((*g).flags, GF_BQ_CASTLE) || mod) &&
1428 pgn_piece_to_int(board[0][4].icon) == KING && islower(piece)) {
1429 if (mod)
1430 TOGGLE_FLAG((*g).flags, GF_BQ_CASTLE);
1431 return 1;
1433 else if (pgn_piece_to_int(piece) == KING && col == 4
1434 && row == 7 &&
1435 (mod || (pgn_piece_to_int(board[7][7].icon) == ROOK &&
1436 TEST_FLAG((*g).flags, GF_WK_CASTLE))
1438 (pgn_piece_to_int(board[7][0].icon) == ROOK &&
1439 TEST_FLAG((*g).flags, GF_WQ_CASTLE))) && isupper(piece)) {
1440 if (mod) {
1441 if (TEST_FLAG((*g).flags, GF_WK_CASTLE) ||
1442 TEST_FLAG((*g).flags, GF_WQ_CASTLE))
1443 CLEAR_FLAG((*g).flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1444 else
1445 SET_FLAG((*g).flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1447 return 1;
1449 else if (pgn_piece_to_int(piece) == KING && col == 4
1450 && row == 0 &&
1451 (mod || (pgn_piece_to_int(board[0][7].icon) == ROOK &&
1452 TEST_FLAG((*g).flags, GF_BK_CASTLE))
1454 (pgn_piece_to_int(board[0][0].icon) == ROOK &&
1455 TEST_FLAG((*g).flags, GF_BQ_CASTLE))) && islower(piece)) {
1456 if (mod) {
1457 if (TEST_FLAG((*g).flags, GF_BK_CASTLE) ||
1458 TEST_FLAG((*g).flags, GF_BQ_CASTLE))
1459 CLEAR_FLAG((*g).flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1460 else
1461 SET_FLAG((*g).flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1463 return 1;
1466 return 0;
1469 static void draw_board(GAME *g, int details, int crow, int ccol)
1471 int row, col;
1472 int bcol = 0, brow = 0;
1473 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1474 int ncols = 0, offset = 1;
1475 unsigned coords_y = 8;
1477 for (row = 0; row < maxy; row++) {
1478 bcol = 0;
1480 for (col = 0; col < maxx; col++) {
1481 int attrwhich = -1;
1482 chtype attrs = 0;
1483 unsigned char piece;
1485 if (row == 0 || row == maxy - 2) {
1486 if (col == 0)
1487 mvwaddch(boardw, row, col,
1488 LINE_GRAPHIC((row) ?
1489 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1490 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1491 else if (col == maxx - 2)
1492 mvwaddch(boardw, row, col,
1493 LINE_GRAPHIC((row) ?
1494 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1495 ACS_URCORNER | CP_BOARD_GRAPHICS));
1496 else if (!(col % 4))
1497 mvwaddch(boardw, row, col,
1498 LINE_GRAPHIC((row) ?
1499 ACS_BTEE | CP_BOARD_GRAPHICS :
1500 ACS_TTEE | CP_BOARD_GRAPHICS));
1501 else {
1502 if (col != maxx - 1)
1503 mvwaddch(boardw, row, col,
1504 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1507 continue;
1510 if ((row % 2) && col == maxx - 1 && coords_y) {
1511 wattron(boardw, CP_BOARD_COORDS);
1512 mvwprintw(boardw, row, col, "%d", coords_y--);
1513 wattroff(boardw, CP_BOARD_COORDS);
1514 continue;
1517 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1518 if (!(row % 2))
1519 mvwaddch(boardw, row, col,
1520 LINE_GRAPHIC((col) ?
1521 ACS_RTEE | CP_BOARD_GRAPHICS :
1522 ACS_LTEE | CP_BOARD_GRAPHICS));
1523 else
1524 mvwaddch(boardw, row, col,
1525 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1527 continue;
1530 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1531 mvwaddch(boardw, row, col,
1532 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1533 continue;
1536 if (!(col % 4) && row != maxy - 1) {
1537 mvwaddch(boardw, row, col,
1538 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1539 continue;
1542 if ((row % 2)) {
1543 if ((col % 4)) {
1544 if (ncols++ == 8) {
1545 offset++;
1546 ncols = 1;
1549 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1550 && (offset % 2)))
1551 attrwhich = BLACK;
1552 else
1553 attrwhich = WHITE;
1555 if (config.validmoves && board[brow][bcol].valid) {
1556 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1557 CP_BOARD_MOVES_BLACK;
1559 else
1560 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1561 CP_BOARD_BLACK;
1563 if (row == ROWTOMATRIX(crow) && col == COLTOMATRIX(ccol)) {
1564 attrs = CP_BOARD_CURSOR;
1567 if (row == ROWTOMATRIX(sp.row) &&
1568 col == COLTOMATRIX(sp.col)) {
1569 attrs = CP_BOARD_SELECTED;
1572 if (row == maxy - 1)
1573 attrs = 0;
1575 mvwaddch(boardw, row, col, ' ' | attrs);
1577 if (row == maxy - 1)
1578 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1579 else {
1580 if (details && board[row / 2][bcol].enpassant)
1581 piece = 'x';
1582 else
1583 piece = board[row / 2][bcol].icon;
1585 if (details && castling_state(g, board, brow, bcol,
1586 piece, 0))
1587 attrs |= A_REVERSE;
1589 if ((*g).side == WHITE && isupper(piece))
1590 attrs |= A_BOLD;
1591 else if ((*g).side == BLACK && islower(piece))
1592 attrs |= A_BOLD;
1594 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1596 CLEAR_FLAG(attrs, A_BOLD);
1597 CLEAR_FLAG(attrs, A_REVERSE);
1600 waddch(boardw, ' ' | attrs);
1601 col += 2;
1602 bcol++;
1605 else {
1606 if (col != maxx - 1)
1607 mvwaddch(boardw, row, col,
1608 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1612 brow = row / 2;
1616 void invalid_move(int n, const char *m)
1618 if (curses_initialized)
1619 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1620 else
1621 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1624 /* Convert the selected piece to SAN format and validate it. */
1625 static char *board_to_san(GAME *g, BOARD b)
1627 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1628 int piece;
1629 int promo;
1630 BOARD oldboard;
1632 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1633 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1635 p = str;
1636 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1638 if (piece == PAWN && ((sp.destrow == 8 && (*g).turn == WHITE) ||
1639 (sp.destrow == 1 && (*g).turn == BLACK))) {
1640 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1642 if (pgn_piece_to_int(promo) == -1)
1643 return NULL;
1645 p = str + strlen(str);
1646 *p++ = toupper(promo);
1647 *p = '\0';
1650 memcpy(oldboard, b, sizeof(BOARD));
1652 if ((p = pgn_a2a4tosan(g, b, str)) == NULL) {
1653 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
1654 memcpy(b, oldboard, sizeof(BOARD));
1655 return NULL;
1658 if (pgn_validate_move(g, b, p)) {
1659 invalid_move(gindex + 1, p);
1660 memcpy(b, oldboard, sizeof(BOARD));
1661 return NULL;
1664 return p;
1667 static int move_to_engine(GAME *g, BOARD b)
1669 char *p;
1671 if ((p = board_to_san(g, b)) == NULL)
1672 return 0;
1674 sp.row = sp.col = sp.icon = 0;
1676 if (noengine) {
1677 (*g).hp = history_add((*g).hp, &(*g).hindex, p);
1678 pgn_switch_turn(g);
1679 SET_FLAG((*g).flags, GF_MODIFIED);
1680 update_all(*g);
1681 return 1;
1684 SEND_TO_ENGINE("%s\n", p);
1685 return 1;
1688 static void update_clock(int n, int *h, int *m, int *s)
1690 *h = n / 3600;
1691 *m = (n % 3600) / 60;
1692 *s = (n % 3600) % 60;
1694 return;
1697 void update_status_window(GAME g)
1699 int i = 0;
1700 char *buf;
1701 char tmp[15], *engine, *mode;
1702 int w;
1703 int h, m, s;
1704 char *p;
1705 int maxy, maxx;
1706 int len;
1708 getmaxyx(statusw, maxy, maxx);
1709 w = maxx = 10;
1710 len = maxx - 2;
1711 buf = Malloc(len);
1713 *tmp = '\0';
1714 p = tmp;
1716 if (TEST_FLAG(g.flags, GF_DELETE)) {
1717 *p++ = '(';
1718 *p++ = 'x';
1719 i++;
1722 if (TEST_FLAG(g.flags, GF_PERROR)) {
1723 if (!i)
1724 *p++ = '(';
1725 else
1726 *p++ = '/';
1728 *p++ = '!';
1729 i++;
1732 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1733 if (!i)
1734 *p++ = '(';
1735 else
1736 *p++ = '/';
1738 *p++ = '*';
1739 i++;
1742 if (*tmp != '\0')
1743 *p++ = ')';
1745 *p = '\0';
1747 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1748 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1749 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1750 (*tmp) ? tmp : "");
1751 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1753 switch (g.mode) {
1754 case MODE_HISTORY:
1755 mode = MODE_HISTORY_STR;
1756 break;
1757 case MODE_EDIT:
1758 mode = MODE_EDIT_STR;
1759 break;
1760 case MODE_PLAY:
1761 mode = MODE_PLAY_STR;
1762 break;
1763 default:
1764 mode = UNKNOWN;
1765 break;
1768 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
1770 switch (status.engine) {
1771 case ENGINE_THINKING:
1772 engine = ENGINE_THINKING_STR;
1773 break;
1774 case ENGINE_READY:
1775 engine = ENGINE_READY_STR;
1776 break;
1777 case ENGINE_INITIALIZING:
1778 engine = ENGINE_INITIALIZING_STR;
1779 break;
1780 case ENGINE_OFFLINE:
1781 engine = ENGINE_OFFLINE_STR;
1782 break;
1783 default:
1784 engine = UNKNOWN;
1785 break;
1788 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1789 wattron(statusw, CP_STATUS_ENGINE);
1790 mvwaddstr(statusw, 5, 9, engine);
1791 wattroff(statusw, CP_STATUS_ENGINE);
1793 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1794 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1796 strncpy(tmp, WHITE_STR, sizeof(tmp));
1797 tmp[0] = toupper(tmp[0]);
1798 update_clock(g.moveclock, &h, &m, &s);
1799 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1800 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1802 strncpy(tmp, BLACK_STR, sizeof(tmp));
1803 tmp[0] = toupper(tmp[0]);
1804 update_clock(g.moveclock, &h, &m, &s);
1805 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1806 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1807 free(buf);
1809 for (i = 1; i < maxx - 4; i++)
1810 mvwprintw(statusw, maxy - 2, i, " ");
1812 if (!status.notify)
1813 status.notify = strdup(GAME_HELP_PROMPT);
1815 wattron(statusw, CP_STATUS_NOTIFY);
1816 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1817 status.notify);
1818 wattroff(statusw, CP_STATUS_NOTIFY);
1821 void update_history_window(GAME g)
1823 char buf[HISTORY_WIDTH];
1824 HISTORY *h = NULL;
1825 int n, total;
1826 int t = history_total(g.hp);
1828 n = (g.hindex + 1) / 2;
1830 if (t % 2)
1831 total = (t + 1) / 2;
1832 else
1833 total = t / 2;
1835 if (t)
1836 snprintf(buf, sizeof(buf), "%u %s %u%s",n, N_OF_N_STR, total,
1837 (movestep == 1) ? HISTORY_MOVE_STEP : "");
1838 else
1839 strncpy(buf, UNAVAILABLE, sizeof(buf));
1841 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1842 HISTORY_WIDTH - 13, buf);
1844 h = history_by_n(g.hp, g.hindex);
1846 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1848 n = 0;
1850 if (h && ((h->comment && h->comment[0]) || h->nag[0])) {
1851 strncat(buf, " (v", sizeof(buf));
1852 n++;
1855 if (h && h->rav) {
1856 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1857 n++;
1860 if (g.ravlevel) {
1861 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1862 n++;
1865 if (n)
1866 strncat(buf, ")", sizeof(buf));
1868 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1869 HISTORY_WIDTH - 13, buf);
1871 h = history_by_n(g.hp, game[gindex].hindex - 1);
1873 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1875 n = 0;
1877 if (h && ((h->comment && h->comment[0]) || h->nag[0])) {
1878 strncat(buf, " (V", sizeof(buf));
1879 n++;
1882 if (h && h->rav) {
1883 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1884 n++;
1887 if (g.ravlevel) {
1888 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1889 n++;
1892 if (n)
1893 strncat(buf, ")", sizeof(buf));
1895 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1896 HISTORY_WIDTH - 13, buf);
1899 void update_tag_window(TAG *t)
1901 int i;
1902 int w = TAG_WIDTH - 10;
1904 for (i = 0; i < 7; i++)
1905 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i].name, w, t[i].value);
1908 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1910 int i;
1912 wattron(win, attr);
1914 for (i = 1; i < width - 1; i++)
1915 mvwaddch(win, y, i, ' ');
1917 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1918 wattroff(win, attr);
1921 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1922 chtype battr)
1924 int i;
1926 if (title) {
1927 wattron(win, attr);
1929 for (i = 1; i < width - 1; i++)
1930 mvwaddch(win, 1, i, ' ');
1932 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1933 wattroff(win, attr);
1936 wattron(win, battr);
1937 box(win, ACS_VLINE, ACS_HLINE);
1938 wattroff(win, battr);
1941 void update_all(GAME g)
1943 update_status_window(g);
1944 update_history_window(g);
1945 update_tag_window(g.tag);
1948 static void game_next_prev(GAME g, int n, int count)
1950 if (gtotal < 2)
1951 return;
1953 if (n == 1) {
1954 if (gindex + count > gtotal - 1) {
1955 if (count != 1)
1956 gindex = gtotal - 1;
1957 else
1958 gindex = 0;
1960 else
1961 gindex += count;
1963 else {
1964 if (gindex - count < 0) {
1965 if (count != 1)
1966 gindex = 0;
1967 else
1968 gindex = gtotal - 1;
1970 else
1971 gindex -= count;
1975 static void delete_game(int which)
1977 GAME *g = NULL;
1978 int gi = 0;
1979 int i;
1981 for (i = 0; i < gtotal; i++) {
1982 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
1983 pgn_free(game[i]);
1984 continue;
1987 g = Realloc(g, (gi + 1) * sizeof(GAME));
1988 memcpy(&g[gi], &game[i], sizeof(GAME));
1989 g[gi].tag = game[i].tag;
1990 g[gi].history = game[i].history;
1991 g[gi].hp = game[i].hp;
1992 gi++;
1995 game = g;
1996 gtotal = gi;
1998 if (which != -1) {
1999 if (which + 1 >= gtotal)
2000 gindex = gtotal - 1;
2001 else
2002 gindex = which;
2004 else
2005 gindex = gtotal - 1;
2007 game[gindex].hp = game[gindex].history;
2010 static int find_move_exp(GAME g, const char *str, int init, int which,
2011 int count)
2013 int i;
2014 int ret;
2015 static regex_t r;
2016 static int firstrun = 1;
2017 char errbuf[255];
2018 int incr;
2019 int found;
2021 if (init) {
2022 if (!firstrun)
2023 regfree(&r);
2025 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2026 regerror(ret, &r, errbuf, sizeof(errbuf));
2027 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2028 return -1;
2031 firstrun = 1;
2034 incr = (which == 0) ? -(1) : 1;
2036 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2037 if (i == g.hindex - 1)
2038 break;
2040 if (i > history_total(g.hp))
2041 i = 0;
2042 else if (i < 0)
2043 i = history_total(g.hp);
2045 // FIXME RAV
2046 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2048 if (ret == 0) {
2049 if (count == ++found) {
2050 return i + 1;
2053 else {
2054 if (ret != REG_NOMATCH) {
2055 regerror(ret, &r, errbuf, sizeof(errbuf));
2056 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2057 return -1;
2062 return -1;
2065 static int toggle_delete_flag(int n)
2067 int i, x;
2069 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2071 for (i = x = 0; i < gtotal; i++) {
2072 if (TEST_FLAG(game[i].flags, GF_DELETE))
2073 x++;
2076 if (x == gtotal) {
2077 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2078 CLEAR_FLAG(game[n].flags, GF_DELETE);
2079 return 1;
2082 return 0;
2085 static void edit_save_tags(GAME *g)
2087 int i;
2088 TAG *t;
2090 if ((t = edit_tags(*g, board, 1)) == NULL)
2091 return;
2093 (*g).tindex = 0;
2095 for (i = 0; t[i].name; i++)
2096 pgn_add_tag(&(*g).tag, &(*g).tindex, t[i].name, t[i].value);
2098 pgn_tag_free(t, i);
2099 SET_FLAG((*g).flags, GF_MODIFIED);
2100 pgn_sort_tags(*g);
2103 static int find_game_exp(char *str, int which, int count)
2105 char *nstr = NULL, *exp = NULL;
2106 regex_t nexp, vexp;
2107 int ret = -1;
2108 int g = 0;
2109 char buf[255], *tmp;
2110 char errbuf[255];
2111 int found = 0;
2112 int incr = (which == 0) ? -(1) : 1;
2114 strncpy(buf, str, sizeof(buf));
2115 tmp = buf;
2117 if (strstr(tmp, ":") != NULL) {
2118 nstr = strsep(&tmp, ":");
2120 if ((ret = regcomp(&nexp, nstr,
2121 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2122 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2123 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2124 ret = g = -1;
2125 goto cleanup;
2129 exp = tmp;
2131 if (exp == NULL)
2132 goto cleanup;
2134 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2135 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2136 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2137 ret = -1;
2138 goto cleanup;
2141 ret = -1;
2143 for (g = gindex + incr, found = 0; ; g += incr) {
2144 int t;
2146 if (g == gindex)
2147 break;
2149 if (g == gtotal)
2150 g = 0;
2151 else if (g < 0)
2152 g = gtotal - 1;
2154 for (t = 0; t < game[g].tindex; t++) {
2155 if (nstr) {
2156 if (regexec(&nexp, game[g].tag[t].name, 0, 0, 0) == 0) {
2157 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
2158 if (count == ++found) {
2159 ret = g;
2160 goto cleanup;
2165 else {
2166 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
2167 if (count == ++found) {
2168 ret = g;
2169 goto cleanup;
2175 ret = -1;
2178 cleanup:
2179 if (nstr)
2180 regfree(&nexp);
2182 if (g != -1)
2183 regfree(&vexp);
2185 return ret;
2188 void edit_board(GAME g, BOARD b)
2190 chtype p;
2192 p = b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2193 b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2194 b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(g.turn, OPEN_SQUARE);
2197 // Updates the notification line in the status window then refreshes the
2198 // status window.
2199 void update_status_notify(GAME g, char *fmt, ...)
2201 va_list ap;
2202 #ifdef HAVE_VASPRINTF
2203 char *line;
2204 #else
2205 char line[COLS];
2206 #endif
2208 if (!fmt) {
2209 if (status.notify) {
2210 free(status.notify);
2211 status.notify = NULL;
2213 if (curses_initialized)
2214 update_status_window(g);
2217 return;
2220 va_start(ap, fmt);
2221 #ifdef HAVE_VASPRINTF
2222 vasprintf(&line, fmt, ap);
2223 #else
2224 vsnprintf(line, sizeof(line), fmt, ap);
2225 #endif
2226 va_end(ap);
2228 if (status.notify)
2229 free(status.notify);
2231 status.notify = strdup(line);
2233 #ifdef HAVE_VASPRINTF
2234 free(line);
2235 #endif
2236 if (curses_initialized)
2237 update_status_window(g);
2240 static void switch_side(GAME *g)
2242 if ((*g).side == WHITE)
2243 (*g).side = BLACK;
2244 else
2245 (*g).side = WHITE;
2248 int rav_next_prev(GAME *g, BOARD b, int n)
2250 // Next RAV.
2251 if (n) {
2252 if ((*g).hp[(*g).hindex]->rav == NULL)
2253 return 1;
2255 (*g).rav = Realloc((*g).rav, ((*g).ravlevel + 1) * sizeof(RAV));
2256 (*g).rav[(*g).ravlevel].hp = (*g).hp;
2257 (*g).rav[(*g).ravlevel].flags = (*g).flags;
2258 (*g).rav[(*g).ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2259 (*g).rav[(*g).ravlevel].hindex = (*g).hindex;
2260 (*g).hp = (*g).hp[(*g).hindex]->rav;
2261 (*g).hindex = 0;
2262 (*g).ravlevel++;
2263 return 0;
2266 if ((*g).ravlevel - 1 < 0)
2267 return 1;
2269 // Previous RAV.
2270 (*g).ravlevel--;
2271 pgn_init_fen_board(g, b, (*g).rav[(*g).ravlevel].fen);
2272 free((*g).rav[(*g).ravlevel].fen);
2273 (*g).hp = (*g).rav[(*g).ravlevel].hp;
2274 (*g).flags = (*g).rav[(*g).ravlevel].flags;
2275 (*g).hindex = (*g).rav[(*g).ravlevel].hindex;
2276 return 0;
2279 static void draw_window_decor()
2281 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2282 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2283 wbkgd(boardw, CP_BOARD_WINDOW);
2284 wbkgd(statusw, CP_STATUS_WINDOW);
2285 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2286 CP_STATUS_TITLE, CP_STATUS_BORDER);
2287 wbkgd(tagw, CP_TAG_WINDOW);
2288 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2289 CP_TAG_BORDER);
2290 wbkgd(historyw, CP_HISTORY_WINDOW);
2291 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2292 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2295 static void do_window_resize()
2297 if (LINES < 24 || COLS < 80)
2298 return;
2300 resizeterm(LINES, COLS);
2301 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2302 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2303 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2304 wmove(historyw, 0, 0);
2305 wclrtobot(historyw);
2306 wmove(tagw, 0, 0);
2307 wclrtobot(tagw);
2308 wmove(statusw, 0, 0);
2309 wclrtobot(statusw);
2310 draw_window_decor();
2311 update_all(game[gindex]);
2314 void game_loop()
2316 int error_recover = 0;
2317 int pushkey = 0;
2318 int count = 0;
2319 int crow = 2, ccol = 5;
2320 char moveexp[255] = {0};
2321 char gameexp[255] = {0};
2322 int delete_count = 0;
2323 int markstart = -1, markend = -1;
2324 int editmode = 0;
2325 int board_details = 0;
2327 gindex = gtotal - 1;
2328 markstart = -1, markend = -1;
2330 if (history_total(game[gindex].hp))
2331 game[gindex].mode = MODE_HISTORY;
2332 else
2333 game[gindex].mode = MODE_PLAY;
2335 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2336 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
2337 movestep = 2;
2338 paused = 1; //FIXME clock
2339 flushinp();
2340 update_all(game[gindex]);
2341 update_tag_window(game[gindex].tag);
2343 while (!quit) {
2344 int c = 0;
2345 int i, x, n = 0, len = 0;
2347 fd_set fds;
2348 char fdbuf[8192] = {0};
2349 struct timeval tv;
2351 char *tmp = NULL;
2352 char buf[78];
2353 char tfile[FILENAME_MAX];
2354 int minr, maxr, minc, maxc;
2356 // FIXME game.fds
2357 #if 0
2358 if (engine_initialized) {
2359 tv.tv_sec = 0;
2360 tv.tv_usec = 0;
2362 FD_ZERO(&fds);
2363 FD_SET(enginefd[0], &fds);
2365 for (i = 0; i < gtotal; i++) {
2366 if (game[i].sockfd > 0) {
2367 if (game[i].sockfd > n)
2368 n = game[i].sockfd;
2370 FD_SET(game[i].sockfd, &fds);
2374 n = (n > enginefd[0]) ? n : enginefd[0];
2376 if ((n = select(n + 1, &fds, NULL, NULL, &tv)) > 0) {
2377 if (FD_ISSET(enginefd[0], &fds)) {
2378 len = read(enginefd[0], fdbuf, sizeof(fdbuf));
2380 if (len == -1) {
2381 if (errno != EAGAIN) {
2382 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
2383 ++error_recover, strerror(errno));
2384 continue;
2387 else {
2388 if (len) {
2389 // FIXME engine may be associated with another
2390 // selected game.
2391 parse_engine_output(&game[gindex], fdbuf);
2392 update_all(game[gindex]);
2397 for (i = 0; i < gtotal; i++) {
2398 if (game[i].sockfd <= 0)
2399 continue;
2401 if (FD_ISSET(game[i].sockfd, &fds)) {
2402 len = recv(game[i].sockfd, fdbuf, sizeof(fdbuf), 0);
2404 if (len == -1) {
2405 if (errno != EAGAIN) {
2406 cmessage(ERROR, ANYKEY,
2407 "Attempt #%i. recv(): %s",
2408 ++error_recover, strerror(errno));
2409 continue;
2412 else {
2413 if (len)
2414 parse_ics_output(fdbuf);
2416 update_all(game[gindex]);
2421 else {
2422 if (n == -1)
2423 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
2424 else {
2425 /* timeout */
2429 #endif
2431 error_recover = 0;
2432 draw_board(&game[gindex], board_details, crow, ccol);
2433 wmove(boardw, ROWTOMATRIX(crow), COLTOMATRIX(ccol));
2435 if (!paused) {
2438 update_panels();
2439 doupdate();
2441 if (pushkey)
2442 c = pushkey;
2443 else {
2444 if ((c = wgetch(boardw)) == ERR)
2445 continue;
2448 if (!count && status.notify)
2449 update_status_notify(game[gindex], NULL);
2451 switch (c) {
2452 int annotate;
2454 #ifdef DEBUG
2455 case 'O':
2456 message("DEBUG BOARD", ANYKEY, "%s", debug_board(board));
2457 break;
2458 #endif
2459 case KEY_RESIZE:
2460 do_window_resize();
2461 break;
2462 case '-':
2463 case '+':
2464 if (game[gindex].mode == MODE_PLAY) {
2465 break;
2468 if (game[gindex].mode == MODE_HISTORY) {
2469 rav_next_prev(&game[gindex], board, (c == '-') ? 0 : 1);
2470 update_all(game[gindex]);
2471 break;
2473 break;
2474 case 'p':
2475 if (game[gindex].mode == MODE_EDIT) {
2476 if (crow != 6 && crow != 3)
2477 break;
2479 pgn_reset_enpassant(board);
2480 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].enpassant = 1;
2481 break;
2484 if (paused)
2485 paused = 0;
2486 else
2487 paused = 1;
2489 break;
2490 case 'd':
2491 if (board_details)
2492 board_details = 0;
2493 else
2494 board_details = 1;
2495 break;
2496 case 'e':
2497 if (history_total(game[gindex].hp))
2498 break;
2500 if (editmode) {
2501 board_details--;
2502 editmode = 0;
2503 pgn_add_tag(&game[gindex].tag, &game[gindex].tindex,
2504 "FEN", pgn_game_to_fen(game[gindex], board));
2505 pgn_add_tag(&game[gindex].tag, &game[gindex].tindex,
2506 "SetUp", "1");
2507 game[gindex].mode = MODE_PLAY;
2508 pgn_sort_tags(game[gindex]);
2510 else {
2511 game[gindex].mode = MODE_EDIT;
2512 editmode = 1;
2514 if (pgn_init_fen_board(&game[gindex], board, NULL))
2515 break;
2517 board_details++;
2520 update_all(game[gindex]);
2521 break;
2522 case '}':
2523 case '{':
2524 case '?':
2525 if (gtotal < 2)
2526 break;
2528 if (!*gameexp || c == '?') {
2529 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2530 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2531 NULL, 0, -1)) == NULL)
2532 break;
2534 strncpy(gameexp, tmp, sizeof(gameexp));
2537 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
2538 (count) ? count : 1)) == -1)
2539 break;
2541 gindex = n;
2543 if (history_total(game[gindex].hp))
2544 game[gindex].mode = MODE_HISTORY;
2546 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2547 update_all(game[gindex]);
2548 update_tag_window(game[gindex].tag);
2549 break;
2550 case '!':
2551 crow = 1;
2552 break;
2553 case '@':
2554 crow = 2;
2555 break;
2556 case '#':
2557 crow = 3;
2558 break;
2559 case '$':
2560 crow = 4;
2561 break;
2562 case '%':
2563 crow = 5;
2564 break;
2565 case '^':
2566 crow = 6;
2567 break;
2568 case '&':
2569 crow = 7;
2570 break;
2571 case '*':
2572 crow = 8;
2573 break;
2574 case 'A':
2575 ccol = 1;
2576 break;
2577 case 'B':
2578 ccol = 2;
2579 break;
2580 case 'C':
2581 ccol = 3;
2582 break;
2583 case 'D':
2584 ccol = 4;
2585 break;
2586 case 'E':
2587 ccol = 5;
2588 break;
2589 case 'F':
2590 ccol = 6;
2591 break;
2592 case 'G':
2593 ccol = 7;
2594 break;
2595 case 'H':
2596 ccol = 8;
2597 break;
2598 case ']':
2599 case '[':
2600 case '/':
2601 if (history_total(game[gindex].hp) < 2)
2602 break;
2604 n = 0;
2606 if (!*moveexp || c == '/') {
2607 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
2608 NULL, NULL, 0, -1)) == NULL)
2609 break;
2611 strncpy(moveexp, tmp, sizeof(moveexp));
2612 n = 1;
2615 if ((n = find_move_exp(game[gindex], moveexp, n,
2616 (c == '[') ? 0 : 1, (count) ? count : 1)) == -1)
2617 break;
2619 game[gindex].hindex = n;
2620 history_update_board(&game[gindex], board, game[gindex].hindex);
2621 update_all(game[gindex]);
2622 break;
2623 case 'v':
2624 if (game[gindex].hindex == history_total(game[gindex].hp))
2625 break;
2627 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2628 break;
2629 case 'V':
2630 if (game[gindex].hindex - 1 >= 0)
2631 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2632 break;
2633 case '>':
2634 case '<':
2635 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (count) ? count : 1);
2637 if (delete_count) {
2638 markend = gindex;
2639 pushkey = 'x';
2640 delete_count = 0;
2643 game[gindex].mode = MODE_HISTORY;
2644 editmode = 0;
2646 if (history_total(game[gindex].hp))
2647 game[gindex].mode = MODE_HISTORY;
2649 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2650 update_cursor(game[gindex], game[gindex].hindex, &crow, &ccol);
2651 update_all(game[gindex]);
2652 update_tag_window(game[gindex].tag);
2653 break;
2654 case 'j':
2655 if (game[gindex].mode != MODE_HISTORY ||
2656 history_total(game[gindex].hp) < 2)
2657 break;
2660 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2661 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2662 game[gindex].htotal)) == NULL)
2663 break;
2666 if (!count) {
2667 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2668 NULL, NULL, NULL, 0, -1)) == NULL)
2669 break;
2671 if (!isinteger(tmp))
2672 break;
2674 i = atoi(tmp);
2676 else
2677 i = count;
2679 if (i > (history_total(game[gindex].hp) / 2) || i < 0)
2680 break;
2682 game[gindex].hindex = i * 2;
2684 if (history_total(game[gindex].hp))
2685 game[gindex].mode = MODE_HISTORY;
2687 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2688 update_all(game[gindex]);
2689 break;
2690 case 'J':
2691 if (gtotal < 2)
2692 break;
2695 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2696 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2697 == NULL)
2698 break;
2701 if (!count) {
2702 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
2703 NULL, NULL, 0, -1)) == NULL)
2704 break;
2706 if (!isinteger(tmp))
2707 break;
2709 i = atoi(tmp);
2711 else
2712 i = count;
2714 if (--i > gtotal - 1 || i < 0)
2715 break;
2717 gindex = i;
2719 if (history_total(game[gindex].hp))
2720 game[gindex].mode = MODE_HISTORY;
2722 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2723 update_cursor(game[gindex], game[gindex].hindex, &crow, &ccol);
2724 update_all(game[gindex]);
2725 update_tag_window(game[gindex].tag);
2726 break;
2727 case 'x':
2728 pushkey = 0;
2730 if (editmode) {
2731 if (sp.icon)
2732 board[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2733 else
2734 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2736 sp.icon = sp.row = sp.col = 0;
2737 break;
2740 if (gtotal < 2)
2741 break;
2743 if (count && !delete_count) {
2744 markstart = gindex;
2745 delete_count = 1;
2746 update_status_notify(game[gindex], "%s (delete)",
2747 status.notify);
2748 continue;
2751 if (markstart >= 0 && markend >= 0) {
2752 if (markstart > markend) {
2753 i = markstart;
2754 markstart = markend;
2755 markend = i;
2758 for (i = markstart; i <= markend; i++) {
2759 if (toggle_delete_flag(i))
2760 break;
2763 else {
2764 if (toggle_delete_flag(gindex))
2765 break;
2768 markstart = markend = -1;
2769 update_status_window(game[gindex]);
2770 break;
2771 case 'X':
2772 if (gtotal < 2) {
2773 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2774 break;
2777 tmp = NULL;
2779 for (i = n = 0; i < gtotal; i++) {
2780 if (TEST_FLAG(game[i].flags, GF_DELETE))
2781 n++;
2784 if (!n)
2785 tmp = GAME_DELETE_GAME_TEXT;
2786 else {
2787 if (n == gtotal) {
2788 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2789 break;
2792 tmp = GAME_DELETE_ALL_TEXT;
2795 if (config.deleteprompt) {
2796 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
2797 break;
2800 delete_game((!n) ? gindex : -1);
2802 if (history_total(game[gindex].hp))
2803 game[gindex].mode = MODE_HISTORY;
2805 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2806 update_all(game[gindex]);
2807 update_tag_window(game[gindex].tag);
2808 break;
2809 case 'a':
2810 annotate = game[gindex].hindex;
2812 if (annotate && game[gindex].hp[annotate - 1]->move)
2813 annotate--;
2814 else
2815 break;
2817 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2818 game[gindex].hp[annotate]->move);
2820 tmp = get_input(buf, game[gindex].hp[annotate]->comment,
2821 0, 0, NAG_PROMPT, history_edit_nag,
2822 (void *)game[gindex].hp[annotate], CTRL('T'), -1);
2824 if (!tmp && (!game[gindex].hp[annotate]->comment ||
2825 !*game[gindex].hp[annotate]->comment))
2826 break;
2827 else if (tmp && game[gindex].hp[annotate]->comment) {
2828 if (strcmp(tmp, game[gindex].hp[annotate]->comment) == 0)
2829 break;
2832 len = (tmp) ? strlen(tmp) + 1 : 1;
2834 game[gindex].hp[annotate]->comment =
2835 Realloc(game[gindex].hp[annotate]->comment, len);
2837 strncpy(game[gindex].hp[annotate]->comment,
2838 (tmp) ? tmp : "", len);
2840 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2841 update_all(game[gindex]);
2842 break;
2843 case 't':
2844 edit_save_tags(&game[gindex]);
2845 pgn_sort_tags(game[gindex]);
2846 update_all(game[gindex]);
2847 update_tag_window(game[gindex].tag);
2848 break;
2849 case 'i':
2850 if (game[gindex].mode == MODE_EDIT) {
2851 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2852 GAME_EDIT_TEXT);
2854 if (pgn_piece_to_int(c) == -1)
2855 break;
2857 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
2858 break;
2861 edit_tags(game[gindex], board, 0);
2862 break;
2863 case 'g':
2864 if (game[gindex].mode == MODE_HISTORY ||
2865 status.engine == ENGINE_THINKING)
2866 break;
2868 status.engine = ENGINE_THINKING;
2869 update_status_window(game[gindex]);
2870 SEND_TO_ENGINE("go\n");
2871 break;
2872 case 'h':
2873 if (game[gindex].mode == MODE_HISTORY) {
2874 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2875 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2876 break;
2879 if (game[gindex].hindex != history_total(game[gindex].hp)) {
2880 if (!pushkey) {
2881 if ((c = message(NULL, YESNO, "%s",
2882 GAME_RESUME_HISTORY_TEXT)) != 'y')
2883 break;
2886 else {
2887 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2888 break;
2891 if (!noengine)
2892 wtimeout(boardw, 70);
2894 if (!noengine && !engine_initialized) {
2895 if (start_chess_engine() < 0)
2896 break;
2898 pushkey = 'h';
2899 break;
2902 pushkey = 0;
2903 oldhistorytotal = history_total(game[gindex].hp);
2904 game[gindex].mode = MODE_PLAY;
2905 status.engine = ENGINE_READY;
2907 /* FIXME crafty */
2908 #if 0
2909 if (config.engine != GNUCHESS)
2910 SEND_TO_ENGINE("read %s\n", config.fifo);
2911 else
2912 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
2913 #endif
2915 update_all(game[gindex]);
2916 break;
2919 if (!history_total(game[gindex].hp) || status.engine ==
2920 ENGINE_THINKING)
2921 break;
2923 wtimeout(boardw, -1);
2925 if (history_total(game[gindex].hp))
2926 game[gindex].mode = MODE_HISTORY;
2928 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2929 break;
2930 case 'u':
2931 /* FIXME dies reading FIFO sometimes. */
2932 if (game[gindex].mode != MODE_PLAY || !history_total(game[gindex].hp))
2933 break;
2935 history_previous(&game[gindex], board, (count) ? count * 2 : 2,
2936 movestep);
2938 #if 0
2939 if (status.engine == CRAFTY)
2940 SEND_TO_ENGINE("read %s\n", config.fifo);
2941 else
2942 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
2943 #endif
2945 update_history_window(game[gindex]);
2946 break;
2947 case 'r':
2948 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
2949 BROWSER_PROMPT, browse_directory, NULL,
2950 '\t', -1)) == NULL)
2951 break;
2953 tmp = tilde_expand(tmp);
2955 if (pgn_parse_file(tmp))
2956 break;
2958 gindex = gtotal - 1;
2959 strncpy(loadfile, tmp, sizeof(loadfile));
2961 if (history_total(game[gindex].hp))
2962 game[gindex].mode = MODE_HISTORY;
2964 history_update_board(&game[gindex], board, history_total(game[gindex].hp));
2965 update_all(game[gindex]);
2966 update_tag_window(game[gindex].tag);
2967 break;
2968 case 'S':
2969 case 's':
2970 x = -1;
2972 if (gtotal > 1) {
2973 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
2974 GAME_SAVE_MULTI_TEXT);
2976 if (n == 'c')
2977 x = gindex;
2978 else if (n == 'a')
2979 x = -1;
2980 else {
2981 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2982 break;
2986 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
2987 BROWSER_PROMPT, browse_directory, NULL,
2988 '\t', -1)) == NULL) {
2989 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
2990 break;
2993 tmp = tilde_expand(tmp);
2995 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
2996 == NULL) {
2997 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
2998 tmp = tfile;
3001 if (save_pgn(tmp, 0, x)) {
3002 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3003 break;
3006 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3007 update_all(game[gindex]);
3008 break;
3009 case CTRL('G'):
3010 n = 0;
3012 while (n != 'q') {
3013 n = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3014 mainhelp);
3016 switch (n) {
3017 case 'h':
3018 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3019 break;
3020 case 'p':
3021 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3022 break;
3023 case 'e':
3024 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3025 break;
3026 case 'g':
3027 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3028 break;
3029 default:
3030 n = 'q';
3031 break;
3035 break;
3036 case 'n':
3037 case 'N':
3038 if (c == 'N') {
3039 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3040 break;
3043 game[gindex].mode = MODE_PLAY;
3044 editmode = 0;
3045 sp.icon = 0;
3047 if (c == 'n') {
3048 pgn_new_game(board);
3049 add_custom_tags(&game[gindex].tag, &game[gindex].tindex);
3051 else {
3052 pgn_parse_file(NULL);
3053 add_custom_tags(&game[gindex].tag, &game[gindex].tindex);
3054 pgn_init_board(board);
3057 game[gindex].mode = MODE_PLAY;
3058 crow = (game[gindex].side == WHITE) ? 2 : 7;
3059 ccol = 4;
3061 if (!noengine && (status.engine == ENGINE_OFFLINE ||
3062 engine_initialized == 0)) {
3063 if (start_chess_engine() < 0)
3064 break;
3067 SEND_TO_ENGINE("\nnew\n");
3068 set_engine_defaults();
3069 status.engine = ENGINE_READY;
3070 update_status_notify(game[gindex], NULL);
3071 update_all(game[gindex]);
3072 update_tag_window(game[gindex].tag);
3073 break;
3074 case CTRL('L'):
3075 case 'R':
3076 endwin();
3077 keypad(boardw, TRUE);
3078 update_panels();
3079 doupdate();
3080 break;
3081 case 'c':
3082 if (game[gindex].mode == MODE_EDIT) {
3083 castling_state(&game[gindex], board, ROWTOBOARD(crow),
3084 COLTOBOARD(ccol),
3085 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon, 1);
3086 break;
3089 if (status.engine == ENGINE_THINKING)
3090 break;
3092 if (status.engine == ENGINE_OFFLINE)
3093 break;
3095 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL))
3096 != NULL) {
3097 SEND_TO_ENGINE("%s\n", tmp);
3099 break;
3100 case KEY_ESCAPE:
3101 sp.icon = sp.row = sp.col = 0;
3102 markend = markstart = 0;
3104 if (count) {
3105 count = 0;
3106 update_status_notify(game[gindex], NULL);
3109 if (config.validmoves)
3110 board_reset_valid_moves(board);
3112 break;
3113 case '0' ... '9':
3114 n = c - '0';
3116 if (count)
3117 count = count * 10 + n;
3118 else
3119 count = n;
3121 update_status_notify(game[gindex], "Repeat %i", count);
3122 continue;
3123 case KEY_UP:
3124 if (game[gindex].mode == MODE_HISTORY) {
3125 history_next(&game[gindex], board, (count > 0) ?
3126 config.jumpcount * count * movestep :
3127 config.jumpcount * movestep, movestep);
3128 update_cursor(game[gindex], game[gindex].hindex, &crow,
3129 &ccol);
3130 update_all(game[gindex]);
3131 break;
3135 if (sp.icon && config.validmoves) {
3136 get_valid_cursor(board, UP, (count) ? count : 1,
3137 &crow, &ccol, minr, maxr, minc, maxc);
3138 break;
3142 if (count) {
3143 crow += count;
3144 pushkey = '\n';
3146 else
3147 crow++;
3149 if (crow > 8)
3150 crow = 1;
3152 break;
3153 case KEY_DOWN:
3154 if (game[gindex].mode == MODE_HISTORY) {
3155 history_previous(&game[gindex], board, (count) ?
3156 config.jumpcount * count * movestep :
3157 config.jumpcount * movestep, movestep);
3158 update_cursor(game[gindex], game[gindex].hindex, &crow,
3159 &ccol);
3160 update_all(game[gindex]);
3161 break;
3165 if (sp.icon && config.validmoves) {
3166 get_valid_cursor(board, DOWN, (count) ? count : 1,
3167 &crow, &ccol, minr, maxr, minc, maxc);
3168 break;
3172 if (count) {
3173 crow -= count;
3174 pushkey = '\n';
3175 update_status_notify(game[gindex], NULL);
3177 else
3178 crow--;
3180 if (crow < 1)
3181 crow = 8;
3183 break;
3184 case KEY_LEFT:
3185 if (game[gindex].mode == MODE_HISTORY) {
3186 history_previous(&game[gindex], board, (count) ?
3187 count * movestep : movestep, movestep);
3188 update_cursor(game[gindex], game[gindex].hindex, &crow,
3189 &ccol);
3190 update_all(game[gindex]);
3191 break;
3195 if (sp.icon && config.validmoves) {
3196 get_valid_cursor(board, LEFT, (count) ? count : 1,
3197 &crow, &ccol, minr, maxr, minc, maxc);
3198 break;
3202 if (count) {
3203 ccol -= count;
3204 pushkey = '\n';
3206 else
3207 ccol--;
3209 if (ccol < 1)
3210 ccol = 8;
3212 break;
3213 case KEY_RIGHT:
3214 if (game[gindex].mode == MODE_HISTORY) {
3215 history_next(&game[gindex], board, (count) ?
3216 count * movestep : movestep, movestep);
3217 update_cursor(game[gindex], game[gindex].hindex, &crow,
3218 &ccol);
3219 update_all(game[gindex]);
3220 break;
3224 if (sp.icon && config.validmoves) {
3225 get_valid_cursor(board, RIGHT, (count) ? count : 1,
3226 &crow, &ccol, minr, maxr, minc, maxc);
3227 break;
3231 if (count) {
3232 ccol += count;
3233 pushkey = '\n';
3235 else
3236 ccol++;
3238 if (ccol > 8)
3239 ccol = 1;
3241 break;
3242 case 'w':
3243 if (game[gindex].mode == MODE_HISTORY)
3244 break;
3246 if (game[gindex].mode == MODE_EDIT)
3247 pgn_switch_turn(&game[gindex]);
3249 /* FIXME crafty. */
3250 SEND_TO_ENGINE("\nswitch\n");
3251 switch_side(&game[gindex]);
3252 update_status_window(game[gindex]);
3253 break;
3254 case ' ':
3255 if (!editmode && game[gindex].mode == MODE_HISTORY) {
3256 if (movestep == 1)
3257 movestep = 2;
3258 else
3259 movestep = 1;
3261 update_history_window(game[gindex]);
3262 break;
3265 if (!noengine && (status.engine == ENGINE_OFFLINE ||
3266 !engine_initialized) && !editmode) {
3267 if (start_chess_engine() < 0) {
3268 sp.icon = 0;
3269 break;
3274 if (!editmode)
3275 wtimeout(boardw, 70);
3277 if (sp.icon || (!editmode && status.engine == ENGINE_THINKING)) {
3278 beep();
3279 break;
3282 sp.icon = mvwinch(boardw, ROWTOMATRIX(crow),
3283 COLTOMATRIX(ccol)+1) & A_CHARTEXT;
3285 if (sp.icon == ' ') {
3286 sp.icon = 0;
3287 break;
3290 if (!editmode && ((islower(sp.icon) &&
3291 game[gindex].turn != BLACK) ||
3292 (isupper(sp.icon) && game[gindex].turn != WHITE))) {
3293 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
3294 sp.icon = 0;
3295 break;
3298 sp.row = crow;
3299 sp.col = ccol;
3301 if (!editmode && config.validmoves) {
3302 board_get_valid_moves(&game[gindex], board,
3303 pgn_piece_to_int(sp.icon), sp.row, sp.col, &minr,
3304 &maxr, &minc, &maxc);
3306 number_valid_moves(board, sp.row, sp.col);
3310 if (game[gindex].mode == MODE_PLAY)
3311 paused = 0;
3312 break;
3313 case '\015':
3314 case '\n':
3315 pushkey = count = 0;
3316 update_status_notify(game[gindex], NULL);
3318 if (!editmode && game[gindex].mode == MODE_HISTORY)
3319 break;
3321 if (status.engine == ENGINE_THINKING) {
3322 beep();
3323 break;
3326 if (!sp.icon)
3327 break;
3329 sp.destrow = crow;
3330 sp.destcol = ccol;
3332 if (editmode) {
3333 edit_board(game[gindex], board);
3334 sp.icon = sp.row = sp.col = 0;
3335 break;
3338 if (move_to_engine(&game[gindex], board)) {
3339 if (config.validmoves)
3340 board_reset_valid_moves(board);
3342 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
3343 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
3344 SET_FLAG(game[gindex].flags, GF_MODIFIED);
3348 break;
3349 case 'q':
3350 quit = 1;
3351 break;
3352 case 0:
3353 break;
3354 default:
3355 beep();
3356 break;
3359 count = 0;
3363 void usage(const char *pn, int ret)
3365 fprintf((ret) ? stderr : stdout, "%s",
3366 "Usage: cboard [-hvNE] [-d <n>] [-VtRS] [-p <file>]\n"
3367 " -p Load PGN file.\n"
3368 " -V Validate a game file.\n"
3369 " -S Validate and output a PGN formatted game.\n"
3370 " -R Like -S but write a reduced PGN formatted game.\n"
3371 " -t Also write custom PGN tags from config file.\n"
3372 " -N Don't enable the chess engine (two human players).\n"
3373 " -E Stop processing on file parsing error (overrides config).\n"
3374 " -d Escape key delay in milliseconds.\n"
3375 " -v Version information.\n"
3376 " -h This help text.\n");
3378 exit(ret);
3381 void catch_signal(int which)
3383 switch (which) {
3384 case SIGINT:
3385 stop_engine();
3386 endwin();
3387 exit(EXIT_FAILURE);
3388 break;
3389 case SIGPIPE:
3390 if (quit)
3391 break;
3393 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3394 endwin();
3395 exit(EXIT_FAILURE);
3396 break;
3397 case SIGSTOP:
3398 savetty();
3399 break;
3400 case SIGCONT:
3401 resetty();
3402 keypad(boardw, TRUE);
3403 break;
3404 default:
3405 break;
3409 static void set_defaults()
3411 filetype = NO_FILE;
3412 set_config_defaults();
3415 int main(int argc, char *argv[])
3417 int opt;
3418 struct stat st;
3419 char buf[FILENAME_MAX];
3420 char datadir[FILENAME_MAX];
3421 int ret = EXIT_SUCCESS;
3422 int validate_only = 0, validate_and_write = 0, reduced = 0;
3423 int write_custom_tags = 0;
3425 if ((config.pwd = getpwuid(getuid())) == NULL)
3426 err(EXIT_FAILURE, "getpwuid()");
3428 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3429 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3430 config.ccfile = strdup(buf);
3431 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3432 config.nagfile = strdup(buf);
3433 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3434 config.agonyfile = strdup(buf);
3435 snprintf(buf, sizeof(buf), "%s/config", datadir);
3436 config.configfile = strdup(buf);
3437 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
3438 config.fifo = strdup(buf);
3440 if (stat(datadir, &st) == -1) {
3441 if (errno == ENOENT) {
3442 if (mkdir(datadir, 0755) == -1)
3443 err(EXIT_FAILURE, "%s", datadir);
3445 else
3446 err(EXIT_FAILURE, "%s", datadir);
3448 stat(datadir, &st);
3451 if (!S_ISDIR(st.st_mode))
3452 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3454 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
3455 if (mkfifo(config.fifo, 0600) == -1)
3456 err(EXIT_FAILURE, "%s", config.fifo);
3459 set_defaults();
3461 while ((opt = getopt(argc, argv, "d:ENVtSRhp:v")) != -1) {
3462 switch (opt) {
3463 case 'd':
3464 ESCDELAY = atoi(optarg);
3465 break;
3466 case 't':
3467 write_custom_tags = 1;
3468 break;
3469 case 'E':
3470 config.stoponerror = 1;
3471 break;
3472 case 'N':
3473 noengine = 1;
3474 break;
3475 case 'R':
3476 reduced = 1;
3477 case 'S':
3478 validate_and_write = 1;
3479 case 'V':
3480 validate_only = 1;
3481 break;
3482 case 'v':
3483 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3484 COPYRIGHT);
3485 exit(EXIT_SUCCESS);
3486 case 'p':
3487 filetype = PGN_FILE;
3488 strncpy(loadfile, optarg, sizeof(loadfile));
3489 break;
3490 case 'h':
3491 default:
3492 usage(argv[0], EXIT_SUCCESS);
3496 if ((validate_only || validate_and_write) && !*loadfile)
3497 usage(argv[0], EXIT_FAILURE);
3499 if (access(config.configfile, R_OK) == 0)
3500 parse_rcfile(config.configfile);
3502 signal(SIGPIPE, catch_signal);
3503 signal(SIGCONT, catch_signal);
3504 signal(SIGSTOP, catch_signal);
3505 signal(SIGINT, catch_signal);
3507 srandom(getpid());
3509 switch (filetype) {
3510 case PGN_FILE:
3511 ret = pgn_parse_file(loadfile);
3512 break;
3513 case FEN_FILE:
3514 //ret = parse_fen_file(loadfile);
3515 break;
3516 case EPD_FILE: // Not implemented.
3517 case NO_FILE:
3518 default:
3519 // No file specified. Empty game.
3520 ret = pgn_parse_file(NULL);
3521 add_custom_tags(&game[gindex].tag, &game[gindex].tindex);
3522 break;
3525 if (ret == -1)
3526 err(EXIT_FAILURE, "%s", loadfile);
3528 if (validate_only || validate_and_write) {
3529 if (validate_and_write) {
3530 int i;
3532 for (i = 0; i < gtotal; i++) {
3533 if (write_custom_tags)
3534 add_custom_tags(&game[i].tag, &game[i].tindex);
3536 pgn_write(stdout, game[i], reduced);
3540 pgn_free_all();
3541 exit(ret);
3543 else if (ret)
3544 exit(ret);
3546 if (initscr() == NULL)
3547 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3548 else
3549 curses_initialized = 1;
3551 if (LINES < 24 || COLS < 80) {
3552 endwin();
3553 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3556 if (has_colors() == TRUE && start_color() == OK)
3557 init_color_pairs();
3559 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3560 boardp = new_panel(boardw);
3561 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3562 COLS - HISTORY_WIDTH);
3563 historyp = new_panel(historyw);
3564 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3565 statusp = new_panel(statusw);
3566 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3567 tagp = new_panel(tagw);
3568 keypad(boardw, TRUE);
3569 // leaveok(boardw, TRUE);
3570 leaveok(tagw, TRUE);
3571 leaveok(statusw, TRUE);
3572 leaveok(historyw, TRUE);
3573 curs_set(0);
3574 cbreak();
3575 noecho();
3576 draw_window_decor();
3578 game_loop();
3579 stop_engine();
3581 endwin();
3582 pgn_free_all();
3583 del_panel(boardp);
3584 del_panel(historyp);
3585 del_panel(statusp);
3586 del_panel(tagp);
3587 delwin(boardw);
3588 delwin(historyw);
3589 delwin(statusw);
3590 delwin(tagw);
3591 exit(EXIT_SUCCESS);