libchess: Removed GAME.mode.
[cboard.git] / src / cboard.c
blob4df2ff0b3ca738cb44d783f5e078b899226a4d79
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 if (selected == (LINES / 5) * 4 - 4)
230 toppos = 1;
231 else if (selected <= rows)
232 toppos = 0;
233 else
234 toppos = selected - rows + 1;
236 if (toppos < 0)
237 toppos = 0;
238 break;
241 *item = selected;
242 *top = toppos;
245 int test_nag_selected(unsigned char nag[], int s)
247 int i;
249 for (i = 0; i < MAX_PGN_NAG; i++) {
250 if (nag[i] == s)
251 return i;
254 return -1;
257 char *history_edit_nag(void *arg)
259 WINDOW *win;
260 PANEL *panel;
261 int i = 0, n;
262 int itemcount = 0;
263 int rows, cols;
264 HISTORY *anno = (HISTORY *)arg;
265 int selected = 0;
266 int toppos = 0;
267 int len = 0;
268 int total = 0;
269 unsigned char nag[MAX_PGN_NAG] = {0};
270 char menubuf[64] = {0}, *mp = menubuf;
272 if (!nags) {
273 if (init_nag())
274 return NULL;
277 for (i = 1, n = 0; nags[i]; i++) {
278 n = strlen(nags[i]);
280 if (len < n)
281 len = n;
284 total = i;
285 cols = len + 2;
286 rows = (total + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : total + 4;
288 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
289 panel = new_panel(win);
290 cbreak();
291 noecho();
292 keypad(win, TRUE);
293 nl();
294 wbkgd(win, CP_MENU);
295 memcpy(&nag, &anno->nag, sizeof(nag));
297 for (i = 0; i < MAX_PGN_NAG; i++) {
298 if (nag[i])
299 itemcount++;
302 while (1) {
303 int c;
304 char buf[cols - 4];
306 wmove(win, 0, 0);
307 wclrtobot(win);
308 draw_window_title(win, NAG_EDIT_TITLE, cols, CP_INPUT_TITLE,
309 CP_INPUT_BORDER);
311 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
312 if (i == selected) {
313 wattron(win, CP_MENU_HIGHLIGHT);
314 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
315 wattroff(win, CP_MENU_HIGHLIGHT);
316 continue;
319 if (test_nag_selected(nag, i) != -1) {
320 wattron(win, CP_MENU_SELECTED);
321 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
322 wattroff(win, CP_MENU_SELECTED);
323 continue;
326 mvwprintw(win, c, 1, "%s", (nags[i]) ? nags[i] : "none");
329 snprintf(buf, sizeof(buf), "NAG %i of %i (%i of %i selected) %s",
330 selected + 1, total, itemcount, MAX_PGN_NAG, NAG_EDIT_PROMPT);
331 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
333 nl();
334 refresh_all();
335 c = wgetch(win);
337 switch (c) {
338 int found;
340 case KEY_F(1):
341 help(NAG_EDIT_HELP, ANYKEY, naghelp);
342 break;
343 case KEY_HOME:
344 case KEY_END:
345 case KEY_UP:
346 case KEY_DOWN:
347 case KEY_PPAGE:
348 case KEY_NPAGE:
349 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
350 menubuf[0] = 0;
351 mp = menubuf;
352 break;
353 case ' ':
354 if (selected == 0) {
355 for (i = 0; i < MAX_PGN_NAG; i++)
356 nag[i] = 0;
358 itemcount = 0;
359 break;
362 if ((found = test_nag_selected(nag, selected)) != -1) {
363 nag[found] = 0;
364 itemcount--;
366 else {
367 if (itemcount + 1 > MAX_PGN_NAG)
368 break;
370 for (i = 0; i < MAX_PGN_NAG; i++) {
371 if (nag[i] == 0) {
372 nag[i] = selected;
373 break;
377 itemcount++;
380 break;
381 case '\n':
382 goto done;
383 break;
384 case KEY_ESCAPE:
385 goto done;
386 break;
387 default:
388 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
389 menubuf[0] = 0;
390 mp = menubuf;
393 *mp++ = c;
394 *mp = 0;
395 n = selected;
397 for (i = 0; i < total; i++) {
398 if (!nags[i])
399 continue;
401 if (strncasecmp(menubuf, nags[i], strlen(menubuf)) == 0) {
402 selected = i;
403 break;
407 if (n == selected) {
408 menubuf[0] = 0;
409 mp = menubuf;
412 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
413 break;
417 done:
418 memcpy(&anno->nag, &nag, sizeof(nag));
419 del_panel(panel);
420 delwin(win);
421 return NULL;
424 static void view_nag(void *arg)
426 HISTORY *h = (HISTORY *)arg;
427 char buf[80];
428 char line[LINE_MAX] = {0};
429 int i = 0;
431 snprintf(buf, sizeof(buf), "Viewing NAG for \"%s\"", h->move);
433 if (!nags) {
434 if (init_nag())
435 return;
438 for (i = 0; i < MAX_PGN_NAG; i++) {
439 if (!h->nag[i])
440 break;
442 strncat(line, nags[h->nag[i]], sizeof(line));
443 strncat(line, "\n", sizeof(line));
446 line[strlen(line) - 1] = 0;
447 message(buf, ANYKEY, "%s", line);
450 void view_annotation(HISTORY h)
452 char buf[strlen(h.move) + strlen(ANNOTATION_VIEW_TITLE) + 4];
453 int nag = 0, comment = 0;
455 if (h.comment && h.comment[0])
456 comment++;
458 if (h.nag[0])
459 nag++;
461 if (!nag && !comment)
462 return;
464 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_VIEW_TITLE, h.move);
466 if (comment)
467 show_message(buf, (nag) ? "Any other key to continue" : ANYKEY,
468 (nag) ? "Press 'n' to view NAG" : NULL,
469 (nag) ? view_nag : NULL, (nag) ? (void *)&h : NULL,
470 (nag) ? 'n' : 0, "%s", h.comment);
471 else
472 show_message(buf, "Any other key to continue", "Press 'n' to view NAG",
473 view_nag, (void *)&h, 'n', "%s", "No annotations for this move");
476 static void cleanup(WINDOW *win, PANEL *panel, struct file_s *files)
478 int i;
480 if (files) {
481 for (i = 0; files[i].name; i++) {
482 free(files[i].path);
483 free(files[i].name);
484 free(files[i].st);
487 free(files);
490 del_panel(panel);
491 delwin(win);
494 static int sort_files(const void *a, const void *b)
496 const struct file_s *aa = a;
497 const struct file_s *bb = b;
499 return strcmp(aa->name, bb->name);
502 char *file_browser(void *arg)
504 char pattern[FILENAME_MAX];
505 static char path[FILENAME_MAX];
506 static char file[FILENAME_MAX];
507 struct stat st;
508 char *p;
509 int cursor = curs_set(0);
510 char menubuf[64] = {0}, *mp = menubuf;
512 if (!*path) {
513 if (config.savedirectory) {
514 if ((p = word_expand(config.savedirectory)) == NULL)
515 return NULL;
517 strncpy(path, p, sizeof(path));
519 if (access(path, R_OK) == -1) {
520 cmessage(ERROR, ANYKEY, "%s: %s", path, strerror(errno));
521 getcwd(path, sizeof(path));
524 else
525 getcwd(path, sizeof(path));
528 again:
530 * First find directories (including hidden) in the working directory.
531 * Then apply the config.pattern to regular files.
533 if ((p = word_split_append(path, '/', ".* *")) == NULL)
534 return NULL;
536 strncpy(pattern, p, sizeof(pattern));
538 while (1) {
539 WINDOW *win;
540 PANEL *panel;
541 char *tmp = NULL;
542 int rows, cols = 0;
543 int selected = 0;
544 int toppos = 0;
545 int len = strlen(path);
546 wordexp_t w;
547 int i, n = 0;
548 struct file_s *files = NULL;
549 int which = 1;
550 int x = WRDE_NOCMD;
551 int nlen = 0;
553 new_we:
554 if (wordexp(pattern, &w, x) != 0) {
555 cmessage(ERROR, ANYKEY, "Error in pattern\n%s", pattern);
556 return NULL;
559 for (i = 0; i < w.we_wordc; i++) {
560 struct tm *tp;
561 char tbuf[16];
562 char sbuf[64];
564 if (stat(w.we_wordv[i], &st) == -1)
565 continue;
567 if ((p = strrchr(w.we_wordv[i], '/')) != NULL)
568 p++;
569 else
570 p = w.we_wordv[i];
572 if (which) {
573 if (!S_ISDIR(st.st_mode))
574 continue;
576 if (p[0] == '.' && p[1] == 0)
577 continue;
579 else {
580 if (S_ISDIR(st.st_mode))
581 continue;
584 len = strlen(p) + 2;
585 files = Realloc(files, (n + 2) * sizeof(struct file_s));
586 files[n].path = strdup(w.we_wordv[i]);
587 files[n].name = Malloc(len);
588 strncpy(files[n].name, p, len);
590 if (S_ISDIR(st.st_mode))
591 files[n].name[len - 2] = '/';
593 tp = localtime(&st.st_mtime);
594 strftime(tbuf, sizeof(tbuf), "%b %d %T", tp);
595 snprintf(sbuf, sizeof(sbuf), "%9i %s", (int)st.st_size, tbuf);
596 files[n].st = strdup(sbuf);
597 memset(&files[++n], '\0', sizeof(struct file_s));
600 which--;
602 if (which == 0) {
603 if ((p = word_split_append(path, '/', config.pattern)) == NULL)
604 return NULL;
606 strncpy(pattern, p, sizeof(pattern));
607 x |= WRDE_REUSE;
608 goto new_we;
611 wordfree(&w);
612 qsort(files, n, sizeof(struct file_s), sort_files);
614 for (i = x = nlen = 0; i < n; i++) {
615 if (strlen(files[i].name) > nlen)
616 nlen = strlen(files[i].name);
618 if (x < nlen + strlen(files[i].st))
619 x = nlen + strlen(files[i].st);
622 cols = x + 1;
624 if (cols < strlen(path))
625 cols = strlen(path);
627 if (cols < strlen(HELP_PROMPT))
628 cols = strlen(HELP_PROMPT);
630 if (cols > COLS)
631 cols = COLS - 4;
633 cols += 2;
634 rows = (n + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : n + 4;
636 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
637 wbkgd(win, CP_MENU);
638 panel = new_panel(win);
639 draw_window_title(win, path, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
640 draw_prompt(win, rows - 2, cols, HELP_PROMPT, CP_INPUT_PROMPT);
641 cbreak();
642 noecho();
643 keypad(win, TRUE);
644 nl();
646 while (1) {
647 int c;
649 for (i = toppos, c = 2; i < n && c < rows - 2; i++, c++) {
650 if (i == selected) {
651 wattron(win, CP_MENU_HIGHLIGHT);
652 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
653 cols - nlen - 2 - 1, files[i].st);
654 wattroff(win, CP_MENU_HIGHLIGHT);
655 continue;
658 mvwprintw(win, c, 1, "%-*s %-*s", nlen, files[i].name,
659 cols - nlen - 2 - 1, files[i].st);
662 refresh_all();
663 c = wgetch(win);
665 switch (c) {
666 case KEY_HOME:
667 case KEY_END:
668 case KEY_UP:
669 case KEY_DOWN:
670 case KEY_PPAGE:
671 case KEY_NPAGE:
672 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
673 menubuf[0] = 0;
674 mp = menubuf;
675 break;
676 case '\n':
677 goto gotitem;
678 break;
679 case KEY_ESCAPE:
680 cleanup(win, panel, files);
681 file[0] = 0;
682 goto done;
683 break;
684 case KEY_F(1):
685 help(BROWSER_HELP, ANYKEY, file_browser_help);
686 break;
687 case '~':
688 strncpy(path, "~", sizeof(path));
689 cleanup(win, panel, files);
690 goto again;
691 break;
692 case CTRL('X'):
693 if ((tmp = get_input_str_clear(BROWSER_CHDIR_TITLE, NULL))
694 == NULL)
695 break;
697 if (tmp[strlen(tmp) - 1] == '/')
698 tmp[strlen(tmp) - 1] = 0;
700 strncpy(path, tmp, sizeof(path));
701 cleanup(win, panel, files);
702 goto again;
703 break;
704 default:
705 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
706 menubuf[0] = 0;
707 mp = menubuf;
710 *mp++ = c;
711 *mp = 0;
712 x = selected;
714 for (i = 0; i < n; i++) {
715 if (strncasecmp(menubuf, files[i].name,
716 strlen(menubuf)) == 0) {
717 selected = i;
718 break;
722 if (x == selected) {
723 menubuf[0] = 0;
724 mp = menubuf;
727 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
728 break;
732 gotitem:
733 menubuf[0] = 0;
734 mp = menubuf;
735 strncpy(file, files[selected].path, sizeof(file));
736 cleanup(win, panel, files);
738 if (stat(file, &st) == -1) {
739 cmessage(ERROR, ANYKEY, "%s\n%s", file, strerror(errno));
740 continue;
743 if (S_ISDIR(st.st_mode)) {
744 p = file + strlen(file) - 2;
746 if (strcmp(p, "..") == 0) {
747 p = file + strlen(file) - 3;
748 *p = 0;
750 if ((p = strrchr(file, '/')) != NULL)
751 file[strlen(file) - strlen(p)] = 0;
754 strncpy(path, file, sizeof(path));
755 goto again;
758 if (S_ISREG(st.st_mode))
759 break;
761 cmessage(ERROR, ANYKEY, "%s\n%s", file, E_NOTAREGFILE);
764 done:
765 curs_set(cursor);
766 return (*file) ? file : NULL;
769 static int init_country_codes()
771 FILE *fp;
772 char line[LINE_MAX], *s;
773 int cindex = 0;
775 if ((fp = fopen(config.ccfile, "r")) == NULL) {
776 cmessage(ERROR, ANYKEY, "%s: %s", config.ccfile, strerror(errno));
777 return 1;
780 while ((s = fgets(line, sizeof(line), fp)) != NULL) {
781 char *tmp;
783 if ((tmp = strsep(&s, " ")) == NULL)
784 continue;
786 s = trim(s);
787 tmp = trim(tmp);
789 if (!s || !tmp)
790 continue;
792 ccodes = Realloc(ccodes, (cindex + 2) * sizeof(struct country_codes));
793 strncpy(ccodes[cindex].code, tmp, sizeof(ccodes[cindex].code));
794 strncpy(ccodes[cindex].country, s, sizeof(ccodes[cindex].country));
795 cindex++;
798 memset(&ccodes[cindex], '\0', sizeof(struct country_codes));
799 fclose(fp);
801 return 0;
804 char *country_codes(void *arg)
806 WINDOW *win;
807 PANEL *panel;
808 int i = 0, n;
809 int rows, cols;
810 char *tmp = NULL;
811 int len = 0;
812 int total;
813 int selected = 0;
814 int toppos = 0;
815 char menubuf[64] = {0}, *mp = menubuf;
817 if (!ccodes) {
818 if (init_country_codes())
819 return NULL;
822 for (i = n = 0; ccodes[i].code && ccodes[i].code[0]; i++) {
823 n = strlen(ccodes[i].code) + strlen(ccodes[i].country);
825 if (len < n)
826 len = n;
829 total = i;
830 cols = len;
832 if (cols < strlen(HELP_PROMPT) + 21)
833 cols = strlen(HELP_PROMPT) + 21;
835 cols += 1;
837 if (cols > COLS)
838 cols = COLS - 4;
840 cols += 2;
841 rows = (i + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 : i + 4;
842 win = newwin(rows, cols, CALCPOSY(rows) - 2, CALCPOSX(cols));
843 panel = new_panel(win);
844 cbreak();
845 noecho();
846 keypad(win, TRUE);
847 nl();
848 wbkgd(win, CP_MENU);
850 while (1) {
851 int c;
852 char buf[cols - 4];
854 wmove(win, 0, 0);
855 wclrtobot(win);
857 draw_window_title(win, CC_TITLE, cols, CP_INPUT_TITLE, CP_INPUT_BORDER);
859 for (i = toppos, c = 2; i < total && c < rows - 2; i++, c++) {
860 if (i == selected) {
861 wattron(win, CP_MENU_HIGHLIGHT);
862 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code,
863 ccodes[i].country);
864 wattroff(win, CP_MENU_HIGHLIGHT);
865 continue;
868 mvwprintw(win, c, 1, "%3s %s", ccodes[i].code, ccodes[i].country);
871 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_ITEM_STR,
872 selected + 1, N_OF_N_STR, total, HELP_PROMPT);
873 draw_prompt(win, rows - 2, cols, buf, CP_INPUT_PROMPT);
874 refresh_all();
875 c = wgetch(win);
877 switch (c) {
878 case KEY_F(1):
879 help(CC_KEY_HELP, ANYKEY, cc_help);
880 break;
881 case KEY_HOME:
882 case KEY_END:
883 case KEY_UP:
884 case KEY_DOWN:
885 case KEY_PPAGE:
886 case KEY_NPAGE:
887 set_menu_vars(c, rows - 4, total - 1, &selected, &toppos);
888 menubuf[0] = 0;
889 mp = menubuf;
890 break;
891 case '\n':
892 tmp = ccodes[selected].code;
893 goto done;
894 break;
895 case KEY_ESCAPE:
896 tmp = NULL;
897 goto done;
898 break;
899 default:
900 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
901 menubuf[0] = 0;
902 mp = menubuf;
905 *mp++ = c;
906 *mp = 0;
907 n = selected;
909 for (i = 0; i < total; i++) {
910 if (strncasecmp(menubuf, ccodes[i].code,
911 strlen(menubuf)) == 0) {
912 selected = i;
913 break;
917 if (n == selected) {
918 menubuf[0] = 0;
919 mp = menubuf;
922 set_menu_vars(c, rows - 4, n - 1, &selected, &toppos);
923 break;
927 done:
928 del_panel(panel);
929 delwin(win);
930 return tmp;
933 static void add_custom_tags(TAG ***t)
935 int i;
936 int total = pgn_tag_total(config.tag);
938 if (!config.tag)
939 return;
941 for (i = 0; i < total; i++)
942 pgn_tag_add(t, config.tag[i]->name, config.tag[i]->value);
944 pgn_tag_sort(*t);
947 TAG **edit_tags(GAME g, BOARD b, int edit)
949 TAG **data = NULL;
950 struct tm tp;
951 int data_index = 0;
952 int len;
953 int selected = 0;
954 int n;
955 int toppos = 0;
956 char menubuf[64] = {0}, *mp = menubuf;
958 /* Edit the backup copy, not the original in case the save fails. */
959 len = pgn_tag_total(g.tag);
961 for (n = 0; n < len; n++)
962 pgn_tag_add(&data, g.tag[n]->name, g.tag[n]->value);
964 data_index = pgn_tag_total(data);
966 while (1) {
967 WINDOW *win;
968 PANEL *panel;
969 int i;
970 char buf[76] = {0};
971 char *tmp = NULL;
972 int rows, cols;
973 int nlen = 0;
975 data_index = pgn_tag_total(data);
977 for (i = cols = 0, n = 4; i < data_index; i++) {
978 n = strlen(data[i]->name);
980 if (nlen < n)
981 nlen = n;
983 if (data[i]->value)
984 n += strlen(data[i]->value);
985 else
986 n += strlen(UNKNOWN);
988 if (cols < n)
989 cols = n;
992 cols += nlen + 2;
994 if (cols > COLS)
995 cols = COLS - 2;
997 /* +14 for the extra prompt info. */
998 if (cols < strlen(HELP_PROMPT) + 14 + 2)
999 cols = strlen(HELP_PROMPT) + 14 + 2;
1001 rows = (data_index + 4 > (LINES / 5) * 4) ? (LINES / 5) * 4 :
1002 data_index + 4;
1004 win = newwin(rows, cols, CALCPOSY(rows), CALCPOSX(cols));
1005 panel = new_panel(win);
1006 cbreak();
1007 noecho();
1008 keypad(win, TRUE);
1009 nl();
1010 wbkgd(win, CP_MENU);
1011 draw_window_title(win, (edit) ? TAG_EDIT_TITLE : TAG_VIEW_TITLE,
1012 cols, (edit) ? CP_INPUT_TITLE : CP_MESSAGE_TITLE,
1013 (edit) ? CP_INPUT_BORDER : CP_MESSAGE_BORDER);
1015 if (selected >= data_index - 1)
1016 selected = data_index - 1;
1018 while (1) {
1019 int c;
1020 TAG **tmppgn = NULL;
1021 char *newtag = NULL;
1023 for (i = toppos, c = 2; i < data_index && c < rows - 2; i++, c++) {
1024 if (i == selected) {
1025 wattron(win, CP_MENU_HIGHLIGHT);
1026 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1027 cols - nlen - 2 - 2, (data[i]->value &&
1028 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1029 wattroff(win, CP_MENU_HIGHLIGHT);
1030 continue;
1033 mvwprintw(win, c, 1, "%*s: %-*s", nlen, data[i]->name,
1034 cols - nlen - 2 - 2, (data[i]->value &&
1035 data[i]->value[0]) ? data[i]->value : UNKNOWN);
1038 snprintf(buf, sizeof(buf), "%s %i %s %i %s", MENU_TAG_STR,
1039 selected + 1, N_OF_N_STR, data_index, HELP_PROMPT);
1040 draw_prompt(win, rows - 2, cols, buf,
1041 (edit) ? CP_INPUT_PROMPT : CP_MESSAGE_PROMPT);
1042 refresh_all();
1043 c = wgetch(win);
1045 switch (c) {
1046 case CTRL('T'):
1047 if (!edit)
1048 break;
1050 add_custom_tags(&data);
1051 selected = data_index - 1;
1052 toppos = data_index - (rows - 4);
1053 goto cleanup;
1054 break;
1055 case KEY_F(1):
1056 if (edit)
1057 help(TAG_EDIT_HELP, ANYKEY, pgn_edit_help);
1058 else
1059 help(TAG_VIEW_HELP, ANYKEY, pgn_info_help);
1060 break;
1061 case CTRL('R'):
1062 if (!edit)
1063 break;
1065 if (selected <= 6) {
1066 cmessage(NULL, ANYKEY, "%s", E_REMOVE_STR);
1067 goto cleanup;
1070 data_index = pgn_tag_total(data);
1072 for (i = 0; i < data_index; i++) {
1073 if (i == selected)
1074 continue;
1076 pgn_tag_add(&tmppgn, data[i]->name, data[i]->value);
1079 pgn_tag_free(data);
1080 data = NULL;
1082 for (i = 0; tmppgn[i]; i++)
1083 pgn_tag_add(&data, tmppgn[i]->name, tmppgn[i]->value);
1085 pgn_tag_free(tmppgn);
1087 if (selected >= data_index)
1088 selected = data_index - 1;
1089 else {
1090 if (selected > rows - 4)
1091 toppos = selected - (rows - 4);
1092 else
1093 toppos -= (toppos) ? 1 : 0;
1096 goto cleanup;
1097 break;
1098 case CTRL('A'):
1099 if (!edit)
1100 break;
1102 if ((newtag = get_input(TAG_NEW_TITLE, NULL, 1, 1, NULL,
1103 NULL, NULL, 0, FIELD_TYPE_PGN_TAG_NAME))
1104 == NULL)
1105 break;
1107 newtag[0] = toupper(newtag[0]);
1109 if (strlen(newtag) > MAX_VALUE_WIDTH - 6 -
1110 strlen(PRESS_ENTER)) {
1111 cmessage(ERROR, ANYKEY, "%s", E_TAG_NAMETOOLONG);
1112 break;
1115 for (i = 0; i < data_index; i++) {
1116 if (strcasecmp(data[i]->name, newtag) == 0) {
1117 selected = i;
1118 goto gotitem;
1122 pgn_tag_add(&data, newtag, NULL);
1123 data_index = pgn_tag_total(data);
1124 selected = data_index - 1;
1125 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1126 &toppos);
1127 goto gotitem;
1128 break;
1129 case KEY_HOME:
1130 case KEY_END:
1131 case KEY_UP:
1132 case KEY_DOWN:
1133 case KEY_NPAGE:
1134 case KEY_PPAGE:
1135 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1136 &toppos);
1137 menubuf[0] = 0;
1138 mp = menubuf;
1139 break;
1140 case CTRL('F'):
1141 if (!edit)
1142 break;
1144 pgn_tag_add(&data, "FEN", pgn_game_to_fen(g, b));
1145 data_index = pgn_tag_total(data);
1146 selected = data_index - 1;
1147 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1148 &toppos);
1149 goto gotitem;
1150 break;
1151 case CTRL('X'):
1152 pgn_tag_free(data);
1153 del_panel(panel);
1154 delwin(win);
1155 return NULL;
1156 case '\n':
1157 goto gotitem;
1158 break;
1159 case KEY_ESCAPE:
1160 del_panel(panel);
1161 delwin(win);
1162 goto done;
1163 break;
1164 default:
1165 if (strlen(menubuf) + 1 > sizeof(menubuf) - 1) {
1166 menubuf[0] = 0;
1167 mp = menubuf;
1170 *mp++ = c;
1171 *mp = 0;
1172 n = selected;
1174 for (i = 0; i < data_index; i++) {
1175 if (strncasecmp(menubuf, data[i]->name, strlen(menubuf))
1176 == 0) {
1177 selected = i;
1178 break;
1182 if (n == selected) {
1183 menubuf[0] = 0;
1184 mp = menubuf;
1187 set_menu_vars(c, rows - 4, data_index - 1, &selected,
1188 &toppos);
1189 break;
1193 gotitem:
1194 nlen = strlen(data[selected]->name) + 2;
1195 nlen += (edit) ? strlen(TAG_EDIT_TAG_TITLE) : strlen(TAG_VIEW_TAG_TITLE);
1197 if (nlen > MAX_VALUE_WIDTH)
1198 snprintf(buf, sizeof(buf), "%s", data[selected]->name);
1199 else
1200 snprintf(buf, sizeof(buf), "%s \"%s\"",
1201 (edit) ? TAG_EDIT_TAG_TITLE : TAG_VIEW_TAG_TITLE,
1202 data[selected]->name);
1204 if (!edit) {
1205 if (!data[selected]->value)
1206 goto cleanup;
1208 cmessage(buf, ANYKEY, "%s", data[selected]->value);
1209 goto cleanup;
1212 if (strcmp(data[selected]->name, "Date") == 0) {
1213 tmp = get_input(buf, data[selected]->value, 0, 0, NULL, NULL, NULL,
1214 0, FIELD_TYPE_PGN_DATE);
1216 if (tmp) {
1217 if (strptime(tmp, PGN_TIME_FORMAT, &tp) == NULL) {
1218 cmessage(ERROR, ANYKEY, "%s", E_TAG_DATE_FMT);
1219 goto cleanup;
1222 else
1223 goto cleanup;
1225 else if (strcmp(data[selected]->name, "Site") == 0) {
1226 tmp = get_input(buf, data[selected]->value, 1, 1, CC_PROMPT,
1227 country_codes, NULL, CTRL('t'), -1);
1229 if (!tmp)
1230 tmp = "?";
1232 else if (strcmp(data[selected]->name, "Round") == 0) {
1233 tmp = get_input(buf, NULL, 1, 1, NULL, NULL, NULL, 0,
1234 FIELD_TYPE_PGN_ROUND);
1236 if (!tmp) {
1237 if (gtotal > 1)
1238 tmp = "?";
1239 else
1240 tmp = "-";
1243 else if (strcmp(data[selected]->name, "Result") == 0) {
1244 tmp = get_input(buf, data[selected]->value, 1, 1, NULL, NULL, NULL,
1245 0, -1);
1247 if (!tmp)
1248 tmp = "*";
1250 else {
1251 tmp = (data[selected]->value) ? data[selected]->value : NULL;
1252 tmp = get_input(buf, tmp, 0, 0, NULL, NULL, NULL, 0, -1);
1255 len = (tmp) ? strlen(tmp) + 1 : 1;
1256 data[selected]->value = Realloc(data[selected]->value, len);
1257 strncpy(data[selected]->value, (tmp) ? tmp : "", len);
1259 cleanup:
1260 del_panel(panel);
1261 delwin(win);
1262 menubuf[0] = 0;
1263 mp = menubuf;
1266 done:
1267 if (!edit) {
1268 pgn_tag_free(data);
1269 return NULL;
1272 return data;
1275 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1276 * game index number.
1278 int save_pgn(const char *filename, int saveindex)
1280 FILE *fp;
1281 char *mode = NULL;
1282 int c;
1283 char buf[FILENAME_MAX];
1284 struct stat st;
1285 int i;
1286 char *command = NULL;
1287 int saveindex_max = (saveindex == -1) ? gtotal : saveindex + 1;
1289 if (filename[0] != '/' && config.savedirectory) {
1290 if (stat(config.savedirectory, &st) == -1) {
1291 if (errno == ENOENT) {
1292 if (mkdir(config.savedirectory, 0755) == -1) {
1293 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1294 strerror(errno));
1295 return 1;
1298 else {
1299 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1300 strerror(errno));
1301 return 1;
1305 stat(config.savedirectory, &st);
1307 if (!S_ISDIR(st.st_mode)) {
1308 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1309 return 1;
1312 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1313 filename = buf;
1316 if (access(filename, W_OK) == 0) {
1317 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1318 "%s \"%s\"", E_FILEEXISTS, filename);
1320 switch (c) {
1321 case 'a':
1322 if (pgn_is_compressed(filename) == E_PGN_OK) {
1323 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1324 return 1;
1327 mode = "a";
1328 break;
1329 case 'o':
1330 mode = "w+";
1331 break;
1332 default:
1333 return 1;
1336 else
1337 mode = "a";
1339 if (command) {
1340 if ((fp = popen(command, "w")) == NULL) {
1341 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1342 return 1;
1345 else {
1346 if ((fp = fopen(filename, mode)) == NULL) {
1347 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1348 return 1;
1352 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++)
1353 pgn_write(fp, game[i]);
1355 if (command)
1356 pclose(fp);
1357 else
1358 fclose(fp);
1360 if (saveindex == -1)
1361 strncpy(loadfile, filename, sizeof(loadfile));
1363 return 0;
1366 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1368 if (pgn_piece_to_int(piece) == ROOK && col == 7
1369 && row == 7 &&
1370 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1371 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1372 if (mod)
1373 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1374 return 1;
1376 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1377 && row == 7 &&
1378 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1379 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1380 if (mod)
1381 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1382 return 1;
1384 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1385 && row == 0 &&
1386 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1387 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1388 if (mod)
1389 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1390 return 1;
1392 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1393 && row == 0 &&
1394 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1395 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1396 if (mod)
1397 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1398 return 1;
1400 else if (pgn_piece_to_int(piece) == KING && col == 4
1401 && row == 7 &&
1402 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1403 TEST_FLAG(g->flags, GF_WK_CASTLE))
1405 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1406 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1407 if (mod) {
1408 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1409 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1410 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1411 else
1412 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1414 return 1;
1416 else if (pgn_piece_to_int(piece) == KING && col == 4
1417 && row == 0 &&
1418 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1419 TEST_FLAG(g->flags, GF_BK_CASTLE))
1421 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1422 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1423 if (mod) {
1424 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1425 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1426 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1427 else
1428 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1430 return 1;
1433 return 0;
1436 static void draw_board(GAME *g)
1438 int row, col;
1439 int bcol = 0, brow = 0;
1440 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1441 int ncols = 0, offset = 1;
1442 unsigned coords_y = 8;
1443 struct userdata_s *d = g->data;
1445 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
1446 update_cursor(*g, g->hindex);
1448 for (row = 0; row < maxy; row++) {
1449 bcol = 0;
1451 for (col = 0; col < maxx; col++) {
1452 int attrwhich = -1;
1453 chtype attrs = 0;
1454 unsigned char piece;
1456 if (row == 0 || row == maxy - 2) {
1457 if (col == 0)
1458 mvwaddch(boardw, row, col,
1459 LINE_GRAPHIC((row) ?
1460 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1461 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1462 else if (col == maxx - 2)
1463 mvwaddch(boardw, row, col,
1464 LINE_GRAPHIC((row) ?
1465 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1466 ACS_URCORNER | CP_BOARD_GRAPHICS));
1467 else if (!(col % 4))
1468 mvwaddch(boardw, row, col,
1469 LINE_GRAPHIC((row) ?
1470 ACS_BTEE | CP_BOARD_GRAPHICS :
1471 ACS_TTEE | CP_BOARD_GRAPHICS));
1472 else {
1473 if (col != maxx - 1)
1474 mvwaddch(boardw, row, col,
1475 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1478 continue;
1481 if ((row % 2) && col == maxx - 1 && coords_y) {
1482 wattron(boardw, CP_BOARD_COORDS);
1483 mvwprintw(boardw, row, col, "%d", coords_y--);
1484 wattroff(boardw, CP_BOARD_COORDS);
1485 continue;
1488 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1489 if (!(row % 2))
1490 mvwaddch(boardw, row, col,
1491 LINE_GRAPHIC((col) ?
1492 ACS_RTEE | CP_BOARD_GRAPHICS :
1493 ACS_LTEE | CP_BOARD_GRAPHICS));
1494 else
1495 mvwaddch(boardw, row, col,
1496 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1498 continue;
1501 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1502 mvwaddch(boardw, row, col,
1503 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1504 continue;
1507 if (!(col % 4) && row != maxy - 1) {
1508 mvwaddch(boardw, row, col,
1509 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1510 continue;
1513 if ((row % 2)) {
1514 if ((col % 4)) {
1515 if (ncols++ == 8) {
1516 offset++;
1517 ncols = 1;
1520 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1521 && (offset % 2)))
1522 attrwhich = BLACK;
1523 else
1524 attrwhich = WHITE;
1526 if (config.validmoves && d->b[brow][bcol].valid) {
1527 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1528 CP_BOARD_MOVES_BLACK;
1530 else
1531 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1532 CP_BOARD_BLACK;
1534 if (row == ROWTOMATRIX(d->c_row) && col ==
1535 COLTOMATRIX(d->c_col)) {
1536 attrs = CP_BOARD_CURSOR;
1539 if (row == ROWTOMATRIX(d->sp.srow) &&
1540 col == COLTOMATRIX(d->sp.scol)) {
1541 attrs = CP_BOARD_SELECTED;
1544 if (row == maxy - 1)
1545 attrs = 0;
1547 mvwaddch(boardw, row, col, ' ' | attrs);
1549 if (row == maxy - 1)
1550 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1551 else {
1552 if (config.details && d->b[row / 2][bcol].enpassant)
1553 piece = 'x';
1554 else
1555 piece = d->b[row / 2][bcol].icon;
1557 if (config.details && castling_state(g, d->b, brow,
1558 bcol, piece, 0))
1559 attrs |= A_REVERSE;
1561 if (g->side == WHITE && isupper(piece))
1562 attrs |= A_BOLD;
1563 else if (g->side == BLACK && islower(piece))
1564 attrs |= A_BOLD;
1566 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1568 CLEAR_FLAG(attrs, A_BOLD);
1569 CLEAR_FLAG(attrs, A_REVERSE);
1572 waddch(boardw, ' ' | attrs);
1573 col += 2;
1574 bcol++;
1577 else {
1578 if (col != maxx - 1)
1579 mvwaddch(boardw, row, col,
1580 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1584 brow = row / 2;
1587 mvwaddch(boardw, maxy - 1, maxx - 2, (config.details) ? '!' : ' ');
1590 void invalid_move(int n, int e, const char *m)
1592 if (curses_initialized)
1593 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
1594 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
1595 else
1596 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
1597 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
1600 /* Convert the selected piece to SAN format and validate it. */
1601 static char *board_to_san(GAME *g, BOARD b)
1603 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1604 int piece;
1605 int promo;
1606 struct userdata_s *d = g->data;
1607 int n;
1609 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[d->sp.scol - 1],
1610 d->sp.srow, x_grid_chars[d->sp.col - 1], d->sp.row);
1612 p = str;
1613 piece = pgn_piece_to_int(b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon);
1615 if (piece == PAWN && ((d->sp.row == 8 && g->turn == WHITE) ||
1616 (d->sp.row == 1 && g->turn == BLACK))) {
1617 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1619 if (pgn_piece_to_int(promo) == -1)
1620 return NULL;
1622 p = str + strlen(str);
1623 *p++ = toupper(promo);
1624 *p = '\0';
1627 p = str;
1629 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1630 if ((n = pgn_parse_move(g, b, &p)) != E_PGN_OK) {
1631 invalid_move(d->n + 1, n, p);
1632 return NULL;
1635 return p;
1638 if ((n = pgn_validate_move(g, b, &p)) != E_PGN_OK) {
1639 invalid_move(d->n + 1, n, p);
1640 return NULL;
1643 return p;
1646 static void update_clock(GAME *g, struct itimerval it)
1648 struct userdata_s *d = g->data;
1649 long n;
1651 if (g->turn == WHITE) {
1652 d->wc.tv_sec += it.it_value.tv_sec;
1653 d->wc.tv_usec += it.it_value.tv_usec;
1655 if (d->wc.tv_usec > 1000000 - 1) {
1656 d->wc.tv_sec += d->wc.tv_usec / 1000000;
1657 d->wc.tv_usec = d->wc.tv_usec % 1000000;
1660 else {
1661 d->bc.tv_sec += it.it_value.tv_sec;
1662 d->bc.tv_usec += it.it_value.tv_usec;
1664 if (d->bc.tv_usec > 1000000 - 1) {
1665 d->bc.tv_sec += d->bc.tv_usec / 1000000;
1666 d->bc.tv_usec = d->bc.tv_usec % 1000000;
1670 d->elapsed = d->wc.tv_sec + d->bc.tv_sec;
1671 n = d->wc.tv_usec + d->bc.tv_usec;
1672 d->elapsed += (n > 1000000 - 1) ? n / 1000000 : 0;
1674 if (TEST_FLAG(d->flags, CF_CLOCK)) {
1675 if (d->elapsed >= d->limit) {
1676 SET_FLAG(g->flags, GF_GAMEOVER);
1677 pgn_tag_add(&g->tag, "Result", "1/2-1/2");
1682 static int move_to_engine(GAME *g, BOARD b)
1684 char *p;
1685 struct userdata_s *d = g->data;
1687 if ((p = board_to_san(g, b)) == NULL)
1688 return 0;
1690 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1692 if (TEST_FLAG(g->flags, GF_GAMEOVER))
1693 d->mode = MODE_HISTORY;
1695 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1696 pgn_history_add(g, p);
1697 pgn_switch_turn(g);
1698 SET_FLAG(g->flags, GF_MODIFIED);
1699 update_all(*g);
1700 return 1;
1703 add_engine_command(g, ENGINE_THINKING, "%s\n", p);
1704 return 1;
1707 static char *clock_to_char(long n)
1709 static char buf[16];
1710 int h = 0, m = 0, s = 0;
1712 h = n / 3600;
1713 m = (n % 3600) / 60;
1714 s = (n % 3600) % 60;
1715 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
1716 return buf;
1719 static char *timeval_to_char(struct timeval t)
1721 static char buf[16];
1722 int h = 0, m = 0, s = 0;
1723 int n = t.tv_sec;
1725 h = n / 3600;
1726 m = (n % 3600) / 60;
1727 s = (n % 3600) % 60;
1728 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i.%.2i", h, m, s,
1729 (int)t.tv_usec / 10000);
1730 return buf;
1733 void update_status_window(GAME g)
1735 int i = 0;
1736 char *buf;
1737 char tmp[15], *engine, *mode;
1738 int w;
1739 char *p;
1740 int maxy, maxx;
1741 int len;
1742 struct userdata_s *d = g.data;
1744 getmaxyx(statusw, maxy, maxx);
1745 w = maxx - 2 - 8;
1746 len = maxx - 2;
1747 buf = Malloc(len);
1749 *tmp = '\0';
1750 p = tmp;
1752 if (TEST_FLAG(g.flags, GF_DELETE)) {
1753 *p++ = '(';
1754 *p++ = 'x';
1755 i++;
1758 if (TEST_FLAG(g.flags, GF_PERROR)) {
1759 if (!i)
1760 *p++ = '(';
1761 else
1762 *p++ = '/';
1764 *p++ = '!';
1765 i++;
1768 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
1769 if (!i)
1770 *p++ = '(';
1771 else
1772 *p++ = '/';
1774 *p++ = '*';
1775 i++;
1778 if (*tmp != '\0')
1779 *p++ = ')';
1781 *p = '\0';
1783 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1784 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1785 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1786 (*tmp) ? tmp : "");
1787 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1789 switch (d->mode) {
1790 case MODE_HISTORY:
1791 mode = MODE_HISTORY_STR;
1792 break;
1793 case MODE_EDIT:
1794 mode = MODE_EDIT_STR;
1795 break;
1796 case MODE_PLAY:
1797 mode = MODE_PLAY_STR;
1798 break;
1799 default:
1800 mode = UNKNOWN;
1801 break;
1804 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1806 if (d->mode == MODE_PLAY) {
1807 if (TEST_FLAG(d->flags, CF_HUMAN))
1808 strncat(buf, " (human/human)", len - 1);
1809 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1810 strncat(buf, " (engine/engine)", len - 1);
1811 else
1812 strncat(buf, " (human/engine)", len - 1);
1815 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1817 if (d->engine) {
1818 switch (d->engine->status) {
1819 case ENGINE_THINKING:
1820 engine = ENGINE_PONDER_STR;
1821 break;
1822 case ENGINE_READY:
1823 engine = ENGINE_READY_STR;
1824 break;
1825 case ENGINE_INITIALIZING:
1826 engine = ENGINE_INITIALIZING_STR;
1827 break;
1828 case ENGINE_OFFLINE:
1829 engine = ENGINE_OFFLINE_STR;
1830 break;
1831 default:
1832 engine = UNKNOWN;
1833 break;
1836 else
1837 engine = ENGINE_OFFLINE_STR;
1839 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1840 wattron(statusw, CP_STATUS_ENGINE);
1841 mvwaddstr(statusw, 5, 9, engine);
1842 wattroff(statusw, CP_STATUS_ENGINE);
1844 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1845 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1847 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR, w,
1848 clock_to_char((TEST_FLAG(d->flags, CF_CLOCK)) ?
1849 d->limit - d->elapsed : 0));
1851 strncpy(tmp, WHITE_STR, sizeof(tmp));
1852 tmp[0] = toupper(tmp[0]);
1853 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->wc));
1855 strncpy(tmp, BLACK_STR, sizeof(tmp));
1856 tmp[0] = toupper(tmp[0]);
1857 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->bc));
1858 free(buf);
1860 for (i = 1; i < maxx - 4; i++)
1861 mvwprintw(statusw, maxy - 2, i, " ");
1863 if (!status.notify)
1864 status.notify = strdup(GAME_HELP_PROMPT);
1866 wattron(statusw, CP_STATUS_NOTIFY);
1867 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1868 status.notify);
1869 wattroff(statusw, CP_STATUS_NOTIFY);
1872 void update_history_window(GAME g)
1874 char buf[HISTORY_WIDTH - 1];
1875 HISTORY *h = NULL;
1876 int n, total;
1877 int t = pgn_history_total(g.hp);
1879 n = (g.hindex + 1) / 2;
1881 if (t % 2)
1882 total = (t + 1) / 2;
1883 else
1884 total = t / 2;
1886 if (t)
1887 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1888 (movestep == 1) ? HISTORY_PLY_STEP : "");
1889 else
1890 strncpy(buf, UNAVAILABLE, sizeof(buf));
1892 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1893 HISTORY_WIDTH - 13, buf);
1895 h = pgn_history_by_n(g.hp, g.hindex);
1896 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1897 n = 0;
1899 if (h && ((h->comment) || h->nag[0])) {
1900 strncat(buf, " (v", sizeof(buf));
1901 n++;
1904 if (h && h->rav) {
1905 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1906 n++;
1909 if (g.ravlevel) {
1910 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1911 n++;
1914 if (n)
1915 strncat(buf, ")", sizeof(buf));
1917 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1918 HISTORY_WIDTH - 13, buf);
1920 h = pgn_history_by_n(g.hp, g.hindex - 1);
1921 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1922 n = 0;
1924 if (h && ((h->comment) || h->nag[0])) {
1925 strncat(buf, " (V", sizeof(buf));
1926 n++;
1929 if (h && h->rav) {
1930 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1931 n++;
1934 if (g.ravlevel) {
1935 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1936 n++;
1939 if (n)
1940 strncat(buf, ")", sizeof(buf));
1942 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1943 HISTORY_WIDTH - 13, buf);
1946 void update_tag_window(TAG **t)
1948 int i;
1949 int w = TAG_WIDTH - 10;
1951 for (i = 0; i < 7; i++)
1952 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1955 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1957 int i;
1959 wattron(win, attr);
1961 for (i = 1; i < width - 1; i++)
1962 mvwaddch(win, y, i, ' ');
1964 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1965 wattroff(win, attr);
1968 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1969 chtype battr)
1971 int i;
1973 if (title) {
1974 wattron(win, attr);
1976 for (i = 1; i < width - 1; i++)
1977 mvwaddch(win, 1, i, ' ');
1979 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1980 wattroff(win, attr);
1983 wattron(win, battr);
1984 box(win, ACS_VLINE, ACS_HLINE);
1985 wattroff(win, battr);
1988 void append_enginebuf(char *line)
1990 int i = 0;
1992 if (enginebuf)
1993 for (i = 0; enginebuf[i]; i++);
1995 if (i >= LINES - 3) {
1996 free(enginebuf[0]);
1998 for (i = 0; enginebuf[i+1]; i++)
1999 enginebuf[i] = enginebuf[i+1];
2001 enginebuf[i] = strdup(line);
2003 else {
2004 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
2005 enginebuf[i++] = strdup(line);
2006 enginebuf[i] = NULL;
2010 void update_engine_window()
2012 int i;
2014 if (!enginebuf)
2015 return;
2017 wmove(enginew, 0, 0);
2018 wclrtobot(enginew);
2020 if (enginebuf) {
2021 for (i = 0; enginebuf[i]; i++)
2022 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
2025 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2026 CP_MESSAGE_BORDER);
2029 void toggle_engine_window()
2031 if (!enginew) {
2032 enginew = newwin(LINES, COLS, 0, 0);
2033 enginep = new_panel(enginew);
2034 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2035 CP_MESSAGE_BORDER);
2036 hide_panel(enginep);
2039 if (panel_hidden(enginep)) {
2040 update_engine_window();
2041 top_panel(enginep);
2042 refresh_all();
2044 else {
2045 hide_panel(enginep);
2046 refresh_all();
2050 void refresh_all()
2052 update_panels();
2053 doupdate();
2056 void update_all(GAME g)
2058 update_status_window(g);
2059 update_history_window(g);
2060 update_tag_window(g.tag);
2061 update_engine_window();
2064 static void game_next_prev(GAME g, int n, int count)
2066 if (gtotal < 2)
2067 return;
2069 if (n == 1) {
2070 if (gindex + count > gtotal - 1) {
2071 if (count != 1)
2072 gindex = gtotal - 1;
2073 else
2074 gindex = 0;
2076 else
2077 gindex += count;
2079 else {
2080 if (gindex - count < 0) {
2081 if (count != 1)
2082 gindex = 0;
2083 else
2084 gindex = gtotal - 1;
2086 else
2087 gindex -= count;
2091 static void delete_game(int which)
2093 GAME *g = NULL;
2094 int gi = 0;
2095 int i;
2097 for (i = 0; i < gtotal; i++) {
2098 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
2099 pgn_free(game[i]);
2100 continue;
2103 g = Realloc(g, (gi + 1) * sizeof(GAME));
2104 memcpy(&g[gi], &game[i], sizeof(GAME));
2105 g[gi].tag = game[i].tag;
2106 g[gi].history = game[i].history;
2107 g[gi].hp = game[i].hp;
2108 gi++;
2111 game = g;
2112 gtotal = gi;
2114 if (which != -1) {
2115 if (which + 1 >= gtotal)
2116 gindex = gtotal - 1;
2117 else
2118 gindex = which;
2120 else
2121 gindex = gtotal - 1;
2123 game[gindex].hp = game[gindex].history;
2126 static int find_move_exp(GAME g, const char *str, int init, int which,
2127 int count)
2129 int i;
2130 int ret;
2131 static regex_t r;
2132 static int firstrun = 1;
2133 char errbuf[255];
2134 int incr;
2135 int found;
2137 if (init) {
2138 if (!firstrun)
2139 regfree(&r);
2141 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2142 regerror(ret, &r, errbuf, sizeof(errbuf));
2143 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2144 return -1;
2147 firstrun = 1;
2150 incr = (which == 0) ? -1 : 1;
2152 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2153 if (i == g.hindex - 1)
2154 break;
2156 if (i >= pgn_history_total(g.hp))
2157 i = 0;
2158 else if (i < 0)
2159 i = pgn_history_total(g.hp) - 1;
2161 // FIXME RAV
2162 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2164 if (ret == 0) {
2165 if (count == ++found) {
2166 return i + 1;
2169 else {
2170 if (ret != REG_NOMATCH) {
2171 regerror(ret, &r, errbuf, sizeof(errbuf));
2172 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2173 return -1;
2178 return -1;
2181 static int toggle_delete_flag(int n)
2183 int i, x;
2185 TOGGLE_FLAG(game[n].flags, GF_DELETE);
2186 gindex = n;
2187 update_all(game[gindex]);
2189 for (i = x = 0; i < gtotal; i++) {
2190 if (TEST_FLAG(game[i].flags, GF_DELETE))
2191 x++;
2194 if (x == gtotal) {
2195 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2196 CLEAR_FLAG(game[n].flags, GF_DELETE);
2197 return 1;
2200 return 0;
2203 static void edit_save_tags(GAME *g)
2205 TAG **t;
2206 struct userdata_s *d = g->data;
2208 if ((t = edit_tags(*g, d->b, 1)) == NULL)
2209 return;
2211 pgn_tag_free(g->tag);
2212 g->tag = t;
2213 SET_FLAG(g->flags, GF_MODIFIED);
2214 pgn_tag_sort(g->tag);
2217 static int find_game_exp(char *str, int which, int count)
2219 char *nstr = NULL, *exp = NULL;
2220 regex_t nexp, vexp;
2221 int ret = -1;
2222 int g = 0;
2223 char buf[255], *tmp;
2224 char errbuf[255];
2225 int found = 0;
2226 int incr = (which == 0) ? -(1) : 1;
2228 strncpy(buf, str, sizeof(buf));
2229 tmp = buf;
2231 if (strstr(tmp, ":") != NULL) {
2232 nstr = strsep(&tmp, ":");
2234 if ((ret = regcomp(&nexp, nstr,
2235 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2236 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2237 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2238 ret = g = -1;
2239 goto cleanup;
2243 exp = tmp;
2245 if (exp == NULL)
2246 goto cleanup;
2248 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2249 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2250 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2251 ret = -1;
2252 goto cleanup;
2255 ret = -1;
2257 for (g = gindex + incr, found = 0; ; g += incr) {
2258 int t;
2260 if (g == gindex)
2261 break;
2263 if (g == gtotal)
2264 g = 0;
2265 else if (g < 0)
2266 g = gtotal - 1;
2268 for (t = 0; game[g].tag[t]; t++) {
2269 if (nstr) {
2270 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2271 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2272 if (count == ++found) {
2273 ret = g;
2274 goto cleanup;
2279 else {
2280 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2281 if (count == ++found) {
2282 ret = g;
2283 goto cleanup;
2289 ret = -1;
2292 cleanup:
2293 if (nstr)
2294 regfree(&nexp);
2296 if (g != -1)
2297 regfree(&vexp);
2299 return ret;
2303 * Updates the notification line in the status window then refreshes the
2304 * status window.
2306 void update_status_notify(GAME g, char *fmt, ...)
2308 va_list ap;
2309 #ifdef HAVE_VASPRINTF
2310 char *line;
2311 #else
2312 char line[COLS];
2313 #endif
2315 if (!fmt) {
2316 if (status.notify) {
2317 free(status.notify);
2318 status.notify = NULL;
2320 if (curses_initialized)
2321 update_status_window(g);
2324 return;
2327 va_start(ap, fmt);
2328 #ifdef HAVE_VASPRINTF
2329 vasprintf(&line, fmt, ap);
2330 #else
2331 vsnprintf(line, sizeof(line), fmt, ap);
2332 #endif
2333 va_end(ap);
2335 if (status.notify)
2336 free(status.notify);
2338 status.notify = strdup(line);
2340 #ifdef HAVE_VASPRINTF
2341 free(line);
2342 #endif
2343 if (curses_initialized)
2344 update_status_window(g);
2347 int rav_next_prev(GAME *g, BOARD b, int n)
2349 // Next RAV.
2350 if (n) {
2351 if ((!g->ravlevel && g->hp[g->hindex - 1]->rav == NULL) ||
2352 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
2353 return 1;
2355 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2356 g->rav[g->ravlevel].hp = g->hp;
2357 g->rav[g->ravlevel].flags = g->flags;
2358 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2359 g->rav[g->ravlevel].hindex = g->hindex;
2360 g->hp = (!g->ravlevel) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav;
2361 g->hindex = 0;
2362 g->ravlevel++;
2363 pgn_board_update(g, b, g->hindex + 1);
2364 return 0;
2367 if (g->ravlevel - 1 < 0)
2368 return 1;
2370 // Previous RAV.
2371 g->ravlevel--;
2372 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2373 free(g->rav[g->ravlevel].fen);
2374 g->hp = g->rav[g->ravlevel].hp;
2375 g->flags = g->rav[g->ravlevel].flags;
2376 g->hindex = g->rav[g->ravlevel].hindex;
2377 return 0;
2380 static void draw_window_decor()
2382 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2383 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2384 wbkgd(boardw, CP_BOARD_WINDOW);
2385 wbkgd(statusw, CP_STATUS_WINDOW);
2386 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2387 CP_STATUS_TITLE, CP_STATUS_BORDER);
2388 wbkgd(tagw, CP_TAG_WINDOW);
2389 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2390 CP_TAG_BORDER);
2391 wbkgd(historyw, CP_HISTORY_WINDOW);
2392 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2393 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2396 static void do_window_resize()
2398 if (LINES < 24 || COLS < 80)
2399 return;
2401 resizeterm(LINES, COLS);
2402 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2403 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2404 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2405 wmove(historyw, 0, 0);
2406 wclrtobot(historyw);
2407 wmove(tagw, 0, 0);
2408 wclrtobot(tagw);
2409 wmove(statusw, 0, 0);
2410 wclrtobot(statusw);
2411 draw_window_decor();
2412 update_all(game[gindex]);
2415 void stop_clock()
2417 memset(&clock_timer, 0, sizeof(struct itimerval));
2418 setitimer(ITIMER_REAL, &clock_timer, NULL);
2421 void start_clock()
2423 if (clock_timer.it_interval.tv_usec)
2424 return;
2426 clock_timer.it_value.tv_sec = 0;
2427 clock_timer.it_value.tv_usec = 100000;
2428 clock_timer.it_interval.tv_sec = 0;
2429 clock_timer.it_interval.tv_usec = 100000;
2430 setitimer(ITIMER_REAL, &clock_timer, NULL);
2433 static void update_clocks()
2435 int i;
2436 struct userdata_s *d;
2437 struct itimerval it;
2439 getitimer(ITIMER_REAL, &it);
2441 for (i = 0; i < gtotal; i++) {
2442 d = game[i].data;
2444 if (d->mode == MODE_PLAY) {
2445 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
2446 continue;
2447 else if (d->paused == -1) {
2448 if (game[i].side == game[i].turn) {
2449 d->paused = 1;
2450 continue;
2454 update_clock(&game[i], it);
2459 static int init_chess_engine(GAME *g)
2461 struct userdata_s *d = g->data;
2462 int w, x;
2464 if (start_chess_engine(g) > 0) {
2465 d->sp.icon = 0;
2466 return 1;
2469 x = pgn_tag_find(g->tag, "FEN");
2470 w = pgn_tag_find(g->tag, "SetUp");
2472 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
2473 (x >= 0 && w == -1))
2474 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
2475 else
2476 add_engine_command(g, ENGINE_READY, "setboard %s\n",
2477 pgn_game_to_fen(*g, d->b));
2479 return 0;
2482 static int parse_clock_input(struct userdata_s *d, char *str)
2484 char *p = str;
2485 long n = 0;
2486 int t = 0;
2487 int plus = 0;
2489 if (*p == '+') {
2490 plus = 1;
2491 p++;
2494 while (*p) {
2495 if (isdigit(*p)) {
2496 t = atoi(p);
2498 while (isdigit(*p))
2499 p++;
2501 continue;
2504 if (!t && *p != ' ')
2505 return 1;
2507 switch (*p) {
2508 case 'H':
2509 case 'h':
2510 n += t * (60 * 60);
2511 t = 0;
2512 break;
2513 case 'M':
2514 case 'm':
2515 n += t * 60;
2516 t = 0;
2517 break;
2518 case 'S':
2519 case 's':
2520 n += t;
2521 t = 0;
2522 break;
2523 case ' ':
2524 t = 0;
2525 break;
2526 default:
2527 return 1;
2530 p++;
2533 if (t)
2534 n += t;
2536 if (!n) {
2537 d->limit = 0;
2538 CLEAR_FLAG(d->flags, CF_CLOCK);
2540 else {
2541 SET_FLAG(d->flags, CF_CLOCK);
2543 if (plus)
2544 d->limit += n;
2545 else
2546 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
2549 return 0;
2552 static void historymode_keys(chtype);
2553 static int playmode_keys(chtype c)
2555 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2556 struct userdata_s *d = game[gindex].data;
2557 int editmode = (d->mode == MODE_EDIT) ? 1 : 0;
2558 chtype p;
2559 int w, x;
2560 char *tmp;
2562 switch (c) {
2563 case 'C':
2564 if ((tmp = get_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL,
2565 NULL, 0, -1)) == NULL)
2566 break;
2568 if (parse_clock_input(d, tmp))
2569 cmessage(ERROR, ANYKEY, "Invalid time specification");
2570 break;
2571 case 'H':
2572 TOGGLE_FLAG(d->flags, CF_HUMAN);
2574 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2575 pgn_history_total(game[gindex].hp)) {
2576 if (init_chess_engine(&game[gindex]))
2577 break;
2580 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2582 if (d->engine)
2583 d->engine->status = ENGINE_READY;
2585 update_all(game[gindex]);
2586 break;
2587 case 'E':
2588 if (!d)
2589 break;
2591 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2592 CLEAR_FLAG(d->flags, CF_HUMAN);
2594 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2595 pgn_board_update(&game[gindex], d->b,
2596 pgn_history_total(game[gindex].hp));
2597 add_engine_command(&game[gindex], ENGINE_READY,
2598 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2601 update_all(game[gindex]);
2602 break;
2603 case '|':
2604 if (!d->engine)
2605 break;
2607 if (d->engine->status == ENGINE_OFFLINE)
2608 break;
2610 x = d->engine->status;
2612 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2613 send_to_engine(&game[gindex], -1, "%s\n", tmp);
2614 d->engine->status = x;
2615 break;
2616 case '\015':
2617 case '\n':
2618 pushkey = keycount = 0;
2619 update_status_notify(game[gindex], NULL);
2621 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2622 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2623 beep();
2624 break;
2627 if (!d->sp.icon)
2628 break;
2630 d->sp.row = d->c_row;
2631 d->sp.col = d->c_col;
2633 if (editmode) {
2634 p = d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon;
2635 d->b[ROWTOBOARD(d->sp.row)][COLTOBOARD(d->sp.col)].icon = p;
2636 d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon =
2637 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2638 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2639 break;
2642 if (move_to_engine(&game[gindex], d->b)) {
2643 if (config.validmoves)
2644 pgn_reset_valid_moves(d->b);
2646 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2647 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2648 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2651 d->paused = 0;
2654 break;
2655 case ' ':
2656 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2657 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2658 if (init_chess_engine(&game[gindex]))
2659 break;
2662 if (d->sp.icon || (!editmode && d->engine &&
2663 d->engine->status == ENGINE_THINKING)) {
2664 beep();
2665 break;
2668 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
2669 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
2671 if (d->sp.icon == ' ') {
2672 d->sp.icon = 0;
2673 break;
2676 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2677 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2678 if (pgn_history_total(game[gindex].hp)) {
2679 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2680 d->sp.icon = 0;
2681 break;
2683 else {
2684 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR)
2685 break;
2687 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2688 pgn_switch_turn(&game[gindex]);
2690 if (game[gindex].side != BLACK)
2691 pgn_switch_side(&game[gindex]);
2695 d->sp.srow = d->c_row;
2696 d->sp.scol = d->c_col;
2698 if (!editmode && config.validmoves)
2699 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2701 if (!editmode) {
2702 CLEAR_FLAG(d->flags, CF_NEW);
2703 start_clock();
2706 break;
2707 case 'w':
2708 pgn_switch_side(&game[gindex]);
2709 pgn_switch_turn(&game[gindex]);
2710 add_engine_command(&game[gindex], -1,
2711 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2712 update_status_window(game[gindex]);
2713 break;
2714 case 'u':
2715 if (!pgn_history_total(game[gindex].hp))
2716 break;
2718 if (d->engine && d->engine->status == ENGINE_READY) {
2719 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2720 d->engine->status = ENGINE_READY;
2723 game[gindex].hindex -= 2;
2724 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2725 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2726 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2727 update_history_window(game[gindex]);
2728 break;
2729 case 'a':
2730 historymode_keys(c);
2731 break;
2732 case 'd':
2733 config.details = (config.details) ? 0 : 1;
2734 break;
2735 case 'p':
2736 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex].turn !=
2737 game[gindex].side) {
2738 d->paused = -1;
2739 break;
2742 d->paused = (d->paused) ? 0 : 1;
2743 break;
2744 case 'g':
2745 if (TEST_FLAG(d->flags, CF_HUMAN))
2746 break;
2748 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2749 if (init_chess_engine(&game[gindex]))
2750 break;
2753 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2754 break;
2755 default:
2756 if (!d->engine)
2757 break;
2759 if (config.keys) {
2760 for (x = 0; config.keys[x]; x++) {
2761 if (config.keys[x]->c == c) {
2762 switch (config.keys[x]->type) {
2763 case KEY_DEFAULT:
2764 add_engine_command(&game[gindex], -1, "%s\n",
2765 config.keys[x]->str);
2766 break;
2767 case KEY_SET:
2768 if (!keycount)
2769 break;
2771 add_engine_command(&game[gindex], -1,
2772 "%s %i\n", config.keys[x]->str, keycount);
2773 keycount = 0;
2774 break;
2775 case KEY_REPEAT:
2776 if (!keycount)
2777 break;
2779 for (w = 0; w < keycount; w++)
2780 add_engine_command(&game[gindex], -1,
2781 "%s\n", config.keys[x]->str);
2782 keycount = 0;
2783 break;
2788 update_status_notify(game[gindex], NULL);
2790 break;
2793 return 0;
2796 static void editmode_keys(chtype c)
2798 struct userdata_s *d = game[gindex].data;
2800 switch (c) {
2801 case '\015':
2802 case '\n':
2803 case ' ':
2804 playmode_keys(c);
2805 break;
2806 case 'd':
2807 if (d->sp.icon)
2808 d->b[ROWTOBOARD(d->sp.srow)][COLTOBOARD(d->sp.scol)].icon =
2809 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2810 else
2811 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon =
2812 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2814 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2815 break;
2816 case 'w':
2817 pgn_switch_turn(&game[gindex]);
2818 update_all(game[gindex]);
2819 break;
2820 case 'c':
2821 castling_state(&game[gindex], d->b, ROWTOBOARD(d->c_row),
2822 COLTOBOARD(d->c_col),
2823 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon, 1);
2824 break;
2825 case 'i':
2826 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2827 GAME_EDIT_TEXT);
2829 if (pgn_piece_to_int(c) == -1)
2830 break;
2832 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].icon = c;
2833 break;
2834 case 'p':
2835 if (d->c_row == 6 || d->c_row == 3) {
2836 pgn_reset_enpassant(d->b);
2837 d->b[ROWTOBOARD(d->c_row)][COLTOBOARD(d->c_col)].enpassant = 1;
2839 break;
2840 default:
2841 break;
2845 static void historymode_keys(chtype c)
2847 int n, len;
2848 char *tmp, *buf;
2849 static char moveexp[255] = {0};
2850 struct userdata_s *d = game[gindex].data;
2852 switch (c) {
2853 case 'd':
2854 config.details = (config.details) ? 0 : 1;
2855 break;
2856 case ' ':
2857 movestep = (movestep == 1) ? 2 : 1;
2858 update_history_window(game[gindex]);
2859 break;
2860 case KEY_UP:
2861 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2862 config.jumpcount * keycount * movestep :
2863 config.jumpcount * movestep);
2864 update_all(game[gindex]);
2865 break;
2866 case KEY_DOWN:
2867 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2868 config.jumpcount * keycount * movestep :
2869 config.jumpcount * movestep);
2870 update_all(game[gindex]);
2871 break;
2872 case KEY_LEFT:
2873 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2874 keycount * movestep : movestep);
2875 update_all(game[gindex]);
2876 break;
2877 case KEY_RIGHT:
2878 pgn_history_next(&game[gindex], d->b, (keycount) ?
2879 keycount * movestep : movestep);
2880 update_all(game[gindex]);
2881 break;
2882 case 'a':
2883 n = game[gindex].hindex;
2885 if (n && game[gindex].hp[n - 1]->move)
2886 n--;
2887 else
2888 break;
2890 buf = Malloc(COLS);
2891 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2892 game[gindex].hp[n]->move);
2894 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2895 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2896 -1);
2897 free(buf);
2899 if (!tmp && (!game[gindex].hp[n]->comment ||
2900 !*game[gindex].hp[n]->comment))
2901 break;
2902 else if (tmp && game[gindex].hp[n]->comment) {
2903 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2904 break;
2907 len = (tmp) ? strlen(tmp) + 1 : 1;
2908 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2909 len);
2910 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2911 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2912 update_all(game[gindex]);
2913 break;
2914 case ']':
2915 case '[':
2916 case '/':
2917 if (pgn_history_total(game[gindex].hp) < 2)
2918 break;
2920 n = 0;
2922 if (!*moveexp || c == '/') {
2923 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2924 break;
2926 strncpy(moveexp, tmp, sizeof(moveexp));
2927 n = 1;
2930 if ((n = find_move_exp(game[gindex], moveexp, n,
2931 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2932 == -1)
2933 break;
2935 game[gindex].hindex = n;
2936 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2937 update_all(game[gindex]);
2938 break;
2939 case 'v':
2940 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2941 break;
2942 case 'V':
2943 if (game[gindex].hindex - 1 >= 0)
2944 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2945 break;
2946 case '-':
2947 case '+':
2948 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2949 update_all(game[gindex]);
2950 break;
2951 case 'j':
2952 if (pgn_history_total(game[gindex].hp) < 2)
2953 break;
2955 /* FIXME field validation
2956 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2957 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2958 game[gindex].htotal)) == NULL)
2959 break;
2962 if (!keycount) {
2963 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2964 NULL, NULL, NULL, 0, -1)) == NULL)
2965 break;
2967 if (!isinteger(tmp))
2968 break;
2970 n = atoi(tmp);
2972 else
2973 n = keycount;
2975 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2976 break;
2978 keycount = 0;
2979 update_status_notify(game[gindex], NULL);
2980 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2981 pgn_board_update(&game[gindex], d->b,
2982 game[gindex].hindex);
2983 update_all(game[gindex]);
2984 break;
2985 default:
2986 break;
2990 static void free_userdata()
2992 int i;
2994 for (i = 0; i < gtotal; i++) {
2995 struct userdata_s *d;
2997 if (game[i].data) {
2998 d = game[i].data;
3000 if (d->engine) {
3001 stop_engine(&game[i]);
3002 free(d->engine);
3005 free(game[i].data);
3006 game[i].data = NULL;
3011 void update_loading_window(int n)
3013 if (!loadingw) {
3014 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
3015 loadingp = new_panel(loadingw);
3016 wbkgd(loadingw, CP_MESSAGE_WINDOW);
3019 wmove(loadingw, 0, 0);
3020 wclrtobot(loadingw);
3021 wattron(loadingw, CP_MESSAGE_BORDER);
3022 box(loadingw, ACS_VLINE, ACS_HLINE);
3023 wattroff(loadingw, CP_MESSAGE_BORDER);
3024 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
3025 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
3026 gtotal);
3027 refresh_all();
3030 static void init_userdata_once(GAME *g, int n)
3032 struct userdata_s *d = NULL;
3034 d = Calloc(1, sizeof(struct userdata_s));
3035 d->n = n;
3036 d->c_row = 2, d->c_col = 5;
3037 SET_FLAG(d->flags, CF_NEW);
3038 g->data = d;
3040 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
3041 pgn_board_init(d->b);
3044 void init_userdata()
3046 int i;
3048 for (i = 0; i < gtotal; i++)
3049 init_userdata_once(&game[i], i);
3052 void fix_marks(int *start, int *end)
3054 int i;
3056 *start = (*start < 0) ? 0 : *start;
3057 *end = (*end < 0) ? 0 : *end;
3059 if (*start > *end) {
3060 i = *start;
3061 *start = *end;
3062 *end = i + 1;
3065 *end = (*end > gtotal) ? gtotal : *end;
3068 // Global and other keys.
3069 static int globalkeys(chtype c)
3071 static char gameexp[255] = {0};
3072 FILE *fp;
3073 char *tmp, *p;
3074 int n, i;
3075 char tfile[FILENAME_MAX];
3076 struct userdata_s *d = game[gindex].data;
3078 switch (c) {
3079 case 'W':
3080 toggle_engine_window();
3081 break;
3082 case KEY_F(10):
3083 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
3084 "and %i color pairs\nCopyright 2002-2006 %s",
3085 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
3086 COLOR_PAIRS, PACKAGE_BUGREPORT);
3087 break;
3088 case 'h':
3089 if (d->mode != MODE_HISTORY) {
3090 if (!pgn_history_total(game[gindex].hp) ||
3091 (d->engine && d->engine->status == ENGINE_THINKING))
3092 return 1;
3094 d->mode = MODE_HISTORY;
3095 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3096 update_all(game[gindex]);
3097 return 1;
3100 // FIXME Resuming from previous history could append to a RAV.
3101 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
3102 if (!pushkey) {
3103 if ((c = message(NULL, YESNO, "%s",
3104 GAME_RESUME_HISTORY_TEXT)) != 'y')
3105 return 1;
3107 pgn_history_free(game[gindex].hp, game[gindex].hindex);
3108 pgn_board_update(&game[gindex], d->b,
3109 pgn_history_total(game[gindex].hp));
3111 if (!TEST_FLAG(d->flags, CF_HUMAN))
3112 add_engine_command(&game[gindex], ENGINE_READY,
3113 "setboard %s\n", pgn_game_to_fen(game[gindex],
3114 d->b));
3117 else {
3118 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3119 return 1;
3122 pushkey = 0;
3123 d->mode = MODE_PLAY;
3124 update_all(game[gindex]);
3125 return 1;
3126 case '>':
3127 case '<':
3128 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
3129 keycount : 1);
3130 d = game[gindex].data;
3132 if (delete_count) {
3133 if (c == '>') {
3134 markend = markstart + delete_count;
3135 delete_count = 0;
3137 else {
3138 markend = markstart - delete_count;
3139 delete_count = -1; // to fix gindex in the other direction
3142 pushkey = 'x';
3143 fix_marks(&markstart, &markend);
3146 if (d->mode != MODE_EDIT)
3147 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3149 update_status_notify(game[gindex], NULL);
3150 update_all(game[gindex]);
3151 return 1;
3152 case '}':
3153 case '{':
3154 case '?':
3155 if (gtotal < 2)
3156 return 1;
3158 if (!*gameexp || c == '?') {
3159 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
3160 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
3161 NULL, 0, -1)) == NULL)
3162 return 1;
3164 strncpy(gameexp, tmp, sizeof(gameexp));
3167 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
3168 ? keycount : 1)) ==
3170 return 1;
3172 gindex = n;
3173 d = game[gindex].data;
3175 if (pgn_history_total(game[gindex].hp))
3176 d->mode = MODE_HISTORY;
3178 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3179 update_all(game[gindex]);
3180 return 1;
3181 case 'J':
3182 if (gtotal < 2)
3183 return 1;
3185 /* FIXME field validation
3186 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
3187 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
3188 == NULL)
3189 return 1;
3192 if (!keycount) {
3193 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
3194 NULL, NULL, 0, -1)) == NULL)
3195 return 1;
3197 if (!isinteger(tmp))
3198 return 1;
3200 i = atoi(tmp);
3202 else
3203 i = keycount;
3205 if (--i > gtotal - 1 || i < 0)
3206 return 1;
3208 gindex = i;
3209 d = game[gindex].data;
3210 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3211 update_status_notify(game[gindex], NULL);
3212 update_all(game[gindex]);
3213 return 1;
3214 case 'x':
3215 pushkey = 0;
3217 if (gtotal < 2)
3218 return 1;
3220 if (keycount && delete_count == 0) {
3221 markstart = gindex;
3222 delete_count = keycount;
3223 update_status_notify(game[gindex], "%s (delete)",
3224 status.notify);
3225 return 1;
3228 if (markstart >= 0 && markend >= 0) {
3229 for (i = markstart; i < markend; i++) {
3230 if (toggle_delete_flag(i)) {
3231 update_all(game[gindex]);
3232 return 1;
3236 gindex = (delete_count < 0) ? markstart : i - 1;
3237 update_all(game[gindex]);
3239 else {
3240 if (toggle_delete_flag(gindex))
3241 return 1;
3244 markstart = markend = -1;
3245 delete_count = 0;
3246 update_status_window(game[gindex]);
3247 return 1;
3248 case 'X':
3249 if (gtotal < 2) {
3250 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3251 return 1;
3254 tmp = NULL;
3256 for (i = n = 0; i < gtotal; i++) {
3257 if (TEST_FLAG(game[i].flags, GF_DELETE))
3258 n++;
3261 if (!n)
3262 tmp = GAME_DELETE_GAME_TEXT;
3263 else {
3264 if (n == gtotal) {
3265 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3266 return 1;
3269 tmp = GAME_DELETE_ALL_TEXT;
3272 if (config.deleteprompt) {
3273 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
3274 return 1;
3277 delete_game((!n) ? gindex : -1);
3278 d = game[gindex].data;
3279 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3280 update_all(game[gindex]);
3281 return 1;
3282 case 'T':
3283 edit_save_tags(&game[gindex]);
3284 update_all(game[gindex]);
3285 return 1;
3286 case 't':
3287 edit_tags(game[gindex], d->b, 0);
3288 return 1;
3289 case 'r':
3290 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
3291 BROWSER_PROMPT, file_browser, NULL, '\t',
3292 -1)) == NULL)
3293 return 1;
3295 if ((tmp = word_expand(tmp)) == NULL)
3296 break;
3298 if ((fp = pgn_open(tmp)) == NULL) {
3299 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3300 return 1;
3303 free_userdata();
3305 if (pgn_parse(fp) == E_PGN_ERR) {
3306 del_panel(loadingp);
3307 delwin(loadingw);
3308 loadingw = NULL;
3309 loadingp = NULL;
3310 init_userdata();
3311 update_all(game[gindex]);
3312 return 1;
3315 del_panel(loadingp);
3316 delwin(loadingw);
3317 loadingw = NULL;
3318 loadingp = NULL;
3319 init_userdata();
3320 strncpy(loadfile, tmp, sizeof(loadfile));
3322 if (pgn_history_total(game[gindex].hp))
3323 d->mode = MODE_HISTORY;
3325 d = game[gindex].data;
3326 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3327 update_all(game[gindex]);
3328 return 1;
3329 case 'S':
3330 case 's':
3331 i = -1;
3333 if (gtotal > 1) {
3334 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
3335 GAME_SAVE_MULTI_TEXT);
3337 if (n == 'c')
3338 i = gindex;
3339 else if (n == 'a')
3340 i = -1;
3341 else {
3342 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3343 return 1;
3347 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
3348 BROWSER_PROMPT, file_browser, NULL,
3349 '\t', -1)) == NULL) {
3350 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3351 return 1;
3354 if ((tmp = word_expand(tmp)) == NULL)
3355 break;
3357 if (pgn_is_compressed(tmp)) {
3358 p = tmp + strlen(tmp) - 1;
3360 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3361 *(p-3) != '.') {
3362 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3363 tmp = tfile;
3366 else {
3367 if ((p = strchr(tmp, '.')) != NULL) {
3368 if (strcmp(p, ".pgn") != 0) {
3369 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3370 tmp = tfile;
3373 else {
3374 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3375 tmp = tfile;
3379 if (save_pgn(tmp, i)) {
3380 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3381 return 1;
3384 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3385 update_all(game[gindex]);
3386 return 1;
3387 case KEY_F(1):
3388 n = 0;
3390 switch (d->mode) {
3391 case MODE_PLAY:
3392 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3393 break;
3394 case MODE_HISTORY:
3395 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3396 break;
3397 case MODE_EDIT:
3398 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3399 break;
3400 default:
3401 break;
3404 while (c == KEY_F(1)) {
3405 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3406 mainhelp);
3408 switch (c) {
3409 case 'h':
3410 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3411 break;
3412 case 'p':
3413 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3414 break;
3415 case 'e':
3416 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3417 break;
3418 case 'g':
3419 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3420 break;
3421 default:
3422 break;
3426 return 1;
3427 case 'n':
3428 case 'N':
3429 if (c == 'N') {
3430 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3431 return 1;
3434 if (c == 'n') {
3435 pgn_new_game();
3436 add_custom_tags(&game[gindex].tag);
3437 init_userdata_once(&game[gindex], gindex);
3439 else {
3440 stop_clock();
3441 free_userdata();
3442 pgn_parse(NULL);
3443 add_custom_tags(&game[gindex].tag);
3444 init_userdata();
3445 loadfile[0] = 0;
3448 d->mode = MODE_PLAY;
3449 update_status_notify(game[gindex], NULL);
3450 update_all(game[gindex]);
3451 return 1;
3452 case CTRL('L'):
3453 endwin();
3454 keypad(boardw, TRUE);
3455 refresh_all();
3456 return 1;
3457 case KEY_ESCAPE:
3458 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3459 markend = markstart = 0;
3461 if (keycount) {
3462 keycount = 0;
3463 update_status_notify(game[gindex], NULL);
3466 if (config.validmoves)
3467 pgn_reset_valid_moves(d->b);
3469 return 1;
3470 case '0' ... '9':
3471 n = c - '0';
3473 if (keycount)
3474 keycount = keycount * 10 + n;
3475 else
3476 keycount = n;
3478 update_status_notify(game[gindex], "Repeat %i", keycount);
3479 return -1;
3480 case KEY_UP:
3481 if (d->mode == MODE_HISTORY)
3482 return 0;
3484 if (keycount) {
3485 d->c_row += keycount;
3486 pushkey = '\n';
3488 else
3489 d->c_row++;
3491 if (d->c_row > 8)
3492 d->c_row = 1;
3494 return 1;
3495 case KEY_DOWN:
3496 if (d->mode == MODE_HISTORY)
3497 return 0;
3499 if (keycount) {
3500 d->c_row -= keycount;
3501 pushkey = '\n';
3502 update_status_notify(game[gindex], NULL);
3504 else
3505 d->c_row--;
3507 if (d->c_row < 1)
3508 d->c_row = 8;
3510 return 1;
3511 case KEY_LEFT:
3512 if (d->mode == MODE_HISTORY)
3513 return 0;
3515 if (keycount) {
3516 d->c_col -= keycount;
3517 pushkey = '\n';
3519 else
3520 d->c_col--;
3522 if (d->c_col < 1)
3523 d->c_col = 8;
3525 return 1;
3526 case KEY_RIGHT:
3527 if (d->mode == MODE_HISTORY)
3528 return 0;
3530 if (keycount) {
3531 d->c_col += keycount;
3532 pushkey = '\n';
3534 else
3535 d->c_col++;
3537 if (d->c_col > 8)
3538 d->c_col = 1;
3540 return 1;
3541 case 'e':
3542 if (d->mode != MODE_EDIT && d->mode !=
3543 MODE_PLAY)
3544 return 1;
3546 // Don't edit a running game (for now).
3547 if (pgn_history_total(game[gindex].hp))
3548 return 1;
3550 if (d->mode != MODE_EDIT) {
3551 pgn_board_init_fen(&game[gindex], d->b, NULL);
3552 config.details++;
3553 d->mode = MODE_EDIT;
3554 update_all(game[gindex]);
3555 return 1;
3558 config.details--;
3559 pgn_tag_add(&game[gindex].tag, "FEN",
3560 pgn_game_to_fen(game[gindex], d->b));
3561 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3562 pgn_tag_sort(game[gindex].tag);
3563 d->mode = MODE_PLAY;
3564 update_all(game[gindex]);
3565 return 1;
3566 case 'Q':
3567 quit = 1;
3568 return 1;
3569 case KEY_RESIZE:
3570 do_window_resize();
3571 return 1;
3572 #ifdef DEBUG
3573 case 'D':
3574 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3575 return 1;
3576 #endif
3577 case 0:
3578 default:
3579 break;
3582 return 0;
3585 void game_loop()
3587 int error_recover = 0;
3588 struct userdata_s *d = game[gindex].data;
3590 gindex = gtotal - 1;
3592 if (pgn_history_total(game[gindex].hp))
3593 d->mode = MODE_HISTORY;
3594 else
3595 d->mode = MODE_PLAY;
3597 if (d->mode == MODE_HISTORY) {
3598 pgn_board_update(&game[gindex], d->b,
3599 pgn_history_total(game[gindex].hp));
3602 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3603 movestep = 2;
3604 flushinp();
3605 update_all(game[gindex]);
3606 update_tag_window(game[gindex].tag);
3607 wtimeout(boardw, 70);
3609 while (!quit) {
3610 int c = 0;
3611 int n = 0, i;
3612 char fdbuf[8192] = {0};
3613 int len;
3614 struct timeval tv = {0, 0};
3615 fd_set rfds, wfds;
3617 FD_ZERO(&rfds);
3619 for (i = 0; i < gtotal; i++) {
3620 d = game[i].data;
3622 if (d->engine) {
3623 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3624 if (d->engine->fd[ENGINE_IN_FD] > n)
3625 n = d->engine->fd[ENGINE_IN_FD];
3627 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3630 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3631 if (d->engine->fd[ENGINE_OUT_FD] > n)
3632 n = d->engine->fd[ENGINE_OUT_FD];
3634 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3639 if (n) {
3640 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3641 for (i = 0; i < gtotal; i++) {
3642 d = game[i].data;
3644 if (d->engine) {
3645 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3646 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3647 sizeof(fdbuf));
3649 if (len == -1) {
3650 if (errno != EAGAIN) {
3651 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3652 strerror(errno));
3653 waitpid(d->engine->pid, &n, 0);
3654 free(d->engine);
3655 d->engine = NULL;
3656 break;
3659 else {
3660 if (len)
3661 parse_engine_output(&game[i], fdbuf);
3665 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3666 if (d->engine->queue)
3667 send_engine_command(&game[i]);
3672 else {
3673 if (n == -1)
3674 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3675 /* timeout */
3679 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3680 d->mode = MODE_HISTORY;
3682 d = game[gindex].data;
3683 error_recover = 0;
3684 draw_board(&game[gindex]);
3685 update_all(game[gindex]);
3686 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3687 refresh_all();
3689 if (pushkey)
3690 c = pushkey;
3691 else {
3692 if ((c = wgetch(boardw)) == ERR)
3693 continue;
3696 if (!keycount && status.notify)
3697 update_status_notify(game[gindex], NULL);
3699 if ((n = globalkeys(c)) == 1) {
3700 keycount = 0;
3701 continue;
3703 else if (n == -1)
3704 continue;
3706 switch (d->mode) {
3707 case MODE_EDIT:
3708 editmode_keys(c);
3709 break;
3710 case MODE_PLAY:
3711 if (playmode_keys(c))
3712 continue;
3713 break;
3714 case MODE_HISTORY:
3715 historymode_keys(c);
3716 break;
3717 default:
3718 break;
3721 keycount = 0;
3725 void usage(const char *pn, int ret)
3727 fprintf((ret) ? stderr : stdout, "%s",
3728 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3729 " -p Load PGN file.\n"
3730 " -V Validate a game file.\n"
3731 " -S Validate and output a PGN formatted game.\n"
3732 " -R Like -S but write a reduced PGN formatted game.\n"
3733 " -t Also write custom PGN tags from config file.\n"
3734 " -E Stop processing on file parsing error (overrides config).\n"
3735 " -v Version information.\n"
3736 " -h This help text.\n");
3738 exit(ret);
3741 void cleanup_all()
3743 int i;
3745 stop_clock();
3746 free_userdata();
3747 pgn_free_all();
3748 free(config.engine_cmd);
3749 free(config.pattern);
3750 free(config.ccfile);
3751 free(config.nagfile);
3752 free(config.configfile);
3754 if (config.keys) {
3755 for (i = 0; config.keys[i]; i++)
3756 free(config.keys[i]->str);
3758 free(config.keys);
3761 if (config.einit) {
3762 for (i = 0; config.einit[i]; i++)
3763 free(config.einit[i]);
3765 free(config.einit);
3768 if (config.tag)
3769 pgn_tag_free(config.tag);
3771 if (curses_initialized) {
3772 del_panel(boardp);
3773 del_panel(historyp);
3774 del_panel(statusp);
3775 del_panel(tagp);
3776 delwin(boardw);
3777 delwin(historyw);
3778 delwin(statusw);
3779 delwin(tagw);
3781 if (enginew) {
3782 del_panel(enginep);
3783 delwin(enginew);
3785 if (enginebuf) {
3786 for (i = 0; enginebuf[i]; i++)
3787 free(enginebuf[i]);
3789 free(enginebuf);
3793 endwin();
3797 void catch_signal(int which)
3799 switch (which) {
3800 case SIGALRM:
3801 update_clocks();
3802 break;
3803 case SIGPIPE:
3804 if (which == SIGPIPE && quit)
3805 break;
3807 if (which == SIGPIPE)
3808 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3810 cleanup_all();
3811 exit(EXIT_FAILURE);
3812 break;
3813 case SIGSTOP:
3814 savetty();
3815 break;
3816 case SIGCONT:
3817 resetty();
3818 keypad(boardw, TRUE);
3819 curs_set(0);
3820 cbreak();
3821 noecho();
3822 break;
3823 case SIGINT:
3824 case SIGTERM:
3825 quit = 1;
3826 break;
3827 default:
3828 break;
3832 void loading_progress(long total, long offset)
3834 int n = (100 * (offset / 100) / (total / 100));
3836 if (curses_initialized)
3837 update_loading_window(n);
3838 else {
3839 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3840 fflush(stderr);
3844 static void set_defaults()
3846 set_config_defaults();
3847 filetype = NO_FILE;
3848 pgn_config_set(PGN_PROGRESS, 1024);
3849 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3852 int main(int argc, char *argv[])
3854 int opt;
3855 struct stat st;
3856 char buf[FILENAME_MAX];
3857 char datadir[FILENAME_MAX];
3858 int ret = EXIT_SUCCESS;
3859 int validate_only = 0, validate_and_write = 0;
3860 int write_custom_tags = 0;
3861 FILE *fp;
3862 int i = 0;
3864 if ((config.pwd = getpwuid(getuid())) == NULL)
3865 err(EXIT_FAILURE, "getpwuid()");
3867 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3868 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3869 config.ccfile = strdup(buf);
3870 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3871 config.nagfile = strdup(buf);
3872 snprintf(buf, sizeof(buf), "%s/config", datadir);
3873 config.configfile = strdup(buf);
3875 if (stat(datadir, &st) == -1) {
3876 if (errno == ENOENT) {
3877 if (mkdir(datadir, 0755) == -1)
3878 err(EXIT_FAILURE, "%s", datadir);
3880 else
3881 err(EXIT_FAILURE, "%s", datadir);
3883 stat(datadir, &st);
3886 if (!S_ISDIR(st.st_mode))
3887 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3889 set_defaults();
3891 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3892 switch (opt) {
3893 case 't':
3894 write_custom_tags = 1;
3895 break;
3896 case 'E':
3897 i = 1;
3898 break;
3899 case 'R':
3900 pgn_config_set(PGN_REDUCED, 1);
3901 case 'S':
3902 validate_and_write = 1;
3903 case 'V':
3904 validate_only = 1;
3905 break;
3906 case 'v':
3907 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3908 COPYRIGHT);
3909 exit(EXIT_SUCCESS);
3910 case 'p':
3911 filetype = PGN_FILE;
3912 strncpy(loadfile, optarg, sizeof(loadfile));
3913 break;
3914 case 'h':
3915 default:
3916 usage(argv[0], EXIT_SUCCESS);
3920 if ((validate_only || validate_and_write) && !*loadfile)
3921 usage(argv[0], EXIT_FAILURE);
3923 if (access(config.configfile, R_OK) == 0)
3924 parse_rcfile(config.configfile);
3926 if (i)
3927 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3929 signal(SIGPIPE, catch_signal);
3930 signal(SIGCONT, catch_signal);
3931 signal(SIGSTOP, catch_signal);
3932 signal(SIGINT, catch_signal);
3933 signal(SIGALRM, catch_signal);
3934 signal(SIGTERM, catch_signal);
3936 srandom(getpid());
3938 switch (filetype) {
3939 case PGN_FILE:
3940 if ((fp = pgn_open(loadfile)) == NULL)
3941 err(EXIT_FAILURE, "%s", loadfile);
3943 ret = pgn_parse(fp);
3944 break;
3945 case FEN_FILE:
3946 //ret = parse_fen_file(loadfile);
3947 break;
3948 case EPD_FILE: // Not implemented.
3949 case NO_FILE:
3950 default:
3951 // No file specified. Empty game.
3952 ret = pgn_parse(NULL);
3953 add_custom_tags(&game[gindex].tag);
3954 break;
3957 if (validate_only || validate_and_write) {
3958 if (validate_and_write) {
3959 for (i = 0; i < gtotal; i++) {
3960 if (write_custom_tags)
3961 add_custom_tags(&game[i].tag);
3963 pgn_write(stdout, game[i]);
3967 cleanup_all();
3968 exit(ret);
3970 else if (ret == E_PGN_ERR)
3971 exit(ret);
3973 init_userdata();
3975 if (initscr() == NULL)
3976 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3977 else
3978 curses_initialized = 1;
3980 if (LINES < 24 || COLS < 80) {
3981 endwin();
3982 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3985 if (has_colors() == TRUE && start_color() == OK)
3986 init_color_pairs();
3988 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
3989 boardp = new_panel(boardw);
3990 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
3991 COLS - HISTORY_WIDTH);
3992 historyp = new_panel(historyw);
3993 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
3994 statusp = new_panel(statusw);
3995 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
3996 tagp = new_panel(tagw);
3997 keypad(boardw, TRUE);
3998 // leaveok(boardw, TRUE);
3999 leaveok(tagw, TRUE);
4000 leaveok(statusw, TRUE);
4001 leaveok(historyw, TRUE);
4002 curs_set(0);
4003 cbreak();
4004 noecho();
4005 draw_window_decor();
4006 game_loop();
4007 cleanup_all();
4008 exit(EXIT_SUCCESS);