libchess: A few cleanups. Increased MAX_PGN_NAG from 5 to 8. This
[cboard.git] / src / cboard.c
blobbc24c25d94eef1f1766bfaed9adfca870a64e765
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 = RANKTOINT(*p--);
142 d->c_col = FILETOINT(*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;
1288 struct userdata_s *d;
1290 if (filename[0] != '/' && config.savedirectory) {
1291 if (stat(config.savedirectory, &st) == -1) {
1292 if (errno == ENOENT) {
1293 if (mkdir(config.savedirectory, 0755) == -1) {
1294 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1295 strerror(errno));
1296 return 1;
1299 else {
1300 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory,
1301 strerror(errno));
1302 return 1;
1306 stat(config.savedirectory, &st);
1308 if (!S_ISDIR(st.st_mode)) {
1309 cmessage(ERROR, ANYKEY, "%s: %s", config.savedirectory, E_NOTADIR);
1310 return 1;
1313 snprintf(buf, sizeof(buf), "%s/%s", config.savedirectory, filename);
1314 filename = buf;
1317 if (access(filename, W_OK) == 0) {
1318 c = cmessage(NULL, GAME_SAVE_OVERWRITE_PROMPT,
1319 "%s \"%s\"", E_FILEEXISTS, filename);
1321 switch (c) {
1322 case 'a':
1323 if (pgn_is_compressed(filename) == E_PGN_OK) {
1324 cmessage(NULL, ANYKEY, "%s", E_SAVE_COMPRESS);
1325 return 1;
1328 mode = "a";
1329 break;
1330 case 'o':
1331 mode = "w+";
1332 break;
1333 default:
1334 return 1;
1337 else
1338 mode = "a";
1340 if (command) {
1341 if ((fp = popen(command, "w")) == NULL) {
1342 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1343 return 1;
1346 else {
1347 if ((fp = fopen(filename, mode)) == NULL) {
1348 cmessage(ERROR, ANYKEY, "%s: %s", filename, strerror(errno));
1349 return 1;
1353 for (i = (saveindex == -1) ? 0 : saveindex; i < saveindex_max; i++) {
1354 d = game[i].data;
1355 pgn_write(fp, game[i]);
1356 CLEAR_FLAG(d->flags, CF_MODIFIED);
1359 if (command)
1360 pclose(fp);
1361 else
1362 fclose(fp);
1364 if (saveindex == -1)
1365 strncpy(loadfile, filename, sizeof(loadfile));
1367 return 0;
1370 static int castling_state(GAME *g, BOARD b, int row, int col, int piece, int mod)
1372 if (pgn_piece_to_int(piece) == ROOK && col == 7
1373 && row == 7 &&
1374 (TEST_FLAG(g->flags, GF_WK_CASTLE) || mod) &&
1375 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1376 if (mod)
1377 TOGGLE_FLAG(g->flags, GF_WK_CASTLE);
1378 return 1;
1380 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1381 && row == 7 &&
1382 (TEST_FLAG(g->flags, GF_WQ_CASTLE) || mod) &&
1383 pgn_piece_to_int(b[7][4].icon) == KING && isupper(piece)) {
1384 if (mod)
1385 TOGGLE_FLAG(g->flags, GF_WQ_CASTLE);
1386 return 1;
1388 else if (pgn_piece_to_int(piece) == ROOK && col == 7
1389 && row == 0 &&
1390 (TEST_FLAG(g->flags, GF_BK_CASTLE) || mod) &&
1391 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1392 if (mod)
1393 TOGGLE_FLAG(g->flags, GF_BK_CASTLE);
1394 return 1;
1396 else if (pgn_piece_to_int(piece) == ROOK && col == 0
1397 && row == 0 &&
1398 (TEST_FLAG(g->flags, GF_BQ_CASTLE) || mod) &&
1399 pgn_piece_to_int(b[0][4].icon) == KING && islower(piece)) {
1400 if (mod)
1401 TOGGLE_FLAG(g->flags, GF_BQ_CASTLE);
1402 return 1;
1404 else if (pgn_piece_to_int(piece) == KING && col == 4
1405 && row == 7 &&
1406 (mod || (pgn_piece_to_int(b[7][7].icon) == ROOK &&
1407 TEST_FLAG(g->flags, GF_WK_CASTLE))
1409 (pgn_piece_to_int(b[7][0].icon) == ROOK &&
1410 TEST_FLAG(g->flags, GF_WQ_CASTLE))) && isupper(piece)) {
1411 if (mod) {
1412 if (TEST_FLAG(g->flags, GF_WK_CASTLE) ||
1413 TEST_FLAG(g->flags, GF_WQ_CASTLE))
1414 CLEAR_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1415 else
1416 SET_FLAG(g->flags, GF_WK_CASTLE|GF_WQ_CASTLE);
1418 return 1;
1420 else if (pgn_piece_to_int(piece) == KING && col == 4
1421 && row == 0 &&
1422 (mod || (pgn_piece_to_int(b[0][7].icon) == ROOK &&
1423 TEST_FLAG(g->flags, GF_BK_CASTLE))
1425 (pgn_piece_to_int(b[0][0].icon) == ROOK &&
1426 TEST_FLAG(g->flags, GF_BQ_CASTLE))) && islower(piece)) {
1427 if (mod) {
1428 if (TEST_FLAG(g->flags, GF_BK_CASTLE) ||
1429 TEST_FLAG(g->flags, GF_BQ_CASTLE))
1430 CLEAR_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1431 else
1432 SET_FLAG(g->flags, GF_BK_CASTLE|GF_BQ_CASTLE);
1434 return 1;
1437 return 0;
1440 static void draw_board(GAME *g)
1442 int row, col;
1443 int bcol = 0, brow = 0;
1444 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
1445 int ncols = 0, offset = 1;
1446 unsigned coords_y = 8;
1447 struct userdata_s *d = g->data;
1449 if (d->mode != MODE_PLAY && d->mode != MODE_EDIT)
1450 update_cursor(*g, g->hindex);
1452 for (row = 0; row < maxy; row++) {
1453 bcol = 0;
1455 for (col = 0; col < maxx; col++) {
1456 int attrwhich = -1;
1457 chtype attrs = 0;
1458 unsigned char piece;
1460 if (row == 0 || row == maxy - 2) {
1461 if (col == 0)
1462 mvwaddch(boardw, row, col,
1463 LINE_GRAPHIC((row) ?
1464 ACS_LLCORNER | CP_BOARD_GRAPHICS :
1465 ACS_ULCORNER | CP_BOARD_GRAPHICS));
1466 else if (col == maxx - 2)
1467 mvwaddch(boardw, row, col,
1468 LINE_GRAPHIC((row) ?
1469 ACS_LRCORNER | CP_BOARD_GRAPHICS :
1470 ACS_URCORNER | CP_BOARD_GRAPHICS));
1471 else if (!(col % 4))
1472 mvwaddch(boardw, row, col,
1473 LINE_GRAPHIC((row) ?
1474 ACS_BTEE | CP_BOARD_GRAPHICS :
1475 ACS_TTEE | CP_BOARD_GRAPHICS));
1476 else {
1477 if (col != maxx - 1)
1478 mvwaddch(boardw, row, col,
1479 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1482 continue;
1485 if ((row % 2) && col == maxx - 1 && coords_y) {
1486 wattron(boardw, CP_BOARD_COORDS);
1487 mvwprintw(boardw, row, col, "%d", coords_y--);
1488 wattroff(boardw, CP_BOARD_COORDS);
1489 continue;
1492 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
1493 if (!(row % 2))
1494 mvwaddch(boardw, row, col,
1495 LINE_GRAPHIC((col) ?
1496 ACS_RTEE | CP_BOARD_GRAPHICS :
1497 ACS_LTEE | CP_BOARD_GRAPHICS));
1498 else
1499 mvwaddch(boardw, row, col,
1500 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1502 continue;
1505 if ((row % 2) && !(col % 4) && row != maxy - 1) {
1506 mvwaddch(boardw, row, col,
1507 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
1508 continue;
1511 if (!(col % 4) && row != maxy - 1) {
1512 mvwaddch(boardw, row, col,
1513 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
1514 continue;
1517 if ((row % 2)) {
1518 if ((col % 4)) {
1519 if (ncols++ == 8) {
1520 offset++;
1521 ncols = 1;
1524 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
1525 && (offset % 2)))
1526 attrwhich = BLACK;
1527 else
1528 attrwhich = WHITE;
1530 if (config.validmoves && d->b[brow][bcol].valid) {
1531 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
1532 CP_BOARD_MOVES_BLACK;
1534 else
1535 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
1536 CP_BOARD_BLACK;
1538 if (row == ROWTOMATRIX(d->c_row) && col ==
1539 COLTOMATRIX(d->c_col)) {
1540 attrs = CP_BOARD_CURSOR;
1543 if (row == ROWTOMATRIX(d->sp.srow) &&
1544 col == COLTOMATRIX(d->sp.scol)) {
1545 attrs = CP_BOARD_SELECTED;
1548 if (row == maxy - 1)
1549 attrs = 0;
1551 mvwaddch(boardw, row, col, ' ' | attrs);
1553 if (row == maxy - 1)
1554 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
1555 else {
1556 if (config.details && d->b[row / 2][bcol].enpassant)
1557 piece = 'x';
1558 else
1559 piece = d->b[row / 2][bcol].icon;
1561 if (config.details && castling_state(g, d->b, brow,
1562 bcol, piece, 0))
1563 attrs |= A_REVERSE;
1565 if (g->side == WHITE && isupper(piece))
1566 attrs |= A_BOLD;
1567 else if (g->side == BLACK && islower(piece))
1568 attrs |= A_BOLD;
1570 waddch(boardw, (pgn_piece_to_int(piece) != OPEN_SQUARE) ? piece | attrs : ' ' | attrs);
1572 CLEAR_FLAG(attrs, A_BOLD);
1573 CLEAR_FLAG(attrs, A_REVERSE);
1576 waddch(boardw, ' ' | attrs);
1577 col += 2;
1578 bcol++;
1581 else {
1582 if (col != maxx - 1)
1583 mvwaddch(boardw, row, col,
1584 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
1588 brow = row / 2;
1591 mvwaddch(boardw, maxy - 1, maxx - 2, (config.details) ? '!' : ' ');
1594 void invalid_move(int n, int e, const char *m)
1596 if (curses_initialized)
1597 cmessage(ERROR, ANYKEY, "%s \"%s\" (round #%i)", (e == E_PGN_AMBIGUOUS)
1598 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
1599 else
1600 warnx("%s: %s \"%s\" (round #%i)", loadfile, (e == E_PGN_AMBIGUOUS)
1601 ? E_AMBIGUOUS : E_INVALID_MOVE, m, n);
1604 /* Convert the selected piece to SAN format and validate it. */
1605 static char *board_to_san(GAME *g, BOARD b)
1607 static char str[MAX_SAN_MOVE_LEN + 1], *p;
1608 int piece;
1609 int promo;
1610 struct userdata_s *d = g->data;
1611 int n;
1613 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[d->sp.scol - 1],
1614 d->sp.srow, x_grid_chars[d->sp.col - 1], d->sp.row);
1616 p = str;
1617 piece = pgn_piece_to_int(b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon);
1619 if (piece == PAWN && ((d->sp.row == 8 && g->turn == WHITE) ||
1620 (d->sp.row == 1 && g->turn == BLACK))) {
1621 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
1623 if (pgn_piece_to_int(promo) == -1)
1624 return NULL;
1626 p = str + strlen(str);
1627 *p++ = toupper(promo);
1628 *p = '\0';
1631 p = str;
1633 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1634 if ((n = pgn_parse_move(g, b, &p)) != E_PGN_OK) {
1635 invalid_move(d->n + 1, n, p);
1636 return NULL;
1639 return p;
1642 if ((n = pgn_validate_move(g, b, &p)) != E_PGN_OK) {
1643 invalid_move(d->n + 1, n, p);
1644 return NULL;
1647 return p;
1650 static void update_clock(GAME *g, struct itimerval it)
1652 struct userdata_s *d = g->data;
1653 long n;
1655 if (g->turn == WHITE) {
1656 d->wc.tv_sec += it.it_value.tv_sec;
1657 d->wc.tv_usec += it.it_value.tv_usec;
1659 if (d->wc.tv_usec > 1000000 - 1) {
1660 d->wc.tv_sec += d->wc.tv_usec / 1000000;
1661 d->wc.tv_usec = d->wc.tv_usec % 1000000;
1664 else {
1665 d->bc.tv_sec += it.it_value.tv_sec;
1666 d->bc.tv_usec += it.it_value.tv_usec;
1668 if (d->bc.tv_usec > 1000000 - 1) {
1669 d->bc.tv_sec += d->bc.tv_usec / 1000000;
1670 d->bc.tv_usec = d->bc.tv_usec % 1000000;
1674 d->elapsed = d->wc.tv_sec + d->bc.tv_sec;
1675 n = d->wc.tv_usec + d->bc.tv_usec;
1676 d->elapsed += (n > 1000000 - 1) ? n / 1000000 : 0;
1678 if (TEST_FLAG(d->flags, CF_CLOCK)) {
1679 if (d->elapsed >= d->limit) {
1680 SET_FLAG(g->flags, GF_GAMEOVER);
1681 pgn_tag_add(&g->tag, "Result", "1/2-1/2");
1686 static int move_to_engine(GAME *g, BOARD b)
1688 char *p;
1689 struct userdata_s *d = g->data;
1691 if ((p = board_to_san(g, b)) == NULL)
1692 return 0;
1694 d->sp.srow = d->sp.scol = d->sp.icon = 0;
1696 if (TEST_FLAG(g->flags, GF_GAMEOVER))
1697 d->mode = MODE_HISTORY;
1699 if (TEST_FLAG(d->flags, CF_HUMAN)) {
1700 pgn_history_add(g, p);
1701 pgn_switch_turn(g);
1702 SET_FLAG(d->flags, CF_MODIFIED);
1703 update_all(*g);
1704 return 1;
1707 add_engine_command(g, ENGINE_THINKING, "%s\n", p);
1708 return 1;
1711 static char *clock_to_char(long n)
1713 static char buf[16];
1714 int h = 0, m = 0, s = 0;
1716 h = n / 3600;
1717 m = (n % 3600) / 60;
1718 s = (n % 3600) % 60;
1719 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i", h, m, s);
1720 return buf;
1723 static char *timeval_to_char(struct timeval t)
1725 static char buf[16];
1726 int h = 0, m = 0, s = 0;
1727 int n = t.tv_sec;
1729 h = n / 3600;
1730 m = (n % 3600) / 60;
1731 s = (n % 3600) % 60;
1732 snprintf(buf, sizeof(buf), "%.2i:%.2i:%.2i.%.2i", h, m, s,
1733 (int)t.tv_usec / 10000);
1734 return buf;
1737 void update_status_window(GAME g)
1739 int i = 0;
1740 char *buf;
1741 char tmp[15], *engine, *mode;
1742 int w;
1743 char *p;
1744 int maxy, maxx;
1745 int len;
1746 struct userdata_s *d = g.data;
1748 getmaxyx(statusw, maxy, maxx);
1749 w = maxx - 2 - 8;
1750 len = maxx - 2;
1751 buf = Malloc(len);
1753 *tmp = '\0';
1754 p = tmp;
1756 if (TEST_FLAG(d->flags, CF_DELETE)) {
1757 *p++ = '(';
1758 *p++ = 'x';
1759 i++;
1762 if (TEST_FLAG(g.flags, GF_PERROR)) {
1763 if (!i)
1764 *p++ = '(';
1765 else
1766 *p++ = '/';
1768 *p++ = '!';
1769 i++;
1772 if (TEST_FLAG(d->flags, CF_MODIFIED)) {
1773 if (!i)
1774 *p++ = '(';
1775 else
1776 *p++ = '/';
1778 *p++ = '*';
1779 i++;
1782 if (*tmp != '\0')
1783 *p++ = ')';
1785 *p = '\0';
1787 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
1788 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
1789 snprintf(buf, len, "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
1790 (*tmp) ? tmp : "");
1791 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
1793 switch (d->mode) {
1794 case MODE_HISTORY:
1795 mode = MODE_HISTORY_STR;
1796 break;
1797 case MODE_EDIT:
1798 mode = MODE_EDIT_STR;
1799 break;
1800 case MODE_PLAY:
1801 mode = MODE_PLAY_STR;
1802 break;
1803 default:
1804 mode = UNKNOWN;
1805 break;
1808 snprintf(buf, len - 1, "%*s %s", 7, STATUS_MODE_STR, mode);
1810 if (d->mode == MODE_PLAY) {
1811 if (TEST_FLAG(d->flags, CF_HUMAN))
1812 strncat(buf, " (human/human)", len - 1);
1813 else if (TEST_FLAG(d->flags, CF_ENGINE_LOOP))
1814 strncat(buf, " (engine/engine)", len - 1);
1815 else
1816 strncat(buf, " (human/engine)", len - 1);
1819 mvwprintw(statusw, 4, 1, "%-*s", len, buf);
1821 if (d->engine) {
1822 switch (d->engine->status) {
1823 case ENGINE_THINKING:
1824 engine = ENGINE_PONDER_STR;
1825 break;
1826 case ENGINE_READY:
1827 engine = ENGINE_READY_STR;
1828 break;
1829 case ENGINE_INITIALIZING:
1830 engine = ENGINE_INITIALIZING_STR;
1831 break;
1832 case ENGINE_OFFLINE:
1833 engine = ENGINE_OFFLINE_STR;
1834 break;
1835 default:
1836 engine = UNKNOWN;
1837 break;
1840 else
1841 engine = ENGINE_OFFLINE_STR;
1843 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
1844 wattron(statusw, CP_STATUS_ENGINE);
1845 mvwaddstr(statusw, 5, 9, engine);
1846 wattroff(statusw, CP_STATUS_ENGINE);
1848 mvwprintw(statusw, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
1849 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
1851 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR, w,
1852 clock_to_char((TEST_FLAG(d->flags, CF_CLOCK)) ?
1853 d->limit - d->elapsed : 0));
1855 strncpy(tmp, WHITE_STR, sizeof(tmp));
1856 tmp[0] = toupper(tmp[0]);
1857 mvwprintw(statusw, 8, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->wc));
1859 strncpy(tmp, BLACK_STR, sizeof(tmp));
1860 tmp[0] = toupper(tmp[0]);
1861 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, timeval_to_char(d->bc));
1862 free(buf);
1864 for (i = 1; i < maxx - 4; i++)
1865 mvwprintw(statusw, maxy - 2, i, " ");
1867 if (!status.notify)
1868 status.notify = strdup(GAME_HELP_PROMPT);
1870 wattron(statusw, CP_STATUS_NOTIFY);
1871 mvwprintw(statusw, maxy - 2, CENTERX(maxx, status.notify), "%s",
1872 status.notify);
1873 wattroff(statusw, CP_STATUS_NOTIFY);
1876 void update_history_window(GAME g)
1878 char buf[HISTORY_WIDTH - 1];
1879 HISTORY *h = NULL;
1880 int n, total;
1881 int t = pgn_history_total(g.hp);
1883 n = (g.hindex + 1) / 2;
1885 if (t % 2)
1886 total = (t + 1) / 2;
1887 else
1888 total = t / 2;
1890 if (t)
1891 snprintf(buf, sizeof(buf), "%u %s %u%s", n, N_OF_N_STR, total,
1892 (movestep == 1) ? HISTORY_PLY_STEP : "");
1893 else
1894 strncpy(buf, UNAVAILABLE, sizeof(buf));
1896 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
1897 HISTORY_WIDTH - 13, buf);
1899 h = pgn_history_by_n(g.hp, g.hindex);
1900 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1901 n = 0;
1903 if (h && ((h->comment) || h->nag[0])) {
1904 strncat(buf, " (v", sizeof(buf));
1905 n++;
1908 if (h && h->rav) {
1909 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1910 n++;
1913 if (g.ravlevel) {
1914 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1915 n++;
1918 if (n)
1919 strncat(buf, ")", sizeof(buf));
1921 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
1922 HISTORY_WIDTH - 13, buf);
1924 h = pgn_history_by_n(g.hp, g.hindex - 1);
1925 snprintf(buf, sizeof(buf), "%s", (h && h->move) ? h->move : UNAVAILABLE);
1926 n = 0;
1928 if (h && ((h->comment) || h->nag[0])) {
1929 strncat(buf, " (V", sizeof(buf));
1930 n++;
1933 if (h && h->rav) {
1934 strncat(buf, (n) ? ",+" : " (+", sizeof(buf));
1935 n++;
1938 if (g.ravlevel) {
1939 strncat(buf, (n) ? ",-" : " (-", sizeof(buf));
1940 n++;
1943 if (n)
1944 strncat(buf, ")", sizeof(buf));
1946 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
1947 HISTORY_WIDTH - 13, buf);
1950 void update_tag_window(TAG **t)
1952 int i;
1953 int w = TAG_WIDTH - 10;
1955 for (i = 0; i < 7; i++)
1956 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i]->name, w, t[i]->value);
1959 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1961 int i;
1963 wattron(win, attr);
1965 for (i = 1; i < width - 1; i++)
1966 mvwaddch(win, y, i, ' ');
1968 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1969 wattroff(win, attr);
1972 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1973 chtype battr)
1975 int i;
1977 if (title) {
1978 wattron(win, attr);
1980 for (i = 1; i < width - 1; i++)
1981 mvwaddch(win, 1, i, ' ');
1983 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1984 wattroff(win, attr);
1987 wattron(win, battr);
1988 box(win, ACS_VLINE, ACS_HLINE);
1989 wattroff(win, battr);
1992 void append_enginebuf(char *line)
1994 int i = 0;
1996 if (enginebuf)
1997 for (i = 0; enginebuf[i]; i++);
1999 if (i >= LINES - 3) {
2000 free(enginebuf[0]);
2002 for (i = 0; enginebuf[i+1]; i++)
2003 enginebuf[i] = enginebuf[i+1];
2005 enginebuf[i] = strdup(line);
2007 else {
2008 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
2009 enginebuf[i++] = strdup(line);
2010 enginebuf[i] = NULL;
2014 void update_engine_window()
2016 int i;
2018 if (!enginebuf)
2019 return;
2021 wmove(enginew, 0, 0);
2022 wclrtobot(enginew);
2024 if (enginebuf) {
2025 for (i = 0; enginebuf[i]; i++)
2026 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
2029 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2030 CP_MESSAGE_BORDER);
2033 void toggle_engine_window()
2035 if (!enginew) {
2036 enginew = newwin(LINES, COLS, 0, 0);
2037 enginep = new_panel(enginew);
2038 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2039 CP_MESSAGE_BORDER);
2040 hide_panel(enginep);
2043 if (panel_hidden(enginep)) {
2044 update_engine_window();
2045 top_panel(enginep);
2046 refresh_all();
2048 else {
2049 hide_panel(enginep);
2050 refresh_all();
2054 void refresh_all()
2056 update_panels();
2057 doupdate();
2060 void update_all(GAME g)
2062 update_status_window(g);
2063 update_history_window(g);
2064 update_tag_window(g.tag);
2065 update_engine_window();
2068 static void game_next_prev(GAME g, int n, int count)
2070 if (gtotal < 2)
2071 return;
2073 if (n == 1) {
2074 if (gindex + count > gtotal - 1) {
2075 if (count != 1)
2076 gindex = gtotal - 1;
2077 else
2078 gindex = 0;
2080 else
2081 gindex += count;
2083 else {
2084 if (gindex - count < 0) {
2085 if (count != 1)
2086 gindex = 0;
2087 else
2088 gindex = gtotal - 1;
2090 else
2091 gindex -= count;
2095 static void delete_game(int which)
2097 GAME *g = NULL;
2098 int gi = 0;
2099 int i;
2100 struct userdata_s *d;
2102 for (i = 0; i < gtotal; i++) {
2103 d = game[i].data;
2105 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
2106 pgn_free(game[i]);
2107 continue;
2110 g = Realloc(g, (gi + 1) * sizeof(GAME));
2111 memcpy(&g[gi], &game[i], sizeof(GAME));
2112 g[gi].tag = game[i].tag;
2113 g[gi].history = game[i].history;
2114 g[gi].hp = game[i].hp;
2115 gi++;
2118 game = g;
2119 gtotal = gi;
2121 if (which != -1) {
2122 if (which + 1 >= gtotal)
2123 gindex = gtotal - 1;
2124 else
2125 gindex = which;
2127 else
2128 gindex = gtotal - 1;
2130 game[gindex].hp = game[gindex].history;
2133 static int find_move_exp(GAME g, const char *str, int init, int which,
2134 int count)
2136 int i;
2137 int ret;
2138 static regex_t r;
2139 static int firstrun = 1;
2140 char errbuf[255];
2141 int incr;
2142 int found;
2144 if (init) {
2145 if (!firstrun)
2146 regfree(&r);
2148 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2149 regerror(ret, &r, errbuf, sizeof(errbuf));
2150 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2151 return -1;
2154 firstrun = 1;
2157 incr = (which == 0) ? -1 : 1;
2159 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2160 if (i == g.hindex - 1)
2161 break;
2163 if (i >= pgn_history_total(g.hp))
2164 i = 0;
2165 else if (i < 0)
2166 i = pgn_history_total(g.hp) - 1;
2168 // FIXME RAV
2169 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2171 if (ret == 0) {
2172 if (count == ++found) {
2173 return i + 1;
2176 else {
2177 if (ret != REG_NOMATCH) {
2178 regerror(ret, &r, errbuf, sizeof(errbuf));
2179 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2180 return -1;
2185 return -1;
2188 static int toggle_delete_flag(int n)
2190 int i, x;
2191 struct userdata_s *d = game[n].data;
2193 TOGGLE_FLAG(d->flags, CF_DELETE);
2194 gindex = n;
2195 update_all(game[gindex]);
2197 for (i = x = 0; i < gtotal; i++) {
2198 d = game[i].data;
2200 if (TEST_FLAG(d->flags, CF_DELETE))
2201 x++;
2204 if (x == gtotal) {
2205 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2206 d = game[n].data;
2207 CLEAR_FLAG(d->flags, CF_DELETE);
2208 return 1;
2211 return 0;
2214 static void edit_save_tags(GAME *g)
2216 TAG **t;
2217 struct userdata_s *d = g->data;
2219 if ((t = edit_tags(*g, d->b, 1)) == NULL)
2220 return;
2222 pgn_tag_free(g->tag);
2223 g->tag = t;
2224 SET_FLAG(d->flags, CF_MODIFIED);
2225 pgn_tag_sort(g->tag);
2228 static int find_game_exp(char *str, int which, int count)
2230 char *nstr = NULL, *exp = NULL;
2231 regex_t nexp, vexp;
2232 int ret = -1;
2233 int g = 0;
2234 char buf[255], *tmp;
2235 char errbuf[255];
2236 int found = 0;
2237 int incr = (which == 0) ? -(1) : 1;
2239 strncpy(buf, str, sizeof(buf));
2240 tmp = buf;
2242 if (strstr(tmp, ":") != NULL) {
2243 nstr = strsep(&tmp, ":");
2245 if ((ret = regcomp(&nexp, nstr,
2246 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2247 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2248 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2249 ret = g = -1;
2250 goto cleanup;
2254 exp = tmp;
2256 if (exp == NULL)
2257 goto cleanup;
2259 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2260 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2261 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2262 ret = -1;
2263 goto cleanup;
2266 ret = -1;
2268 for (g = gindex + incr, found = 0; ; g += incr) {
2269 int t;
2271 if (g == gindex)
2272 break;
2274 if (g == gtotal)
2275 g = 0;
2276 else if (g < 0)
2277 g = gtotal - 1;
2279 for (t = 0; game[g].tag[t]; t++) {
2280 if (nstr) {
2281 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2282 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2283 if (count == ++found) {
2284 ret = g;
2285 goto cleanup;
2290 else {
2291 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2292 if (count == ++found) {
2293 ret = g;
2294 goto cleanup;
2300 ret = -1;
2303 cleanup:
2304 if (nstr)
2305 regfree(&nexp);
2307 if (g != -1)
2308 regfree(&vexp);
2310 return ret;
2314 * Updates the notification line in the status window then refreshes the
2315 * status window.
2317 void update_status_notify(GAME g, char *fmt, ...)
2319 va_list ap;
2320 #ifdef HAVE_VASPRINTF
2321 char *line;
2322 #else
2323 char line[COLS];
2324 #endif
2326 if (!fmt) {
2327 if (status.notify) {
2328 free(status.notify);
2329 status.notify = NULL;
2331 if (curses_initialized)
2332 update_status_window(g);
2335 return;
2338 va_start(ap, fmt);
2339 #ifdef HAVE_VASPRINTF
2340 vasprintf(&line, fmt, ap);
2341 #else
2342 vsnprintf(line, sizeof(line), fmt, ap);
2343 #endif
2344 va_end(ap);
2346 if (status.notify)
2347 free(status.notify);
2349 status.notify = strdup(line);
2351 #ifdef HAVE_VASPRINTF
2352 free(line);
2353 #endif
2354 if (curses_initialized)
2355 update_status_window(g);
2358 int rav_next_prev(GAME *g, BOARD b, int n)
2360 // Next RAV.
2361 if (n) {
2362 if ((!g->ravlevel && g->hp[g->hindex - 1]->rav == NULL) ||
2363 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
2364 return 1;
2366 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2367 g->rav[g->ravlevel].hp = g->hp;
2368 g->rav[g->ravlevel].flags = g->flags;
2369 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2370 g->rav[g->ravlevel].hindex = g->hindex;
2371 g->hp = (!g->ravlevel) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav;
2372 g->hindex = 0;
2373 g->ravlevel++;
2374 pgn_board_update(g, b, g->hindex + 1);
2375 return 0;
2378 if (g->ravlevel - 1 < 0)
2379 return 1;
2381 // Previous RAV.
2382 g->ravlevel--;
2383 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2384 free(g->rav[g->ravlevel].fen);
2385 g->hp = g->rav[g->ravlevel].hp;
2386 g->flags = g->rav[g->ravlevel].flags;
2387 g->hindex = g->rav[g->ravlevel].hindex;
2388 return 0;
2391 static void draw_window_decor()
2393 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2394 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2395 wbkgd(boardw, CP_BOARD_WINDOW);
2396 wbkgd(statusw, CP_STATUS_WINDOW);
2397 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2398 CP_STATUS_TITLE, CP_STATUS_BORDER);
2399 wbkgd(tagw, CP_TAG_WINDOW);
2400 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2401 CP_TAG_BORDER);
2402 wbkgd(historyw, CP_HISTORY_WINDOW);
2403 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2404 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2407 static void do_window_resize()
2409 if (LINES < 24 || COLS < 80)
2410 return;
2412 resizeterm(LINES, COLS);
2413 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2414 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2415 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2416 wmove(historyw, 0, 0);
2417 wclrtobot(historyw);
2418 wmove(tagw, 0, 0);
2419 wclrtobot(tagw);
2420 wmove(statusw, 0, 0);
2421 wclrtobot(statusw);
2422 draw_window_decor();
2423 update_all(game[gindex]);
2426 void stop_clock()
2428 memset(&clock_timer, 0, sizeof(struct itimerval));
2429 setitimer(ITIMER_REAL, &clock_timer, NULL);
2432 void start_clock()
2434 if (clock_timer.it_interval.tv_usec)
2435 return;
2437 clock_timer.it_value.tv_sec = 0;
2438 clock_timer.it_value.tv_usec = 100000;
2439 clock_timer.it_interval.tv_sec = 0;
2440 clock_timer.it_interval.tv_usec = 100000;
2441 setitimer(ITIMER_REAL, &clock_timer, NULL);
2444 static void update_clocks()
2446 int i;
2447 struct userdata_s *d;
2448 struct itimerval it;
2450 getitimer(ITIMER_REAL, &it);
2452 for (i = 0; i < gtotal; i++) {
2453 d = game[i].data;
2455 if (d->mode == MODE_PLAY) {
2456 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
2457 continue;
2458 else if (d->paused == -1) {
2459 if (game[i].side == game[i].turn) {
2460 d->paused = 1;
2461 continue;
2465 update_clock(&game[i], it);
2470 static int init_chess_engine(GAME *g)
2472 struct userdata_s *d = g->data;
2473 int w, x;
2475 if (start_chess_engine(g) > 0) {
2476 d->sp.icon = 0;
2477 return 1;
2480 x = pgn_tag_find(g->tag, "FEN");
2481 w = pgn_tag_find(g->tag, "SetUp");
2483 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
2484 (x >= 0 && w == -1))
2485 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
2486 else
2487 add_engine_command(g, ENGINE_READY, "setboard %s\n",
2488 pgn_game_to_fen(*g, d->b));
2490 return 0;
2493 static int parse_clock_input(struct userdata_s *d, char *str)
2495 char *p = str;
2496 long n = 0;
2497 int t = 0;
2498 int plus = 0;
2500 if (*p == '+') {
2501 plus = 1;
2502 p++;
2505 while (*p) {
2506 if (isdigit(*p)) {
2507 t = atoi(p);
2509 while (isdigit(*p))
2510 p++;
2512 continue;
2515 if (!t && *p != ' ')
2516 return 1;
2518 switch (*p) {
2519 case 'H':
2520 case 'h':
2521 n += t * (60 * 60);
2522 t = 0;
2523 break;
2524 case 'M':
2525 case 'm':
2526 n += t * 60;
2527 t = 0;
2528 break;
2529 case 'S':
2530 case 's':
2531 n += t;
2532 t = 0;
2533 break;
2534 case ' ':
2535 t = 0;
2536 break;
2537 default:
2538 return 1;
2541 p++;
2544 if (t)
2545 n += t;
2547 if (!n) {
2548 d->limit = 0;
2549 CLEAR_FLAG(d->flags, CF_CLOCK);
2551 else {
2552 SET_FLAG(d->flags, CF_CLOCK);
2554 if (plus)
2555 d->limit += n;
2556 else
2557 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
2560 return 0;
2563 static void historymode_keys(chtype);
2564 static int playmode_keys(chtype c)
2566 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2567 struct userdata_s *d = game[gindex].data;
2568 int editmode = (d->mode == MODE_EDIT) ? 1 : 0;
2569 chtype p;
2570 int w, x;
2571 char *tmp;
2573 switch (c) {
2574 case 'C':
2575 if ((tmp = get_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL,
2576 NULL, 0, -1)) == NULL)
2577 break;
2579 if (parse_clock_input(d, tmp))
2580 cmessage(ERROR, ANYKEY, "Invalid time specification");
2581 break;
2582 case 'H':
2583 TOGGLE_FLAG(d->flags, CF_HUMAN);
2585 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2586 pgn_history_total(game[gindex].hp)) {
2587 if (init_chess_engine(&game[gindex]))
2588 break;
2591 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2593 if (d->engine)
2594 d->engine->status = ENGINE_READY;
2596 update_all(game[gindex]);
2597 break;
2598 case 'E':
2599 if (!d)
2600 break;
2602 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2603 CLEAR_FLAG(d->flags, CF_HUMAN);
2605 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2606 pgn_board_update(&game[gindex], d->b,
2607 pgn_history_total(game[gindex].hp));
2608 add_engine_command(&game[gindex], ENGINE_READY,
2609 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2612 update_all(game[gindex]);
2613 break;
2614 case '|':
2615 if (!d->engine)
2616 break;
2618 if (d->engine->status == ENGINE_OFFLINE)
2619 break;
2621 x = d->engine->status;
2623 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2624 send_to_engine(&game[gindex], -1, "%s\n", tmp);
2625 d->engine->status = x;
2626 break;
2627 case '\015':
2628 case '\n':
2629 pushkey = keycount = 0;
2630 update_status_notify(game[gindex], NULL);
2632 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2633 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2634 beep();
2635 break;
2638 if (!d->sp.icon)
2639 break;
2641 d->sp.row = d->c_row;
2642 d->sp.col = d->c_col;
2644 if (editmode) {
2645 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
2646 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
2647 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2648 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2649 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2650 break;
2653 if (move_to_engine(&game[gindex], d->b)) {
2654 if (config.validmoves)
2655 pgn_reset_valid_moves(d->b);
2657 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2658 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2659 SET_FLAG(d->flags, CF_MODIFIED);
2662 d->paused = 0;
2665 break;
2666 case ' ':
2667 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2668 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2669 if (init_chess_engine(&game[gindex]))
2670 break;
2673 if (d->sp.icon || (!editmode && d->engine &&
2674 d->engine->status == ENGINE_THINKING)) {
2675 beep();
2676 break;
2679 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
2680 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
2682 if (d->sp.icon == ' ') {
2683 d->sp.icon = 0;
2684 break;
2687 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2688 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2689 if (pgn_history_total(game[gindex].hp)) {
2690 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2691 d->sp.icon = 0;
2692 break;
2694 else {
2695 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR)
2696 break;
2698 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2699 pgn_switch_turn(&game[gindex]);
2701 if (game[gindex].side != BLACK)
2702 pgn_switch_side(&game[gindex]);
2706 d->sp.srow = d->c_row;
2707 d->sp.scol = d->c_col;
2709 if (!editmode && config.validmoves)
2710 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2712 if (!editmode) {
2713 CLEAR_FLAG(d->flags, CF_NEW);
2714 start_clock();
2717 break;
2718 case 'w':
2719 pgn_switch_side(&game[gindex]);
2720 pgn_switch_turn(&game[gindex]);
2721 add_engine_command(&game[gindex], -1,
2722 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2723 update_status_window(game[gindex]);
2724 break;
2725 case 'u':
2726 if (!pgn_history_total(game[gindex].hp))
2727 break;
2729 if (d->engine && d->engine->status == ENGINE_READY) {
2730 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2731 d->engine->status = ENGINE_READY;
2734 game[gindex].hindex -= 2;
2735 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2736 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2737 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2738 update_history_window(game[gindex]);
2739 break;
2740 case 'a':
2741 historymode_keys(c);
2742 break;
2743 case 'd':
2744 config.details = (config.details) ? 0 : 1;
2745 break;
2746 case 'p':
2747 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex].turn !=
2748 game[gindex].side) {
2749 d->paused = -1;
2750 break;
2753 d->paused = (d->paused) ? 0 : 1;
2754 break;
2755 case 'g':
2756 if (TEST_FLAG(d->flags, CF_HUMAN))
2757 break;
2759 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2760 if (init_chess_engine(&game[gindex]))
2761 break;
2764 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2765 break;
2766 default:
2767 if (!d->engine)
2768 break;
2770 if (config.keys) {
2771 for (x = 0; config.keys[x]; x++) {
2772 if (config.keys[x]->c == c) {
2773 switch (config.keys[x]->type) {
2774 case KEY_DEFAULT:
2775 add_engine_command(&game[gindex], -1, "%s\n",
2776 config.keys[x]->str);
2777 break;
2778 case KEY_SET:
2779 if (!keycount)
2780 break;
2782 add_engine_command(&game[gindex], -1,
2783 "%s %i\n", config.keys[x]->str, keycount);
2784 keycount = 0;
2785 break;
2786 case KEY_REPEAT:
2787 if (!keycount)
2788 break;
2790 for (w = 0; w < keycount; w++)
2791 add_engine_command(&game[gindex], -1,
2792 "%s\n", config.keys[x]->str);
2793 keycount = 0;
2794 break;
2799 update_status_notify(game[gindex], NULL);
2801 break;
2804 return 0;
2807 static void editmode_keys(chtype c)
2809 struct userdata_s *d = game[gindex].data;
2811 switch (c) {
2812 case '\015':
2813 case '\n':
2814 case ' ':
2815 playmode_keys(c);
2816 break;
2817 case 'd':
2818 if (d->sp.icon)
2819 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2820 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2821 else
2822 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2823 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2825 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2826 break;
2827 case 'w':
2828 pgn_switch_turn(&game[gindex]);
2829 update_all(game[gindex]);
2830 break;
2831 case 'c':
2832 castling_state(&game[gindex], d->b, RANKTOBOARD(d->c_row),
2833 FILETOBOARD(d->c_col),
2834 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2835 break;
2836 case 'i':
2837 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2838 GAME_EDIT_TEXT);
2840 if (pgn_piece_to_int(c) == -1)
2841 break;
2843 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = c;
2844 break;
2845 case 'p':
2846 if (d->c_row == 6 || d->c_row == 3) {
2847 pgn_reset_enpassant(d->b);
2848 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2850 break;
2851 default:
2852 break;
2856 static void historymode_keys(chtype c)
2858 int n, len;
2859 char *tmp, *buf;
2860 static char moveexp[255] = {0};
2861 struct userdata_s *d = game[gindex].data;
2863 switch (c) {
2864 case 'd':
2865 config.details = (config.details) ? 0 : 1;
2866 break;
2867 case ' ':
2868 movestep = (movestep == 1) ? 2 : 1;
2869 update_history_window(game[gindex]);
2870 break;
2871 case KEY_UP:
2872 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2873 config.jumpcount * keycount * movestep :
2874 config.jumpcount * movestep);
2875 update_all(game[gindex]);
2876 break;
2877 case KEY_DOWN:
2878 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2879 config.jumpcount * keycount * movestep :
2880 config.jumpcount * movestep);
2881 update_all(game[gindex]);
2882 break;
2883 case KEY_LEFT:
2884 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2885 keycount * movestep : movestep);
2886 update_all(game[gindex]);
2887 break;
2888 case KEY_RIGHT:
2889 pgn_history_next(&game[gindex], d->b, (keycount) ?
2890 keycount * movestep : movestep);
2891 update_all(game[gindex]);
2892 break;
2893 case 'a':
2894 n = game[gindex].hindex;
2896 if (n && game[gindex].hp[n - 1]->move)
2897 n--;
2898 else
2899 break;
2901 buf = Malloc(COLS);
2902 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2903 game[gindex].hp[n]->move);
2905 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2906 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2907 -1);
2908 free(buf);
2910 if (!tmp && (!game[gindex].hp[n]->comment ||
2911 !*game[gindex].hp[n]->comment))
2912 break;
2913 else if (tmp && game[gindex].hp[n]->comment) {
2914 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2915 break;
2918 len = (tmp) ? strlen(tmp) + 1 : 1;
2919 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2920 len);
2921 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2922 SET_FLAG(d->flags, CF_MODIFIED);
2923 update_all(game[gindex]);
2924 break;
2925 case ']':
2926 case '[':
2927 case '/':
2928 if (pgn_history_total(game[gindex].hp) < 2)
2929 break;
2931 n = 0;
2933 if (!*moveexp || c == '/') {
2934 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2935 break;
2937 strncpy(moveexp, tmp, sizeof(moveexp));
2938 n = 1;
2941 if ((n = find_move_exp(game[gindex], moveexp, n,
2942 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2943 == -1)
2944 break;
2946 game[gindex].hindex = n;
2947 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2948 update_all(game[gindex]);
2949 break;
2950 case 'v':
2951 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2952 break;
2953 case 'V':
2954 if (game[gindex].hindex - 1 >= 0)
2955 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2956 break;
2957 case '-':
2958 case '+':
2959 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2960 update_all(game[gindex]);
2961 break;
2962 case 'j':
2963 if (pgn_history_total(game[gindex].hp) < 2)
2964 break;
2966 /* FIXME field validation
2967 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2968 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2969 game[gindex].htotal)) == NULL)
2970 break;
2973 if (!keycount) {
2974 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2975 NULL, NULL, NULL, 0, -1)) == NULL)
2976 break;
2978 if (!isinteger(tmp))
2979 break;
2981 n = atoi(tmp);
2983 else
2984 n = keycount;
2986 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2987 break;
2989 keycount = 0;
2990 update_status_notify(game[gindex], NULL);
2991 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2992 pgn_board_update(&game[gindex], d->b,
2993 game[gindex].hindex);
2994 update_all(game[gindex]);
2995 break;
2996 default:
2997 break;
3001 static void free_userdata()
3003 int i;
3005 for (i = 0; i < gtotal; i++) {
3006 struct userdata_s *d;
3008 if (game[i].data) {
3009 d = game[i].data;
3011 if (d->engine) {
3012 stop_engine(&game[i]);
3013 free(d->engine);
3016 free(game[i].data);
3017 game[i].data = NULL;
3022 void update_loading_window(int n)
3024 if (!loadingw) {
3025 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
3026 loadingp = new_panel(loadingw);
3027 wbkgd(loadingw, CP_MESSAGE_WINDOW);
3030 wmove(loadingw, 0, 0);
3031 wclrtobot(loadingw);
3032 wattron(loadingw, CP_MESSAGE_BORDER);
3033 box(loadingw, ACS_VLINE, ACS_HLINE);
3034 wattroff(loadingw, CP_MESSAGE_BORDER);
3035 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
3036 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
3037 gtotal);
3038 refresh_all();
3041 static void init_userdata_once(GAME *g, int n)
3043 struct userdata_s *d = NULL;
3045 d = Calloc(1, sizeof(struct userdata_s));
3046 d->n = n;
3047 d->c_row = 2, d->c_col = 5;
3048 SET_FLAG(d->flags, CF_NEW);
3049 g->data = d;
3051 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
3052 pgn_board_init(d->b);
3055 void init_userdata()
3057 int i;
3059 for (i = 0; i < gtotal; i++)
3060 init_userdata_once(&game[i], i);
3063 void fix_marks(int *start, int *end)
3065 int i;
3067 *start = (*start < 0) ? 0 : *start;
3068 *end = (*end < 0) ? 0 : *end;
3070 if (*start > *end) {
3071 i = *start;
3072 *start = *end;
3073 *end = i + 1;
3076 *end = (*end > gtotal) ? gtotal : *end;
3079 // Global and other keys.
3080 static int globalkeys(chtype c)
3082 static char gameexp[255] = {0};
3083 FILE *fp;
3084 char *tmp, *p;
3085 int n, i;
3086 char tfile[FILENAME_MAX];
3087 struct userdata_s *d = game[gindex].data;
3089 switch (c) {
3090 case 'W':
3091 toggle_engine_window();
3092 break;
3093 case KEY_F(10):
3094 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
3095 "and %i color pairs\nCopyright 2002-2006 %s",
3096 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
3097 COLOR_PAIRS, PACKAGE_BUGREPORT);
3098 break;
3099 case 'h':
3100 if (d->mode != MODE_HISTORY) {
3101 if (!pgn_history_total(game[gindex].hp) ||
3102 (d->engine && d->engine->status == ENGINE_THINKING))
3103 return 1;
3105 d->mode = MODE_HISTORY;
3106 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3107 update_all(game[gindex]);
3108 return 1;
3111 // FIXME Resuming from previous history could append to a RAV.
3112 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
3113 if (!pushkey) {
3114 if ((c = message(NULL, YESNO, "%s",
3115 GAME_RESUME_HISTORY_TEXT)) != 'y')
3116 return 1;
3118 pgn_history_free(game[gindex].hp, game[gindex].hindex);
3119 pgn_board_update(&game[gindex], d->b,
3120 pgn_history_total(game[gindex].hp));
3122 if (!TEST_FLAG(d->flags, CF_HUMAN))
3123 add_engine_command(&game[gindex], ENGINE_READY,
3124 "setboard %s\n", pgn_game_to_fen(game[gindex],
3125 d->b));
3128 else {
3129 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3130 return 1;
3133 pushkey = 0;
3134 d->mode = MODE_PLAY;
3135 update_all(game[gindex]);
3136 return 1;
3137 case '>':
3138 case '<':
3139 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
3140 keycount : 1);
3141 d = game[gindex].data;
3143 if (delete_count) {
3144 if (c == '>') {
3145 markend = markstart + delete_count;
3146 delete_count = 0;
3148 else {
3149 markend = markstart - delete_count;
3150 delete_count = -1; // to fix gindex in the other direction
3153 pushkey = 'x';
3154 fix_marks(&markstart, &markend);
3157 if (d->mode != MODE_EDIT)
3158 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3160 update_status_notify(game[gindex], NULL);
3161 update_all(game[gindex]);
3162 return 1;
3163 case '}':
3164 case '{':
3165 case '?':
3166 if (gtotal < 2)
3167 return 1;
3169 if (!*gameexp || c == '?') {
3170 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
3171 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
3172 NULL, 0, -1)) == NULL)
3173 return 1;
3175 strncpy(gameexp, tmp, sizeof(gameexp));
3178 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
3179 ? keycount : 1)) ==
3181 return 1;
3183 gindex = n;
3184 d = game[gindex].data;
3186 if (pgn_history_total(game[gindex].hp))
3187 d->mode = MODE_HISTORY;
3189 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3190 update_all(game[gindex]);
3191 return 1;
3192 case 'J':
3193 if (gtotal < 2)
3194 return 1;
3196 /* FIXME field validation
3197 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
3198 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
3199 == NULL)
3200 return 1;
3203 if (!keycount) {
3204 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
3205 NULL, NULL, 0, -1)) == NULL)
3206 return 1;
3208 if (!isinteger(tmp))
3209 return 1;
3211 i = atoi(tmp);
3213 else
3214 i = keycount;
3216 if (--i > gtotal - 1 || i < 0)
3217 return 1;
3219 gindex = i;
3220 d = game[gindex].data;
3221 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3222 update_status_notify(game[gindex], NULL);
3223 update_all(game[gindex]);
3224 return 1;
3225 case 'x':
3226 pushkey = 0;
3228 if (gtotal < 2)
3229 return 1;
3231 if (keycount && delete_count == 0) {
3232 markstart = gindex;
3233 delete_count = keycount;
3234 update_status_notify(game[gindex], "%s (delete)",
3235 status.notify);
3236 return 1;
3239 if (markstart >= 0 && markend >= 0) {
3240 for (i = markstart; i < markend; i++) {
3241 if (toggle_delete_flag(i)) {
3242 update_all(game[gindex]);
3243 return 1;
3247 gindex = (delete_count < 0) ? markstart : i - 1;
3248 update_all(game[gindex]);
3250 else {
3251 if (toggle_delete_flag(gindex))
3252 return 1;
3255 markstart = markend = -1;
3256 delete_count = 0;
3257 update_status_window(game[gindex]);
3258 return 1;
3259 case 'X':
3260 if (gtotal < 2) {
3261 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3262 return 1;
3265 tmp = NULL;
3267 for (i = n = 0; i < gtotal; i++) {
3268 d = game[i].data;
3270 if (TEST_FLAG(d->flags, CF_DELETE))
3271 n++;
3274 if (!n)
3275 tmp = GAME_DELETE_GAME_TEXT;
3276 else {
3277 if (n == gtotal) {
3278 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3279 return 1;
3282 tmp = GAME_DELETE_ALL_TEXT;
3285 if (config.deleteprompt) {
3286 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
3287 return 1;
3290 delete_game((!n) ? gindex : -1);
3291 d = game[gindex].data;
3292 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3293 update_all(game[gindex]);
3294 return 1;
3295 case 'T':
3296 edit_save_tags(&game[gindex]);
3297 update_all(game[gindex]);
3298 return 1;
3299 case 't':
3300 edit_tags(game[gindex], d->b, 0);
3301 return 1;
3302 case 'r':
3303 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
3304 BROWSER_PROMPT, file_browser, NULL, '\t',
3305 -1)) == NULL)
3306 return 1;
3308 if ((tmp = word_expand(tmp)) == NULL)
3309 break;
3311 if ((fp = pgn_open(tmp)) == NULL) {
3312 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3313 return 1;
3316 free_userdata();
3318 if (pgn_parse(fp) == E_PGN_ERR) {
3319 del_panel(loadingp);
3320 delwin(loadingw);
3321 loadingw = NULL;
3322 loadingp = NULL;
3323 init_userdata();
3324 update_all(game[gindex]);
3325 return 1;
3328 del_panel(loadingp);
3329 delwin(loadingw);
3330 loadingw = NULL;
3331 loadingp = NULL;
3332 init_userdata();
3333 strncpy(loadfile, tmp, sizeof(loadfile));
3335 if (pgn_history_total(game[gindex].hp))
3336 d->mode = MODE_HISTORY;
3338 d = game[gindex].data;
3339 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3340 update_all(game[gindex]);
3341 return 1;
3342 case 'S':
3343 case 's':
3344 i = -1;
3346 if (gtotal > 1) {
3347 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
3348 GAME_SAVE_MULTI_TEXT);
3350 if (n == 'c')
3351 i = gindex;
3352 else if (n == 'a')
3353 i = -1;
3354 else {
3355 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3356 return 1;
3360 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
3361 BROWSER_PROMPT, file_browser, NULL,
3362 '\t', -1)) == NULL) {
3363 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3364 return 1;
3367 if ((tmp = word_expand(tmp)) == NULL)
3368 break;
3370 if (pgn_is_compressed(tmp)) {
3371 p = tmp + strlen(tmp) - 1;
3373 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3374 *(p-3) != '.') {
3375 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3376 tmp = tfile;
3379 else {
3380 if ((p = strchr(tmp, '.')) != NULL) {
3381 if (strcmp(p, ".pgn") != 0) {
3382 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3383 tmp = tfile;
3386 else {
3387 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3388 tmp = tfile;
3392 if (save_pgn(tmp, i)) {
3393 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3394 return 1;
3397 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3398 update_all(game[gindex]);
3399 return 1;
3400 case KEY_F(1):
3401 n = 0;
3403 switch (d->mode) {
3404 case MODE_PLAY:
3405 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3406 break;
3407 case MODE_HISTORY:
3408 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3409 break;
3410 case MODE_EDIT:
3411 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3412 break;
3413 default:
3414 break;
3417 while (c == KEY_F(1)) {
3418 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3419 mainhelp);
3421 switch (c) {
3422 case 'h':
3423 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3424 break;
3425 case 'p':
3426 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3427 break;
3428 case 'e':
3429 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3430 break;
3431 case 'g':
3432 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3433 break;
3434 default:
3435 break;
3439 return 1;
3440 case 'n':
3441 case 'N':
3442 if (c == 'N') {
3443 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3444 return 1;
3447 if (c == 'n') {
3448 pgn_new_game();
3449 add_custom_tags(&game[gindex].tag);
3450 init_userdata_once(&game[gindex], gindex);
3452 else {
3453 stop_clock();
3454 free_userdata();
3455 pgn_parse(NULL);
3456 add_custom_tags(&game[gindex].tag);
3457 init_userdata();
3458 loadfile[0] = 0;
3461 d->mode = MODE_PLAY;
3462 update_status_notify(game[gindex], NULL);
3463 update_all(game[gindex]);
3464 return 1;
3465 case CTRL('L'):
3466 endwin();
3467 keypad(boardw, TRUE);
3468 refresh_all();
3469 return 1;
3470 case KEY_ESCAPE:
3471 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3472 markend = markstart = 0;
3474 if (keycount) {
3475 keycount = 0;
3476 update_status_notify(game[gindex], NULL);
3479 if (config.validmoves)
3480 pgn_reset_valid_moves(d->b);
3482 return 1;
3483 case '0' ... '9':
3484 n = c - '0';
3486 if (keycount)
3487 keycount = keycount * 10 + n;
3488 else
3489 keycount = n;
3491 update_status_notify(game[gindex], "Repeat %i", keycount);
3492 return -1;
3493 case KEY_UP:
3494 if (d->mode == MODE_HISTORY)
3495 return 0;
3497 if (keycount) {
3498 d->c_row += keycount;
3499 pushkey = '\n';
3501 else
3502 d->c_row++;
3504 if (d->c_row > 8)
3505 d->c_row = 1;
3507 return 1;
3508 case KEY_DOWN:
3509 if (d->mode == MODE_HISTORY)
3510 return 0;
3512 if (keycount) {
3513 d->c_row -= keycount;
3514 pushkey = '\n';
3515 update_status_notify(game[gindex], NULL);
3517 else
3518 d->c_row--;
3520 if (d->c_row < 1)
3521 d->c_row = 8;
3523 return 1;
3524 case KEY_LEFT:
3525 if (d->mode == MODE_HISTORY)
3526 return 0;
3528 if (keycount) {
3529 d->c_col -= keycount;
3530 pushkey = '\n';
3532 else
3533 d->c_col--;
3535 if (d->c_col < 1)
3536 d->c_col = 8;
3538 return 1;
3539 case KEY_RIGHT:
3540 if (d->mode == MODE_HISTORY)
3541 return 0;
3543 if (keycount) {
3544 d->c_col += keycount;
3545 pushkey = '\n';
3547 else
3548 d->c_col++;
3550 if (d->c_col > 8)
3551 d->c_col = 1;
3553 return 1;
3554 case 'e':
3555 if (d->mode != MODE_EDIT && d->mode !=
3556 MODE_PLAY)
3557 return 1;
3559 // Don't edit a running game (for now).
3560 if (pgn_history_total(game[gindex].hp))
3561 return 1;
3563 if (d->mode != MODE_EDIT) {
3564 pgn_board_init_fen(&game[gindex], d->b, NULL);
3565 config.details++;
3566 d->mode = MODE_EDIT;
3567 update_all(game[gindex]);
3568 return 1;
3571 config.details--;
3572 pgn_tag_add(&game[gindex].tag, "FEN",
3573 pgn_game_to_fen(game[gindex], d->b));
3574 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3575 pgn_tag_sort(game[gindex].tag);
3576 d->mode = MODE_PLAY;
3577 update_all(game[gindex]);
3578 return 1;
3579 case 'Q':
3580 quit = 1;
3581 return 1;
3582 case KEY_RESIZE:
3583 do_window_resize();
3584 return 1;
3585 #ifdef DEBUG
3586 case 'D':
3587 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3588 return 1;
3589 #endif
3590 case 0:
3591 default:
3592 break;
3595 return 0;
3598 void game_loop()
3600 int error_recover = 0;
3601 struct userdata_s *d = game[gindex].data;
3603 gindex = gtotal - 1;
3605 if (pgn_history_total(game[gindex].hp))
3606 d->mode = MODE_HISTORY;
3607 else
3608 d->mode = MODE_PLAY;
3610 if (d->mode == MODE_HISTORY) {
3611 pgn_board_update(&game[gindex], d->b,
3612 pgn_history_total(game[gindex].hp));
3615 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3616 movestep = 2;
3617 flushinp();
3618 update_all(game[gindex]);
3619 update_tag_window(game[gindex].tag);
3620 wtimeout(boardw, 70);
3622 while (!quit) {
3623 int c = 0;
3624 int n = 0, i;
3625 char fdbuf[8192] = {0};
3626 int len;
3627 struct timeval tv = {0, 0};
3628 fd_set rfds, wfds;
3630 FD_ZERO(&rfds);
3632 for (i = 0; i < gtotal; i++) {
3633 d = game[i].data;
3635 if (d->engine) {
3636 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3637 if (d->engine->fd[ENGINE_IN_FD] > n)
3638 n = d->engine->fd[ENGINE_IN_FD];
3640 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3643 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3644 if (d->engine->fd[ENGINE_OUT_FD] > n)
3645 n = d->engine->fd[ENGINE_OUT_FD];
3647 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3652 if (n) {
3653 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3654 for (i = 0; i < gtotal; i++) {
3655 d = game[i].data;
3657 if (d->engine) {
3658 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3659 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3660 sizeof(fdbuf));
3662 if (len == -1) {
3663 if (errno != EAGAIN) {
3664 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3665 strerror(errno));
3666 waitpid(d->engine->pid, &n, 0);
3667 free(d->engine);
3668 d->engine = NULL;
3669 break;
3672 else {
3673 if (len)
3674 parse_engine_output(&game[i], fdbuf);
3678 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3679 if (d->engine->queue)
3680 send_engine_command(&game[i]);
3685 else {
3686 if (n == -1)
3687 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3688 /* timeout */
3692 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3693 d->mode = MODE_HISTORY;
3695 d = game[gindex].data;
3696 error_recover = 0;
3697 draw_board(&game[gindex]);
3698 update_all(game[gindex]);
3699 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3700 refresh_all();
3702 if (pushkey)
3703 c = pushkey;
3704 else {
3705 if ((c = wgetch(boardw)) == ERR)
3706 continue;
3709 if (!keycount && status.notify)
3710 update_status_notify(game[gindex], NULL);
3712 if ((n = globalkeys(c)) == 1) {
3713 keycount = 0;
3714 continue;
3716 else if (n == -1)
3717 continue;
3719 switch (d->mode) {
3720 case MODE_EDIT:
3721 editmode_keys(c);
3722 break;
3723 case MODE_PLAY:
3724 if (playmode_keys(c))
3725 continue;
3726 break;
3727 case MODE_HISTORY:
3728 historymode_keys(c);
3729 break;
3730 default:
3731 break;
3734 keycount = 0;
3738 void usage(const char *pn, int ret)
3740 fprintf((ret) ? stderr : stdout, "%s",
3741 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3742 " -p Load PGN file.\n"
3743 " -V Validate a game file.\n"
3744 " -S Validate and output a PGN formatted game.\n"
3745 " -R Like -S but write a reduced PGN formatted game.\n"
3746 " -t Also write custom PGN tags from config file.\n"
3747 " -E Stop processing on file parsing error (overrides config).\n"
3748 " -v Version information.\n"
3749 " -h This help text.\n");
3751 exit(ret);
3754 void cleanup_all()
3756 int i;
3758 stop_clock();
3759 free_userdata();
3760 pgn_free_all();
3761 free(config.engine_cmd);
3762 free(config.pattern);
3763 free(config.ccfile);
3764 free(config.nagfile);
3765 free(config.configfile);
3767 if (config.keys) {
3768 for (i = 0; config.keys[i]; i++)
3769 free(config.keys[i]->str);
3771 free(config.keys);
3774 if (config.einit) {
3775 for (i = 0; config.einit[i]; i++)
3776 free(config.einit[i]);
3778 free(config.einit);
3781 if (config.tag)
3782 pgn_tag_free(config.tag);
3784 if (curses_initialized) {
3785 del_panel(boardp);
3786 del_panel(historyp);
3787 del_panel(statusp);
3788 del_panel(tagp);
3789 delwin(boardw);
3790 delwin(historyw);
3791 delwin(statusw);
3792 delwin(tagw);
3794 if (enginew) {
3795 del_panel(enginep);
3796 delwin(enginew);
3798 if (enginebuf) {
3799 for (i = 0; enginebuf[i]; i++)
3800 free(enginebuf[i]);
3802 free(enginebuf);
3806 endwin();
3810 void catch_signal(int which)
3812 switch (which) {
3813 case SIGALRM:
3814 update_clocks();
3815 break;
3816 case SIGPIPE:
3817 if (which == SIGPIPE && quit)
3818 break;
3820 if (which == SIGPIPE)
3821 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3823 cleanup_all();
3824 exit(EXIT_FAILURE);
3825 break;
3826 case SIGSTOP:
3827 savetty();
3828 break;
3829 case SIGCONT:
3830 resetty();
3831 keypad(boardw, TRUE);
3832 curs_set(0);
3833 cbreak();
3834 noecho();
3835 break;
3836 case SIGINT:
3837 case SIGTERM:
3838 quit = 1;
3839 break;
3840 default:
3841 break;
3845 void loading_progress(long total, long offset)
3847 int n = (100 * (offset / 100) / (total / 100));
3849 if (curses_initialized)
3850 update_loading_window(n);
3851 else {
3852 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3853 fflush(stderr);
3857 static void set_defaults()
3859 set_config_defaults();
3860 filetype = NO_FILE;
3861 pgn_config_set(PGN_PROGRESS, 1024);
3862 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3865 int main(int argc, char *argv[])
3867 int opt;
3868 struct stat st;
3869 char buf[FILENAME_MAX];
3870 char datadir[FILENAME_MAX];
3871 int ret = EXIT_SUCCESS;
3872 int validate_only = 0, validate_and_write = 0;
3873 int write_custom_tags = 0;
3874 FILE *fp;
3875 int i = 0;
3877 if ((config.pwd = getpwuid(getuid())) == NULL)
3878 err(EXIT_FAILURE, "getpwuid()");
3880 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3881 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3882 config.ccfile = strdup(buf);
3883 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3884 config.nagfile = strdup(buf);
3885 snprintf(buf, sizeof(buf), "%s/config", datadir);
3886 config.configfile = strdup(buf);
3888 if (stat(datadir, &st) == -1) {
3889 if (errno == ENOENT) {
3890 if (mkdir(datadir, 0755) == -1)
3891 err(EXIT_FAILURE, "%s", datadir);
3893 else
3894 err(EXIT_FAILURE, "%s", datadir);
3896 stat(datadir, &st);
3899 if (!S_ISDIR(st.st_mode))
3900 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3902 set_defaults();
3904 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3905 switch (opt) {
3906 case 't':
3907 write_custom_tags = 1;
3908 break;
3909 case 'E':
3910 i = 1;
3911 break;
3912 case 'R':
3913 pgn_config_set(PGN_REDUCED, 1);
3914 case 'S':
3915 validate_and_write = 1;
3916 case 'V':
3917 validate_only = 1;
3918 break;
3919 case 'v':
3920 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3921 COPYRIGHT);
3922 exit(EXIT_SUCCESS);
3923 case 'p':
3924 filetype = PGN_FILE;
3925 strncpy(loadfile, optarg, sizeof(loadfile));
3926 break;
3927 case 'h':
3928 default:
3929 usage(argv[0], EXIT_SUCCESS);
3933 if ((validate_only || validate_and_write) && !*loadfile)
3934 usage(argv[0], EXIT_FAILURE);
3936 if (access(config.configfile, R_OK) == 0)
3937 parse_rcfile(config.configfile);
3939 if (i)
3940 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3942 signal(SIGPIPE, catch_signal);
3943 signal(SIGCONT, catch_signal);
3944 signal(SIGSTOP, catch_signal);
3945 signal(SIGINT, catch_signal);
3946 signal(SIGALRM, catch_signal);
3947 signal(SIGTERM, catch_signal);
3949 srandom(getpid());
3951 switch (filetype) {
3952 case PGN_FILE:
3953 if ((fp = pgn_open(loadfile)) == NULL)
3954 err(EXIT_FAILURE, "%s", loadfile);
3956 ret = pgn_parse(fp);
3957 break;
3958 case FEN_FILE:
3959 //ret = parse_fen_file(loadfile);
3960 break;
3961 case EPD_FILE: // Not implemented.
3962 case NO_FILE:
3963 default:
3964 // No file specified. Empty game.
3965 ret = pgn_parse(NULL);
3966 add_custom_tags(&game[gindex].tag);
3967 break;
3970 if (validate_only || validate_and_write) {
3971 if (validate_and_write) {
3972 for (i = 0; i < gtotal; i++) {
3973 if (write_custom_tags)
3974 add_custom_tags(&game[i].tag);
3976 pgn_write(stdout, game[i]);
3980 cleanup_all();
3981 exit(ret);
3983 else if (ret == E_PGN_ERR)
3984 exit(ret);
3986 init_userdata();
3988 if (initscr() == NULL)
3989 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3990 else
3991 curses_initialized = 1;
3993 if (LINES < 24 || COLS < 80) {
3994 endwin();
3995 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3998 if (has_colors() == TRUE && start_color() == OK)
3999 init_color_pairs();
4001 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
4002 boardp = new_panel(boardw);
4003 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
4004 COLS - HISTORY_WIDTH);
4005 historyp = new_panel(historyw);
4006 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
4007 statusp = new_panel(statusw);
4008 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
4009 tagp = new_panel(tagw);
4010 keypad(boardw, TRUE);
4011 // leaveok(boardw, TRUE);
4012 leaveok(tagw, TRUE);
4013 leaveok(statusw, TRUE);
4014 leaveok(historyw, TRUE);
4015 curs_set(0);
4016 cbreak();
4017 noecho();
4018 draw_window_decor();
4019 game_loop();
4020 cleanup_all();
4021 exit(EXIT_SUCCESS);