Clear the filename when 'N' is pressed.
[cboard.git] / src / cboard.c
blob4027e3c3b79b506ee8c30daba48008a9a865af28
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <time.h>
35 #ifdef HAVE_CONFIG_H
36 #include <config.h>
37 #endif
39 #ifdef HAVE_SYS_WAIT_H
40 #include <sys/wait.h>
41 #endif
43 #ifdef HAVE_WORDEXP_H
44 #include <wordexp.h>
45 #endif
47 #ifdef HAVE_DIRENT_H
48 #include <dirent.h>
49 #endif
51 #ifdef HAVE_REGEX_H
52 #include <regex.h>
53 #endif
55 #include "chess.h"
56 #include "conf.h"
57 #include "window.h"
58 #include "colors.h"
59 #include "input.h"
60 #include "misc.h"
61 #include "engine.h"
62 #include "rcfile.h"
63 #include "strings.h"
64 #include "common.h"
65 #include "cboard.h"
67 #ifdef DEBUG
68 #include "debug.h"
69 #endif
71 #ifdef WITH_DMALLOC
72 #include <dmalloc.h>
73 #endif
75 static char *str_etc(const char *str, int maxlen, int rev)
77 int len = strlen(str);
78 static char buf[80], *p = buf;
79 int i;
81 strncpy(buf, str, sizeof(buf));
83 if (len > maxlen) {
84 if (rev) {
85 p = buf;
86 *p++ = '.';
87 *p++ = '.';
88 *p++ = '.';
90 for (i = 0; i < maxlen + 3; i++)
91 *p++ = buf[(len - maxlen) + i + 3];
93 else {
94 p = buf + maxlen - 4;
95 *p++ = '.';
96 *p++ = '.';
97 *p++ = '.';
100 *p = '\0';
103 return buf;
106 void update_cursor(GAME g, int idx)
108 char *p;
109 int len;
110 int t = pgn_history_total(g.hp);
111 struct userdata_s *d = g.data;
114 * If not deincremented then r and c would be the next move.
116 idx--;
118 if (idx > t || idx < 0 || !t || !g.hp[idx]->move) {
119 d->c_row = 2, d->c_col = 5;
120 return;
123 p = g.hp[idx]->move;
124 len = strlen(p);
126 if (*p == 'O') {
127 if (len <= 4)
128 d->c_col = 7;
129 else
130 d->c_col = 3;
132 d->c_row = (g.turn == WHITE) ? 1 : 8;
133 return;
136 p += len;
138 while (!isdigit(*p))
139 p--;
141 d->c_row = ROWTOINT(*p--);
142 d->c_col = COLTOINT(*p);
145 static int init_nag()
147 FILE *fp;
148 char line[LINE_MAX];
149 int i = 0;
151 if ((fp = fopen(config.nagfile, "r")) == NULL) {
152 cmessage(ERROR, ANYKEY, "%s: %s", config.nagfile, strerror(errno));
153 return 1;
156 nags = Realloc(nags, 2 * sizeof(char *));
157 nags[0] = NULL;
158 i++;
160 while (!feof(fp)) {
161 if (fscanf(fp, " %[^\n] ", line) == 1) {
162 nags = Realloc(nags, (i + 2) * sizeof(char *));
163 nags[i++] = strdup(line);
167 return 0;
170 void set_menu_vars(int c, int rows, int items, int *item, int *top)
172 int selected = *item;
173 int toppos = *top;
175 switch (c) {
176 case KEY_HOME:
177 selected = toppos = 0;
178 break;
179 case KEY_END:
180 selected = items;
181 toppos = items - rows + 1;
182 break;
183 case KEY_UP:
184 if (selected - 1 < 0) {
185 selected = items;
187 toppos = selected - rows + 1;
189 else {
190 selected--;
192 if (toppos && selected <= toppos)
193 toppos = selected;
195 break;
196 case KEY_DOWN:
197 if (selected + 1 > items )
198 selected = toppos = 0;
199 else {
200 selected++;
202 if (selected - toppos >= rows)
203 toppos++;
205 break;
206 case KEY_PPAGE:
207 selected -= rows;
209 if (selected < 0)
210 selected = 0;
212 toppos = selected - rows + 1;
214 if (toppos < 0)
215 toppos = 0;
216 break;
217 case KEY_NPAGE:
218 selected += rows;
220 if (selected > items)
221 selected = items;
223 toppos = selected - rows + 1;
225 if (toppos < 0)
226 toppos = 0;
227 break;
228 default:
229 toppos = selected - rows + 1;
231 if (toppos < 0)
232 toppos = 0;
233 break;
236 *item = selected;
237 *top = toppos;
240 int test_nag_selected(unsigned char nag[], int s)
242 int i;
244 for (i = 0; i < MAX_PGN_NAG; i++) {
245 if (nag[i] == s)
246 return i;
249 return -1;
252 char *history_edit_nag(void *arg)
254 WINDOW *win;
255 PANEL *panel;
256 int i = 0, n;
257 int itemcount = 0;
258 int rows, cols;
259 HISTORY *anno = (HISTORY *)arg;
260 int selected = 0;
261 int toppos = 0;
262 int len = 0;
263 int total = 0;
264 unsigned char nag[MAX_PGN_NAG] = {0};
265 char menubuf[64] = {0}, *mp = menubuf;
267 if (!nags) {
268 if (init_nag())
269 return NULL;
272 for (i = 1, n = 0; nags[i]; i++) {
273 n = strlen(nags[i]);
275 if (len < n)
276 len = n;
279 total = i;
280 cols = len + 2;
281 rows = (total + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : total + 4;
283 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
284 panel = new_panel(win);
285 cbreak();
286 noecho();
287 keypad(win, TRUE);
288 nl();
289 wbkgd(win, CP_MENU);
290 memcpy(&nag, &anno->nag, sizeof(nag));
292 for (i = 0; i < MAX_PGN_NAG; i++) {
293 if (nag[i])
294 itemcount++;
297 while (1) {
298 int c;
299 char buf[cols - 4];
301 wmove(win, 0, 0);
302 wclrtobot(win);
303 draw_window_title(win, NAG_EDIT_TITLE, cols, CP_INPUT_TITLE,
304 CP_INPUT_BORDER);
306 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
307 if (i == selected) {
308 wattron(win, CP_MENU_HIGHLIGHT);
309 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
310 wattroff(win, CP_MENU_HIGHLIGHT);
311 continue;
314 if (test_nag_selected(nag, i) != -1) {
315 wattron(win, CP_MENU_SELECTED);
316 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
317 wattroff(win, CP_MENU_SELECTED);
318 continue;
321 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
324 snprintf(buf, sizeof(buf), "NAG %i of %i (%i of %i selected) %s",
325 selected + 1, total, itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
326 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
328 nl();
329 refresh_all();
330 c = wgetch(win);
332 switch (c) {
333 int found;
335 case KEY_F(1):
336 help(NAG_EDIT_HELP, ANYKEY, naghelp);
337 break;
338 case KEY_HOME:
339 case KEY_END:
340 case KEY_UP:
341 case KEY_DOWN:
342 case KEY_PPAGE:
343 case KEY_NPAGE:
344 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
345 menubuf[0] = 0;
346 mp = menubuf;
347 break;
348 case ' ':
349 if (selected == 0) {
350 for (i = 0; i < MAX_PGN_NAG; i++)
351 nag[i] = 0;
353 itemcount = 0;
354 break;
357 if ((found = test_nag_selected(nag, selected)) != -1) {
358 nag[found] = 0;
359 itemcount--;
361 else {
362 if (itemcount + 1 > MAX_PGN_NAG)
363 break;
365 for (i = 0; i < MAX_PGN_NAG; i++) {
366 if (nag[i] == 0) {
367 nag[i] = selected;
368 break;
372 itemcount++;
375 break;
376 case '\n':
377 goto done;
378 break;
379 case KEY_ESCAPE:
380 goto done;
381 break;
382 default:
383 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
384 menubuf[0] = 0;
385 mp = menubuf;
388 *mp++ = c;
389 *mp = 0;
390 n = selected;
392 for (i = 0; i < total; i++) {
393 if (!nags[i])
394 continue;
396 if (strncasecmp(menubuf, nags[i], strlen(menubuf)) == 0) {
397 selected = i;
398 break;
402 if (n == selected) {
403 menubuf[0] = 0;
404 mp = menubuf;
407 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
408 break;
412 done:
413 memcpy(&anno->nag, &nag, sizeof(nag));
414 del_panel(panel);
415 delwin(win);
416 return NULL;
419 static void view_nag(void *arg)
421 HISTORY *h = (HISTORY *)arg;
422 char buf[80];
423 char line[LINE_MAX] = {0};
424 int i = 0;
426 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
428 if (!nags) {
429 if (init_nag())
430 return;
433 for (i = 0; i < MAX_PGN_NAG; i++) {
434 if (!h->nag[i])
435 break;
437 strncat(line, nags[h->nag[i]], sizeof(line));
438 strncat(line, "\n", sizeof(line));
441 line[strlen(line) - 1] = 0;
442 message(buf, ANYKEY, "%s", line);
445 void view_annotation(HISTORY h)
447 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
448 int nag = 0, comment = 0;
450 if (h.comment && h.comment[0])
451 comment++;
453 if (h.nag[0])
454 nag++;
456 if (!nag && !comment)
457 return;
459 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
461 if (comment)
462 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
463 (nag) ? "Press 'n' to view NAG" : NULL,
464 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
465 (nag) ? 'n' : 0, "%s", h.comment);
466 else
467 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
468 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
471 static void cleanup(WINDOW *win, PANEL *panel, struct file_s *files)
473 int i;
475 if (files) {
476 for (i = 0; files[i].name; i++) {
477 free(files[i].path);
478 free(files[i].name);
479 free(files[i].st);
482 free(files);
485 del_panel(panel);
486 delwin(win);
489 static int sort_files(const void *a, const void *b)
491 const struct file_s *aa = a;
492 const struct file_s *bb = b;
494 return strcmp(aa->name, bb->name);
497 char *file_browser(void *arg)
499 char pattern[FILENAME_MAX];
500 static char path[FILENAME_MAX];
501 static char file[FILENAME_MAX];
502 struct stat st;
503 char *p;
504 int cursor = curs_set(0);
505 char menubuf[64] = {0}, *mp = menubuf;
507 if (!*path) {
508 if (config.savedirectory) {
509 if ((p = word_expand(config.savedirectory)) == NULL)
510 return NULL;
512 strncpy(path, p, sizeof(path));
514 if (access(path, R_OK) == -1) {
515 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
516 getcwd(path, sizeof(path));
519 else
520 getcwd(path, sizeof(path));
523 again:
525 * First find directories (including hidden) in the working directory.
526 * Then apply the config.pattern to regular files.
528 if ((p = word_split_append(path, '/', ".* *")) == NULL)
529 return NULL;
531 strncpy(pattern, p, sizeof(pattern));
533 while (1) {
534 WINDOW *win;
535 PANEL *panel;
536 char *tmp = NULL;
537 int rows, cols = 0;
538 int selected = 0;
539 int toppos = 0;
540 int len = strlen(path);
541 wordexp_t w;
542 int i, n = 0;
543 struct file_s *files = NULL;
544 int which = 1;
545 int x = WRDE_NOCMD;
546 int nlen = 0;
548 new_we:
549 if (wordexp(pattern, &w, x) != 0) {
550 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
551 return NULL;
554 for (i = 0; i < w.we_wordc; i++) {
555 struct tm *tp;
556 char tbuf[16];
557 char sbuf[64];
559 if (stat(w.we_wordv[i], &st) == -1)
560 continue;
562 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
563 p++;
564 else
565 p = w.we_wordv[i];
567 if (which) {
568 if (!S_ISDIR(st.st_mode))
569 continue;
571 if (p[0] == '.' && p[1] == 0)
572 continue;
574 else {
575 if (S_ISDIR(st.st_mode))
576 continue;
579 len = strlen(p) + 2;
580 files = Realloc(files, (n + 2) * sizeof(struct file_s));
581 files[n].path = strdup(w.we_wordv[i]);
582 files[n].name = Malloc(len);
583 strncpy(files[n].name, p, len);
585 if (S_ISDIR(st.st_mode))
586 files[n].name[len - 2] = '/';
588 tp = localtime(&st.st_mtime);
589 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
590 snprintf(sbuf, sizeof(sbuf), "%9i %s", (int)st.st_size, tbuf);
591 files[n].st = strdup(sbuf);
592 memset(&files[++n], '\0', sizeof(struct file_s));
595 which--;
597 if (which == 0) {
598 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
599 return NULL;
601 strncpy(pattern, p, sizeof(pattern));
602 x |= WRDE_REUSE;
603 goto new_we;
606 wordfree(&w);
607 qsort(files, n, sizeof(struct file_s), sort_files);
609 for (i = x = nlen = 0; i < n; i++) {
610 if (strlen(files[i].name) > nlen)
611 nlen = strlen(files[i].name);
613 if (x < nlen + strlen(files[i].st))
614 x = nlen + strlen(files[i].st);
617 cols = x + 1;
619 if (cols < strlen(path))
620 cols = strlen(path);
622 if (cols < strlen(HELP_PROMPT))
623 cols = strlen(HELP_PROMPT);
625 if (cols > COLS)
626 cols = COLS - 4;
628 cols += 2;
629 rows = (n + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : n + 4;
631 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
632 wbkgd(win, CP_MENU);
633 panel = new_panel(win);
634 draw_window_title(win, path, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
635 draw_prompt(win, rows - 2, cols, HELP_PROMPT, CP_INPUT_PROMPT);
636 cbreak();
637 noecho();
638 keypad(win, TRUE);
639 nl();
641 while (1) {
642 int c;
644 for (i = toppos, c = 2; i < n && c < rows - 2; i++, c++) {
645 if (i == selected) {
646 wattron(win, CP_MENU_HIGHLIGHT);
647 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
648 cols - nlen - 2 - 1, files[i].st);
649 wattroff(win, CP_MENU_HIGHLIGHT);
650 continue;
653 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
654 cols - nlen - 2 - 1, files[i].st);
657 refresh_all();
658 c = wgetch(win);
660 switch (c) {
661 case KEY_HOME:
662 case KEY_END:
663 case KEY_UP:
664 case KEY_DOWN:
665 case KEY_PPAGE:
666 case KEY_NPAGE:
667 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
668 menubuf[0] = 0;
669 mp = menubuf;
670 break;
671 case '\n':
672 goto gotitem;
673 break;
674 case KEY_ESCAPE:
675 cleanup(win, panel, files);
676 file[0] = 0;
677 goto done;
678 break;
679 case KEY_F(1):
680 help(BROWSER_HELP, ANYKEY, file_browser_help);
681 break;
682 case '~':
683 strncpy(path, "~", sizeof(path));
684 cleanup(win, panel, files);
685 goto again;
686 break;
687 case CTRL('X'):
688 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
689 == NULL)
690 break;
692 if (tmp[strlen(tmp) - 1] == '/')
693 tmp[strlen(tmp) - 1] = 0;
695 strncpy(path, tmp, sizeof(path));
696 cleanup(win, panel, files);
697 goto again;
698 break;
699 default:
700 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
701 menubuf[0] = 0;
702 mp = menubuf;
705 *mp++ = c;
706 *mp = 0;
707 x = selected;
709 for (i = 0; i < n; i++) {
710 if (strncasecmp(menubuf, files[i].name,
711 strlen(menubuf)) == 0) {
712 selected = i;
713 break;
717 if (x == selected) {
718 menubuf[0] = 0;
719 mp = menubuf;
722 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
723 break;
727 gotitem:
728 menubuf[0] = 0;
729 mp = menubuf;
730 strncpy(file, files[selected].path, sizeof(file));
731 cleanup(win, panel, files);
733 if (stat(file, &st) == -1) {
734 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
735 continue;
738 if (S_ISDIR(st.st_mode)) {
739 p = file + strlen(file) - 2;
741 if (strcmp(p, "..") == 0) {
742 p = file + strlen(file) - 3;
743 *p = 0;
745 if ((p = strrchr(file, '/')) != NULL)
746 file[strlen(file) - strlen(p)] = 0;
749 strncpy(path, file, sizeof(path));
750 goto again;
753 if (S_ISREG(st.st_mode))
754 break;
756 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
759 done:
760 curs_set(cursor);
761 return (*file) ? file : NULL;
764 static int init_country_codes()
766 FILE *fp;
767 char line[LINE_MAX], *s;
768 int cindex = 0;
770 if ((fp = fopen(config.ccfile, "r")) == NULL) {
771 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
772 return 1;
775 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
776 char *tmp;
778 if ((tmp = strsep(&s, " ")) == NULL)
779 continue;
781 s = trim(s);
782 tmp = trim(tmp);
784 if (!s || !tmp)
785 continue;
787 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
788 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
789 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
790 cindex++;
793 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
794 fclose(fp);
796 return 0;
799 char *country_codes(void *arg)
801 WINDOW *win;
802 PANEL *panel;
803 int i = 0, n;
804 int rows, cols;
805 char *tmp = NULL;
806 int len = 0;
807 int total;
808 int selected = 0;
809 int toppos = 0;
810 char menubuf[64] = {0}, *mp = menubuf;
812 if (!ccodes) {
813 if (init_country_codes())
814 return NULL;
817 for (i = n = 0; ccodes[i].code && ccodes[i].code[0]; i++) {
818 n = strlen(ccodes[i].code) + strlen(ccodes[i].country);
820 if (len < n)
821 len = n;
824 total = i;
825 cols = len;
827 if (cols < strlen(HELP_PROMPT) + 21)
828 cols = strlen(HELP_PROMPT) + 21;
830 cols += 1;
832 if (cols > COLS)
833 cols = COLS - 4;
835 cols += 2;
836 rows = (i + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : i + 4;
837 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
838 panel = new_panel(win);
839 cbreak();
840 noecho();
841 keypad(win, TRUE);
842 nl();
843 wbkgd(win, CP_MENU);
845 while (1) {
846 int c;
847 char buf[cols - 4];
849 wmove(win, 0, 0);
850 wclrtobot(win);
852 draw_window_title(win, CC_TITLE, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
854 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
855 if (i == selected) {
856 wattron(win, CP_MENU_HIGHLIGHT);
857 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code,
858 ccodes[i].country);
859 wattroff(win, CP_MENU_HIGHLIGHT);
860 continue;
863 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code, ccodes[i].country);
866 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR,
867 selected + 1, N_OF_N_STR, total, HELP_PROMPT);
868 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
869 refresh_all();
870 c = wgetch(win);
872 switch (c) {
873 case KEY_F(1):
874 help(CC_KEY_HELP, ANYKEY, cc_help);
875 break;
876 case KEY_HOME:
877 case KEY_END:
878 case KEY_UP:
879 case KEY_DOWN:
880 case KEY_PPAGE:
881 case KEY_NPAGE:
882 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
883 menubuf[0] = 0;
884 mp = menubuf;
885 break;
886 case '\n':
887 tmp = ccodes[selected].code;
888 goto done;
889 break;
890 case KEY_ESCAPE:
891 tmp = NULL;
892 goto done;
893 break;
894 default:
895 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
896 menubuf[0] = 0;
897 mp = menubuf;
900 *mp++ = c;
901 *mp = 0;
902 n = selected;
904 for (i = 0; i < total; i++) {
905 if (strncasecmp(menubuf, ccodes[i].code,
906 strlen(menubuf)) == 0) {
907 selected = i;
908 break;
912 if (n == selected) {
913 menubuf[0] = 0;
914 mp = menubuf;
917 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
918 break;
922 done:
923 del_panel(panel);
924 delwin(win);
925 return tmp;
928 static void add_custom_tags(TAG ***t)
930 int i;
931 int total = pgn_tag_total(config.tag);
933 if (!config.tag)
934 return;
936 for (i = 0; i < total; i++)
937 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
939 pgn_tag_sort(*t);
942 TAG **edit_tags(GAME g, BOARD b, int edit)
944 TAG **data = NULL;
945 struct tm tp;
946 int data_index = 0;
947 int len;
948 int selected = 0;
949 int n;
950 int toppos = 0;
951 char menubuf[64] = {0}, *mp = menubuf;
953 /* Edit the backup copy, not the original in case the save fails. */
954 len = pgn_tag_total(g.tag);
956 for (n = 0; n < len; n++)
957 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
959 data_index = pgn_tag_total(data);
961 while (1) {
962 WINDOW *win;
963 PANEL *panel;
964 int i;
965 char buf[76] = {0};
966 char *tmp = NULL;
967 int rows, cols;
968 int nlen = 0;
970 data_index = pgn_tag_total(data);
972 for (i = cols = 0, n = 4; i < data_index; i++) {
973 n = strlen(data[i]->name);
975 if (nlen < n)
976 nlen = n;
978 if (data[i]->value)
979 n += strlen(data[i]->value);
980 else
981 n += strlen(UNKNOWN);
983 if (cols < n)
984 cols = n;
987 cols += nlen + 2;
989 if (cols > COLS)
990 cols = COLS - 2;
992 /* +14 for the extra prompt info. */
993 if (cols < strlen(HELP_PROMPT) + 14 + 2)
994 cols = strlen(HELP_PROMPT) + 14 + 2;
996 rows = (data_index + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 :
997 data_index + 4;
999 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
1000 panel = new_panel(win);
1001 cbreak();
1002 noecho();
1003 keypad(win, TRUE);
1004 nl();
1005 wbkgd(win, CP_MENU);
1006 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1007 cols, (edit) ? CP_INPUT_TITLE : CP_MESSAGE_TITLE,
1008 (edit) ? CP_INPUT_BORDER : CP_MESSAGE_BORDER);
1010 if (selected >= data_index - 1)
1011 selected = data_index - 1;
1013 while (1) {
1014 int c;
1015 TAG **tmppgn = NULL;
1016 char *newtag = NULL;
1018 for (i = toppos, c = 2; i < data_index && c < rows - 2; i++, c++) {
1019 if (i == selected) {
1020 wattron(win, CP_MENU_HIGHLIGHT);
1021 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1022 cols - nlen - 2 - 2, (data[i]->value &&
1023 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1024 wattroff(win, CP_MENU_HIGHLIGHT);
1025 continue;
1028 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1029 cols - nlen - 2 - 2, (data[i]->value &&
1030 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1033 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1034 selected + 1, N_OF_N_STR, data_index, HELP_PROMPT);
1035 draw_prompt(win, rows - 2, cols, buf,
1036 (edit) ? CP_INPUT_PROMPT : CP_MESSAGE_PROMPT);
1037 refresh_all();
1038 c = wgetch(win);
1040 switch (c) {
1041 case CTRL('T'):
1042 if (!edit)
1043 break;
1045 add_custom_tags(&data);
1046 selected = data_index - 1;
1047 toppos = data_index - (rows - 4);
1048 goto cleanup;
1049 break;
1050 case KEY_F(1):
1051 if (edit)
1052 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1053 else
1054 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1055 break;
1056 case CTRL('R'):
1057 if (!edit)
1058 break;
1060 if (selected <= 6) {
1061 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1062 goto cleanup;
1065 data_index = pgn_tag_total(data);
1067 for (i = 0; i < data_index; i++) {
1068 if (i == selected)
1069 continue;
1071 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
1074 pgn_tag_free(data);
1075 data = NULL;
1077 for (i = 0; tmppgn[i]; i++)
1078 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
1080 pgn_tag_free(tmppgn);
1082 if (selected >= data_index)
1083 selected = data_index - 1;
1085 if (selected > rows - 5)
1086 toppos = selected - (rows - 5);
1087 else
1088 toppos -= (toppos) ? 1 : 0;
1090 goto cleanup;
1091 break;
1092 case CTRL('A'):
1093 if (!edit)
1094 break;
1096 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1097 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1098 == NULL)
1099 break;
1101 newtag[0] = toupper(newtag[0]);
1103 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1104 strlen(PRESS_ENTER)) {
1105 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1106 break;
1109 for (i = 0; i < data_index; i++) {
1110 if (strcasecmp(data[i]->name, newtag) == 0) {
1111 selected = i;
1112 goto gotitem;
1116 pgn_tag_add(&data, newtag, NULL);
1117 data_index = pgn_tag_total(data);
1118 selected = data_index - 1;
1119 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1120 &toppos);
1121 goto gotitem;
1122 break;
1123 case KEY_HOME:
1124 case KEY_END:
1125 case KEY_UP:
1126 case KEY_DOWN:
1127 case KEY_NPAGE:
1128 case KEY_PPAGE:
1129 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1130 &toppos);
1131 menubuf[0] = 0;
1132 mp = menubuf;
1133 break;
1134 case CTRL('F'):
1135 if (!edit)
1136 break;
1138 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1139 data_index = pgn_tag_total(data);
1140 selected = data_index - 1;
1141 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1142 &toppos);
1143 goto gotitem;
1144 break;
1145 case '\n':
1146 goto gotitem;
1147 break;
1148 case KEY_ESCAPE:
1149 del_panel(panel);
1150 delwin(win);
1151 goto done;
1152 break;
1153 default:
1154 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
1155 menubuf[0] = 0;
1156 mp = menubuf;
1159 *mp++ = c;
1160 *mp = 0;
1161 n = selected;
1163 for (i = 0; i < data_index; i++) {
1164 if (strncasecmp(menubuf, data[i]->name, strlen(menubuf))
1165 == 0) {
1166 selected = i;
1167 break;
1171 if (n == selected) {
1172 menubuf[0] = 0;
1173 mp = menubuf;
1176 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1177 &toppos);
1178 break;
1182 gotitem:
1183 nlen = strlen(data[selected]->name) + 2;
1184 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1186 if (nlen > MAX_VALUE_WIDTH)
1187 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1188 else
1189 snprintf(buf, sizeof(buf), "%s \"%s\"",
1190 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1191 data[selected]->name);
1193 if (!edit) {
1194 if (!data[selected]->value)
1195 goto cleanup;
1197 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1198 goto cleanup;
1201 if (strcmp(data[selected]->name, "Date") == 0) {
1202 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1203 0, FIELD_TYPE_PGN_DATE);
1205 if (tmp) {
1206 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1207 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1208 goto cleanup;
1211 else
1212 goto cleanup;
1214 else if (strcmp(data[selected]->name, "Site") == 0) {
1215 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1216 country_codes, NULL, CTRL('t'), -1);
1218 if (!tmp)
1219 tmp = "?";
1221 else if (strcmp(data[selected]->name, "Round") == 0) {
1222 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1223 FIELD_TYPE_PGN_ROUND);
1225 if (!tmp) {
1226 if (gtotal > 1)
1227 tmp = "?";
1228 else
1229 tmp = "-";
1232 else if (strcmp(data[selected]->name, "Result") == 0) {
1233 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1234 0, -1);
1236 if (!tmp)
1237 tmp = "*";
1239 else {
1240 tmp = (data[selected]->value) ? data[selected]->value : NULL;
1241 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1244 len = (tmp) ? strlen(tmp) + 1 : 1;
1245 data[selected]->value = Realloc(data[selected]->value, len);
1246 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1248 cleanup:
1249 del_panel(panel);
1250 delwin(win);
1251 menubuf[0] = 0;
1252 mp = menubuf;
1255 done:
1256 if (!edit) {
1257 pgn_tag_free(data);
1258 return NULL;
1261 return data;
1264 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1265 * game index number.
1267 int save_pgn(const char *filename, int saveindex)
1269 FILE *fp;
1270 char *mode = NULL;
1271 int c;
1272 char buf[FILENAME_MAX];
1273 struct stat st;
1274 int i;
1275 char *command = NULL;
1276 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1278 if (filename[0] != '/' && config.savedirectory) {
1279 if (stat(config.savedirectory, &st) == -1) {
1280 if (errno == ENOENT) {
1281 if (mkdir(config.savedirectory, 0755) == -1) {
1282 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1283 strerror(errno));
1284 return 1;
1287 else {
1288 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1289 strerror(errno));
1290 return 1;
1294 stat(config.savedirectory, &st);
1296 if (!S_ISDIR(st.st_mode)) {
1297 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1298 return 1;
1301 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1302 filename = buf;
1305 if (access(filename, W_OK) == 0) {
1306 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1307 "%s \"%s\"", E_FILEEXISTS, filename);
1309 switch (c) {
1310 case 'a':
1311 if (pgn_is_compressed(filename) == E_PGN_OK) {
1312 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1313 return 1;
1316 mode = "a";
1317 break;
1318 case 'o':
1319 mode = "w+";
1320 break;
1321 default:
1322 return 1;
1325 else
1326 mode = "a";
1328 if (command) {
1329 if ((fp = popen(command, "w")) == NULL) {
1330 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1331 return 1;
1334 else {
1335 if ((fp = fopen(filename, mode)) == NULL) {
1336 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1337 return 1;
1341 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1342 pgn_write(fp, game[i]);
1344 if (command)
1345 pclose(fp);
1346 else
1347 fclose(fp);
1349 if (saveindex == -1)
1350 strncpy(loadfile, filename, sizeof(loadfile));
1352 return 0;
1355 char *random_agony(GAME g)
1357 static int n;
1358 FILE *fp;
1359 char line[LINE_MAX];
1361 if (n == -1 || !config.agony || !curses_initialized ||
1362 (g.mode == MODE_HISTORY && !config.historyagony))
1363 return NULL;
1365 if (!agony) {
1366 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
1367 n = -1;
1368 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
1369 return NULL;
1372 while (!feof(fp)) {
1373 if (fscanf(fp, " %[^\n] ", line) == 1) {
1374 agony = Realloc(agony, (n + 2) * sizeof(char *));
1375 agony[n++] = strdup(trim(line));
1379 agony[n] = NULL;
1380 fclose(fp);
1382 if (agony[0] == NULL || !n) {
1383 n = -1;
1384 return NULL;
1388 return agony[random() % n];
1391 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1393 if (pgn_piece_to_int(piece) == ROOK && col == 7
1394 && row == 7 &&
1395 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1396 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1397 if (mod)
1398 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1399 return 1;
1401 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1402 && row == 7 &&
1403 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1404 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1405 if (mod)
1406 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1407 return 1;
1409 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1410 && row == 0 &&
1411 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1412 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1413 if (mod)
1414 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1415 return 1;
1417 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1418 && row == 0 &&
1419 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1420 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1421 if (mod)
1422 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1423 return 1;
1425 else if (pgn_piece_to_int(piece) == KING && col == 4
1426 && row == 7 &&
1427 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1428 TEST_FLAG(g->flags, GF_WK_CASTLE))
1430 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1431 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1432 if (mod) {
1433 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1434 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1435 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1436 else
1437 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1439 return 1;
1441 else if (pgn_piece_to_int(piece) == KING && col == 4
1442 && row == 0 &&
1443 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1444 TEST_FLAG(g->flags, GF_BK_CASTLE))
1446 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1447 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1448 if (mod) {
1449 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1450 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1451 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1452 else
1453 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1455 return 1;
1458 return 0;
1461 static void draw_board(GAME *g, int details)
1463 int row, col;
1464 int bcol = 0, brow = 0;
1465 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1466 int ncols = 0, offset = 1;
1467 unsigned coords_y = 8;
1468 struct userdata_s *d = g->data;
1470 if (g->mode != MODE_PLAY && g->mode != MODE_EDIT)
1471 update_cursor(*g, g->hindex);
1473 for (row = 0; row < maxy; row++) {
1474 bcol = 0;
1476 for (col = 0; col < maxx; col++) {
1477 int attrwhich = -1;
1478 chtype attrs = 0;
1479 unsigned char piece;
1481 if (row == 0 || row == maxy - 2) {
1482 if (col == 0)
1483 mvwaddch(boardw, row, col,
1484 LINE_GRAPHIC((row) ?
1485 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1486 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1487 else if (col == maxx - 2)
1488 mvwaddch(boardw, row, col,
1489 LINE_GRAPHIC((row) ?
1490 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1491 ACS_URCORNER | CP_BOARD_GRAPHICS));
1492 else if (!(col % 4))
1493 mvwaddch(boardw, row, col,
1494 LINE_GRAPHIC((row) ?
1495 ACS_BTEE | CP_BOARD_GRAPHICS :
1496 ACS_TTEE | CP_BOARD_GRAPHICS));
1497 else {
1498 if (col != maxx - 1)
1499 mvwaddch(boardw, row, col,
1500 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1503 continue;
1506 if ((row % 2) && col == maxx - 1 && coords_y) {
1507 wattron(boardw, CP_BOARD_COORDS);
1508 mvwprintw(boardw, row, col, "%d", coords_y--);
1509 wattroff(boardw, CP_BOARD_COORDS);
1510 continue;
1513 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1514 if (!(row % 2))
1515 mvwaddch(boardw, row, col,
1516 LINE_GRAPHIC((col) ?
1517 ACS_RTEE | CP_BOARD_GRAPHICS :
1518 ACS_LTEE | CP_BOARD_GRAPHICS));
1519 else
1520 mvwaddch(boardw, row, col,
1521 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1523 continue;
1526 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1527 mvwaddch(boardw, row, col,
1528 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1529 continue;
1532 if (!(col % 4) && row != maxy - 1) {
1533 mvwaddch(boardw, row, col,
1534 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1535 continue;
1538 if ((row % 2)) {
1539 if ((col % 4)) {
1540 if (ncols++ == 8) {
1541 offset++;
1542 ncols = 1;
1545 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1546 && (offset % 2)))
1547 attrwhich = BLACK;
1548 else
1549 attrwhich = WHITE;
1551 if (config.validmoves && d->b[brow][bcol].valid) {
1552 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1553 CP_BOARD_MOVES_BLACK;
1555 else
1556 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1557 CP_BOARD_BLACK;
1559 if (row == ROWTOMATRIX(d->c_row) && col ==
1560 COLTOMATRIX(d->c_col)) {
1561 attrs = CP_BOARD_CURSOR;
1564 if (row == ROWTOMATRIX(d->sp.srow) &&
1565 col == COLTOMATRIX(d->sp.scol)) {
1566 attrs = CP_BOARD_SELECTED;
1569 if (row == maxy - 1)
1570 attrs = 0;
1572 mvwaddch(boardw, row, col, ' ' | attrs);
1574 if (row == maxy - 1)
1575 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1576 else {
1577 if (details && d->b[row / 2][bcol].enpassant)
1578 piece = 'x';
1579 else
1580 piece = d->b[row / 2][bcol].icon;
1582 if (details && castling_state(g, d->b, brow, bcol,
1583 piece, 0))
1584 attrs |= A_REVERSE;
1586 if (g->side == WHITE && isupper(piece))
1587 attrs |= A_BOLD;
1588 else if (g->side == BLACK && islower(piece))
1589 attrs |= A_BOLD;
1591 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1593 CLEAR_FLAG(attrs, A_BOLD);
1594 CLEAR_FLAG(attrs, A_REVERSE);
1597 waddch(boardw, ' ' | attrs);
1598 col += 2;
1599 bcol++;
1602 else {
1603 if (col != maxx - 1)
1604 mvwaddch(boardw, row, col,
1605 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1609 brow = row / 2;
1613 void invalid_move(int n, const char *m)
1615 if (curses_initialized)
1616 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", E_INVALID_MOVE, m, n);
1617 else
1618 warnx("%s: %s \"%s\" (round #%i)", loadfile, E_INVALID_MOVE, m, n);
1621 /* Convert the selected piece to SAN format and validate it. */
1622 static char *board_to_san(GAME *g, BOARD b)
1624 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1625 int piece;
1626 int promo;
1627 struct userdata_s *d = g->data;
1629 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[d->sp.scol - 1],
1630 d->sp.srow, x_grid_chars[d->sp.col - 1], d->sp.row);
1632 p = str;
1633 piece = pgn_piece_to_int(b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon);
1635 if (piece == PAWN && ((d->sp.row == 8 && g->turn == WHITE) ||
1636 (d->sp.row == 1 && g->turn == BLACK))) {
1637 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1639 if (pgn_piece_to_int(promo) == -1)
1640 return NULL;
1642 p = str + strlen(str);
1643 *p++ = toupper(promo);
1644 *p = '\0';
1647 p = str;
1649 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1650 if (pgn_parse_move(g, b, &p) != E_PGN_OK) {
1651 invalid_move(gindex + 1, p);
1652 return NULL;
1655 return p;
1658 if (pgn_validate_move(g, b, &p) != E_PGN_OK) {
1659 invalid_move(gindex + 1, p);
1660 return NULL;
1663 return p;
1666 static int move_to_engine(GAME *g, BOARD b)
1668 char *p;
1669 struct userdata_s *d = g->data;
1671 if ((p = board_to_san(g, b)) == NULL)
1672 return 0;
1674 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1676 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1677 pgn_history_add(g, p);
1678 pgn_switch_turn(g);
1679 SET_FLAG(g->flags, GF_MODIFIED);
1680 update_all(*g);
1681 return 1;
1684 add_engine_command(g, ENGINE_THINKING, "%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;
1707 struct userdata_s *d = g.data;
1709 getmaxyx(statusw, maxy, maxx);
1710 w = maxx - 2 - 8;
1711 len = maxx - 2;
1712 buf = Malloc(len);
1714 *tmp = '\0';
1715 p = tmp;
1717 if (TEST_FLAG(g.flags, GF_DELETE)) {
1718 *p++ = '(';
1719 *p++ = 'x';
1720 i++;
1723 if (TEST_FLAG(g.flags, GF_PERROR)) {
1724 if (!i)
1725 *p++ = '(';
1726 else
1727 *p++ = '/';
1729 *p++ = '!';
1730 i++;
1733 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1734 if (!i)
1735 *p++ = '(';
1736 else
1737 *p++ = '/';
1739 *p++ = '*';
1740 i++;
1743 if (*tmp != '\0')
1744 *p++ = ')';
1746 *p = '\0';
1748 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1749 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1750 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1751 (*tmp) ? tmp : "");
1752 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1754 switch (g.mode) {
1755 case MODE_HISTORY:
1756 mode = MODE_HISTORY_STR;
1757 break;
1758 case MODE_EDIT:
1759 mode = MODE_EDIT_STR;
1760 break;
1761 case MODE_PLAY:
1762 mode = MODE_PLAY_STR;
1763 break;
1764 default:
1765 mode = UNKNOWN;
1766 break;
1769 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1771 if (g.mode == MODE_PLAY) {
1772 if (TEST_FLAG(d->flags, CF_HUMAN))
1773 strncat(buf, " (human/human)", len - 1);
1774 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1775 strncat(buf, " (engine/engine)", len - 1);
1776 else
1777 strncat(buf, " (human/engine)", len - 1);
1780 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1782 if (d->engine) {
1783 switch (d->engine->status) {
1784 case ENGINE_THINKING:
1785 engine = ENGINE_PONDER_STR;
1786 break;
1787 case ENGINE_READY:
1788 engine = ENGINE_READY_STR;
1789 break;
1790 case ENGINE_INITIALIZING:
1791 engine = ENGINE_INITIALIZING_STR;
1792 break;
1793 case ENGINE_OFFLINE:
1794 engine = ENGINE_OFFLINE_STR;
1795 break;
1796 default:
1797 engine = UNKNOWN;
1798 break;
1801 else
1802 engine = ENGINE_OFFLINE_STR;
1804 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1805 wattron(statusw, CP_STATUS_ENGINE);
1806 mvwaddstr(statusw, 5, 9, engine);
1807 wattroff(statusw, CP_STATUS_ENGINE);
1809 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1810 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1812 strncpy(tmp, WHITE_STR, sizeof(tmp));
1813 tmp[0] = toupper(tmp[0]);
1814 update_clock(g.moveclock, &h, &m, &s);
1815 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1816 mvwprintw(statusw, 7, 1, "%*s: %-*s", 6, tmp, w, buf);
1818 strncpy(tmp, BLACK_STR, sizeof(tmp));
1819 tmp[0] = toupper(tmp[0]);
1820 update_clock(g.moveclock, &h, &m, &s);
1821 snprintf(buf, len, "%.2i:%.2i:%.2i", h, m, s);
1822 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, buf);
1823 free(buf);
1825 for (i = 1; i < maxx - 4; i++)
1826 mvwprintw(statusw, maxy - 2, i, " ");
1828 if (!status.notify)
1829 status.notify = strdup(GAME_HELP_PROMPT);
1831 wattron(statusw, CP_STATUS_NOTIFY);
1832 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1833 status.notify);
1834 wattroff(statusw, CP_STATUS_NOTIFY);
1837 void update_history_window(GAME g)
1839 char buf[HISTORY_WIDTH - 1];
1840 HISTORY *h = NULL;
1841 int n, total;
1842 int t = pgn_history_total(g.hp);
1844 n = (g.hindex + 1) / 2;
1846 if (t % 2)
1847 total = (t + 1) / 2;
1848 else
1849 total = t / 2;
1851 if (t)
1852 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1853 (movestep == 1) ? HISTORY_PLY_STEP : "");
1854 else
1855 strncpy(buf, UNAVAILABLE, sizeof(buf));
1857 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1858 HISTORY_WIDTH - 13, buf);
1860 h = pgn_history_by_n(g.hp, g.hindex);
1861 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1862 n = 0;
1864 if (h && ((h->comment) || h->nag[0])) {
1865 strncat(buf, " (v", sizeof(buf));
1866 n++;
1869 if (h && h->rav) {
1870 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1871 n++;
1874 if (g.ravlevel) {
1875 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1876 n++;
1879 if (n)
1880 strncat(buf, ")", sizeof(buf));
1882 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1883 HISTORY_WIDTH - 13, buf);
1885 h = pgn_history_by_n(g.hp, game[gindex].hindex - 1);
1886 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1887 n = 0;
1889 if (h && ((h->comment) || h->nag[0])) {
1890 strncat(buf, " (V", sizeof(buf));
1891 n++;
1894 if (h && h->rav) {
1895 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1896 n++;
1899 if (g.ravlevel) {
1900 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1901 n++;
1904 if (n)
1905 strncat(buf, ")", sizeof(buf));
1907 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1908 HISTORY_WIDTH - 13, buf);
1911 void update_tag_window(TAG **t)
1913 int i;
1914 int w = TAG_WIDTH - 10;
1916 for (i = 0; i < 7; i++)
1917 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1920 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1922 int i;
1924 wattron(win, attr);
1926 for (i = 1; i < width - 1; i++)
1927 mvwaddch(win, y, i, ' ');
1929 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1930 wattroff(win, attr);
1933 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1934 chtype battr)
1936 int i;
1938 if (title) {
1939 wattron(win, attr);
1941 for (i = 1; i < width - 1; i++)
1942 mvwaddch(win, 1, i, ' ');
1944 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1945 wattroff(win, attr);
1948 wattron(win, battr);
1949 box(win, ACS_VLINE, ACS_HLINE);
1950 wattroff(win, battr);
1953 void append_enginebuf(char *line)
1955 int i = 0;
1957 if (enginebuf)
1958 for (i = 0; enginebuf[i]; i++);
1960 if (i >= LINES - 3) {
1961 free(enginebuf[0]);
1963 for (i = 0; enginebuf[i+1]; i++)
1964 enginebuf[i] = enginebuf[i+1];
1966 enginebuf[i] = strdup(line);
1968 else {
1969 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
1970 enginebuf[i++] = strdup(line);
1971 enginebuf[i] = NULL;
1975 void update_engine_window()
1977 int i;
1979 if (!enginebuf)
1980 return;
1982 wmove(enginew, 0, 0);
1983 wclrtobot(enginew);
1985 if (enginebuf) {
1986 for (i = 0; enginebuf[i]; i++)
1987 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
1990 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
1991 CP_MESSAGE_BORDER);
1994 void toggle_engine_window()
1996 if (!enginew) {
1997 enginew = newwin(LINES, COLS, 0, 0);
1998 enginep = new_panel(enginew);
1999 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2000 CP_MESSAGE_BORDER);
2001 hide_panel(enginep);
2004 if (panel_hidden(enginep)) {
2005 update_engine_window();
2006 top_panel(enginep);
2007 refresh_all();
2009 else {
2010 hide_panel(enginep);
2011 refresh_all();
2015 void refresh_all()
2017 update_panels();
2018 doupdate();
2021 void update_all(GAME g)
2023 update_status_window(g);
2024 update_history_window(g);
2025 update_tag_window(g.tag);
2026 update_engine_window();
2029 static void game_next_prev(GAME g, int n, int count)
2031 if (gtotal < 2)
2032 return;
2034 if (n == 1) {
2035 if (gindex + count > gtotal - 1) {
2036 if (count != 1)
2037 gindex = gtotal - 1;
2038 else
2039 gindex = 0;
2041 else
2042 gindex += count;
2044 else {
2045 if (gindex - count < 0) {
2046 if (count != 1)
2047 gindex = 0;
2048 else
2049 gindex = gtotal - 1;
2051 else
2052 gindex -= count;
2056 static void delete_game(int which)
2058 GAME *g = NULL;
2059 int gi = 0;
2060 int i;
2062 for (i = 0; i < gtotal; i++) {
2063 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
2064 pgn_free(game[i]);
2065 continue;
2068 g = Realloc(g, (gi + 1) * sizeof(GAME));
2069 memcpy(&g[gi], &game[i], sizeof(GAME));
2070 g[gi].tag = game[i].tag;
2071 g[gi].history = game[i].history;
2072 g[gi].hp = game[i].hp;
2073 gi++;
2076 game = g;
2077 gtotal = gi;
2079 if (which != -1) {
2080 if (which + 1 >= gtotal)
2081 gindex = gtotal - 1;
2082 else
2083 gindex = which;
2085 else
2086 gindex = gtotal - 1;
2088 game[gindex].hp = game[gindex].history;
2091 static int find_move_exp(GAME g, const char *str, int init, int which,
2092 int count)
2094 int i;
2095 int ret;
2096 static regex_t r;
2097 static int firstrun = 1;
2098 char errbuf[255];
2099 int incr;
2100 int found;
2102 if (init) {
2103 if (!firstrun)
2104 regfree(&r);
2106 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2107 regerror(ret, &r, errbuf, sizeof(errbuf));
2108 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2109 return -1;
2112 firstrun = 1;
2115 incr = (which == 0) ? -1 : 1;
2117 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2118 if (i == g.hindex - 1)
2119 break;
2121 if (i >= pgn_history_total(g.hp))
2122 i = 0;
2123 else if (i < 0)
2124 i = pgn_history_total(g.hp) - 1;
2126 // FIXME RAV
2127 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2129 if (ret == 0) {
2130 if (count == ++found) {
2131 return i + 1;
2134 else {
2135 if (ret != REG_NOMATCH) {
2136 regerror(ret, &r, errbuf, sizeof(errbuf));
2137 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2138 return -1;
2143 return -1;
2146 static int toggle_delete_flag(int n)
2148 int i, x;
2150 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2151 gindex = n;
2152 update_all(game[gindex]);
2154 for (i = x = 0; i < gtotal; i++) {
2155 if (TEST_FLAG(game[i].flags, GF_DELETE))
2156 x++;
2159 if (x == gtotal) {
2160 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2161 CLEAR_FLAG(game[n].flags, GF_DELETE);
2162 return 1;
2165 return 0;
2168 static void edit_save_tags(GAME *g)
2170 TAG **t;
2171 struct userdata_s *d = g->data;
2173 if ((t = edit_tags(*g, d->b, 1)) == NULL)
2174 return;
2176 pgn_tag_free(g->tag);
2177 g->tag = t;
2178 SET_FLAG(g->flags, GF_MODIFIED);
2179 pgn_tag_sort(g->tag);
2182 static int find_game_exp(char *str, int which, int count)
2184 char *nstr = NULL, *exp = NULL;
2185 regex_t nexp, vexp;
2186 int ret = -1;
2187 int g = 0;
2188 char buf[255], *tmp;
2189 char errbuf[255];
2190 int found = 0;
2191 int incr = (which == 0) ? -(1) : 1;
2193 strncpy(buf, str, sizeof(buf));
2194 tmp = buf;
2196 if (strstr(tmp, ":") != NULL) {
2197 nstr = strsep(&tmp, ":");
2199 if ((ret = regcomp(&nexp, nstr,
2200 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2201 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2202 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2203 ret = g = -1;
2204 goto cleanup;
2208 exp = tmp;
2210 if (exp == NULL)
2211 goto cleanup;
2213 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2214 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2215 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2216 ret = -1;
2217 goto cleanup;
2220 ret = -1;
2222 for (g = gindex + incr, found = 0; ; g += incr) {
2223 int t;
2225 if (g == gindex)
2226 break;
2228 if (g == gtotal)
2229 g = 0;
2230 else if (g < 0)
2231 g = gtotal - 1;
2233 for (t = 0; game[g].tag[t]; t++) {
2234 if (nstr) {
2235 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2236 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2237 if (count == ++found) {
2238 ret = g;
2239 goto cleanup;
2244 else {
2245 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2246 if (count == ++found) {
2247 ret = g;
2248 goto cleanup;
2254 ret = -1;
2257 cleanup:
2258 if (nstr)
2259 regfree(&nexp);
2261 if (g != -1)
2262 regfree(&vexp);
2264 return ret;
2268 * Updates the notification line in the status window then refreshes the
2269 * status window.
2271 void update_status_notify(GAME g, char *fmt, ...)
2273 va_list ap;
2274 #ifdef HAVE_VASPRINTF
2275 char *line;
2276 #else
2277 char line[COLS];
2278 #endif
2280 if (!fmt) {
2281 if (status.notify) {
2282 free(status.notify);
2283 status.notify = NULL;
2285 if (curses_initialized)
2286 update_status_window(g);
2289 return;
2292 va_start(ap, fmt);
2293 #ifdef HAVE_VASPRINTF
2294 vasprintf(&line, fmt, ap);
2295 #else
2296 vsnprintf(line, sizeof(line), fmt, ap);
2297 #endif
2298 va_end(ap);
2300 if (status.notify)
2301 free(status.notify);
2303 status.notify = strdup(line);
2305 #ifdef HAVE_VASPRINTF
2306 free(line);
2307 #endif
2308 if (curses_initialized)
2309 update_status_window(g);
2312 int rav_next_prev(GAME *g, BOARD b, int n)
2314 // Next RAV.
2315 if (n) {
2316 if (g->hp[g->hindex]->rav == NULL)
2317 return 1;
2319 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2320 g->rav[g->ravlevel].hp = g->hp;
2321 g->rav[g->ravlevel].flags = g->flags;
2322 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2323 g->rav[g->ravlevel].hindex = g->hindex;
2324 g->hp = g->hp[g->hindex]->rav;
2325 g->hindex = 0;
2326 g->ravlevel++;
2327 return 0;
2330 if (g->ravlevel - 1 < 0)
2331 return 1;
2333 // Previous RAV.
2334 g->ravlevel--;
2335 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2336 free(g->rav[g->ravlevel].fen);
2337 g->hp = g->rav[g->ravlevel].hp;
2338 g->flags = g->rav[g->ravlevel].flags;
2339 g->hindex = g->rav[g->ravlevel].hindex;
2340 return 0;
2343 static void draw_window_decor()
2345 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2346 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2347 wbkgd(boardw, CP_BOARD_WINDOW);
2348 wbkgd(statusw, CP_STATUS_WINDOW);
2349 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2350 CP_STATUS_TITLE, CP_STATUS_BORDER);
2351 wbkgd(tagw, CP_TAG_WINDOW);
2352 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2353 CP_TAG_BORDER);
2354 wbkgd(historyw, CP_HISTORY_WINDOW);
2355 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2356 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2359 static void do_window_resize()
2361 if (LINES < 24 || COLS < 80)
2362 return;
2364 resizeterm(LINES, COLS);
2365 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2366 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2367 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2368 wmove(historyw, 0, 0);
2369 wclrtobot(historyw);
2370 wmove(tagw, 0, 0);
2371 wclrtobot(tagw);
2372 wmove(statusw, 0, 0);
2373 wclrtobot(statusw);
2374 draw_window_decor();
2375 update_all(game[gindex]);
2378 static void historymode_keys(chtype);
2379 static int playmode_keys(chtype c)
2381 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2382 int editmode = (game[gindex].mode == MODE_EDIT) ? 1 : 0;
2383 chtype p;
2384 int w, x;
2385 char *tmp;
2386 struct userdata_s *d = game[gindex].data;
2388 switch (c) {
2389 case 'H':
2390 TOGGLE_FLAG(d->flags, CF_HUMAN);
2392 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2393 pgn_history_total(game[gindex].hp)) {
2394 pgn_tag_add(&game[gindex].tag, "FEN",
2395 pgn_game_to_fen(game[gindex], d->b));
2396 x = pgn_tag_find(game[gindex].tag, "FEN");
2398 if (start_chess_engine(&game[gindex]) <= 0) {
2399 add_engine_command(&game[gindex], ENGINE_READY,
2400 "setboard %s\n", game[gindex].tag[x]->value);
2401 d->engine->status = ENGINE_READY;
2405 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2407 if (d->engine)
2408 d->engine->status = ENGINE_READY;
2410 update_all(game[gindex]);
2411 break;
2412 case 'E':
2413 if (!d)
2414 break;
2416 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2417 CLEAR_FLAG(d->flags, CF_HUMAN);
2419 if (d->engine && !TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2420 pgn_board_update(&game[gindex], d->b,
2421 pgn_history_total(game[gindex].hp));
2422 add_engine_command(&game[gindex], ENGINE_READY,
2423 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2426 update_all(game[gindex]);
2427 break;
2428 case '|':
2429 if (!d->engine)
2430 break;
2432 if (d->engine->status == ENGINE_OFFLINE)
2433 break;
2435 x = d->engine->status;
2437 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2438 send_to_engine(&game[gindex], ENGINE_READY, "%s\n", tmp);
2439 d->engine->status = x;
2440 break;
2441 case '\015':
2442 case '\n':
2443 pushkey = keycount = 0;
2444 update_status_notify(game[gindex], NULL);
2446 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2447 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2448 beep();
2449 break;
2452 if (!d->sp.icon)
2453 break;
2455 d->sp.row = d->c_row;
2456 d->sp.col = d->c_col;
2458 if (editmode) {
2459 p = d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon;
2460 d->b[ROWTOBOARD(d->sp.row)][COLTOBOARD(d->sp.col)].icon = p;
2461 d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon =
2462 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2463 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2464 break;
2467 if (move_to_engine(&game[gindex], d->b)) {
2468 if (config.validmoves)
2469 pgn_reset_valid_moves(d->b);
2471 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2472 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2473 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2477 break;
2478 case ' ':
2479 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2480 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2481 if (start_chess_engine(&game[gindex])) {
2482 d->sp.icon = 0;
2483 break;
2486 x = pgn_tag_find(game[gindex].tag, "FEN");
2487 w = pgn_tag_find(game[gindex].tag, "SetUp");
2489 if ((w >= 0 && x >= 0 && atoi(game[gindex].tag[w]->value) == 1)
2490 || (x >= 0 && w == -1)) {
2491 add_engine_command(&game[gindex], ENGINE_READY,
2492 "setboard %s\n", game[gindex].tag[x]->value);
2496 if (d->sp.icon || (!editmode && d->engine &&
2497 d->engine->status == ENGINE_THINKING)) {
2498 beep();
2499 break;
2502 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
2503 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
2505 if (d->sp.icon == ' ') {
2506 d->sp.icon = 0;
2507 break;
2510 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2511 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2512 if (pgn_history_total(game[gindex].hp)) {
2513 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2514 d->sp.icon = 0;
2515 break;
2517 else {
2518 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2519 pgn_switch_turn(&game[gindex]);
2521 if (game[gindex].side != BLACK)
2522 pgn_switch_side(&game[gindex]);
2526 d->sp.srow = d->c_row;
2527 d->sp.scol = d->c_col;
2529 if (!editmode && config.validmoves)
2530 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2532 paused = 0;
2533 break;
2534 case 'w':
2535 pgn_switch_side(&game[gindex]);
2536 add_engine_command(&game[gindex], -1,
2537 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2538 update_status_window(game[gindex]);
2539 break;
2540 case 'u':
2541 if (!pgn_history_total(game[gindex].hp))
2542 break;
2544 if (d->engine && d->engine->status == ENGINE_READY) {
2545 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2546 d->engine->status = ENGINE_READY;
2549 game[gindex].hindex -= 2;
2550 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2551 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2552 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2553 update_history_window(game[gindex]);
2554 break;
2555 case 'a':
2556 historymode_keys(c);
2557 break;
2558 case 'd':
2559 board_details = (board_details) ? 0 : 1;
2560 break;
2561 case 'p':
2562 paused = (paused) ? 0 : 1;
2563 break;
2564 case 'g':
2565 if (!d->engine || d->engine->status == ENGINE_OFFLINE)
2566 start_chess_engine(&game[gindex]);
2568 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2569 break;
2570 default:
2571 if (!d->engine)
2572 break;
2574 if (config.keys) {
2575 for (x = 0; config.keys[x]; x++) {
2576 if (config.keys[x]->c == c) {
2577 switch (config.keys[x]->type) {
2578 case KEY_DEFAULT:
2579 add_engine_command(&game[gindex], -1, "%s\n",
2580 config.keys[x]->str);
2581 break;
2582 case KEY_SET:
2583 if (!keycount)
2584 break;
2586 add_engine_command(&game[gindex], -1,
2587 "%s %i\n", config.keys[x]->str, keycount);
2588 keycount = 0;
2589 break;
2590 case KEY_REPEAT:
2591 if (!keycount)
2592 break;
2594 for (w = 0; w < keycount; w++)
2595 add_engine_command(&game[gindex], -1,
2596 "%s\n", config.keys[x]->str);
2597 keycount = 0;
2598 break;
2603 update_status_notify(game[gindex], NULL);
2605 break;
2608 return 0;
2611 static void editmode_keys(chtype c)
2613 struct userdata_s *d = game[gindex].data;
2615 switch (c) {
2616 case '\015':
2617 case '\n':
2618 case ' ':
2619 playmode_keys(c);
2620 break;
2621 case 'd':
2622 if (d->sp.icon)
2623 d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon =
2624 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2625 else
2626 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon =
2627 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2629 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2630 break;
2631 case 'w':
2632 pgn_switch_turn(&game[gindex]);
2633 update_all(game[gindex]);
2634 break;
2635 case 'c':
2636 castling_state(&game[gindex], d->b, ROWTOBOARD(d->c_row),
2637 COLTOBOARD(d->c_col),
2638 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon, 1);
2639 break;
2640 case 'i':
2641 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2642 GAME_EDIT_TEXT);
2644 if (pgn_piece_to_int(c) == -1)
2645 break;
2647 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon = c;
2648 break;
2649 case 'p':
2650 if (d->c_row == 6 || d->c_row == 3) {
2651 pgn_reset_enpassant(d->b);
2652 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].enpassant = 1;
2654 break;
2655 default:
2656 break;
2660 static void historymode_keys(chtype c)
2662 int n, len;
2663 char *tmp, *buf;
2664 static char moveexp[255] = {0};
2665 struct userdata_s *d = game[gindex].data;
2667 switch (c) {
2668 case 'd':
2669 board_details = (board_details) ? 0 : 1;
2670 break;
2671 case ' ':
2672 movestep = (movestep == 1) ? 2 : 1;
2673 update_history_window(game[gindex]);
2674 break;
2675 case KEY_UP:
2676 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2677 config.jumpcount * keycount * movestep :
2678 config.jumpcount * movestep);
2679 update_all(game[gindex]);
2680 break;
2681 case KEY_DOWN:
2682 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2683 config.jumpcount * keycount * movestep :
2684 config.jumpcount * movestep);
2685 update_all(game[gindex]);
2686 break;
2687 case KEY_LEFT:
2688 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2689 keycount * movestep : movestep);
2690 update_all(game[gindex]);
2691 break;
2692 case KEY_RIGHT:
2693 pgn_history_next(&game[gindex], d->b, (keycount) ?
2694 keycount * movestep : movestep);
2695 update_all(game[gindex]);
2696 break;
2697 case 'a':
2698 n = game[gindex].hindex;
2700 if (n && game[gindex].hp[n - 1]->move)
2701 n--;
2702 else
2703 break;
2705 buf = Malloc(COLS);
2706 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2707 game[gindex].hp[n]->move);
2709 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2710 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2711 -1);
2712 free(buf);
2714 if (!tmp && (!game[gindex].hp[n]->comment ||
2715 !*game[gindex].hp[n]->comment))
2716 break;
2717 else if (tmp && game[gindex].hp[n]->comment) {
2718 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2719 break;
2722 len = (tmp) ? strlen(tmp) + 1 : 1;
2723 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2724 len);
2725 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2726 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2727 update_all(game[gindex]);
2728 break;
2729 case ']':
2730 case '[':
2731 case '/':
2732 if (pgn_history_total(game[gindex].hp) < 2)
2733 break;
2735 n = 0;
2737 if (!*moveexp || c == '/') {
2738 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2739 break;
2741 strncpy(moveexp, tmp, sizeof(moveexp));
2742 n = 1;
2745 if ((n = find_move_exp(game[gindex], moveexp, n,
2746 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2747 == -1)
2748 break;
2750 game[gindex].hindex = n;
2751 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2752 update_all(game[gindex]);
2753 break;
2754 case 'v':
2755 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2756 break;
2757 case 'V':
2758 if (game[gindex].hindex - 1 >= 0)
2759 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2760 break;
2761 case '-':
2762 case '+':
2763 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2764 update_all(game[gindex]);
2765 break;
2766 case 'j':
2767 if (pgn_history_total(game[gindex].hp) < 2)
2768 break;
2770 /* FIXME field validation
2771 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2772 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2773 game[gindex].htotal)) == NULL)
2774 break;
2777 if (!keycount) {
2778 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2779 NULL, NULL, NULL, 0, -1)) == NULL)
2780 break;
2782 if (!isinteger(tmp))
2783 break;
2785 n = atoi(tmp);
2787 else
2788 n = keycount;
2790 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2791 break;
2793 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2794 pgn_board_update(&game[gindex], d->b,
2795 game[gindex].hindex);
2796 update_all(game[gindex]);
2797 break;
2798 default:
2799 break;
2803 static void free_userdata()
2805 int i;
2807 for (i = 0; i < gtotal; i++) {
2808 struct userdata_s *d;
2810 if (game[i].data) {
2811 d = game[i].data;
2813 if (d->engine) {
2814 stop_engine(&game[i]);
2815 free(d->engine);
2818 free(game[i].data);
2819 game[i].data = NULL;
2824 void update_loading_window()
2826 if (!loadingw) {
2827 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
2828 loadingp = new_panel(loadingw);
2829 wbkgd(loadingw, CP_MESSAGE_WINDOW);
2832 wmove(loadingw, 0, 0);
2833 wclrtobot(loadingw);
2834 wattron(loadingw, CP_MESSAGE_BORDER);
2835 box(loadingw, ACS_VLINE, ACS_HLINE);
2836 wattroff(loadingw, CP_MESSAGE_BORDER);
2837 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
2838 11 + strlen(itoa(gtotal))), "Loading... %i", gtotal);
2839 update_panels();
2840 doupdate();
2843 void init_userdata()
2845 int i;
2847 for (i = 0; i < gtotal; i++) {
2848 struct userdata_s *d = NULL;
2850 d = Calloc(1, sizeof(struct userdata_s));
2851 game[i].data = d;
2852 d->n = i;
2853 pgn_board_init(d->b);
2857 void fix_marks(int *start, int *end)
2859 int i;
2861 *start = (*start < 0) ? 0 : *start;
2862 *end = (*end < 0) ? 0 : *end;
2864 if (*start > *end) {
2865 i = *start;
2866 *start = *end;
2867 *end = i + 1;
2870 *end = (*end > gtotal) ? gtotal : *end;
2873 // Global and other keys.
2874 static int globalkeys(chtype c)
2876 static char gameexp[255] = {0};
2877 FILE *fp;
2878 char *tmp, *p;
2879 int n, i;
2880 char tfile[FILENAME_MAX];
2881 struct userdata_s *d = game[gindex].data;
2883 switch (c) {
2884 case 'W':
2885 toggle_engine_window();
2886 break;
2887 case KEY_F(10):
2888 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
2889 "and %i color pairs\nCopyright 2002-2006 %s",
2890 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
2891 COLOR_PAIRS, PACKAGE_BUGREPORT);
2892 break;
2893 case 'h':
2894 if (game[gindex].mode != MODE_HISTORY) {
2895 if (!pgn_history_total(game[gindex].hp) ||
2896 (d->engine && d->engine->status == ENGINE_THINKING))
2897 return 1;
2899 game[gindex].mode = MODE_HISTORY;
2900 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2901 update_all(game[gindex]);
2902 return 1;
2905 // FIXME Resuming from previous history could append to a RAV.
2906 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
2907 if (!pushkey) {
2908 if ((c = message(NULL, YESNO, "%s",
2909 GAME_RESUME_HISTORY_TEXT)) != 'y')
2910 return 1;
2913 else {
2914 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
2915 return 1;
2918 if (!TEST_FLAG(d->flags, CF_HUMAN)) {
2919 if (!d->engine) {
2920 if (start_chess_engine(&game[gindex]))
2921 return 1;
2924 add_engine_command(&game[gindex], ENGINE_READY, "setboard %s\n",
2925 pgn_game_to_fen(game[gindex], d->b));
2928 pushkey = 0;
2929 game[gindex].mode = MODE_PLAY;
2930 update_all(game[gindex]);
2931 return 1;
2932 case '>':
2933 case '<':
2934 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
2935 keycount : 1);
2936 d = game[gindex].data;
2938 if (delete_count) {
2939 if (c == '>') {
2940 markend = markstart + delete_count;
2941 delete_count = 0;
2943 else {
2944 markend = markstart - delete_count;
2945 delete_count = -1; // to fix gindex in the other direction
2948 pushkey = 'x';
2949 fix_marks(&markstart, &markend);
2952 if (game[gindex].mode != MODE_EDIT)
2953 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2955 update_status_notify(game[gindex], NULL);
2956 update_all(game[gindex]);
2957 return 1;
2958 case '}':
2959 case '{':
2960 case '?':
2961 if (gtotal < 2)
2962 return 1;
2964 if (!*gameexp || c == '?') {
2965 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
2966 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
2967 NULL, 0, -1)) == NULL)
2968 return 1;
2970 strncpy(gameexp, tmp, sizeof(gameexp));
2973 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
2974 ? keycount : 1)) ==
2976 return 1;
2978 gindex = n;
2979 d = game[gindex].data;
2981 if (pgn_history_total(game[gindex].hp))
2982 game[gindex].mode = MODE_HISTORY;
2984 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
2985 update_all(game[gindex]);
2986 return 1;
2987 case 'J':
2988 if (gtotal < 2)
2989 return 1;
2991 /* FIXME field validation
2992 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2993 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2994 == NULL)
2995 return 1;
2998 if (!keycount) {
2999 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
3000 NULL, NULL, 0, -1)) == NULL)
3001 return 1;
3003 if (!isinteger(tmp))
3004 return 1;
3006 i = atoi(tmp);
3008 else
3009 i = keycount;
3011 if (--i > gtotal - 1 || i < 0)
3012 return 1;
3014 gindex = i;
3015 d = game[gindex].data;
3016 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3017 update_status_notify(game[gindex], NULL);
3018 update_all(game[gindex]);
3019 return 1;
3020 case 'x':
3021 pushkey = 0;
3023 if (gtotal < 2)
3024 return 1;
3026 if (keycount && delete_count == 0) {
3027 markstart = gindex;
3028 delete_count = keycount;
3029 update_status_notify(game[gindex], "%s (delete)",
3030 status.notify);
3031 return 1;
3034 if (markstart >= 0 && markend >= 0) {
3035 for (i = markstart; i < markend; i++) {
3036 if (toggle_delete_flag(i)) {
3037 update_all(game[gindex]);
3038 return 1;
3042 gindex = (delete_count < 0) ? markstart : i - 1;
3043 update_all(game[gindex]);
3045 else {
3046 if (toggle_delete_flag(gindex))
3047 return 1;
3050 markstart = markend = -1;
3051 delete_count = 0;
3052 update_status_window(game[gindex]);
3053 return 1;
3054 case 'X':
3055 if (gtotal < 2) {
3056 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3057 return 1;
3060 tmp = NULL;
3062 for (i = n = 0; i < gtotal; i++) {
3063 if (TEST_FLAG(game[i].flags, GF_DELETE))
3064 n++;
3067 if (!n)
3068 tmp = GAME_DELETE_GAME_TEXT;
3069 else {
3070 if (n == gtotal) {
3071 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3072 return 1;
3075 tmp = GAME_DELETE_ALL_TEXT;
3078 if (config.deleteprompt) {
3079 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
3080 return 1;
3083 delete_game((!n) ? gindex : -1);
3085 if (pgn_history_total(game[gindex].hp))
3086 game[gindex].mode = MODE_HISTORY;
3088 d = game[gindex].data;
3089 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3090 update_all(game[gindex]);
3091 return 1;
3092 case 'T':
3093 edit_save_tags(&game[gindex]);
3094 update_all(game[gindex]);
3095 return 1;
3096 case 't':
3097 edit_tags(game[gindex], d->b, 0);
3098 return 1;
3099 case 'r':
3100 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
3101 BROWSER_PROMPT, file_browser, NULL, '\t',
3102 -1)) == NULL)
3103 return 1;
3105 if ((tmp = word_expand(tmp)) == NULL)
3106 break;
3108 if ((fp = pgn_open(tmp)) == NULL) {
3109 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3110 return 1;
3113 free_userdata();
3115 if (pgn_parse(fp)) {
3116 init_userdata();
3117 update_all(game[gindex]);
3118 return 1;
3121 del_panel(loadingp);
3122 delwin(loadingw);
3123 loadingw = NULL;
3124 loadingp = NULL;
3125 init_userdata();
3126 strncpy(loadfile, tmp, sizeof(loadfile));
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 'S':
3136 case 's':
3137 i = -1;
3139 if (gtotal > 1) {
3140 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
3141 GAME_SAVE_MULTI_TEXT);
3143 if (n == 'c')
3144 i = gindex;
3145 else if (n == 'a')
3146 i = -1;
3147 else {
3148 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3149 return 1;
3153 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
3154 BROWSER_PROMPT, file_browser, NULL,
3155 '\t', -1)) == NULL) {
3156 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3157 return 1;
3160 if ((tmp = word_expand(tmp)) == NULL)
3161 break;
3163 if (pgn_is_compressed(tmp)) {
3164 p = tmp + strlen(tmp) - 1;
3166 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3167 *(p-3) != '.') {
3168 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3169 tmp = tfile;
3172 else {
3173 if ((p = strchr(tmp, '.')) != NULL) {
3174 if (strcmp(p, ".pgn") != 0) {
3175 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3176 tmp = tfile;
3179 else {
3180 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3181 tmp = tfile;
3185 if (save_pgn(tmp, i)) {
3186 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3187 return 1;
3190 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3191 update_all(game[gindex]);
3192 return 1;
3193 case KEY_F(1):
3194 n = 0;
3196 switch (game[gindex].mode) {
3197 case MODE_PLAY:
3198 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3199 break;
3200 case MODE_HISTORY:
3201 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3202 break;
3203 case MODE_EDIT:
3204 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3205 break;
3206 default:
3207 break;
3210 while (c == KEY_F(1)) {
3211 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3212 mainhelp);
3214 switch (c) {
3215 case 'h':
3216 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3217 break;
3218 case 'p':
3219 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3220 break;
3221 case 'e':
3222 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3223 break;
3224 case 'g':
3225 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3226 break;
3227 default:
3228 break;
3232 return 1;
3233 case 'n':
3234 case 'N':
3235 if (c == 'N') {
3236 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3237 return 1;
3240 if (c == 'n') {
3241 pgn_new_game();
3242 add_custom_tags(&game[gindex].tag);
3243 d = Calloc(1, sizeof(struct userdata_s));
3244 pgn_board_init(d->b);
3245 game[gindex].data = d;
3247 else {
3248 free_userdata();
3249 pgn_parse(NULL);
3250 add_custom_tags(&game[gindex].tag);
3251 d = Calloc(1, sizeof(struct userdata_s));
3252 pgn_board_init(d->b);
3253 game[gindex].data = d;
3254 loadfile[0] = 0;
3257 game[gindex].mode = MODE_PLAY;
3258 d->c_row = (game[gindex].side == WHITE) ? 2 : 7;
3259 d->c_col = 4;
3260 update_status_notify(game[gindex], NULL);
3261 update_all(game[gindex]);
3262 return 1;
3263 case CTRL('L'):
3264 endwin();
3265 keypad(boardw, TRUE);
3266 refresh_all();
3267 return 1;
3268 case KEY_ESCAPE:
3269 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3270 markend = markstart = 0;
3272 if (keycount) {
3273 keycount = 0;
3274 update_status_notify(game[gindex], NULL);
3277 if (config.validmoves)
3278 pgn_reset_valid_moves(d->b);
3280 return 1;
3281 case '0' ... '9':
3282 n = c - '0';
3284 if (keycount)
3285 keycount = keycount * 10 + n;
3286 else
3287 keycount = n;
3289 update_status_notify(game[gindex], "Repeat %i", keycount);
3290 return -1;
3291 case KEY_UP:
3292 if (game[gindex].mode == MODE_HISTORY)
3293 return 0;
3295 if (keycount) {
3296 d->c_row += keycount;
3297 pushkey = '\n';
3299 else
3300 d->c_row++;
3302 if (d->c_row > 8)
3303 d->c_row = 1;
3305 return 1;
3306 case KEY_DOWN:
3307 if (game[gindex].mode == MODE_HISTORY)
3308 return 0;
3310 if (keycount) {
3311 d->c_row -= keycount;
3312 pushkey = '\n';
3313 update_status_notify(game[gindex], NULL);
3315 else
3316 d->c_row--;
3318 if (d->c_row < 1)
3319 d->c_row = 8;
3321 return 1;
3322 case KEY_LEFT:
3323 if (game[gindex].mode == MODE_HISTORY)
3324 return 0;
3326 if (keycount) {
3327 d->c_col -= keycount;
3328 pushkey = '\n';
3330 else
3331 d->c_col--;
3333 if (d->c_col < 1)
3334 d->c_col = 8;
3336 return 1;
3337 case KEY_RIGHT:
3338 if (game[gindex].mode == MODE_HISTORY)
3339 return 0;
3341 if (keycount) {
3342 d->c_col += keycount;
3343 pushkey = '\n';
3345 else
3346 d->c_col++;
3348 if (d->c_col > 8)
3349 d->c_col = 1;
3351 return 1;
3352 case 'e':
3353 if (game[gindex].mode != MODE_EDIT && game[gindex].mode !=
3354 MODE_PLAY)
3355 return 1;
3357 // Don't edit a running game (for now).
3358 if (pgn_history_total(game[gindex].hp))
3359 return 1;
3361 if (game[gindex].mode != MODE_EDIT) {
3362 pgn_board_init_fen(&game[gindex], d->b, NULL);
3363 board_details++;
3364 game[gindex].mode = MODE_EDIT;
3365 update_all(game[gindex]);
3366 return 1;
3369 board_details--;
3370 pgn_tag_add(&game[gindex].tag, "FEN",
3371 pgn_game_to_fen(game[gindex], d->b));
3372 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3373 pgn_tag_sort(game[gindex].tag);
3374 game[gindex].mode = MODE_PLAY;
3375 update_all(game[gindex]);
3376 return 1;
3377 case 'Q':
3378 quit = 1;
3379 return 1;
3380 case KEY_RESIZE:
3381 do_window_resize();
3382 return 1;
3383 #ifdef DEBUG
3384 case 'D':
3385 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3386 return 1;
3387 #endif
3388 case 0:
3389 default:
3390 break;
3393 return 0;
3396 void game_loop()
3398 int error_recover = 0;
3399 struct userdata_s *d = game[gindex].data;
3401 d->c_row = 2, d->c_col = 5;
3402 gindex = gtotal - 1;
3404 if (pgn_history_total(game[gindex].hp))
3405 game[gindex].mode = MODE_HISTORY;
3406 else
3407 game[gindex].mode = MODE_PLAY;
3409 if (game[gindex].mode == MODE_HISTORY) {
3410 pgn_board_update(&game[gindex], d->b,
3411 pgn_history_total(game[gindex].hp));
3414 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3415 movestep = 2;
3416 paused = 1; //FIXME clock
3417 flushinp();
3418 update_all(game[gindex]);
3419 update_tag_window(game[gindex].tag);
3420 wtimeout(boardw, 70);
3422 while (!quit) {
3423 int c = 0;
3424 int n = 0, i;
3425 char fdbuf[8192] = {0};
3426 int len;
3427 struct timeval tv = {0, 0};
3428 fd_set rfds;
3430 FD_ZERO(&rfds);
3432 for (i = 0; i < gtotal; i++) {
3433 d = game[i].data;
3435 if (d->engine) {
3436 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3437 if (d->engine->fd[ENGINE_IN_FD] > n)
3438 n = d->engine->fd[ENGINE_IN_FD];
3440 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3445 if (n) {
3446 if ((n = select(n + 1, &rfds, NULL, NULL, &tv)) > 0) {
3447 for (i = 0; i < gtotal; i++) {
3448 d = game[i].data;
3450 if (d->engine) {
3451 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3452 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3453 sizeof(fdbuf));
3455 if (len == -1) {
3456 if (errno != EAGAIN) {
3457 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3458 strerror(errno));
3459 waitpid(d->engine->pid, &n, 0);
3460 free(d->engine);
3461 d->engine = NULL;
3462 break;
3465 else {
3466 if (len)
3467 parse_engine_output(&game[i], fdbuf);
3473 else {
3474 if (n == -1)
3475 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3476 /* timeout */
3480 for (i = 0; i < gtotal; i++) {
3481 d = game[i].data;
3483 if (d->engine && d->engine->queue)
3484 send_engine_command(&game[i]);
3487 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER) && game[gindex].mode
3488 != MODE_HISTORY)
3489 game[gindex].mode = MODE_HISTORY;
3491 d = game[gindex].data;
3492 error_recover = 0;
3493 draw_board(&game[gindex], board_details);
3494 update_all(game[gindex]);
3495 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3497 if (!paused) {
3500 refresh_all();
3502 if (pushkey)
3503 c = pushkey;
3504 else {
3505 if ((c = wgetch(boardw)) == ERR)
3506 continue;
3509 if (!keycount && status.notify)
3510 update_status_notify(game[gindex], NULL);
3512 if ((n = globalkeys(c)) == 1) {
3513 keycount = 0;
3514 continue;
3516 else if (n == -1)
3517 continue;
3519 switch (game[gindex].mode) {
3520 case MODE_EDIT:
3521 editmode_keys(c);
3522 break;
3523 case MODE_PLAY:
3524 if (playmode_keys(c))
3525 continue;
3526 break;
3527 case MODE_HISTORY:
3528 historymode_keys(c);
3529 break;
3530 default:
3531 break;
3534 keycount = 0;
3538 void usage(const char *pn, int ret)
3540 fprintf((ret) ? stderr : stdout, "%s",
3541 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3542 " -p Load PGN file.\n"
3543 " -V Validate a game file.\n"
3544 " -S Validate and output a PGN formatted game.\n"
3545 " -R Like -S but write a reduced PGN formatted game.\n"
3546 " -t Also write custom PGN tags from config file.\n"
3547 " -E Stop processing on file parsing error (overrides config).\n"
3548 " -v Version information.\n"
3549 " -h This help text.\n");
3551 exit(ret);
3554 void cleanup_all()
3556 int i;
3558 free_userdata();
3559 pgn_free_all();
3560 free(config.engine_cmd);
3561 free(config.pattern);
3563 if (config.einit) {
3564 for (i = 0; config.einit[i]; i++)
3565 free(config.einit[i]);
3567 free(config.einit);
3570 if (config.tag)
3571 pgn_tag_free(config.tag);
3573 if (curses_initialized) {
3574 del_panel(boardp);
3575 del_panel(historyp);
3576 del_panel(statusp);
3577 del_panel(tagp);
3578 delwin(boardw);
3579 delwin(historyw);
3580 delwin(statusw);
3581 delwin(tagw);
3583 if (enginew) {
3584 del_panel(enginep);
3585 delwin(enginew);
3587 if (enginebuf) {
3588 for (i = 0; enginebuf[i]; i++)
3589 free(enginebuf[i]);
3591 free(enginebuf);
3595 endwin();
3599 void catch_signal(int which)
3601 switch (which) {
3602 case SIGINT:
3603 case SIGPIPE:
3604 if (which == SIGPIPE && quit)
3605 break;
3607 if (which == SIGPIPE)
3608 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3610 cleanup_all();
3611 exit(EXIT_FAILURE);
3612 break;
3613 case SIGSTOP:
3614 savetty();
3615 break;
3616 case SIGCONT:
3617 resetty();
3618 keypad(boardw, TRUE);
3619 curs_set(0);
3620 cbreak();
3621 noecho();
3622 break;
3623 case SIGUSR1:
3624 if (curses_initialized) {
3625 update_loading_window(game[gindex]);
3626 break;
3629 fprintf(stderr, "Loading... %i\r", gtotal);
3630 fflush(stderr);
3631 break;
3632 default:
3633 break;
3637 static void set_defaults()
3639 filetype = NO_FILE;
3640 set_config_defaults();
3643 int main(int argc, char *argv[])
3645 int opt;
3646 struct stat st;
3647 char buf[FILENAME_MAX];
3648 char datadir[FILENAME_MAX];
3649 int ret = EXIT_SUCCESS;
3650 int validate_only = 0, validate_and_write = 0;
3651 int write_custom_tags = 0;
3652 FILE *fp;
3653 int i;
3655 if ((config.pwd = getpwuid(getuid())) == NULL)
3656 err(EXIT_FAILURE, "getpwuid()");
3658 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3659 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3660 config.ccfile = strdup(buf);
3661 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3662 config.nagfile = strdup(buf);
3663 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
3664 config.agonyfile = strdup(buf);
3665 snprintf(buf, sizeof(buf), "%s/config", datadir);
3666 config.configfile = strdup(buf);
3668 if (stat(datadir, &st) == -1) {
3669 if (errno == ENOENT) {
3670 if (mkdir(datadir, 0755) == -1)
3671 err(EXIT_FAILURE, "%s", datadir);
3673 else
3674 err(EXIT_FAILURE, "%s", datadir);
3676 stat(datadir, &st);
3679 if (!S_ISDIR(st.st_mode))
3680 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3682 set_defaults();
3684 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3685 switch (opt) {
3686 case 't':
3687 write_custom_tags = 1;
3688 break;
3689 case 'E':
3690 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3691 break;
3692 case 'R':
3693 pgn_config_set(PGN_REDUCED, 1);
3694 case 'S':
3695 validate_and_write = 1;
3696 case 'V':
3697 validate_only = 1;
3698 break;
3699 case 'v':
3700 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3701 COPYRIGHT);
3702 exit(EXIT_SUCCESS);
3703 case 'p':
3704 filetype = PGN_FILE;
3705 strncpy(loadfile, optarg, sizeof(loadfile));
3706 break;
3707 case 'h':
3708 default:
3709 usage(argv[0], EXIT_SUCCESS);
3713 if ((validate_only || validate_and_write) && !*loadfile)
3714 usage(argv[0], EXIT_FAILURE);
3716 if (access(config.configfile, R_OK) == 0)
3717 parse_rcfile(config.configfile);
3719 signal(SIGPIPE, catch_signal);
3720 signal(SIGCONT, catch_signal);
3721 signal(SIGSTOP, catch_signal);
3722 signal(SIGINT, catch_signal);
3723 signal(SIGUSR1, catch_signal);
3725 srandom(getpid());
3727 switch (filetype) {
3728 case PGN_FILE:
3729 if ((fp = pgn_open(loadfile)) == NULL)
3730 err(EXIT_FAILURE, "%s", loadfile);
3732 ret = pgn_parse(fp);
3733 break;
3734 case FEN_FILE:
3735 //ret = parse_fen_file(loadfile);
3736 break;
3737 case EPD_FILE: // Not implemented.
3738 case NO_FILE:
3739 default:
3740 // No file specified. Empty game.
3741 ret = pgn_parse(NULL);
3742 add_custom_tags(&game[gindex].tag);
3743 break;
3746 if (validate_only || validate_and_write) {
3747 if (validate_and_write) {
3748 for (i = 0; i < gtotal; i++) {
3749 if (write_custom_tags)
3750 add_custom_tags(&game[i].tag);
3752 pgn_write(stdout, game[i]);
3756 cleanup_all();
3757 exit(ret);
3759 else if (ret)
3760 exit(ret);
3762 init_userdata();
3764 if (initscr() == NULL)
3765 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3766 else
3767 curses_initialized = 1;
3769 if (LINES < 24 || COLS < 80) {
3770 endwin();
3771 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3774 if (has_colors() == TRUE && start_color() == OK)
3775 init_color_pairs();
3777 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3778 boardp = new_panel(boardw);
3779 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3780 COLS - HISTORY_WIDTH);
3781 historyp = new_panel(historyw);
3782 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3783 statusp = new_panel(statusw);
3784 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3785 tagp = new_panel(tagw);
3786 keypad(boardw, TRUE);
3787 // leaveok(boardw, TRUE);
3788 leaveok(tagw, TRUE);
3789 leaveok(statusw, TRUE);
3790 leaveok(historyw, TRUE);
3791 curs_set(0);
3792 cbreak();
3793 noecho();
3794 draw_window_decor();
3795 game_loop();
3796 cleanup_all();
3797 exit(EXIT_SUCCESS);