Updates.
[cboard.git] / src / cboard.c
blobc99296692347142d586501f544d7d94e28eb695d
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_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 "common.h"
61 #include "cboard.h"
63 #ifdef DEBUG
64 #include "debug.h"
65 #endif
67 #ifdef WITH_DMALLOC
68 #include <dmalloc.h>
69 #endif
71 static char *str_etc(const char *str, int maxlen, int rev)
73 int len = strlen(str);
74 static char buf[80], *p = buf;
75 int i;
77 strncpy(buf, str, sizeof(buf));
79 if (len > maxlen) {
80 if (rev) {
81 p = buf;
82 *p++ = '.';
83 *p++ = '.';
84 *p++ = '.';
86 for (i = 0; i < maxlen + 3; i++)
87 *p++ = buf[(len - maxlen) + i + 3];
89 else {
90 p = buf + maxlen - 4;
91 *p++ = '.';
92 *p++ = '.';
93 *p++ = '.';
96 *p = '\0';
99 return buf;
102 void update_cursor(GAME g, int idx)
104 char *p;
105 int len;
106 int t = pgn_history_total(g.hp);
107 struct userdata_s *d = g.data;
110 * If not deincremented then r and c would be the next move.
112 idx--;
114 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
115 d->c_row = 2, d->c_col = 5;
116 return;
119 p = g.hp[idx]->move;
120 len = strlen(p);
122 if (*p == 'O') {
123 if (len <= 4)
124 d->c_col = 7;
125 else
126 d->c_col = 3;
128 d->c_row = (g.turn == WHITE) ? 1 : 8;
129 return;
132 p += len;
134 while (!isdigit(*p))
135 p--;
137 d->c_row = ROWTOINT(*p--);
138 d->c_col = COLTOINT(*p);
141 static int init_nag()
143 FILE *fp;
144 char line[LINE_MAX];
145 int i = 0;
147 if ((fp = fopen(config.nagfile, "r")) == NULL) {
148 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
149 return 1;
152 nags = Realloc(nags, 2 * sizeof(char *));
153 nags[0] = NULL;
154 i++;
156 while (!feof(fp)) {
157 if (fscanf(fp, " %[^\n] ", line) == 1) {
158 nags = Realloc(nags, (i + 2) * sizeof(char *));
159 nags[i++] = strdup(line);
163 return 0;
166 void set_menu_vars(int c, int rows, int items, int *item, int *top)
168 int selected = *item;
169 int toppos = *top;
171 switch (c) {
172 case KEY_HOME:
173 selected = toppos = 0;
174 break;
175 case KEY_END:
176 selected = items;
177 toppos = items - rows + 1;
178 break;
179 case KEY_UP:
180 if (selected - 1 < 0) {
181 selected = items;
183 toppos = selected - rows + 1;
185 else {
186 selected--;
188 if (toppos && selected <= toppos)
189 toppos = selected;
191 break;
192 case KEY_DOWN:
193 if (selected + 1 > items )
194 selected = toppos = 0;
195 else {
196 selected++;
198 if (selected - toppos >= rows)
199 toppos++;
201 break;
202 case KEY_PPAGE:
203 selected -= rows;
205 if (selected < 0)
206 selected = 0;
208 toppos = selected - rows + 1;
210 if (toppos < 0)
211 toppos = 0;
212 break;
213 case KEY_NPAGE:
214 selected += rows;
216 if (selected > items)
217 selected = items;
219 toppos = selected - rows + 1;
221 if (toppos < 0)
222 toppos = 0;
223 break;
224 default:
225 toppos = selected - rows + 1;
227 if (toppos < 0)
228 toppos = 0;
229 break;
232 *item = selected;
233 *top = toppos;
236 int test_nag_selected(unsigned char nag[], int s)
238 int i;
240 for (i = 0; i < MAX_PGN_NAG; i++) {
241 if (nag[i] == s)
242 return i;
245 return -1;
248 char *history_edit_nag(void *arg)
250 WINDOW *win;
251 PANEL *panel;
252 int i = 0, n;
253 int itemcount = 0;
254 int rows, cols;
255 HISTORY *anno = (HISTORY *)arg;
256 int selected = 0;
257 int toppos = 0;
258 int len = 0;
259 int total = 0;
260 unsigned char nag[MAX_PGN_NAG] = {0};
261 char menubuf[64] = {0}, *mp = menubuf;
263 if (!nags) {
264 if (init_nag())
265 return NULL;
268 for (i = 1, n = 0; nags[i]; i++) {
269 n = strlen(nags[i]);
271 if (len < n)
272 len = n;
275 total = i;
276 cols = len + 2;
277 rows = (total + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : total + 4;
279 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
280 panel = new_panel(win);
281 cbreak();
282 noecho();
283 keypad(win, TRUE);
284 nl();
285 wbkgd(win, CP_MENU);
286 memcpy(&nag, &anno->nag, sizeof(nag));
288 for (i = 0; i < MAX_PGN_NAG; i++) {
289 if (nag[i])
290 itemcount++;
293 while (1) {
294 int c;
295 char buf[cols - 4];
297 wmove(win, 0, 0);
298 wclrtobot(win);
299 draw_window_title(win, NAG_EDIT_TITLE, cols, CP_INPUT_TITLE,
300 CP_INPUT_BORDER);
302 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
303 if (i == selected) {
304 wattron(win, CP_MENU_HIGHLIGHT);
305 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
306 wattroff(win, CP_MENU_HIGHLIGHT);
307 continue;
310 if (test_nag_selected(nag, i) != -1) {
311 wattron(win, CP_MENU_SELECTED);
312 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
313 wattroff(win, CP_MENU_SELECTED);
314 continue;
317 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
320 snprintf(buf, sizeof(buf), "NAG %i of %i (%i of %i selected) %s",
321 selected + 1, total, itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
322 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
324 nl();
325 refresh_all();
326 c = wgetch(win);
328 switch (c) {
329 int found;
331 case KEY_F(1):
332 help(NAG_EDIT_HELP, ANYKEY, naghelp);
333 break;
334 case KEY_HOME:
335 case KEY_END:
336 case KEY_UP:
337 case KEY_DOWN:
338 case KEY_PPAGE:
339 case KEY_NPAGE:
340 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
341 menubuf[0] = 0;
342 mp = menubuf;
343 break;
344 case ' ':
345 if (selected == 0) {
346 for (i = 0; i < MAX_PGN_NAG; i++)
347 nag[i] = 0;
349 itemcount = 0;
350 break;
353 if ((found = test_nag_selected(nag, selected)) != -1) {
354 nag[found] = 0;
355 itemcount--;
357 else {
358 if (itemcount + 1 > MAX_PGN_NAG)
359 break;
361 for (i = 0; i < MAX_PGN_NAG; i++) {
362 if (nag[i] == 0) {
363 nag[i] = selected;
364 break;
368 itemcount++;
371 break;
372 case '\n':
373 goto done;
374 break;
375 case KEY_ESCAPE:
376 goto done;
377 break;
378 default:
379 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
380 menubuf[0] = 0;
381 mp = menubuf;
384 *mp++ = c;
385 *mp = 0;
386 n = selected;
388 for (i = 0; i < total; i++) {
389 if (!nags[i])
390 continue;
392 if (strncasecmp(menubuf, nags[i], strlen(menubuf)) == 0) {
393 selected = i;
394 break;
398 if (n == selected) {
399 menubuf[0] = 0;
400 mp = menubuf;
403 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
404 break;
408 done:
409 memcpy(&anno->nag, &nag, sizeof(nag));
410 del_panel(panel);
411 delwin(win);
412 return NULL;
415 static void view_nag(void *arg)
417 HISTORY *h = (HISTORY *)arg;
418 char buf[80];
419 char line[LINE_MAX] = {0};
420 int i = 0;
422 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
424 if (!nags) {
425 if (init_nag())
426 return;
429 for (i = 0; i < MAX_PGN_NAG; i++) {
430 if (!h->nag[i])
431 break;
433 strncat(line, nags[h->nag[i]], sizeof(line));
434 strncat(line, "\n", sizeof(line));
437 line[strlen(line) - 1] = 0;
438 message(buf, ANYKEY, "%s", line);
441 void view_annotation(HISTORY h)
443 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
444 int nag = 0, comment = 0;
446 if (h.comment && h.comment[0])
447 comment++;
449 if (h.nag[0])
450 nag++;
452 if (!nag && !comment)
453 return;
455 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
457 if (comment)
458 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
459 (nag) ? "Press 'n' to view NAG" : NULL,
460 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
461 (nag) ? 'n' : 0, "%s", h.comment);
462 else
463 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
464 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
467 static void cleanup(WINDOW *win, PANEL *panel, struct file_s *files)
469 int i;
471 if (files) {
472 for (i = 0; files[i].name; i++) {
473 free(files[i].path);
474 free(files[i].name);
475 free(files[i].st);
478 free(files);
481 del_panel(panel);
482 delwin(win);
485 static int sort_files(const void *a, const void *b)
487 const struct file_s *aa = a;
488 const struct file_s *bb = b;
490 return strcmp(aa->name, bb->name);
493 char *file_browser(void *arg)
495 char pattern[FILENAME_MAX];
496 static char path[FILENAME_MAX];
497 static char file[FILENAME_MAX];
498 struct stat st;
499 char *p;
500 int cursor = curs_set(0);
501 char menubuf[64] = {0}, *mp = menubuf;
503 if (!*path) {
504 if (config.savedirectory) {
505 if ((p = word_expand(config.savedirectory)) == NULL)
506 return NULL;
508 strncpy(path, p, sizeof(path));
510 if (access(path, R_OK) == -1) {
511 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
512 getcwd(path, sizeof(path));
515 else
516 getcwd(path, sizeof(path));
519 again:
521 * First find directories (including hidden) in the working directory.
522 * Then apply the config.pattern to regular files.
524 if ((p = word_split_append(path, '/', ".* *")) == NULL)
525 return NULL;
527 strncpy(pattern, p, sizeof(pattern));
529 while (1) {
530 WINDOW *win;
531 PANEL *panel;
532 char *tmp = NULL;
533 int rows, cols = 0;
534 int selected = 0;
535 int toppos = 0;
536 int len = strlen(path);
537 wordexp_t w;
538 int i, n = 0;
539 struct file_s *files = NULL;
540 int which = 1;
541 int x = WRDE_NOCMD;
542 int nlen = 0;
544 new_we:
545 if (wordexp(pattern, &w, x) != 0) {
546 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
547 return NULL;
550 for (i = 0; i < w.we_wordc; i++) {
551 struct tm *tp;
552 char tbuf[16];
553 char sbuf[64];
555 if (stat(w.we_wordv[i], &st) == -1)
556 continue;
558 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
559 p++;
560 else
561 p = w.we_wordv[i];
563 if (which) {
564 if (!S_ISDIR(st.st_mode))
565 continue;
567 if (p[0] == '.' && p[1] == 0)
568 continue;
570 else {
571 if (S_ISDIR(st.st_mode))
572 continue;
575 len = strlen(p) + 2;
576 files = Realloc(files, (n + 2) * sizeof(struct file_s));
577 files[n].path = strdup(w.we_wordv[i]);
578 files[n].name = Malloc(len);
579 strncpy(files[n].name, p, len);
581 if (S_ISDIR(st.st_mode))
582 files[n].name[len - 2] = '/';
584 tp = localtime(&st.st_mtime);
585 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
586 snprintf(sbuf, sizeof(sbuf), "%9i %s", (int)st.st_size, tbuf);
587 files[n].st = strdup(sbuf);
588 memset(&files[++n], '\0', sizeof(struct file_s));
591 which--;
593 if (which == 0) {
594 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
595 return NULL;
597 strncpy(pattern, p, sizeof(pattern));
598 x |= WRDE_REUSE;
599 goto new_we;
602 wordfree(&w);
603 qsort(files, n, sizeof(struct file_s), sort_files);
605 for (i = x = nlen = 0; i < n; i++) {
606 if (strlen(files[i].name) > nlen)
607 nlen = strlen(files[i].name);
609 if (x < nlen + strlen(files[i].st))
610 x = nlen + strlen(files[i].st);
613 cols = x + 1;
615 if (cols < strlen(path))
616 cols = strlen(path);
618 if (cols < strlen(HELP_PROMPT))
619 cols = strlen(HELP_PROMPT);
621 if (cols > COLS)
622 cols = COLS - 4;
624 cols += 2;
625 rows = (n + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : n + 4;
627 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
628 wbkgd(win, CP_MENU);
629 panel = new_panel(win);
630 draw_window_title(win, path, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
631 draw_prompt(win, rows - 2, cols, HELP_PROMPT, CP_INPUT_PROMPT);
632 cbreak();
633 noecho();
634 keypad(win, TRUE);
635 nl();
637 while (1) {
638 int c;
640 for (i = toppos, c = 2; i < n && c < rows - 2; i++, c++) {
641 if (i == selected) {
642 wattron(win, CP_MENU_HIGHLIGHT);
643 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
644 cols - nlen - 2 - 1, files[i].st);
645 wattroff(win, CP_MENU_HIGHLIGHT);
646 continue;
649 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
650 cols - nlen - 2 - 1, files[i].st);
653 refresh_all();
654 c = wgetch(win);
656 switch (c) {
657 case KEY_HOME:
658 case KEY_END:
659 case KEY_UP:
660 case KEY_DOWN:
661 case KEY_PPAGE:
662 case KEY_NPAGE:
663 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
664 menubuf[0] = 0;
665 mp = menubuf;
666 break;
667 case '\n':
668 goto gotitem;
669 break;
670 case KEY_ESCAPE:
671 cleanup(win, panel, files);
672 file[0] = 0;
673 goto done;
674 break;
675 case KEY_F(1):
676 help(BROWSER_HELP, ANYKEY, file_browser_help);
677 break;
678 case '~':
679 strncpy(path, "~", sizeof(path));
680 cleanup(win, panel, files);
681 goto again;
682 break;
683 case CTRL('X'):
684 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
685 == NULL)
686 break;
688 if (tmp[strlen(tmp) - 1] == '/')
689 tmp[strlen(tmp) - 1] = 0;
691 strncpy(path, tmp, sizeof(path));
692 cleanup(win, panel, files);
693 goto again;
694 break;
695 default:
696 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
697 menubuf[0] = 0;
698 mp = menubuf;
701 *mp++ = c;
702 *mp = 0;
703 x = selected;
705 for (i = 0; i < n; i++) {
706 if (strncasecmp(menubuf, files[i].name,
707 strlen(menubuf)) == 0) {
708 selected = i;
709 break;
713 if (x == selected) {
714 menubuf[0] = 0;
715 mp = menubuf;
718 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
719 break;
723 gotitem:
724 menubuf[0] = 0;
725 mp = menubuf;
726 strncpy(file, files[selected].path, sizeof(file));
727 cleanup(win, panel, files);
729 if (stat(file, &st) == -1) {
730 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
731 continue;
734 if (S_ISDIR(st.st_mode)) {
735 p = file + strlen(file) - 2;
737 if (strcmp(p, "..") == 0) {
738 p = file + strlen(file) - 3;
739 *p = 0;
741 if ((p = strrchr(file, '/')) != NULL)
742 file[strlen(file) - strlen(p)] = 0;
745 strncpy(path, file, sizeof(path));
746 goto again;
749 if (S_ISREG(st.st_mode))
750 break;
752 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
755 done:
756 curs_set(cursor);
757 return (*file) ? file : NULL;
760 static int init_country_codes()
762 FILE *fp;
763 char line[LINE_MAX], *s;
764 int cindex = 0;
766 if ((fp = fopen(config.ccfile, "r")) == NULL) {
767 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
768 return 1;
771 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
772 char *tmp;
774 if ((tmp = strsep(&s, " ")) == NULL)
775 continue;
777 s = trim(s);
778 tmp = trim(tmp);
780 if (!s || !tmp)
781 continue;
783 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
784 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
785 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
786 cindex++;
789 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
790 fclose(fp);
792 return 0;
795 char *country_codes(void *arg)
797 WINDOW *win;
798 PANEL *panel;
799 int i = 0, n;
800 int rows, cols;
801 char *tmp = NULL;
802 int len = 0;
803 int total;
804 int selected = 0;
805 int toppos = 0;
806 char menubuf[64] = {0}, *mp = menubuf;
808 if (!ccodes) {
809 if (init_country_codes())
810 return NULL;
813 for (i = n = 0; ccodes[i].code && ccodes[i].code[0]; i++) {
814 n = strlen(ccodes[i].code) + strlen(ccodes[i].country);
816 if (len < n)
817 len = n;
820 total = i;
821 cols = len;
823 if (cols < strlen(HELP_PROMPT) + 21)
824 cols = strlen(HELP_PROMPT) + 21;
826 cols += 1;
828 if (cols > COLS)
829 cols = COLS - 4;
831 cols += 2;
832 rows = (i + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : i + 4;
833 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
834 panel = new_panel(win);
835 cbreak();
836 noecho();
837 keypad(win, TRUE);
838 nl();
839 wbkgd(win, CP_MENU);
841 while (1) {
842 int c;
843 char buf[cols - 4];
845 wmove(win, 0, 0);
846 wclrtobot(win);
848 draw_window_title(win, CC_TITLE, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
850 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
851 if (i == selected) {
852 wattron(win, CP_MENU_HIGHLIGHT);
853 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code,
854 ccodes[i].country);
855 wattroff(win, CP_MENU_HIGHLIGHT);
856 continue;
859 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code, ccodes[i].country);
862 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR,
863 selected + 1, N_OF_N_STR, total, HELP_PROMPT);
864 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
865 refresh_all();
866 c = wgetch(win);
868 switch (c) {
869 case KEY_F(1):
870 help(CC_KEY_HELP, ANYKEY, cc_help);
871 break;
872 case KEY_HOME:
873 case KEY_END:
874 case KEY_UP:
875 case KEY_DOWN:
876 case KEY_PPAGE:
877 case KEY_NPAGE:
878 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
879 menubuf[0] = 0;
880 mp = menubuf;
881 break;
882 case '\n':
883 tmp = ccodes[selected].code;
884 goto done;
885 break;
886 case KEY_ESCAPE:
887 tmp = NULL;
888 goto done;
889 break;
890 default:
891 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
892 menubuf[0] = 0;
893 mp = menubuf;
896 *mp++ = c;
897 *mp = 0;
898 n = selected;
900 for (i = 0; i < total; i++) {
901 if (strncasecmp(menubuf, ccodes[i].code,
902 strlen(menubuf)) == 0) {
903 selected = i;
904 break;
908 if (n == selected) {
909 menubuf[0] = 0;
910 mp = menubuf;
913 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
914 break;
918 done:
919 del_panel(panel);
920 delwin(win);
921 return tmp;
924 static void add_custom_tags(TAG ***t)
926 int i;
927 int total = pgn_tag_total(config.tag);
929 if (!config.tag)
930 return;
932 for (i = 0; i < total; i++)
933 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
935 pgn_tag_sort(*t);
938 TAG **edit_tags(GAME g, BOARD b, int edit)
940 TAG **data = NULL;
941 struct tm tp;
942 int data_index = 0;
943 int len;
944 int selected = 0;
945 int n;
946 int toppos = 0;
947 char menubuf[64] = {0}, *mp = menubuf;
949 /* Edit the backup copy, not the original in case the save fails. */
950 len = pgn_tag_total(g.tag);
952 for (n = 0; n < len; n++)
953 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
955 data_index = pgn_tag_total(data);
957 while (1) {
958 WINDOW *win;
959 PANEL *panel;
960 int i;
961 char buf[76] = {0};
962 char *tmp = NULL;
963 int rows, cols;
964 int nlen = 0;
966 data_index = pgn_tag_total(data);
968 for (i = cols = 0, n = 4; i < data_index; i++) {
969 n = strlen(data[i]->name);
971 if (nlen < n)
972 nlen = n;
974 if (data[i]->value)
975 n += strlen(data[i]->value);
976 else
977 n += strlen(UNKNOWN);
979 if (cols < n)
980 cols = n;
983 cols += nlen + 2;
985 if (cols > COLS)
986 cols = COLS - 2;
988 /* +14 for the extra prompt info. */
989 if (cols < strlen(HELP_PROMPT) + 14 + 2)
990 cols = strlen(HELP_PROMPT) + 14 + 2;
992 rows = (data_index + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 :
993 data_index + 4;
995 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
996 panel = new_panel(win);
997 cbreak();
998 noecho();
999 keypad(win, TRUE);
1000 nl();
1001 wbkgd(win, CP_MENU);
1002 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1003 cols, (edit) ? CP_INPUT_TITLE : CP_MESSAGE_TITLE,
1004 (edit) ? CP_INPUT_BORDER : CP_MESSAGE_BORDER);
1006 if (selected >= data_index - 1)
1007 selected = data_index - 1;
1009 while (1) {
1010 int c;
1011 TAG **tmppgn = NULL;
1012 char *newtag = NULL;
1014 for (i = toppos, c = 2; i < data_index && c < rows - 2; i++, c++) {
1015 if (i == selected) {
1016 wattron(win, CP_MENU_HIGHLIGHT);
1017 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1018 cols - nlen - 2 - 2, (data[i]->value &&
1019 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1020 wattroff(win, CP_MENU_HIGHLIGHT);
1021 continue;
1024 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1025 cols - nlen - 2 - 2, (data[i]->value &&
1026 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1029 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1030 selected + 1, N_OF_N_STR, data_index, HELP_PROMPT);
1031 draw_prompt(win, rows - 2, cols, buf,
1032 (edit) ? CP_INPUT_PROMPT : CP_MESSAGE_PROMPT);
1033 refresh_all();
1034 c = wgetch(win);
1036 switch (c) {
1037 case CTRL('T'):
1038 if (!edit)
1039 break;
1041 add_custom_tags(&data);
1042 selected = data_index - 1;
1043 toppos = data_index - (rows - 4);
1044 goto cleanup;
1045 break;
1046 case KEY_F(1):
1047 if (edit)
1048 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1049 else
1050 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1051 break;
1052 case CTRL('R'):
1053 if (!edit)
1054 break;
1056 if (selected <= 6) {
1057 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1058 goto cleanup;
1061 data_index = pgn_tag_total(data);
1063 for (i = 0; i < data_index; i++) {
1064 if (i == selected)
1065 continue;
1067 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
1070 pgn_tag_free(data);
1071 data = NULL;
1073 for (i = 0; tmppgn[i]; i++)
1074 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
1076 pgn_tag_free(tmppgn);
1078 if (selected >= data_index)
1079 selected = data_index - 1;
1081 if (selected > rows - 5)
1082 toppos = selected - (rows - 5);
1083 else
1084 toppos -= (toppos) ? 1 : 0;
1086 goto cleanup;
1087 break;
1088 case CTRL('A'):
1089 if (!edit)
1090 break;
1092 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1093 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1094 == NULL)
1095 break;
1097 newtag[0] = toupper(newtag[0]);
1099 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1100 strlen(PRESS_ENTER)) {
1101 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1102 break;
1105 for (i = 0; i < data_index; i++) {
1106 if (strcasecmp(data[i]->name, newtag) == 0) {
1107 selected = i;
1108 goto gotitem;
1112 pgn_tag_add(&data, newtag, NULL);
1113 data_index = pgn_tag_total(data);
1114 selected = data_index - 1;
1115 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1116 &toppos);
1117 goto gotitem;
1118 break;
1119 case KEY_HOME:
1120 case KEY_END:
1121 case KEY_UP:
1122 case KEY_DOWN:
1123 case KEY_NPAGE:
1124 case KEY_PPAGE:
1125 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1126 &toppos);
1127 menubuf[0] = 0;
1128 mp = menubuf;
1129 break;
1130 case CTRL('F'):
1131 if (!edit)
1132 break;
1134 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1135 data_index = pgn_tag_total(data);
1136 selected = data_index - 1;
1137 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1138 &toppos);
1139 goto gotitem;
1140 break;
1141 case '\n':
1142 goto gotitem;
1143 break;
1144 case KEY_ESCAPE:
1145 del_panel(panel);
1146 delwin(win);
1147 goto done;
1148 break;
1149 default:
1150 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
1151 menubuf[0] = 0;
1152 mp = menubuf;
1155 *mp++ = c;
1156 *mp = 0;
1157 n = selected;
1159 for (i = 0; i < data_index; i++) {
1160 if (strncasecmp(menubuf, data[i]->name, strlen(menubuf))
1161 == 0) {
1162 selected = i;
1163 break;
1167 if (n == selected) {
1168 menubuf[0] = 0;
1169 mp = menubuf;
1172 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1173 &toppos);
1174 break;
1178 gotitem:
1179 nlen = strlen(data[selected]->name) + 2;
1180 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1182 if (nlen > MAX_VALUE_WIDTH)
1183 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1184 else
1185 snprintf(buf, sizeof(buf), "%s \"%s\"",
1186 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1187 data[selected]->name);
1189 if (!edit) {
1190 if (!data[selected]->value)
1191 goto cleanup;
1193 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1194 goto cleanup;
1197 if (strcmp(data[selected]->name, "Date") == 0) {
1198 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1199 0, FIELD_TYPE_PGN_DATE);
1201 if (tmp) {
1202 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1203 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1204 goto cleanup;
1207 else
1208 goto cleanup;
1210 else if (strcmp(data[selected]->name, "Site") == 0) {
1211 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1212 country_codes, NULL, CTRL('t'), -1);
1214 if (!tmp)
1215 tmp = "?";
1217 else if (strcmp(data[selected]->name, "Round") == 0) {
1218 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1219 FIELD_TYPE_PGN_ROUND);
1221 if (!tmp) {
1222 if (gtotal > 1)
1223 tmp = "?";
1224 else
1225 tmp = "-";
1228 else if (strcmp(data[selected]->name, "Result") == 0) {
1229 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1230 0, -1);
1232 if (!tmp)
1233 tmp = "*";
1235 else {
1236 tmp = (data[selected]->value) ? data[selected]->value : NULL;
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 del_panel(panel);
1246 delwin(win);
1247 menubuf[0] = 0;
1248 mp = menubuf;
1251 done:
1252 if (!edit) {
1253 pgn_tag_free(data);
1254 return NULL;
1257 return data;
1260 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1261 * game index number.
1263 int save_pgn(const char *filename, int saveindex)
1265 FILE *fp;
1266 char *mode = NULL;
1267 int c;
1268 char buf[FILENAME_MAX];
1269 struct stat st;
1270 int i;
1271 char *command = NULL;
1272 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1274 if (filename[0] != '/' && config.savedirectory) {
1275 if (stat(config.savedirectory, &st) == -1) {
1276 if (errno == ENOENT) {
1277 if (mkdir(config.savedirectory, 0755) == -1) {
1278 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1279 strerror(errno));
1280 return 1;
1283 else {
1284 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1285 strerror(errno));
1286 return 1;
1290 stat(config.savedirectory, &st);
1292 if (!S_ISDIR(st.st_mode)) {
1293 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1294 return 1;
1297 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1298 filename = buf;
1301 if (access(filename, W_OK) == 0) {
1302 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1303 "%s \"%s\"", E_FILEEXISTS, filename);
1305 switch (c) {
1306 case 'a':
1307 if (pgn_is_compressed(filename) == E_PGN_OK) {
1308 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1309 return 1;
1312 mode = "a";
1313 break;
1314 case 'o':
1315 mode = "w+";
1316 break;
1317 default:
1318 return 1;
1321 else
1322 mode = "a";
1324 if (command) {
1325 if ((fp = popen(command, "w")) == NULL) {
1326 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1327 return 1;
1330 else {
1331 if ((fp = fopen(filename, mode)) == NULL) {
1332 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1333 return 1;
1337 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1338 pgn_write(fp, game[i]);
1340 if (command)
1341 pclose(fp);
1342 else
1343 fclose(fp);
1345 if (saveindex == -1)
1346 strncpy(loadfile, filename, sizeof(loadfile));
1348 return 0;
1351 char *random_agony(GAME g)
1353 static int n;
1354 FILE *fp;
1355 char line[LINE_MAX];
1357 if (n == -1 || !config.agony || !curses_initialized ||
1358 (g.mode == MODE_HISTORY && !config.historyagony))
1359 return NULL;
1361 if (!agony) {
1362 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1363 n = -1;
1364 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1365 return NULL;
1368 while (!feof(fp)) {
1369 if (fscanf(fp, " %[^\n] ", line) == 1) {
1370 agony = Realloc(agony, (n + 2) * sizeof(char *));
1371 agony[n++] = strdup(trim(line));
1375 agony[n] = NULL;
1376 fclose(fp);
1378 if (agony[0] == NULL || !n) {
1379 n = -1;
1380 return NULL;
1384 return agony[random() % n];
1387 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1389 if (pgn_piece_to_int(piece) == ROOK && col == 7
1390 && row == 7 &&
1391 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1392 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1393 if (mod)
1394 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1395 return 1;
1397 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1398 && row == 7 &&
1399 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1400 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1401 if (mod)
1402 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1403 return 1;
1405 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1406 && row == 0 &&
1407 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1408 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1409 if (mod)
1410 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1411 return 1;
1413 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1414 && row == 0 &&
1415 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1416 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1417 if (mod)
1418 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1419 return 1;
1421 else if (pgn_piece_to_int(piece) == KING && col == 4
1422 && row == 7 &&
1423 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1424 TEST_FLAG(g->flags, GF_WK_CASTLE))
1426 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1427 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1428 if (mod) {
1429 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1430 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1431 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1432 else
1433 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1435 return 1;
1437 else if (pgn_piece_to_int(piece) == KING && col == 4
1438 && row == 0 &&
1439 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1440 TEST_FLAG(g->flags, GF_BK_CASTLE))
1442 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1443 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1444 if (mod) {
1445 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1446 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1447 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1448 else
1449 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1451 return 1;
1454 return 0;
1457 static void draw_board(GAME *g, int details)
1459 int row, col;
1460 int bcol = 0, brow = 0;
1461 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1462 int ncols = 0, offset = 1;
1463 unsigned coords_y = 8;
1464 struct userdata_s *d = g->data;
1466 if (g->mode != MODE_PLAY && g->mode != MODE_EDIT)
1467 update_cursor(*g, g->hindex);
1469 for (row = 0; row < maxy; row++) {
1470 bcol = 0;
1472 for (col = 0; col < maxx; col++) {
1473 int attrwhich = -1;
1474 chtype attrs = 0;
1475 unsigned char piece;
1477 if (row == 0 || row == maxy - 2) {
1478 if (col == 0)
1479 mvwaddch(boardw, row, col,
1480 LINE_GRAPHIC((row) ?
1481 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1482 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1483 else if (col == maxx - 2)
1484 mvwaddch(boardw, row, col,
1485 LINE_GRAPHIC((row) ?
1486 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1487 ACS_URCORNER | CP_BOARD_GRAPHICS));
1488 else if (!(col % 4))
1489 mvwaddch(boardw, row, col,
1490 LINE_GRAPHIC((row) ?
1491 ACS_BTEE | CP_BOARD_GRAPHICS :
1492 ACS_TTEE | CP_BOARD_GRAPHICS));
1493 else {
1494 if (col != maxx - 1)
1495 mvwaddch(boardw, row, col,
1496 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1499 continue;
1502 if ((row % 2) && col == maxx - 1 && coords_y) {
1503 wattron(boardw, CP_BOARD_COORDS);
1504 mvwprintw(boardw, row, col, "%d", coords_y--);
1505 wattroff(boardw, CP_BOARD_COORDS);
1506 continue;
1509 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1510 if (!(row % 2))
1511 mvwaddch(boardw, row, col,
1512 LINE_GRAPHIC((col) ?
1513 ACS_RTEE | CP_BOARD_GRAPHICS :
1514 ACS_LTEE | CP_BOARD_GRAPHICS));
1515 else
1516 mvwaddch(boardw, row, col,
1517 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1519 continue;
1522 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1523 mvwaddch(boardw, row, col,
1524 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1525 continue;
1528 if (!(col % 4) && row != maxy - 1) {
1529 mvwaddch(boardw, row, col,
1530 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1531 continue;
1534 if ((row % 2)) {
1535 if ((col % 4)) {
1536 if (ncols++ == 8) {
1537 offset++;
1538 ncols = 1;
1541 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1542 && (offset % 2)))
1543 attrwhich = BLACK;
1544 else
1545 attrwhich = WHITE;
1547 if (config.validmoves && d->b[brow][bcol].valid) {
1548 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1549 CP_BOARD_MOVES_BLACK;
1551 else
1552 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1553 CP_BOARD_BLACK;
1555 if (row == ROWTOMATRIX(d->c_row) && col ==
1556 COLTOMATRIX(d->c_col)) {
1557 attrs = CP_BOARD_CURSOR;
1560 if (row == ROWTOMATRIX(sp.row) &&
1561 col == COLTOMATRIX(sp.col)) {
1562 attrs = CP_BOARD_SELECTED;
1565 if (row == maxy - 1)
1566 attrs = 0;
1568 mvwaddch(boardw, row, col, ' ' | attrs);
1570 if (row == maxy - 1)
1571 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1572 else {
1573 if (details && d->b[row / 2][bcol].enpassant)
1574 piece = 'x';
1575 else
1576 piece = d->b[row / 2][bcol].icon;
1578 if (details && castling_state(g, d->b, brow, bcol,
1579 piece, 0))
1580 attrs |= A_REVERSE;
1582 if (g->side == WHITE && isupper(piece))
1583 attrs |= A_BOLD;
1584 else if (g->side == BLACK && islower(piece))
1585 attrs |= A_BOLD;
1587 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1589 CLEAR_FLAG(attrs, A_BOLD);
1590 CLEAR_FLAG(attrs, A_REVERSE);
1593 waddch(boardw, ' ' | attrs);
1594 col += 2;
1595 bcol++;
1598 else {
1599 if (col != maxx - 1)
1600 mvwaddch(boardw, row, col,
1601 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1605 brow = row / 2;
1609 void invalid_move(int n, const char *m)
1611 if (curses_initialized)
1612 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1613 else
1614 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1617 /* Convert the selected piece to SAN format and validate it. */
1618 static char *board_to_san(GAME *g, BOARD b)
1620 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1621 int piece;
1622 int promo;
1623 struct userdata_s *d = g->data;
1625 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1],
1626 sp.row, x_grid_chars[sp.destcol - 1], sp.destrow);
1628 p = str;
1629 piece = pgn_piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
1631 if (piece == PAWN && ((sp.destrow == 8 && g->turn == WHITE) ||
1632 (sp.destrow == 1 && g->turn == BLACK))) {
1633 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1635 if (pgn_piece_to_int(promo) == -1)
1636 return NULL;
1638 p = str + strlen(str);
1639 *p++ = toupper(promo);
1640 *p = '\0';
1643 p = str;
1645 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1646 if (pgn_parse_move(g, b, &p) != E_PGN_OK) {
1647 invalid_move(gindex + 1, p);
1648 return NULL;
1651 return p;
1654 if (pgn_validate_move(g, b, &p) != E_PGN_OK) {
1655 invalid_move(gindex + 1, p);
1656 return NULL;
1659 return p;
1662 static int move_to_engine(GAME *g, BOARD b)
1664 char *p;
1665 struct userdata_s *d = g->data;
1667 if ((p = board_to_san(g, b)) == NULL)
1668 return 0;
1670 sp.row = sp.col = sp.icon = 0;
1672 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1673 pgn_history_add(g, p);
1674 pgn_switch_turn(g);
1675 SET_FLAG(g->flags, GF_MODIFIED);
1676 update_all(*g);
1677 return 1;
1680 add_engine_command(g, ENGINE_THINKING, "%s\n", p);
1681 return 1;
1684 static void update_clock(int n, int *h, int *m, int *s)
1686 *h = n / 3600;
1687 *m = (n % 3600) / 60;
1688 *s = (n % 3600) % 60;
1690 return;
1693 void update_status_window(GAME g)
1695 int i = 0;
1696 char *buf;
1697 char tmp[15], *engine, *mode;
1698 int w;
1699 int h, m, s;
1700 char *p;
1701 int maxy, maxx;
1702 int len;
1703 struct userdata_s *d = g.data;
1705 getmaxyx(statusw, maxy, maxx);
1706 w = maxx - 2 - 8;
1707 len = maxx - 2;
1708 buf = Malloc(len);
1710 *tmp = '\0';
1711 p = tmp;
1713 if (TEST_FLAG(g.flags, GF_DELETE)) {
1714 *p++ = '(';
1715 *p++ = 'x';
1716 i++;
1719 if (TEST_FLAG(g.flags, GF_PERROR)) {
1720 if (!i)
1721 *p++ = '(';
1722 else
1723 *p++ = '/';
1725 *p++ = '!';
1726 i++;
1729 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1730 if (!i)
1731 *p++ = '(';
1732 else
1733 *p++ = '/';
1735 *p++ = '*';
1736 i++;
1739 if (*tmp != '\0')
1740 *p++ = ')';
1742 *p = '\0';
1744 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1745 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1746 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1747 (*tmp) ? tmp : "");
1748 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1750 switch (g.mode) {
1751 case MODE_HISTORY:
1752 mode = MODE_HISTORY_STR;
1753 break;
1754 case MODE_EDIT:
1755 mode = MODE_EDIT_STR;
1756 break;
1757 case MODE_PLAY:
1758 mode = MODE_PLAY_STR;
1759 break;
1760 default:
1761 mode = UNKNOWN;
1762 break;
1765 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1767 if (g.mode == MODE_PLAY) {
1768 if (TEST_FLAG(d->flags, CF_HUMAN))
1769 strncat(buf, " (human/human)", len - 1);
1770 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1771 strncat(buf, " (engine/engine)", len - 1);
1772 else
1773 strncat(buf, " (human/engine)", len - 1);
1776 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1778 if (d->engine) {
1779 switch (d->engine->status) {
1780 case ENGINE_THINKING:
1781 engine = ENGINE_PONDER_STR;
1782 break;
1783 case ENGINE_READY:
1784 engine = ENGINE_READY_STR;
1785 break;
1786 case ENGINE_INITIALIZING:
1787 engine = ENGINE_INITIALIZING_STR;
1788 break;
1789 case ENGINE_OFFLINE:
1790 engine = ENGINE_OFFLINE_STR;
1791 break;
1792 default:
1793 engine = UNKNOWN;
1794 break;
1797 else
1798 engine = ENGINE_OFFLINE_STR;
1800 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1801 wattron(statusw, CP_STATUS_ENGINE);
1802 mvwaddstr(statusw, 5, 9, engine);
1803 wattroff(statusw, CP_STATUS_ENGINE);
1805 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1806 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1808 strncpy(tmp, WHITE_STR, sizeof(tmp));
1809 tmp[0] = toupper(tmp[0]);
1810 update_clock(g.moveclock, &h, &m, &s);
1811 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1812 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1814 strncpy(tmp, BLACK_STR, sizeof(tmp));
1815 tmp[0] = toupper(tmp[0]);
1816 update_clock(g.moveclock, &h, &m, &s);
1817 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1818 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1819 free(buf);
1821 for (i = 1; i < maxx - 4; i++)
1822 mvwprintw(statusw, maxy - 2, i, " ");
1824 if (!status.notify)
1825 status.notify = strdup(GAME_HELP_PROMPT);
1827 wattron(statusw, CP_STATUS_NOTIFY);
1828 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1829 status.notify);
1830 wattroff(statusw, CP_STATUS_NOTIFY);
1833 void update_history_window(GAME g)
1835 char buf[HISTORY_WIDTH - 1];
1836 HISTORY *h = NULL;
1837 int n, total;
1838 int t = pgn_history_total(g.hp);
1840 n = (g.hindex + 1) / 2;
1842 if (t % 2)
1843 total = (t + 1) / 2;
1844 else
1845 total = t / 2;
1847 if (t)
1848 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1849 (movestep == 1) ? HISTORY_PLY_STEP : "");
1850 else
1851 strncpy(buf, UNAVAILABLE, sizeof(buf));
1853 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1854 HISTORY_WIDTH - 13, buf);
1856 h = pgn_history_by_n(g.hp, g.hindex);
1857 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1858 n = 0;
1860 if (h && ((h->comment) || h->nag[0])) {
1861 strncat(buf, " (v", sizeof(buf));
1862 n++;
1865 if (h && h->rav) {
1866 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1867 n++;
1870 if (g.ravlevel) {
1871 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1872 n++;
1875 if (n)
1876 strncat(buf, ")", sizeof(buf));
1878 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1879 HISTORY_WIDTH - 13, buf);
1881 h = pgn_history_by_n(g.hp, game[gindex].hindex - 1);
1882 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1883 n = 0;
1885 if (h && ((h->comment) || h->nag[0])) {
1886 strncat(buf, " (V", sizeof(buf));
1887 n++;
1890 if (h && h->rav) {
1891 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1892 n++;
1895 if (g.ravlevel) {
1896 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1897 n++;
1900 if (n)
1901 strncat(buf, ")", sizeof(buf));
1903 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1904 HISTORY_WIDTH - 13, buf);
1907 void update_tag_window(TAG **t)
1909 int i;
1910 int w = TAG_WIDTH - 10;
1912 for (i = 0; i < 7; i++)
1913 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1916 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1918 int i;
1920 wattron(win, attr);
1922 for (i = 1; i < width - 1; i++)
1923 mvwaddch(win, y, i, ' ');
1925 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1926 wattroff(win, attr);
1929 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1930 chtype battr)
1932 int i;
1934 if (title) {
1935 wattron(win, attr);
1937 for (i = 1; i < width - 1; i++)
1938 mvwaddch(win, 1, i, ' ');
1940 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1941 wattroff(win, attr);
1944 wattron(win, battr);
1945 box(win, ACS_VLINE, ACS_HLINE);
1946 wattroff(win, battr);
1949 void append_enginebuf(char *line)
1951 int i = 0;
1953 if (enginebuf)
1954 for (i = 0; enginebuf[i]; i++);
1956 if (i >= LINES - 3) {
1957 free(enginebuf[0]);
1959 for (i = 0; enginebuf[i+1]; i++)
1960 enginebuf[i] = enginebuf[i+1];
1962 enginebuf[i] = strdup(line);
1964 else {
1965 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1966 enginebuf[i++] = strdup(line);
1967 enginebuf[i] = NULL;
1971 void update_engine_window()
1973 int i;
1975 if (!enginebuf)
1976 return;
1978 wmove(enginew, 0, 0);
1979 wclrtobot(enginew);
1981 if (enginebuf) {
1982 for (i = 0; enginebuf[i]; i++)
1983 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1986 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1987 CP_MESSAGE_BORDER);
1990 void toggle_engine_window()
1992 if (!enginew) {
1993 enginew = newwin(LINES, COLS, 0, 0);
1994 enginep = new_panel(enginew);
1995 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1996 CP_MESSAGE_BORDER);
1997 hide_panel(enginep);
2000 if (panel_hidden(enginep)) {
2001 update_engine_window();
2002 top_panel(enginep);
2003 refresh_all();
2005 else {
2006 hide_panel(enginep);
2007 refresh_all();
2011 void refresh_all()
2013 update_panels();
2014 doupdate();
2017 void update_all(GAME g)
2019 update_status_window(g);
2020 update_history_window(g);
2021 update_tag_window(g.tag);
2022 update_engine_window();
2025 static void game_next_prev(GAME g, int n, int count)
2027 if (gtotal < 2)
2028 return;
2030 if (n == 1) {
2031 if (gindex + count > gtotal - 1) {
2032 if (count != 1)
2033 gindex = gtotal - 1;
2034 else
2035 gindex = 0;
2037 else
2038 gindex += count;
2040 else {
2041 if (gindex - count < 0) {
2042 if (count != 1)
2043 gindex = 0;
2044 else
2045 gindex = gtotal - 1;
2047 else
2048 gindex -= count;
2052 static void delete_game(int which)
2054 GAME *g = NULL;
2055 int gi = 0;
2056 int i;
2058 for (i = 0; i < gtotal; i++) {
2059 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
2060 pgn_free(game[i]);
2061 continue;
2064 g = Realloc(g, (gi + 1) * sizeof(GAME));
2065 memcpy(&g[gi], &game[i], sizeof(GAME));
2066 g[gi].tag = game[i].tag;
2067 g[gi].history = game[i].history;
2068 g[gi].hp = game[i].hp;
2069 gi++;
2072 game = g;
2073 gtotal = gi;
2075 if (which != -1) {
2076 if (which + 1 >= gtotal)
2077 gindex = gtotal - 1;
2078 else
2079 gindex = which;
2081 else
2082 gindex = gtotal - 1;
2084 game[gindex].hp = game[gindex].history;
2087 static int find_move_exp(GAME g, const char *str, int init, int which,
2088 int count)
2090 int i;
2091 int ret;
2092 static regex_t r;
2093 static int firstrun = 1;
2094 char errbuf[255];
2095 int incr;
2096 int found;
2098 if (init) {
2099 if (!firstrun)
2100 regfree(&r);
2102 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2103 regerror(ret, &r, errbuf, sizeof(errbuf));
2104 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2105 return -1;
2108 firstrun = 1;
2111 incr = (which == 0) ? -1 : 1;
2113 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2114 if (i == g.hindex - 1)
2115 break;
2117 if (i >= pgn_history_total(g.hp))
2118 i = 0;
2119 else if (i < 0)
2120 i = pgn_history_total(g.hp) - 1;
2122 // FIXME RAV
2123 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2125 if (ret == 0) {
2126 if (count == ++found) {
2127 return i + 1;
2130 else {
2131 if (ret != REG_NOMATCH) {
2132 regerror(ret, &r, errbuf, sizeof(errbuf));
2133 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2134 return -1;
2139 return -1;
2142 static int toggle_delete_flag(int n)
2144 int i, x;
2146 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2147 gindex = n;
2148 update_all(game[gindex]);
2150 for (i = x = 0; i < gtotal; i++) {
2151 if (TEST_FLAG(game[i].flags, GF_DELETE))
2152 x++;
2155 if (x == gtotal) {
2156 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2157 CLEAR_FLAG(game[n].flags, GF_DELETE);
2158 return 1;
2161 return 0;
2164 static void edit_save_tags(GAME *g)
2166 TAG **t;
2167 struct userdata_s *d = g->data;
2169 if ((t = edit_tags(*g, d->b, 1)) == NULL)
2170 return;
2172 pgn_tag_free(g->tag);
2173 g->tag = t;
2174 SET_FLAG(g->flags, GF_MODIFIED);
2175 pgn_tag_sort(g->tag);
2178 static int find_game_exp(char *str, int which, int count)
2180 char *nstr = NULL, *exp = NULL;
2181 regex_t nexp, vexp;
2182 int ret = -1;
2183 int g = 0;
2184 char buf[255], *tmp;
2185 char errbuf[255];
2186 int found = 0;
2187 int incr = (which == 0) ? -(1) : 1;
2189 strncpy(buf, str, sizeof(buf));
2190 tmp = buf;
2192 if (strstr(tmp, ":") != NULL) {
2193 nstr = strsep(&tmp, ":");
2195 if ((ret = regcomp(&nexp, nstr,
2196 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2197 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2198 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2199 ret = g = -1;
2200 goto cleanup;
2204 exp = tmp;
2206 if (exp == NULL)
2207 goto cleanup;
2209 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2210 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2211 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2212 ret = -1;
2213 goto cleanup;
2216 ret = -1;
2218 for (g = gindex + incr, found = 0; ; g += incr) {
2219 int t;
2221 if (g == gindex)
2222 break;
2224 if (g == gtotal)
2225 g = 0;
2226 else if (g < 0)
2227 g = gtotal - 1;
2229 for (t = 0; game[g].tag[t]; t++) {
2230 if (nstr) {
2231 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2232 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2233 if (count == ++found) {
2234 ret = g;
2235 goto cleanup;
2240 else {
2241 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2242 if (count == ++found) {
2243 ret = g;
2244 goto cleanup;
2250 ret = -1;
2253 cleanup:
2254 if (nstr)
2255 regfree(&nexp);
2257 if (g != -1)
2258 regfree(&vexp);
2260 return ret;
2264 * Updates the notification line in the status window then refreshes the
2265 * status window.
2267 void update_status_notify(GAME g, char *fmt, ...)
2269 va_list ap;
2270 #ifdef HAVE_VASPRINTF
2271 char *line;
2272 #else
2273 char line[COLS];
2274 #endif
2276 if (!fmt) {
2277 if (status.notify) {
2278 free(status.notify);
2279 status.notify = NULL;
2281 if (curses_initialized)
2282 update_status_window(g);
2285 return;
2288 va_start(ap, fmt);
2289 #ifdef HAVE_VASPRINTF
2290 vasprintf(&line, fmt, ap);
2291 #else
2292 vsnprintf(line, sizeof(line), fmt, ap);
2293 #endif
2294 va_end(ap);
2296 if (status.notify)
2297 free(status.notify);
2299 status.notify = strdup(line);
2301 #ifdef HAVE_VASPRINTF
2302 free(line);
2303 #endif
2304 if (curses_initialized)
2305 update_status_window(g);
2308 static void switch_side(GAME *g)
2310 int i = pgn_tag_find(g->tag, "White");
2311 int n = pgn_tag_find(g->tag, "Black");
2312 char *w = g->tag[i]->value;
2314 g->tag[i]->value = g->tag[n]->value;
2315 g->tag[n]->value = w;
2316 g->side = (g->side == WHITE) ? BLACK : WHITE;
2317 update_tag_window(g->tag);
2320 int rav_next_prev(GAME *g, BOARD b, int n)
2322 // Next RAV.
2323 if (n) {
2324 if (g->hp[g->hindex]->rav == NULL)
2325 return 1;
2327 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2328 g->rav[g->ravlevel].hp = g->hp;
2329 g->rav[g->ravlevel].flags = g->flags;
2330 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2331 g->rav[g->ravlevel].hindex = g->hindex;
2332 g->hp = g->hp[g->hindex]->rav;
2333 g->hindex = 0;
2334 g->ravlevel++;
2335 return 0;
2338 if (g->ravlevel - 1 < 0)
2339 return 1;
2341 // Previous RAV.
2342 g->ravlevel--;
2343 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2344 free(g->rav[g->ravlevel].fen);
2345 g->hp = g->rav[g->ravlevel].hp;
2346 g->flags = g->rav[g->ravlevel].flags;
2347 g->hindex = g->rav[g->ravlevel].hindex;
2348 return 0;
2351 static void draw_window_decor()
2353 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2354 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2355 wbkgd(boardw, CP_BOARD_WINDOW);
2356 wbkgd(statusw, CP_STATUS_WINDOW);
2357 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2358 CP_STATUS_TITLE, CP_STATUS_BORDER);
2359 wbkgd(tagw, CP_TAG_WINDOW);
2360 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2361 CP_TAG_BORDER);
2362 wbkgd(historyw, CP_HISTORY_WINDOW);
2363 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2364 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2367 static void do_window_resize()
2369 if (LINES < 24 || COLS < 80)
2370 return;
2372 resizeterm(LINES, COLS);
2373 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2374 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2375 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2376 wmove(historyw, 0, 0);
2377 wclrtobot(historyw);
2378 wmove(tagw, 0, 0);
2379 wclrtobot(tagw);
2380 wmove(statusw, 0, 0);
2381 wclrtobot(statusw);
2382 draw_window_decor();
2383 update_all(game[gindex]);
2386 void add_engine_command(GAME *g, int s, char *fmt, ...)
2388 va_list ap;
2389 int i = 0;
2390 struct userdata_s *d = g->data;
2391 struct queue_s **q;
2392 char *line;
2394 if (!d->engine)
2395 return;
2397 q = d->engine->queue;
2399 if (q)
2400 for (i = 0; q[i]; i++);
2402 q = Realloc(q, (i + 2) * sizeof(struct queue_s *));
2403 va_start(ap, fmt);
2404 #ifdef HAVE_VASPRINTF
2405 vasprintf(&line, fmt, ap);
2406 #else
2407 line = Malloc(LINE_MAX + 1);
2408 vsnprintf(line, LINE_MAX, fmt, ap);
2409 #endif
2410 va_end(ap);
2411 q[i] = Malloc(sizeof(struct queue_s));
2412 q[i]->line = line;
2413 q[i++]->status = (s == -1) ? d->engine->status : s;
2414 q[i] = NULL;
2415 d->engine->queue = q;
2418 static void historymode_keys(chtype);
2419 static int playmode_keys(chtype c)
2421 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2422 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2423 chtype p;
2424 int w, x;
2425 char *tmp;
2426 struct userdata_s *d = game[gindex].data;
2428 switch (c) {
2429 case 'H':
2430 TOGGLE_FLAG(d->flags, CF_HUMAN);
2432 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2433 pgn_history_total(game[gindex].hp)) {
2434 pgn_tag_add(&game[gindex].tag, "FEN",
2435 pgn_game_to_fen(game[gindex], d->b));
2436 x = pgn_tag_find(game[gindex].tag, "FEN");
2438 if (start_chess_engine(&game[gindex]) <= 0) {
2439 add_engine_command(&game[gindex], ENGINE_READY,
2440 "setboard %s\n", game[gindex].tag[x]->value);
2441 d->engine->status = ENGINE_READY;
2445 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2447 if (d->engine)
2448 d->engine->status = ENGINE_READY;
2450 update_all(game[gindex]);
2451 break;
2452 case 'E':
2453 if (!d)
2454 break;
2456 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2457 CLEAR_FLAG(d->flags, CF_HUMAN);
2459 if (d->engine && !TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2460 add_engine_command(&game[gindex], ENGINE_READY, "new\n");
2461 pgn_board_update(&game[gindex], d->b,
2462 pgn_history_total(game[gindex].hp));
2463 add_engine_command(&game[gindex], ENGINE_READY,
2464 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2467 update_all(game[gindex]);
2468 break;
2469 case '|':
2470 if (!d->engine)
2471 break;
2473 if (d->engine->status == ENGINE_OFFLINE)
2474 break;
2476 x = d->engine->status;
2478 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2479 send_to_engine(&game[gindex], ENGINE_READY, "%s\n", tmp);
2480 d->engine->status = x;
2481 break;
2482 case '\015':
2483 case '\n':
2484 pushkey = keycount = 0;
2485 update_status_notify(game[gindex], NULL);
2487 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2488 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2489 beep();
2490 break;
2493 if (!sp.icon)
2494 break;
2496 sp.destrow = d->c_row;
2497 sp.destcol = d->c_col;
2499 if (editmode) {
2500 p = d->b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
2501 d->b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
2502 d->b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
2503 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2504 sp.icon = sp.row = sp.col = 0;
2505 break;
2508 if (move_to_engine(&game[gindex], d->b)) {
2509 if (config.validmoves)
2510 pgn_reset_valid_moves(d->b);
2512 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2513 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2514 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2518 break;
2519 case ' ':
2520 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2521 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2522 if (start_chess_engine(&game[gindex])) {
2523 sp.icon = 0;
2524 break;
2527 x = pgn_tag_find(game[gindex].tag, "FEN");
2528 w = pgn_tag_find(game[gindex].tag, "SetUp");
2530 if ((w >= 0 && x >= 0 && atoi(game[gindex].tag[w]->value) == 1)
2531 || (x >= 0 && w == -1)) {
2532 add_engine_command(&game[gindex], ENGINE_READY,
2533 "setboard %s\n", game[gindex].tag[x]->value);
2537 if (sp.icon || (!editmode && d->engine &&
2538 d->engine->status == ENGINE_THINKING)) {
2539 beep();
2540 break;
2543 sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
2544 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
2546 if (sp.icon == ' ') {
2547 sp.icon = 0;
2548 break;
2551 if (!editmode && ((islower(sp.icon) && game[gindex].turn != BLACK)
2552 || (isupper(sp.icon) && game[gindex].turn != WHITE))) {
2553 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2554 sp.icon = 0;
2555 break;
2558 sp.row = d->c_row;
2559 sp.col = d->c_col;
2561 if (!editmode && config.validmoves)
2562 pgn_find_valid_moves(game[gindex], d->b, sp.col, sp.row);
2564 paused = 0;
2565 break;
2566 case 'w':
2567 add_engine_command(&game[gindex], ENGINE_READY, "\nswitch\n");
2568 switch_side(&game[gindex]);
2569 update_status_window(game[gindex]);
2570 break;
2571 case 'u':
2572 if (!pgn_history_total(game[gindex].hp))
2573 break;
2575 if (d->engine && d->engine->status == ENGINE_READY) {
2576 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2577 d->engine->status = ENGINE_READY;
2580 game[gindex].hindex -= 2;
2581 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2582 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2583 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2584 update_history_window(game[gindex]);
2585 break;
2586 case 'a':
2587 historymode_keys(c);
2588 break;
2589 case 'd':
2590 board_details = (board_details) ? 0 : 1;
2591 break;
2592 case 'p':
2593 paused = (paused) ? 0 : 1;
2594 break;
2595 case 'g':
2596 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2597 start_chess_engine(&game[gindex]);
2599 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2600 break;
2601 default:
2602 if (!d->engine)
2603 break;
2605 if (config.keys) {
2606 for (x = 0; config.keys[x]; x++) {
2607 if (config.keys[x]->c == c) {
2608 switch (config.keys[x]->type) {
2609 case KEY_DEFAULT:
2610 add_engine_command(&game[gindex], -1, "%s\n",
2611 config.keys[x]->str);
2612 break;
2613 case KEY_SET:
2614 if (!keycount)
2615 break;
2617 add_engine_command(&game[gindex], -1,
2618 "%s %i\n", config.keys[x]->str, keycount);
2619 keycount = 0;
2620 break;
2621 case KEY_REPEAT:
2622 if (!keycount)
2623 break;
2625 for (w = 0; w < keycount; w++)
2626 add_engine_command(&game[gindex], -1,
2627 "%s\n", config.keys[x]->str);
2628 keycount = 0;
2629 break;
2634 update_status_notify(game[gindex], NULL);
2636 break;
2639 return 0;
2642 static void editmode_keys(chtype c)
2644 struct userdata_s *d = game[gindex].data;
2646 switch (c) {
2647 case '\015':
2648 case '\n':
2649 case ' ':
2650 playmode_keys(c);
2651 break;
2652 case 'd':
2653 if (sp.icon)
2654 d->b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
2655 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2656 else
2657 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon =
2658 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2660 sp.icon = sp.row = sp.col = 0;
2661 break;
2662 case 'w':
2663 pgn_switch_turn(&game[gindex]);
2664 switch_side(&game[gindex]);
2665 update_all(game[gindex]);
2666 break;
2667 case 'c':
2668 castling_state(&game[gindex], d->b, ROWTOBOARD(d->c_row),
2669 COLTOBOARD(d->c_col),
2670 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon, 1);
2671 break;
2672 case 'i':
2673 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2674 GAME_EDIT_TEXT);
2676 if (pgn_piece_to_int(c) == -1)
2677 break;
2679 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon = c;
2680 break;
2681 case 'p':
2682 if (d->c_row == 6 || d->c_row == 3) {
2683 pgn_reset_enpassant(d->b);
2684 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].enpassant = 1;
2686 break;
2687 default:
2688 break;
2692 static void historymode_keys(chtype c)
2694 int n, len;
2695 char *tmp, *buf;
2696 static char moveexp[255] = {0};
2697 struct userdata_s *d = game[gindex].data;
2699 switch (c) {
2700 case 'd':
2701 board_details = (board_details) ? 0 : 1;
2702 break;
2703 case ' ':
2704 movestep = (movestep == 1) ? 2 : 1;
2705 update_history_window(game[gindex]);
2706 break;
2707 case KEY_UP:
2708 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2709 config.jumpcount * keycount * movestep :
2710 config.jumpcount * movestep);
2711 update_all(game[gindex]);
2712 break;
2713 case KEY_DOWN:
2714 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2715 config.jumpcount * keycount * movestep :
2716 config.jumpcount * movestep);
2717 update_all(game[gindex]);
2718 break;
2719 case KEY_LEFT:
2720 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2721 keycount * movestep : movestep);
2722 update_all(game[gindex]);
2723 break;
2724 case KEY_RIGHT:
2725 pgn_history_next(&game[gindex], d->b, (keycount) ?
2726 keycount * movestep : movestep);
2727 update_all(game[gindex]);
2728 break;
2729 case 'a':
2730 n = game[gindex].hindex;
2732 if (n && game[gindex].hp[n - 1]->move)
2733 n--;
2734 else
2735 break;
2737 buf = Malloc(COLS);
2738 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2739 game[gindex].hp[n]->move);
2741 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2742 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2743 -1);
2744 free(buf);
2746 if (!tmp && (!game[gindex].hp[n]->comment ||
2747 !*game[gindex].hp[n]->comment))
2748 break;
2749 else if (tmp && game[gindex].hp[n]->comment) {
2750 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2751 break;
2754 len = (tmp) ? strlen(tmp) + 1 : 1;
2755 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2756 len);
2757 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2758 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2759 update_all(game[gindex]);
2760 break;
2761 case ']':
2762 case '[':
2763 case '/':
2764 if (pgn_history_total(game[gindex].hp) < 2)
2765 break;
2767 n = 0;
2769 if (!*moveexp || c == '/') {
2770 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2771 break;
2773 strncpy(moveexp, tmp, sizeof(moveexp));
2774 n = 1;
2777 if ((n = find_move_exp(game[gindex], moveexp, n,
2778 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2779 == -1)
2780 break;
2782 game[gindex].hindex = n;
2783 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2784 update_all(game[gindex]);
2785 break;
2786 case 'v':
2787 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2788 break;
2789 case 'V':
2790 if (game[gindex].hindex - 1 >= 0)
2791 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2792 break;
2793 case '-':
2794 case '+':
2795 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2796 update_all(game[gindex]);
2797 break;
2798 case 'j':
2799 if (pgn_history_total(game[gindex].hp) < 2)
2800 break;
2802 /* FIXME field validation
2803 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2804 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2805 game[gindex].htotal)) == NULL)
2806 break;
2809 if (!keycount) {
2810 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2811 NULL, NULL, NULL, 0, -1)) == NULL)
2812 break;
2814 if (!isinteger(tmp))
2815 break;
2817 n = atoi(tmp);
2819 else
2820 n = keycount;
2822 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2823 break;
2825 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2826 pgn_board_update(&game[gindex], d->b,
2827 game[gindex].hindex);
2828 update_all(game[gindex]);
2829 break;
2830 default:
2831 break;
2835 static void free_userdata()
2837 int i;
2839 for (i = 0; i < gtotal; i++) {
2840 struct userdata_s *d;
2842 if (game[i].data) {
2843 d = game[i].data;
2845 if (d->engine) {
2846 stop_engine(&game[i]);
2847 free(d->engine);
2850 free(game[i].data);
2851 game[i].data = NULL;
2856 void update_loading_window()
2858 if (!loadingw) {
2859 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2860 loadingp = new_panel(loadingw);
2861 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2864 wmove(loadingw, 0, 0);
2865 wclrtobot(loadingw);
2866 wattron(loadingw, CP_MESSAGE_BORDER);
2867 box(loadingw, ACS_VLINE, ACS_HLINE);
2868 wattroff(loadingw, CP_MESSAGE_BORDER);
2869 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2870 11 + strlen(itoa(gtotal))), "Loading... %i", gtotal);
2871 update_panels();
2872 doupdate();
2875 void init_userdata()
2877 int i;
2879 for (i = 0; i < gtotal; i++) {
2880 struct userdata_s *d = NULL;
2882 d = Calloc(1, sizeof(struct userdata_s));
2883 game[i].data = d;
2884 d->n = i;
2885 pgn_board_init(d->b);
2889 void fix_marks(int *start, int *end)
2891 int i;
2893 *start = (*start < 0) ? 0 : *start;
2894 *end = (*end < 0) ? 0 : *end;
2896 if (*start > *end) {
2897 i = *start;
2898 *start = *end;
2899 *end = i + 1;
2902 *end = (*end > gtotal) ? gtotal : *end;
2905 // Global and other keys.
2906 static int globalkeys(chtype c)
2908 static char gameexp[255] = {0};
2909 FILE *fp;
2910 char *tmp, *p;
2911 int n, i;
2912 char tfile[FILENAME_MAX];
2913 struct userdata_s *d = game[gindex].data;
2915 switch (c) {
2916 case 'W':
2917 toggle_engine_window();
2918 break;
2919 case KEY_F(10):
2920 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
2921 "and %i color pairs\nCopyright 2002-2006 %s",
2922 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
2923 COLOR_PAIRS, PACKAGE_BUGREPORT);
2924 break;
2925 case 'h':
2926 if (game[gindex].mode != MODE_HISTORY) {
2927 if (!pgn_history_total(game[gindex].hp) ||
2928 (d->engine && d->engine->status == ENGINE_THINKING))
2929 return 1;
2931 game[gindex].mode = MODE_HISTORY;
2932 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2933 update_all(game[gindex]);
2934 return 1;
2937 // FIXME
2938 if (TEST_FLAG(game[gindex].flags, GF_BLACK_OPENING)) {
2939 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
2940 return 1;
2943 // FIXME Resuming from previous history could append to a RAV.
2944 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2945 if (!pushkey) {
2946 if ((c = message(NULL, YESNO, "%s",
2947 GAME_RESUME_HISTORY_TEXT)) != 'y')
2948 return 1;
2951 else {
2952 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2953 return 1;
2957 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2958 d->engine->status == ENGINE_OFFLINE)) {
2959 if (start_chess_engine(&game[gindex]) < 0)
2960 return 1;
2962 send_to_engine(&game[gindex], "setboard %s\n",
2963 pgn_game_to_fen(game[gindex], d->b));
2964 d->engine->status = ENGINE_READY;
2965 pushkey = 'h';
2966 return 1;
2970 pushkey = 0;
2971 oldhistorytotal = pgn_history_total(game[gindex].hp);
2972 game[gindex].mode = MODE_PLAY;
2973 update_all(game[gindex]);
2974 return 1;
2975 case '>':
2976 case '<':
2977 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2978 keycount : 1);
2979 d = game[gindex].data;
2981 if (delete_count) {
2982 if (c == '>') {
2983 markend = markstart + delete_count;
2984 delete_count = 0;
2986 else {
2987 markend = markstart - delete_count;
2988 delete_count = -1; // to fix gindex in the other direction
2991 pushkey = 'x';
2992 fix_marks(&markstart, &markend);
2995 if (game[gindex].mode != MODE_EDIT)
2996 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2998 update_status_notify(game[gindex], NULL);
2999 update_all(game[gindex]);
3000 return 1;
3001 case '}':
3002 case '{':
3003 case '?':
3004 if (gtotal < 2)
3005 return 1;
3007 if (!*gameexp || c == '?') {
3008 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
3009 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
3010 NULL, 0, -1)) == NULL)
3011 return 1;
3013 strncpy(gameexp, tmp, sizeof(gameexp));
3016 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
3017 ? keycount : 1)) ==
3019 return 1;
3021 gindex = n;
3022 d = game[gindex].data;
3024 if (pgn_history_total(game[gindex].hp))
3025 game[gindex].mode = MODE_HISTORY;
3027 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3028 update_all(game[gindex]);
3029 return 1;
3030 case 'J':
3031 if (gtotal < 2)
3032 return 1;
3034 /* FIXME field validation
3035 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
3036 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
3037 == NULL)
3038 return 1;
3041 if (!keycount) {
3042 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
3043 NULL, NULL, 0, -1)) == NULL)
3044 return 1;
3046 if (!isinteger(tmp))
3047 return 1;
3049 i = atoi(tmp);
3051 else
3052 i = keycount;
3054 if (--i > gtotal - 1 || i < 0)
3055 return 1;
3057 gindex = i;
3058 d = game[gindex].data;
3059 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3060 update_status_notify(game[gindex], NULL);
3061 update_all(game[gindex]);
3062 return 1;
3063 case 'x':
3064 pushkey = 0;
3066 if (gtotal < 2)
3067 return 1;
3069 if (keycount && delete_count == 0) {
3070 markstart = gindex;
3071 delete_count = keycount;
3072 update_status_notify(game[gindex], "%s (delete)",
3073 status.notify);
3074 return 1;
3077 if (markstart >= 0 && markend >= 0) {
3078 for (i = markstart; i < markend; i++) {
3079 if (toggle_delete_flag(i)) {
3080 update_all(game[gindex]);
3081 return 1;
3085 gindex = (delete_count < 0) ? markstart : i - 1;
3086 update_all(game[gindex]);
3088 else {
3089 if (toggle_delete_flag(gindex))
3090 return 1;
3093 markstart = markend = -1;
3094 delete_count = 0;
3095 update_status_window(game[gindex]);
3096 return 1;
3097 case 'X':
3098 if (gtotal < 2) {
3099 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3100 return 1;
3103 tmp = NULL;
3105 for (i = n = 0; i < gtotal; i++) {
3106 if (TEST_FLAG(game[i].flags, GF_DELETE))
3107 n++;
3110 if (!n)
3111 tmp = GAME_DELETE_GAME_TEXT;
3112 else {
3113 if (n == gtotal) {
3114 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3115 return 1;
3118 tmp = GAME_DELETE_ALL_TEXT;
3121 if (config.deleteprompt) {
3122 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
3123 return 1;
3126 delete_game((!n) ? gindex : -1);
3128 if (pgn_history_total(game[gindex].hp))
3129 game[gindex].mode = MODE_HISTORY;
3131 d = game[gindex].data;
3132 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3133 update_all(game[gindex]);
3134 return 1;
3135 case 'T':
3136 edit_save_tags(&game[gindex]);
3137 update_all(game[gindex]);
3138 return 1;
3139 case 't':
3140 edit_tags(game[gindex], d->b, 0);
3141 return 1;
3142 case 'r':
3143 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
3144 BROWSER_PROMPT, file_browser, NULL, '\t',
3145 -1)) == NULL)
3146 return 1;
3148 if ((tmp = word_expand(tmp)) == NULL)
3149 break;
3151 if ((fp = pgn_open(tmp)) == NULL) {
3152 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3153 return 1;
3156 free_userdata();
3158 if (pgn_parse(fp)) {
3159 init_userdata();
3160 update_all(game[gindex]);
3161 return 1;
3164 del_panel(loadingp);
3165 delwin(loadingw);
3166 loadingw = NULL;
3167 loadingp = NULL;
3168 init_userdata();
3169 strncpy(loadfile, tmp, sizeof(loadfile));
3171 if (pgn_history_total(game[gindex].hp))
3172 game[gindex].mode = MODE_HISTORY;
3174 d = game[gindex].data;
3175 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3176 update_all(game[gindex]);
3177 return 1;
3178 case 'S':
3179 case 's':
3180 i = -1;
3182 if (gtotal > 1) {
3183 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
3184 GAME_SAVE_MULTI_TEXT);
3186 if (n == 'c')
3187 i = gindex;
3188 else if (n == 'a')
3189 i = -1;
3190 else {
3191 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3192 return 1;
3196 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
3197 BROWSER_PROMPT, file_browser, NULL,
3198 '\t', -1)) == NULL) {
3199 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3200 return 1;
3203 if ((tmp = word_expand(tmp)) == NULL)
3204 break;
3206 if (pgn_is_compressed(tmp)) {
3207 p = tmp + strlen(tmp) - 1;
3209 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3210 *(p-3) != '.') {
3211 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3212 tmp = tfile;
3215 else {
3216 if ((p = strchr(tmp, '.')) != NULL) {
3217 if (strcmp(p, ".pgn") != 0) {
3218 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3219 tmp = tfile;
3222 else {
3223 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3224 tmp = tfile;
3228 if (save_pgn(tmp, i)) {
3229 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3230 return 1;
3233 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3234 update_all(game[gindex]);
3235 return 1;
3236 case KEY_F(1):
3237 n = 0;
3239 switch (game[gindex].mode) {
3240 case MODE_PLAY:
3241 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3242 break;
3243 case MODE_HISTORY:
3244 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3245 break;
3246 case MODE_EDIT:
3247 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3248 break;
3249 default:
3250 break;
3253 while (c == KEY_F(1)) {
3254 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3255 mainhelp);
3257 switch (c) {
3258 case 'h':
3259 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3260 break;
3261 case 'p':
3262 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3263 break;
3264 case 'e':
3265 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3266 break;
3267 case 'g':
3268 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3269 break;
3270 default:
3271 break;
3275 return 1;
3276 case 'n':
3277 case 'N':
3278 if (c == 'N') {
3279 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3280 return 1;
3283 if (c == 'n') {
3284 pgn_new_game();
3285 add_custom_tags(&game[gindex].tag);
3286 d = Calloc(1, sizeof(struct userdata_s));
3287 pgn_board_init(d->b);
3288 game[gindex].data = d;
3290 else {
3291 free_userdata();
3292 pgn_parse(NULL);
3293 add_custom_tags(&game[gindex].tag);
3294 d = Calloc(1, sizeof(struct userdata_s));
3295 pgn_board_init(d->b);
3296 game[gindex].data = d;
3299 game[gindex].mode = MODE_PLAY;
3300 d->c_row = (game[gindex].side == WHITE) ? 2 : 7;
3301 d->c_col = 4;
3302 update_status_notify(game[gindex], NULL);
3303 update_all(game[gindex]);
3304 return 1;
3305 case CTRL('L'):
3306 endwin();
3307 keypad(boardw, TRUE);
3308 refresh_all();
3309 return 1;
3310 case KEY_ESCAPE:
3311 sp.icon = sp.row = sp.col = 0;
3312 markend = markstart = 0;
3314 if (keycount) {
3315 keycount = 0;
3316 update_status_notify(game[gindex], NULL);
3319 if (config.validmoves)
3320 pgn_reset_valid_moves(d->b);
3322 return 1;
3323 case '0' ... '9':
3324 n = c - '0';
3326 if (keycount)
3327 keycount = keycount * 10 + n;
3328 else
3329 keycount = n;
3331 update_status_notify(game[gindex], "Repeat %i", keycount);
3332 return -1;
3333 case KEY_UP:
3334 if (game[gindex].mode == MODE_HISTORY)
3335 return 0;
3337 if (keycount) {
3338 d->c_row += keycount;
3339 pushkey = '\n';
3341 else
3342 d->c_row++;
3344 if (d->c_row > 8)
3345 d->c_row = 1;
3347 return 1;
3348 case KEY_DOWN:
3349 if (game[gindex].mode == MODE_HISTORY)
3350 return 0;
3352 if (keycount) {
3353 d->c_row -= keycount;
3354 pushkey = '\n';
3355 update_status_notify(game[gindex], NULL);
3357 else
3358 d->c_row--;
3360 if (d->c_row < 1)
3361 d->c_row = 8;
3363 return 1;
3364 case KEY_LEFT:
3365 if (game[gindex].mode == MODE_HISTORY)
3366 return 0;
3368 if (keycount) {
3369 d->c_col -= keycount;
3370 pushkey = '\n';
3372 else
3373 d->c_col--;
3375 if (d->c_col < 1)
3376 d->c_col = 8;
3378 return 1;
3379 case KEY_RIGHT:
3380 if (game[gindex].mode == MODE_HISTORY)
3381 return 0;
3383 if (keycount) {
3384 d->c_col += keycount;
3385 pushkey = '\n';
3387 else
3388 d->c_col++;
3390 if (d->c_col > 8)
3391 d->c_col = 1;
3393 return 1;
3394 case 'e':
3395 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3396 MODE_PLAY)
3397 return 1;
3399 // Don't edit a running game (for now).
3400 if (pgn_history_total(game[gindex].hp))
3401 return 1;
3403 if (game[gindex].mode != MODE_EDIT) {
3404 pgn_board_init_fen(&game[gindex], d->b, NULL);
3405 board_details++;
3406 game[gindex].mode = MODE_EDIT;
3407 update_all(game[gindex]);
3408 return 1;
3411 board_details--;
3412 pgn_tag_add(&game[gindex].tag, "FEN",
3413 pgn_game_to_fen(game[gindex], d->b));
3414 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3415 pgn_tag_sort(game[gindex].tag);
3416 game[gindex].mode = MODE_PLAY;
3417 update_all(game[gindex]);
3418 return 1;
3419 case 'Q':
3420 quit = 1;
3421 return 1;
3422 case KEY_RESIZE:
3423 do_window_resize();
3424 return 1;
3425 #ifdef DEBUG
3426 case 'D':
3427 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3428 return 1;
3429 #endif
3430 case 0:
3431 default:
3432 break;
3435 return 0;
3438 void send_engine_command(GAME *g)
3440 struct userdata_s *d = g->data;
3441 struct queue_s **q = d->engine->queue;
3442 int i;
3444 if (!d->engine || !d->engine->queue)
3445 return;
3447 if (!q || !q[0])
3448 return;
3450 if (send_to_engine(g, q[0]->status, "%s", q[0]->line) == 0) {
3451 if (d->n == gindex) {
3452 d->engine->status = ENGINE_THINKING;
3453 update_status_window(*g);
3454 refresh_all();
3458 free(q[0]->line);
3459 free(q[0]);
3461 for (i = 0; q[i]; i++) {
3462 if (q[i+1])
3463 q[i] = q[i+1];
3464 else {
3465 q[i] = NULL;
3466 break;
3471 void game_loop()
3473 int error_recover = 0;
3474 struct userdata_s *d = game[gindex].data;
3476 d->c_row = 2, d->c_col = 5;
3477 gindex = gtotal - 1;
3479 if (pgn_history_total(game[gindex].hp))
3480 game[gindex].mode = MODE_HISTORY;
3481 else
3482 game[gindex].mode = MODE_PLAY;
3484 if (game[gindex].mode == MODE_HISTORY) {
3485 pgn_board_update(&game[gindex], d->b,
3486 pgn_history_total(game[gindex].hp));
3489 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3490 movestep = 2;
3491 paused = 1; //FIXME clock
3492 flushinp();
3493 update_all(game[gindex]);
3494 update_tag_window(game[gindex].tag);
3495 wtimeout(boardw, 70);
3497 while (!quit) {
3498 int c = 0;
3499 int n = 0, i;
3500 char fdbuf[8192] = {0};
3501 int len;
3502 struct timeval tv = {0, 0};
3503 fd_set rfds;
3505 FD_ZERO(&rfds);
3507 for (i = 0; i < gtotal; i++) {
3508 d = game[i].data;
3510 if (d->engine) {
3511 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3512 if (d->engine->fd[ENGINE_IN_FD] > n)
3513 n = d->engine->fd[ENGINE_IN_FD];
3515 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3520 if (n) {
3521 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3522 for (i = 0; i < gtotal; i++) {
3523 d = game[i].data;
3525 if (d->engine) {
3526 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3527 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3528 sizeof(fdbuf));
3530 if (len == -1) {
3531 if (errno != EAGAIN) {
3532 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3533 strerror(errno));
3534 waitpid(d->engine->pid, &n, 0);
3535 free(d->engine);
3536 d->engine = NULL;
3537 break;
3540 else {
3541 if (len) {
3542 parse_engine_output(&game[i], fdbuf);
3544 if (d->engine->queue)
3545 send_engine_command(&game[i]);
3552 else {
3553 if (n == -1)
3554 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3555 else {
3556 /* timeout */
3557 for (i = 0; i < gtotal; i++) {
3558 d = game[i].data;
3560 if (d->engine && d->engine->queue) {
3561 send_engine_command(&game[i]);
3568 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER) && game[gindex].mode
3569 != MODE_HISTORY)
3570 game[gindex].mode = MODE_HISTORY;
3572 d = game[gindex].data;
3573 error_recover = 0;
3574 draw_board(&game[gindex], board_details);
3575 update_all(game[gindex]);
3576 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3578 if (!paused) {
3581 refresh_all();
3583 if (pushkey)
3584 c = pushkey;
3585 else {
3586 if ((c = wgetch(boardw)) == ERR)
3587 continue;
3590 if (!keycount && status.notify)
3591 update_status_notify(game[gindex], NULL);
3593 if ((n = globalkeys(c)) == 1) {
3594 keycount = 0;
3595 continue;
3597 else if (n == -1)
3598 continue;
3600 switch (game[gindex].mode) {
3601 case MODE_EDIT:
3602 editmode_keys(c);
3603 break;
3604 case MODE_PLAY:
3605 if (playmode_keys(c))
3606 continue;
3607 break;
3608 case MODE_HISTORY:
3609 historymode_keys(c);
3610 break;
3611 default:
3612 break;
3615 keycount = 0;
3619 void usage(const char *pn, int ret)
3621 fprintf((ret) ? stderr : stdout, "%s",
3622 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3623 " -p Load PGN file.\n"
3624 " -V Validate a game file.\n"
3625 " -S Validate and output a PGN formatted game.\n"
3626 " -R Like -S but write a reduced PGN formatted game.\n"
3627 " -t Also write custom PGN tags from config file.\n"
3628 " -E Stop processing on file parsing error (overrides config).\n"
3629 " -v Version information.\n"
3630 " -h This help text.\n");
3632 exit(ret);
3635 void cleanup_all()
3637 int i;
3639 free_userdata();
3640 pgn_free_all();
3641 free(config.engine_cmd);
3642 free(config.pattern);
3644 if (config.einit) {
3645 for (i = 0; config.einit[i]; i++)
3646 free(config.einit[i]);
3648 free(config.einit);
3651 if (config.tag)
3652 pgn_tag_free(config.tag);
3654 if (curses_initialized) {
3655 del_panel(boardp);
3656 del_panel(historyp);
3657 del_panel(statusp);
3658 del_panel(tagp);
3659 delwin(boardw);
3660 delwin(historyw);
3661 delwin(statusw);
3662 delwin(tagw);
3664 if (enginew) {
3665 del_panel(enginep);
3666 delwin(enginew);
3668 if (enginebuf) {
3669 for (i = 0; enginebuf[i]; i++)
3670 free(enginebuf[i]);
3672 free(enginebuf);
3676 endwin();
3680 void catch_signal(int which)
3682 switch (which) {
3683 case SIGINT:
3684 case SIGPIPE:
3685 if (which == SIGPIPE && quit)
3686 break;
3688 if (which == SIGPIPE)
3689 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3691 cleanup_all();
3692 exit(EXIT_FAILURE);
3693 break;
3694 case SIGSTOP:
3695 savetty();
3696 break;
3697 case SIGCONT:
3698 resetty();
3699 keypad(boardw, TRUE);
3700 curs_set(0);
3701 cbreak();
3702 noecho();
3703 break;
3704 case SIGUSR1:
3705 if (curses_initialized) {
3706 update_loading_window(game[gindex]);
3707 break;
3710 fprintf(stderr, "Loading... %i\r", gtotal);
3711 fflush(stderr);
3712 break;
3713 default:
3714 break;
3718 static void set_defaults()
3720 filetype = NO_FILE;
3721 set_config_defaults();
3724 int main(int argc, char *argv[])
3726 int opt;
3727 struct stat st;
3728 char buf[FILENAME_MAX];
3729 char datadir[FILENAME_MAX];
3730 int ret = EXIT_SUCCESS;
3731 int validate_only = 0, validate_and_write = 0;
3732 int write_custom_tags = 0;
3733 FILE *fp;
3734 int i;
3736 if ((config.pwd = getpwuid(getuid())) == NULL)
3737 err(EXIT_FAILURE, "getpwuid()");
3739 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3740 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3741 config.ccfile = strdup(buf);
3742 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3743 config.nagfile = strdup(buf);
3744 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3745 config.agonyfile = strdup(buf);
3746 snprintf(buf, sizeof(buf), "%s/config", datadir);
3747 config.configfile = strdup(buf);
3749 if (stat(datadir, &st) == -1) {
3750 if (errno == ENOENT) {
3751 if (mkdir(datadir, 0755) == -1)
3752 err(EXIT_FAILURE, "%s", datadir);
3754 else
3755 err(EXIT_FAILURE, "%s", datadir);
3757 stat(datadir, &st);
3760 if (!S_ISDIR(st.st_mode))
3761 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3763 set_defaults();
3765 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3766 switch (opt) {
3767 case 't':
3768 write_custom_tags = 1;
3769 break;
3770 case 'E':
3771 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3772 break;
3773 case 'R':
3774 pgn_config_set(PGN_REDUCED, 1);
3775 case 'S':
3776 validate_and_write = 1;
3777 case 'V':
3778 validate_only = 1;
3779 break;
3780 case 'v':
3781 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3782 COPYRIGHT);
3783 exit(EXIT_SUCCESS);
3784 case 'p':
3785 filetype = PGN_FILE;
3786 strncpy(loadfile, optarg, sizeof(loadfile));
3787 break;
3788 case 'h':
3789 default:
3790 usage(argv[0], EXIT_SUCCESS);
3794 if ((validate_only || validate_and_write) && !*loadfile)
3795 usage(argv[0], EXIT_FAILURE);
3797 if (access(config.configfile, R_OK) == 0)
3798 parse_rcfile(config.configfile);
3800 signal(SIGPIPE, catch_signal);
3801 signal(SIGCONT, catch_signal);
3802 signal(SIGSTOP, catch_signal);
3803 signal(SIGINT, catch_signal);
3804 signal(SIGUSR1, catch_signal);
3806 srandom(getpid());
3808 switch (filetype) {
3809 case PGN_FILE:
3810 if ((fp = pgn_open(loadfile)) == NULL)
3811 err(EXIT_FAILURE, "%s", loadfile);
3813 ret = pgn_parse(fp);
3814 break;
3815 case FEN_FILE:
3816 //ret = parse_fen_file(loadfile);
3817 break;
3818 case EPD_FILE: // Not implemented.
3819 case NO_FILE:
3820 default:
3821 // No file specified. Empty game.
3822 ret = pgn_parse(NULL);
3823 add_custom_tags(&game[gindex].tag);
3824 break;
3827 if (validate_only || validate_and_write) {
3828 if (validate_and_write) {
3829 for (i = 0; i < gtotal; i++) {
3830 if (write_custom_tags)
3831 add_custom_tags(&game[i].tag);
3833 pgn_write(stdout, game[i]);
3837 cleanup_all();
3838 exit(ret);
3840 else if (ret)
3841 exit(ret);
3843 init_userdata();
3845 if (initscr() == NULL)
3846 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3847 else
3848 curses_initialized = 1;
3850 if (LINES < 24 || COLS < 80) {
3851 endwin();
3852 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3855 if (has_colors() == TRUE && start_color() == OK)
3856 init_color_pairs();
3858 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3859 boardp = new_panel(boardw);
3860 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3861 COLS - HISTORY_WIDTH);
3862 historyp = new_panel(historyw);
3863 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3864 statusp = new_panel(statusw);
3865 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3866 tagp = new_panel(tagw);
3867 keypad(boardw, TRUE);
3868 // leaveok(boardw, TRUE);
3869 leaveok(tagw, TRUE);
3870 leaveok(statusw, TRUE);
3871 leaveok(historyw, TRUE);
3872 curs_set(0);
3873 cbreak();
3874 noecho();
3875 draw_window_decor();
3876 game_loop();
3877 cleanup_all();
3878 exit(EXIT_SUCCESS);