libchess: Make the first character of a tag name uppercase.
[cboard.git] / src / cboard.c
blob233d75647f87166250a1864e37cf38ad2b370b06
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,
1957 str_etc(t[i]->value, w, 0));
1960 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
1962 int i;
1964 wattron(win, attr);
1966 for (i = 1; i < width - 1; i++)
1967 mvwaddch(win, y, i, ' ');
1969 mvwprintw(win, y, CENTERX(width, str), "%s", str);
1970 wattroff(win, attr);
1973 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
1974 chtype battr)
1976 int i;
1978 if (title) {
1979 wattron(win, attr);
1981 for (i = 1; i < width - 1; i++)
1982 mvwaddch(win, 1, i, ' ');
1984 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
1985 wattroff(win, attr);
1988 wattron(win, battr);
1989 box(win, ACS_VLINE, ACS_HLINE);
1990 wattroff(win, battr);
1993 void append_enginebuf(char *line)
1995 int i = 0;
1997 if (enginebuf)
1998 for (i = 0; enginebuf[i]; i++);
2000 if (i >= LINES - 3) {
2001 free(enginebuf[0]);
2003 for (i = 0; enginebuf[i+1]; i++)
2004 enginebuf[i] = enginebuf[i+1];
2006 enginebuf[i] = strdup(line);
2008 else {
2009 enginebuf = Realloc(enginebuf, (i + 2) * sizeof(char *));
2010 enginebuf[i++] = strdup(line);
2011 enginebuf[i] = NULL;
2015 void update_engine_window()
2017 int i;
2019 if (!enginebuf)
2020 return;
2022 wmove(enginew, 0, 0);
2023 wclrtobot(enginew);
2025 if (enginebuf) {
2026 for (i = 0; enginebuf[i]; i++)
2027 mvwprintw(enginew, i + 2, 1, "%s", enginebuf[i]);
2030 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2031 CP_MESSAGE_BORDER);
2034 void toggle_engine_window()
2036 if (!enginew) {
2037 enginew = newwin(LINES, COLS, 0, 0);
2038 enginep = new_panel(enginew);
2039 draw_window_title(enginew, "Engine IO Window", COLS, CP_MESSAGE_TITLE,
2040 CP_MESSAGE_BORDER);
2041 hide_panel(enginep);
2044 if (panel_hidden(enginep)) {
2045 update_engine_window();
2046 top_panel(enginep);
2047 refresh_all();
2049 else {
2050 hide_panel(enginep);
2051 refresh_all();
2055 void refresh_all()
2057 update_panels();
2058 doupdate();
2061 void update_all(GAME g)
2063 update_status_window(g);
2064 update_history_window(g);
2065 update_tag_window(g.tag);
2066 update_engine_window();
2069 static void game_next_prev(GAME g, int n, int count)
2071 if (gtotal < 2)
2072 return;
2074 if (n == 1) {
2075 if (gindex + count > gtotal - 1) {
2076 if (count != 1)
2077 gindex = gtotal - 1;
2078 else
2079 gindex = 0;
2081 else
2082 gindex += count;
2084 else {
2085 if (gindex - count < 0) {
2086 if (count != 1)
2087 gindex = 0;
2088 else
2089 gindex = gtotal - 1;
2091 else
2092 gindex -= count;
2096 static void delete_game(int which)
2098 GAME *g = NULL;
2099 int gi = 0;
2100 int i;
2101 struct userdata_s *d;
2103 for (i = 0; i < gtotal; i++) {
2104 d = game[i].data;
2106 if (i == which || TEST_FLAG(d->flags, CF_DELETE)) {
2107 pgn_free(game[i]);
2108 continue;
2111 g = Realloc(g, (gi + 1) * sizeof(GAME));
2112 memcpy(&g[gi], &game[i], sizeof(GAME));
2113 g[gi].tag = game[i].tag;
2114 g[gi].history = game[i].history;
2115 g[gi].hp = game[i].hp;
2116 gi++;
2119 game = g;
2120 gtotal = gi;
2122 if (which != -1) {
2123 if (which + 1 >= gtotal)
2124 gindex = gtotal - 1;
2125 else
2126 gindex = which;
2128 else
2129 gindex = gtotal - 1;
2131 game[gindex].hp = game[gindex].history;
2134 static int find_move_exp(GAME g, const char *str, int init, int which,
2135 int count)
2137 int i;
2138 int ret;
2139 static regex_t r;
2140 static int firstrun = 1;
2141 char errbuf[255];
2142 int incr;
2143 int found;
2145 if (init) {
2146 if (!firstrun)
2147 regfree(&r);
2149 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
2150 regerror(ret, &r, errbuf, sizeof(errbuf));
2151 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2152 return -1;
2155 firstrun = 1;
2158 incr = (which == 0) ? -1 : 1;
2160 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
2161 if (i == g.hindex - 1)
2162 break;
2164 if (i >= pgn_history_total(g.hp))
2165 i = 0;
2166 else if (i < 0)
2167 i = pgn_history_total(g.hp) - 1;
2169 // FIXME RAV
2170 ret = regexec(&r, g.hp[i]->move, 0, 0, 0);
2172 if (ret == 0) {
2173 if (count == ++found) {
2174 return i + 1;
2177 else {
2178 if (ret != REG_NOMATCH) {
2179 regerror(ret, &r, errbuf, sizeof(errbuf));
2180 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
2181 return -1;
2186 return -1;
2189 static int toggle_delete_flag(int n)
2191 int i, x;
2192 struct userdata_s *d = game[n].data;
2194 TOGGLE_FLAG(d->flags, CF_DELETE);
2195 gindex = n;
2196 update_all(game[gindex]);
2198 for (i = x = 0; i < gtotal; i++) {
2199 d = game[i].data;
2201 if (TEST_FLAG(d->flags, CF_DELETE))
2202 x++;
2205 if (x == gtotal) {
2206 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
2207 d = game[n].data;
2208 CLEAR_FLAG(d->flags, CF_DELETE);
2209 return 1;
2212 return 0;
2215 static void edit_save_tags(GAME *g)
2217 TAG **t;
2218 struct userdata_s *d = g->data;
2220 if ((t = edit_tags(*g, d->b, 1)) == NULL)
2221 return;
2223 pgn_tag_free(g->tag);
2224 g->tag = t;
2225 SET_FLAG(d->flags, CF_MODIFIED);
2226 pgn_tag_sort(g->tag);
2229 static int find_game_exp(char *str, int which, int count)
2231 char *nstr = NULL, *exp = NULL;
2232 regex_t nexp, vexp;
2233 int ret = -1;
2234 int g = 0;
2235 char buf[255], *tmp;
2236 char errbuf[255];
2237 int found = 0;
2238 int incr = (which == 0) ? -(1) : 1;
2240 strncpy(buf, str, sizeof(buf));
2241 tmp = buf;
2243 if (strstr(tmp, ":") != NULL) {
2244 nstr = strsep(&tmp, ":");
2246 if ((ret = regcomp(&nexp, nstr,
2247 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
2248 regerror(ret, &nexp, errbuf, sizeof(errbuf));
2249 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2250 ret = g = -1;
2251 goto cleanup;
2255 exp = tmp;
2257 if (exp == NULL)
2258 goto cleanup;
2260 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
2261 regerror(ret, &vexp, errbuf, sizeof(errbuf));
2262 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
2263 ret = -1;
2264 goto cleanup;
2267 ret = -1;
2269 for (g = gindex + incr, found = 0; ; g += incr) {
2270 int t;
2272 if (g == gindex)
2273 break;
2275 if (g == gtotal)
2276 g = 0;
2277 else if (g < 0)
2278 g = gtotal - 1;
2280 for (t = 0; game[g].tag[t]; t++) {
2281 if (nstr) {
2282 if (regexec(&nexp, game[g].tag[t]->name, 0, 0, 0) == 0) {
2283 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2284 if (count == ++found) {
2285 ret = g;
2286 goto cleanup;
2291 else {
2292 if (regexec(&vexp, game[g].tag[t]->value, 0, 0, 0) == 0) {
2293 if (count == ++found) {
2294 ret = g;
2295 goto cleanup;
2301 ret = -1;
2304 cleanup:
2305 if (nstr)
2306 regfree(&nexp);
2308 if (g != -1)
2309 regfree(&vexp);
2311 return ret;
2315 * Updates the notification line in the status window then refreshes the
2316 * status window.
2318 void update_status_notify(GAME g, char *fmt, ...)
2320 va_list ap;
2321 #ifdef HAVE_VASPRINTF
2322 char *line;
2323 #else
2324 char line[COLS];
2325 #endif
2327 if (!fmt) {
2328 if (status.notify) {
2329 free(status.notify);
2330 status.notify = NULL;
2332 if (curses_initialized)
2333 update_status_window(g);
2336 return;
2339 va_start(ap, fmt);
2340 #ifdef HAVE_VASPRINTF
2341 vasprintf(&line, fmt, ap);
2342 #else
2343 vsnprintf(line, sizeof(line), fmt, ap);
2344 #endif
2345 va_end(ap);
2347 if (status.notify)
2348 free(status.notify);
2350 status.notify = strdup(line);
2352 #ifdef HAVE_VASPRINTF
2353 free(line);
2354 #endif
2355 if (curses_initialized)
2356 update_status_window(g);
2359 int rav_next_prev(GAME *g, BOARD b, int n)
2361 // Next RAV.
2362 if (n) {
2363 if ((!g->ravlevel && g->hp[g->hindex - 1]->rav == NULL) ||
2364 (g->ravlevel && g->hp[g->hindex]->rav == NULL))
2365 return 1;
2367 g->rav = Realloc(g->rav, (g->ravlevel + 1) * sizeof(RAV));
2368 g->rav[g->ravlevel].hp = g->hp;
2369 g->rav[g->ravlevel].flags = g->flags;
2370 g->rav[g->ravlevel].fen = strdup(pgn_game_to_fen(*g, b));
2371 g->rav[g->ravlevel].hindex = g->hindex;
2372 g->hp = (!g->ravlevel) ? g->hp[g->hindex - 1]->rav : g->hp[g->hindex]->rav;
2373 g->hindex = 0;
2374 g->ravlevel++;
2375 pgn_board_update(g, b, g->hindex + 1);
2376 return 0;
2379 if (g->ravlevel - 1 < 0)
2380 return 1;
2382 // Previous RAV.
2383 g->ravlevel--;
2384 pgn_board_init_fen(g, b, g->rav[g->ravlevel].fen);
2385 free(g->rav[g->ravlevel].fen);
2386 g->hp = g->rav[g->ravlevel].hp;
2387 g->flags = g->rav[g->ravlevel].flags;
2388 g->hindex = g->rav[g->ravlevel].hindex;
2389 return 0;
2392 static void draw_window_decor()
2394 move_panel(historyp, LINES - HISTORY_HEIGHT, COLS - HISTORY_WIDTH);
2395 move_panel(boardp, 0, COLS - BOARD_WIDTH);
2396 wbkgd(boardw, CP_BOARD_WINDOW);
2397 wbkgd(statusw, CP_STATUS_WINDOW);
2398 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2399 CP_STATUS_TITLE, CP_STATUS_BORDER);
2400 wbkgd(tagw, CP_TAG_WINDOW);
2401 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2402 CP_TAG_BORDER);
2403 wbkgd(historyw, CP_HISTORY_WINDOW);
2404 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2405 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2408 static void do_window_resize()
2410 if (LINES < 24 || COLS < 80)
2411 return;
2413 resizeterm(LINES, COLS);
2414 wresize(historyw, HISTORY_HEIGHT, HISTORY_WIDTH);
2415 wresize(statusw, STATUS_HEIGHT, STATUS_WIDTH);
2416 wresize(tagw, TAG_HEIGHT, TAG_WIDTH);
2417 wmove(historyw, 0, 0);
2418 wclrtobot(historyw);
2419 wmove(tagw, 0, 0);
2420 wclrtobot(tagw);
2421 wmove(statusw, 0, 0);
2422 wclrtobot(statusw);
2423 draw_window_decor();
2424 update_all(game[gindex]);
2427 void stop_clock()
2429 memset(&clock_timer, 0, sizeof(struct itimerval));
2430 setitimer(ITIMER_REAL, &clock_timer, NULL);
2433 void start_clock()
2435 if (clock_timer.it_interval.tv_usec)
2436 return;
2438 clock_timer.it_value.tv_sec = 0;
2439 clock_timer.it_value.tv_usec = 100000;
2440 clock_timer.it_interval.tv_sec = 0;
2441 clock_timer.it_interval.tv_usec = 100000;
2442 setitimer(ITIMER_REAL, &clock_timer, NULL);
2445 static void update_clocks()
2447 int i;
2448 struct userdata_s *d;
2449 struct itimerval it;
2451 getitimer(ITIMER_REAL, &it);
2453 for (i = 0; i < gtotal; i++) {
2454 d = game[i].data;
2456 if (d->mode == MODE_PLAY) {
2457 if (d->paused == 1 || TEST_FLAG(d->flags, CF_NEW))
2458 continue;
2459 else if (d->paused == -1) {
2460 if (game[i].side == game[i].turn) {
2461 d->paused = 1;
2462 continue;
2466 update_clock(&game[i], it);
2471 static int init_chess_engine(GAME *g)
2473 struct userdata_s *d = g->data;
2474 int w, x;
2476 if (start_chess_engine(g) > 0) {
2477 d->sp.icon = 0;
2478 return 1;
2481 x = pgn_tag_find(g->tag, "FEN");
2482 w = pgn_tag_find(g->tag, "SetUp");
2484 if ((w >= 0 && x >= 0 && atoi(g->tag[w]->value) == 1) ||
2485 (x >= 0 && w == -1))
2486 add_engine_command(g, ENGINE_READY, "setboard %s\n", g->tag[x]->value);
2487 else
2488 add_engine_command(g, ENGINE_READY, "setboard %s\n",
2489 pgn_game_to_fen(*g, d->b));
2491 return 0;
2494 static int parse_clock_input(struct userdata_s *d, char *str)
2496 char *p = str;
2497 long n = 0;
2498 int t = 0;
2499 int plus = 0;
2501 if (*p == '+') {
2502 plus = 1;
2503 p++;
2506 while (*p) {
2507 if (isdigit(*p)) {
2508 t = atoi(p);
2510 while (isdigit(*p))
2511 p++;
2513 continue;
2516 if (!t && *p != ' ')
2517 return 1;
2519 switch (*p) {
2520 case 'H':
2521 case 'h':
2522 n += t * (60 * 60);
2523 t = 0;
2524 break;
2525 case 'M':
2526 case 'm':
2527 n += t * 60;
2528 t = 0;
2529 break;
2530 case 'S':
2531 case 's':
2532 n += t;
2533 t = 0;
2534 break;
2535 case ' ':
2536 t = 0;
2537 break;
2538 default:
2539 return 1;
2542 p++;
2545 if (t)
2546 n += t;
2548 if (!n) {
2549 d->limit = 0;
2550 CLEAR_FLAG(d->flags, CF_CLOCK);
2552 else {
2553 SET_FLAG(d->flags, CF_CLOCK);
2555 if (plus)
2556 d->limit += n;
2557 else
2558 d->limit = (n <= d->elapsed) ? d->elapsed + n : n;
2561 return 0;
2564 static void historymode_keys(chtype);
2565 static int playmode_keys(chtype c)
2567 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2568 struct userdata_s *d = game[gindex].data;
2569 int editmode = (d->mode == MODE_EDIT) ? 1 : 0;
2570 chtype p;
2571 int w, x;
2572 char *tmp;
2574 switch (c) {
2575 case 'C':
2576 if ((tmp = get_input(CLOCK_TITLE, NULL, 1, 1, CLOCK_HELP, NULL,
2577 NULL, 0, -1)) == NULL)
2578 break;
2580 if (parse_clock_input(d, tmp))
2581 cmessage(ERROR, ANYKEY, "Invalid time specification");
2582 break;
2583 case 'H':
2584 TOGGLE_FLAG(d->flags, CF_HUMAN);
2586 if (!TEST_FLAG(d->flags, CF_HUMAN) &&
2587 pgn_history_total(game[gindex].hp)) {
2588 if (init_chess_engine(&game[gindex]))
2589 break;
2592 CLEAR_FLAG(d->flags, CF_ENGINE_LOOP);
2594 if (d->engine)
2595 d->engine->status = ENGINE_READY;
2597 update_all(game[gindex]);
2598 break;
2599 case 'E':
2600 if (!d)
2601 break;
2603 TOGGLE_FLAG(d->flags, CF_ENGINE_LOOP);
2604 CLEAR_FLAG(d->flags, CF_HUMAN);
2606 if (d->engine && TEST_FLAG(d->flags, CF_ENGINE_LOOP)) {
2607 pgn_board_update(&game[gindex], d->b,
2608 pgn_history_total(game[gindex].hp));
2609 add_engine_command(&game[gindex], ENGINE_READY,
2610 "setboard %s\n", pgn_game_to_fen(game[gindex], d->b));
2613 update_all(game[gindex]);
2614 break;
2615 case '|':
2616 if (!d->engine)
2617 break;
2619 if (d->engine->status == ENGINE_OFFLINE)
2620 break;
2622 x = d->engine->status;
2624 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL)) != NULL)
2625 send_to_engine(&game[gindex], -1, "%s\n", tmp);
2626 d->engine->status = x;
2627 break;
2628 case '\015':
2629 case '\n':
2630 pushkey = keycount = 0;
2631 update_status_notify(game[gindex], NULL);
2633 if (!editmode && !TEST_FLAG(d->flags, CF_HUMAN) &&
2634 (!d->engine || d->engine->status == ENGINE_THINKING)) {
2635 beep();
2636 break;
2639 if (!d->sp.icon)
2640 break;
2642 d->sp.row = d->c_row;
2643 d->sp.col = d->c_col;
2645 if (editmode) {
2646 p = d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon;
2647 d->b[RANKTOBOARD(d->sp.row)][FILETOBOARD(d->sp.col)].icon = p;
2648 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2649 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2650 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2651 break;
2654 if (move_to_engine(&game[gindex], d->b)) {
2655 if (config.validmoves)
2656 pgn_reset_valid_moves(d->b);
2658 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2659 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2660 SET_FLAG(d->flags, CF_MODIFIED);
2663 d->paused = 0;
2666 break;
2667 case ' ':
2668 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2669 d->engine->status == ENGINE_OFFLINE) && !editmode) {
2670 if (init_chess_engine(&game[gindex]))
2671 break;
2674 if (d->sp.icon || (!editmode && d->engine &&
2675 d->engine->status == ENGINE_THINKING)) {
2676 beep();
2677 break;
2680 d->sp.icon = mvwinch(boardw, ROWTOMATRIX(d->c_row),
2681 COLTOMATRIX(d->c_col)+1) & A_CHARTEXT;
2683 if (d->sp.icon == ' ') {
2684 d->sp.icon = 0;
2685 break;
2688 if (!editmode && ((islower(d->sp.icon) && game[gindex].turn != BLACK)
2689 || (isupper(d->sp.icon) && game[gindex].turn != WHITE))) {
2690 if (pgn_history_total(game[gindex].hp)) {
2691 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
2692 d->sp.icon = 0;
2693 break;
2695 else {
2696 if (pgn_tag_find(game[gindex].tag, "FEN") != E_PGN_ERR)
2697 break;
2699 add_engine_command(&game[gindex], ENGINE_READY, "black\n");
2700 pgn_switch_turn(&game[gindex]);
2702 if (game[gindex].side != BLACK)
2703 pgn_switch_side(&game[gindex]);
2707 d->sp.srow = d->c_row;
2708 d->sp.scol = d->c_col;
2710 if (!editmode && config.validmoves)
2711 pgn_find_valid_moves(game[gindex], d->b, d->sp.scol, d->sp.srow);
2713 if (!editmode) {
2714 CLEAR_FLAG(d->flags, CF_NEW);
2715 start_clock();
2718 break;
2719 case 'w':
2720 pgn_switch_side(&game[gindex]);
2721 pgn_switch_turn(&game[gindex]);
2722 add_engine_command(&game[gindex], -1,
2723 (game[gindex].side == WHITE) ? "white\n" : "black\n");
2724 update_status_window(game[gindex]);
2725 break;
2726 case 'u':
2727 if (!pgn_history_total(game[gindex].hp))
2728 break;
2730 if (d->engine && d->engine->status == ENGINE_READY) {
2731 add_engine_command(&game[gindex], ENGINE_READY, "remove\n");
2732 d->engine->status = ENGINE_READY;
2735 game[gindex].hindex -= 2;
2736 pgn_history_free(game[gindex].hp, game[gindex].hindex);
2737 game[gindex].hindex = pgn_history_total(game[gindex].hp);
2738 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2739 update_history_window(game[gindex]);
2740 break;
2741 case 'a':
2742 historymode_keys(c);
2743 break;
2744 case 'd':
2745 config.details = (config.details) ? 0 : 1;
2746 break;
2747 case 'p':
2748 if (!TEST_FLAG(d->flags, CF_HUMAN) && game[gindex].turn !=
2749 game[gindex].side) {
2750 d->paused = -1;
2751 break;
2754 d->paused = (d->paused) ? 0 : 1;
2755 break;
2756 case 'g':
2757 if (TEST_FLAG(d->flags, CF_HUMAN))
2758 break;
2760 if (!d->engine || d->engine->status == ENGINE_OFFLINE) {
2761 if (init_chess_engine(&game[gindex]))
2762 break;
2765 add_engine_command(&game[gindex], ENGINE_THINKING, "go\n");
2766 break;
2767 default:
2768 if (!d->engine)
2769 break;
2771 if (config.keys) {
2772 for (x = 0; config.keys[x]; x++) {
2773 if (config.keys[x]->c == c) {
2774 switch (config.keys[x]->type) {
2775 case KEY_DEFAULT:
2776 add_engine_command(&game[gindex], -1, "%s\n",
2777 config.keys[x]->str);
2778 break;
2779 case KEY_SET:
2780 if (!keycount)
2781 break;
2783 add_engine_command(&game[gindex], -1,
2784 "%s %i\n", config.keys[x]->str, keycount);
2785 keycount = 0;
2786 break;
2787 case KEY_REPEAT:
2788 if (!keycount)
2789 break;
2791 for (w = 0; w < keycount; w++)
2792 add_engine_command(&game[gindex], -1,
2793 "%s\n", config.keys[x]->str);
2794 keycount = 0;
2795 break;
2800 update_status_notify(game[gindex], NULL);
2802 break;
2805 return 0;
2808 static void editmode_keys(chtype c)
2810 struct userdata_s *d = game[gindex].data;
2812 switch (c) {
2813 case '\015':
2814 case '\n':
2815 case ' ':
2816 playmode_keys(c);
2817 break;
2818 case 'd':
2819 if (d->sp.icon)
2820 d->b[RANKTOBOARD(d->sp.srow)][FILETOBOARD(d->sp.scol)].icon =
2821 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2822 else
2823 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon =
2824 pgn_int_to_piece(game[gindex].turn, OPEN_SQUARE);
2826 d->sp.icon = d->sp.srow = d->sp.scol = 0;
2827 break;
2828 case 'w':
2829 pgn_switch_turn(&game[gindex]);
2830 update_all(game[gindex]);
2831 break;
2832 case 'c':
2833 castling_state(&game[gindex], d->b, RANKTOBOARD(d->c_row),
2834 FILETOBOARD(d->c_col),
2835 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon, 1);
2836 break;
2837 case 'i':
2838 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
2839 GAME_EDIT_TEXT);
2841 if (pgn_piece_to_int(c) == -1)
2842 break;
2844 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].icon = c;
2845 break;
2846 case 'p':
2847 if (d->c_row == 6 || d->c_row == 3) {
2848 pgn_reset_enpassant(d->b);
2849 d->b[RANKTOBOARD(d->c_row)][FILETOBOARD(d->c_col)].enpassant = 1;
2851 break;
2852 default:
2853 break;
2857 static void historymode_keys(chtype c)
2859 int n, len;
2860 char *tmp, *buf;
2861 static char moveexp[255] = {0};
2862 struct userdata_s *d = game[gindex].data;
2864 switch (c) {
2865 case 'd':
2866 config.details = (config.details) ? 0 : 1;
2867 break;
2868 case ' ':
2869 movestep = (movestep == 1) ? 2 : 1;
2870 update_history_window(game[gindex]);
2871 break;
2872 case KEY_UP:
2873 pgn_history_next(&game[gindex], d->b, (keycount > 0) ?
2874 config.jumpcount * keycount * movestep :
2875 config.jumpcount * movestep);
2876 update_all(game[gindex]);
2877 break;
2878 case KEY_DOWN:
2879 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2880 config.jumpcount * keycount * movestep :
2881 config.jumpcount * movestep);
2882 update_all(game[gindex]);
2883 break;
2884 case KEY_LEFT:
2885 pgn_history_prev(&game[gindex], d->b, (keycount) ?
2886 keycount * movestep : movestep);
2887 update_all(game[gindex]);
2888 break;
2889 case KEY_RIGHT:
2890 pgn_history_next(&game[gindex], d->b, (keycount) ?
2891 keycount * movestep : movestep);
2892 update_all(game[gindex]);
2893 break;
2894 case 'a':
2895 n = game[gindex].hindex;
2897 if (n && game[gindex].hp[n - 1]->move)
2898 n--;
2899 else
2900 break;
2902 buf = Malloc(COLS);
2903 snprintf(buf, COLS - 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE,
2904 game[gindex].hp[n]->move);
2906 tmp = get_input(buf, game[gindex].hp[n]->comment, 0, 0, NAG_PROMPT,
2907 history_edit_nag, (void *)game[gindex].hp[n], CTRL('T'),
2908 -1);
2909 free(buf);
2911 if (!tmp && (!game[gindex].hp[n]->comment ||
2912 !*game[gindex].hp[n]->comment))
2913 break;
2914 else if (tmp && game[gindex].hp[n]->comment) {
2915 if (strcmp(tmp, game[gindex].hp[n]->comment) == 0)
2916 break;
2919 len = (tmp) ? strlen(tmp) + 1 : 1;
2920 game[gindex].hp[n]->comment = Realloc(game[gindex].hp[n]->comment,
2921 len);
2922 strncpy(game[gindex].hp[n]->comment, (tmp) ? tmp : "", len);
2923 SET_FLAG(d->flags, CF_MODIFIED);
2924 update_all(game[gindex]);
2925 break;
2926 case ']':
2927 case '[':
2928 case '/':
2929 if (pgn_history_total(game[gindex].hp) < 2)
2930 break;
2932 n = 0;
2934 if (!*moveexp || c == '/') {
2935 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL, NULL, NULL, 0, -1)) == NULL)
2936 break;
2938 strncpy(moveexp, tmp, sizeof(moveexp));
2939 n = 1;
2942 if ((n = find_move_exp(game[gindex], moveexp, n,
2943 (c == '[') ? 0 : 1, (keycount) ? keycount : 1))
2944 == -1)
2945 break;
2947 game[gindex].hindex = n;
2948 pgn_board_update(&game[gindex], d->b, game[gindex].hindex);
2949 update_all(game[gindex]);
2950 break;
2951 case 'v':
2952 view_annotation(*game[gindex].hp[game[gindex].hindex]);
2953 break;
2954 case 'V':
2955 if (game[gindex].hindex - 1 >= 0)
2956 view_annotation(*game[gindex].hp[game[gindex].hindex - 1]);
2957 break;
2958 case '-':
2959 case '+':
2960 rav_next_prev(&game[gindex], d->b, (c == '-') ? 0 : 1);
2961 update_all(game[gindex]);
2962 break;
2963 case 'j':
2964 if (pgn_history_total(game[gindex].hp) < 2)
2965 break;
2967 /* FIXME field validation
2968 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2969 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2970 game[gindex].htotal)) == NULL)
2971 break;
2974 if (!keycount) {
2975 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2976 NULL, NULL, NULL, 0, -1)) == NULL)
2977 break;
2979 if (!isinteger(tmp))
2980 break;
2982 n = atoi(tmp);
2984 else
2985 n = keycount;
2987 if (n < 0 || n > (pgn_history_total(game[gindex].hp) / 2))
2988 break;
2990 keycount = 0;
2991 update_status_notify(game[gindex], NULL);
2992 game[gindex].hindex = (n) ? n * 2 - 1 : n * 2;
2993 pgn_board_update(&game[gindex], d->b,
2994 game[gindex].hindex);
2995 update_all(game[gindex]);
2996 break;
2997 default:
2998 break;
3002 static void free_userdata()
3004 int i;
3006 for (i = 0; i < gtotal; i++) {
3007 struct userdata_s *d;
3009 if (game[i].data) {
3010 d = game[i].data;
3012 if (d->engine) {
3013 stop_engine(&game[i]);
3014 free(d->engine);
3017 free(game[i].data);
3018 game[i].data = NULL;
3023 void update_loading_window(int n)
3025 if (!loadingw) {
3026 loadingw = newwin(3, COLS / 2, CALCPOSY(3), CALCPOSX(COLS / 2));
3027 loadingp = new_panel(loadingw);
3028 wbkgd(loadingw, CP_MESSAGE_WINDOW);
3031 wmove(loadingw, 0, 0);
3032 wclrtobot(loadingw);
3033 wattron(loadingw, CP_MESSAGE_BORDER);
3034 box(loadingw, ACS_VLINE, ACS_HLINE);
3035 wattroff(loadingw, CP_MESSAGE_BORDER);
3036 mvwprintw(loadingw, 1, CENTER_INT((COLS / 2),
3037 11 + strlen(itoa(gtotal))), "Loading... %i%% (%i games)", n,
3038 gtotal);
3039 refresh_all();
3042 static void init_userdata_once(GAME *g, int n)
3044 struct userdata_s *d = NULL;
3046 d = Calloc(1, sizeof(struct userdata_s));
3047 d->n = n;
3048 d->c_row = 2, d->c_col = 5;
3049 SET_FLAG(d->flags, CF_NEW);
3050 g->data = d;
3052 if (pgn_board_init_fen(g, d->b, NULL) != E_PGN_OK)
3053 pgn_board_init(d->b);
3056 void init_userdata()
3058 int i;
3060 for (i = 0; i < gtotal; i++)
3061 init_userdata_once(&game[i], i);
3064 void fix_marks(int *start, int *end)
3066 int i;
3068 *start = (*start < 0) ? 0 : *start;
3069 *end = (*end < 0) ? 0 : *end;
3071 if (*start > *end) {
3072 i = *start;
3073 *start = *end;
3074 *end = i + 1;
3077 *end = (*end > gtotal) ? gtotal : *end;
3080 // Global and other keys.
3081 static int globalkeys(chtype c)
3083 static char gameexp[255] = {0};
3084 FILE *fp;
3085 char *tmp, *p;
3086 int n, i;
3087 char tfile[FILENAME_MAX];
3088 struct userdata_s *d = game[gindex].data;
3090 switch (c) {
3091 case 'W':
3092 toggle_engine_window();
3093 break;
3094 case KEY_F(10):
3095 cmessage("ABOUT", ANYKEY, "%s (%s)\nUsing %s with %i colors "
3096 "and %i color pairs\nCopyright 2002-2006 %s",
3097 PACKAGE_STRING, pgn_version(), curses_version(), COLORS,
3098 COLOR_PAIRS, PACKAGE_BUGREPORT);
3099 break;
3100 case 'h':
3101 if (d->mode != MODE_HISTORY) {
3102 if (!pgn_history_total(game[gindex].hp) ||
3103 (d->engine && d->engine->status == ENGINE_THINKING))
3104 return 1;
3106 d->mode = MODE_HISTORY;
3107 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3108 update_all(game[gindex]);
3109 return 1;
3112 // FIXME Resuming from previous history could append to a RAV.
3113 if (game[gindex].hindex != pgn_history_total(game[gindex].hp)) {
3114 if (!pushkey) {
3115 if ((c = message(NULL, YESNO, "%s",
3116 GAME_RESUME_HISTORY_TEXT)) != 'y')
3117 return 1;
3119 pgn_history_free(game[gindex].hp, game[gindex].hindex);
3120 pgn_board_update(&game[gindex], d->b,
3121 pgn_history_total(game[gindex].hp));
3123 if (!TEST_FLAG(d->flags, CF_HUMAN))
3124 add_engine_command(&game[gindex], ENGINE_READY,
3125 "setboard %s\n", pgn_game_to_fen(game[gindex],
3126 d->b));
3129 else {
3130 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3131 return 1;
3134 pushkey = 0;
3135 d->mode = MODE_PLAY;
3136 update_all(game[gindex]);
3137 return 1;
3138 case '>':
3139 case '<':
3140 game_next_prev(game[gindex], (c == '>') ? 1 : 0, (keycount) ?
3141 keycount : 1);
3142 d = game[gindex].data;
3144 if (delete_count) {
3145 if (c == '>') {
3146 markend = markstart + delete_count;
3147 delete_count = 0;
3149 else {
3150 markend = markstart - delete_count;
3151 delete_count = -1; // to fix gindex in the other direction
3154 pushkey = 'x';
3155 fix_marks(&markstart, &markend);
3158 if (d->mode != MODE_EDIT)
3159 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3161 update_status_notify(game[gindex], NULL);
3162 update_all(game[gindex]);
3163 return 1;
3164 case '}':
3165 case '{':
3166 case '?':
3167 if (gtotal < 2)
3168 return 1;
3170 if (!*gameexp || c == '?') {
3171 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
3172 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
3173 NULL, 0, -1)) == NULL)
3174 return 1;
3176 strncpy(gameexp, tmp, sizeof(gameexp));
3179 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1, (keycount)
3180 ? keycount : 1)) ==
3182 return 1;
3184 gindex = n;
3185 d = game[gindex].data;
3187 if (pgn_history_total(game[gindex].hp))
3188 d->mode = MODE_HISTORY;
3190 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3191 update_all(game[gindex]);
3192 return 1;
3193 case 'J':
3194 if (gtotal < 2)
3195 return 1;
3197 /* FIXME field validation
3198 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
3199 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
3200 == NULL)
3201 return 1;
3204 if (!keycount) {
3205 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
3206 NULL, NULL, 0, -1)) == NULL)
3207 return 1;
3209 if (!isinteger(tmp))
3210 return 1;
3212 i = atoi(tmp);
3214 else
3215 i = keycount;
3217 if (--i > gtotal - 1 || i < 0)
3218 return 1;
3220 gindex = i;
3221 d = game[gindex].data;
3222 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3223 update_status_notify(game[gindex], NULL);
3224 update_all(game[gindex]);
3225 return 1;
3226 case 'x':
3227 pushkey = 0;
3229 if (gtotal < 2)
3230 return 1;
3232 if (keycount && delete_count == 0) {
3233 markstart = gindex;
3234 delete_count = keycount;
3235 update_status_notify(game[gindex], "%s (delete)",
3236 status.notify);
3237 return 1;
3240 if (markstart >= 0 && markend >= 0) {
3241 for (i = markstart; i < markend; i++) {
3242 if (toggle_delete_flag(i)) {
3243 update_all(game[gindex]);
3244 return 1;
3248 gindex = (delete_count < 0) ? markstart : i - 1;
3249 update_all(game[gindex]);
3251 else {
3252 if (toggle_delete_flag(gindex))
3253 return 1;
3256 markstart = markend = -1;
3257 delete_count = 0;
3258 update_status_window(game[gindex]);
3259 return 1;
3260 case 'X':
3261 if (gtotal < 2) {
3262 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3263 return 1;
3266 tmp = NULL;
3268 for (i = n = 0; i < gtotal; i++) {
3269 d = game[i].data;
3271 if (TEST_FLAG(d->flags, CF_DELETE))
3272 n++;
3275 if (!n)
3276 tmp = GAME_DELETE_GAME_TEXT;
3277 else {
3278 if (n == gtotal) {
3279 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
3280 return 1;
3283 tmp = GAME_DELETE_ALL_TEXT;
3286 if (config.deleteprompt) {
3287 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
3288 return 1;
3291 delete_game((!n) ? gindex : -1);
3292 d = game[gindex].data;
3293 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3294 update_all(game[gindex]);
3295 return 1;
3296 case 'T':
3297 edit_save_tags(&game[gindex]);
3298 update_all(game[gindex]);
3299 return 1;
3300 case 't':
3301 edit_tags(game[gindex], d->b, 0);
3302 return 1;
3303 case 'r':
3304 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
3305 BROWSER_PROMPT, file_browser, NULL, '\t',
3306 -1)) == NULL)
3307 return 1;
3309 if ((tmp = word_expand(tmp)) == NULL)
3310 break;
3312 if ((fp = pgn_open(tmp)) == NULL) {
3313 cmessage(ERROR, ANYKEY, "%s\n%s", tmp, strerror(errno));
3314 return 1;
3317 free_userdata();
3319 if (pgn_parse(fp) == E_PGN_ERR) {
3320 del_panel(loadingp);
3321 delwin(loadingw);
3322 loadingw = NULL;
3323 loadingp = NULL;
3324 init_userdata();
3325 update_all(game[gindex]);
3326 return 1;
3329 del_panel(loadingp);
3330 delwin(loadingw);
3331 loadingw = NULL;
3332 loadingp = NULL;
3333 init_userdata();
3334 strncpy(loadfile, tmp, sizeof(loadfile));
3336 if (pgn_history_total(game[gindex].hp))
3337 d->mode = MODE_HISTORY;
3339 d = game[gindex].data;
3340 pgn_board_update(&game[gindex], d->b, pgn_history_total(game[gindex].hp));
3341 update_all(game[gindex]);
3342 return 1;
3343 case 'S':
3344 case 's':
3345 i = -1;
3347 if (gtotal > 1) {
3348 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
3349 GAME_SAVE_MULTI_TEXT);
3351 if (n == 'c')
3352 i = gindex;
3353 else if (n == 'a')
3354 i = -1;
3355 else {
3356 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3357 return 1;
3361 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
3362 BROWSER_PROMPT, file_browser, NULL,
3363 '\t', -1)) == NULL) {
3364 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
3365 return 1;
3368 if ((tmp = word_expand(tmp)) == NULL)
3369 break;
3371 if (pgn_is_compressed(tmp)) {
3372 p = tmp + strlen(tmp) - 1;
3374 if (*p != 'n' || *(p-1) != 'g' || *(p-2) != 'p' ||
3375 *(p-3) != '.') {
3376 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3377 tmp = tfile;
3380 else {
3381 if ((p = strchr(tmp, '.')) != NULL) {
3382 if (strcmp(p, ".pgn") != 0) {
3383 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3384 tmp = tfile;
3387 else {
3388 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
3389 tmp = tfile;
3393 if (save_pgn(tmp, i)) {
3394 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
3395 return 1;
3398 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
3399 update_all(game[gindex]);
3400 return 1;
3401 case KEY_F(1):
3402 n = 0;
3404 switch (d->mode) {
3405 case MODE_PLAY:
3406 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3407 break;
3408 case MODE_HISTORY:
3409 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3410 break;
3411 case MODE_EDIT:
3412 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3413 break;
3414 default:
3415 break;
3418 while (c == KEY_F(1)) {
3419 c = help(GAME_HELP_INDEX_TITLE, GAME_HELP_INDEX_PROMPT,
3420 mainhelp);
3422 switch (c) {
3423 case 'h':
3424 c = help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
3425 break;
3426 case 'p':
3427 c = help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
3428 break;
3429 case 'e':
3430 c = help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
3431 break;
3432 case 'g':
3433 c = help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
3434 break;
3435 default:
3436 break;
3440 return 1;
3441 case 'n':
3442 case 'N':
3443 if (c == 'N') {
3444 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
3445 return 1;
3448 if (c == 'n') {
3449 pgn_new_game();
3450 add_custom_tags(&game[gindex].tag);
3451 init_userdata_once(&game[gindex], gindex);
3453 else {
3454 stop_clock();
3455 free_userdata();
3456 pgn_parse(NULL);
3457 add_custom_tags(&game[gindex].tag);
3458 init_userdata();
3459 loadfile[0] = 0;
3462 d->mode = MODE_PLAY;
3463 update_status_notify(game[gindex], NULL);
3464 update_all(game[gindex]);
3465 return 1;
3466 case CTRL('L'):
3467 endwin();
3468 keypad(boardw, TRUE);
3469 refresh_all();
3470 return 1;
3471 case KEY_ESCAPE:
3472 d->sp.icon = d->sp.srow = d->sp.scol = 0;
3473 markend = markstart = 0;
3475 if (keycount) {
3476 keycount = 0;
3477 update_status_notify(game[gindex], NULL);
3480 if (config.validmoves)
3481 pgn_reset_valid_moves(d->b);
3483 return 1;
3484 case '0' ... '9':
3485 n = c - '0';
3487 if (keycount)
3488 keycount = keycount * 10 + n;
3489 else
3490 keycount = n;
3492 update_status_notify(game[gindex], "Repeat %i", keycount);
3493 return -1;
3494 case KEY_UP:
3495 if (d->mode == MODE_HISTORY)
3496 return 0;
3498 if (keycount) {
3499 d->c_row += keycount;
3500 pushkey = '\n';
3502 else
3503 d->c_row++;
3505 if (d->c_row > 8)
3506 d->c_row = 1;
3508 return 1;
3509 case KEY_DOWN:
3510 if (d->mode == MODE_HISTORY)
3511 return 0;
3513 if (keycount) {
3514 d->c_row -= keycount;
3515 pushkey = '\n';
3516 update_status_notify(game[gindex], NULL);
3518 else
3519 d->c_row--;
3521 if (d->c_row < 1)
3522 d->c_row = 8;
3524 return 1;
3525 case KEY_LEFT:
3526 if (d->mode == MODE_HISTORY)
3527 return 0;
3529 if (keycount) {
3530 d->c_col -= keycount;
3531 pushkey = '\n';
3533 else
3534 d->c_col--;
3536 if (d->c_col < 1)
3537 d->c_col = 8;
3539 return 1;
3540 case KEY_RIGHT:
3541 if (d->mode == MODE_HISTORY)
3542 return 0;
3544 if (keycount) {
3545 d->c_col += keycount;
3546 pushkey = '\n';
3548 else
3549 d->c_col++;
3551 if (d->c_col > 8)
3552 d->c_col = 1;
3554 return 1;
3555 case 'e':
3556 if (d->mode != MODE_EDIT && d->mode !=
3557 MODE_PLAY)
3558 return 1;
3560 // Don't edit a running game (for now).
3561 if (pgn_history_total(game[gindex].hp))
3562 return 1;
3564 if (d->mode != MODE_EDIT) {
3565 pgn_board_init_fen(&game[gindex], d->b, NULL);
3566 config.details++;
3567 d->mode = MODE_EDIT;
3568 update_all(game[gindex]);
3569 return 1;
3572 config.details--;
3573 pgn_tag_add(&game[gindex].tag, "FEN",
3574 pgn_game_to_fen(game[gindex], d->b));
3575 pgn_tag_add(&game[gindex].tag, "SetUp", "1");
3576 pgn_tag_sort(game[gindex].tag);
3577 d->mode = MODE_PLAY;
3578 update_all(game[gindex]);
3579 return 1;
3580 case 'Q':
3581 quit = 1;
3582 return 1;
3583 case KEY_RESIZE:
3584 do_window_resize();
3585 return 1;
3586 #ifdef DEBUG
3587 case 'D':
3588 message("DEBUG BOARD", ANYKEY, "%s", debug_board(d->b));
3589 return 1;
3590 #endif
3591 case 0:
3592 default:
3593 break;
3596 return 0;
3599 void game_loop()
3601 int error_recover = 0;
3602 struct userdata_s *d = game[gindex].data;
3604 gindex = gtotal - 1;
3606 if (pgn_history_total(game[gindex].hp))
3607 d->mode = MODE_HISTORY;
3608 else
3609 d->mode = MODE_PLAY;
3611 if (d->mode == MODE_HISTORY) {
3612 pgn_board_update(&game[gindex], d->b,
3613 pgn_history_total(game[gindex].hp));
3616 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
3617 movestep = 2;
3618 flushinp();
3619 update_all(game[gindex]);
3620 update_tag_window(game[gindex].tag);
3621 wtimeout(boardw, 70);
3623 while (!quit) {
3624 int c = 0;
3625 int n = 0, i;
3626 char fdbuf[8192] = {0};
3627 int len;
3628 struct timeval tv = {0, 0};
3629 fd_set rfds, wfds;
3631 FD_ZERO(&rfds);
3633 for (i = 0; i < gtotal; i++) {
3634 d = game[i].data;
3636 if (d->engine) {
3637 if (d->engine->fd[ENGINE_IN_FD] > 2) {
3638 if (d->engine->fd[ENGINE_IN_FD] > n)
3639 n = d->engine->fd[ENGINE_IN_FD];
3641 FD_SET(d->engine->fd[ENGINE_IN_FD], &rfds);
3644 if (d->engine->fd[ENGINE_OUT_FD] > 2) {
3645 if (d->engine->fd[ENGINE_OUT_FD] > n)
3646 n = d->engine->fd[ENGINE_OUT_FD];
3648 FD_SET(d->engine->fd[ENGINE_OUT_FD], &wfds);
3653 if (n) {
3654 if ((n = select(n + 1, &rfds, &wfds, NULL, &tv)) > 0) {
3655 for (i = 0; i < gtotal; i++) {
3656 d = game[i].data;
3658 if (d->engine) {
3659 if (FD_ISSET(d->engine->fd[ENGINE_IN_FD], &rfds)) {
3660 len = read(d->engine->fd[ENGINE_IN_FD], fdbuf,
3661 sizeof(fdbuf));
3663 if (len == -1) {
3664 if (errno != EAGAIN) {
3665 cmessage(ERROR, ANYKEY, "Engine read(): %s",
3666 strerror(errno));
3667 waitpid(d->engine->pid, &n, 0);
3668 free(d->engine);
3669 d->engine = NULL;
3670 break;
3673 else {
3674 if (len)
3675 parse_engine_output(&game[i], fdbuf);
3679 if (FD_ISSET(d->engine->fd[ENGINE_OUT_FD], &wfds)) {
3680 if (d->engine->queue)
3681 send_engine_command(&game[i]);
3686 else {
3687 if (n == -1)
3688 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
3689 /* timeout */
3693 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
3694 d->mode = MODE_HISTORY;
3696 d = game[gindex].data;
3697 error_recover = 0;
3698 draw_board(&game[gindex]);
3699 update_all(game[gindex]);
3700 wmove(boardw, ROWTOMATRIX(d->c_row), COLTOMATRIX(d->c_col));
3701 refresh_all();
3703 if (pushkey)
3704 c = pushkey;
3705 else {
3706 if ((c = wgetch(boardw)) == ERR)
3707 continue;
3710 if (!keycount && status.notify)
3711 update_status_notify(game[gindex], NULL);
3713 if ((n = globalkeys(c)) == 1) {
3714 keycount = 0;
3715 continue;
3717 else if (n == -1)
3718 continue;
3720 switch (d->mode) {
3721 case MODE_EDIT:
3722 editmode_keys(c);
3723 break;
3724 case MODE_PLAY:
3725 if (playmode_keys(c))
3726 continue;
3727 break;
3728 case MODE_HISTORY:
3729 historymode_keys(c);
3730 break;
3731 default:
3732 break;
3735 keycount = 0;
3739 void usage(const char *pn, int ret)
3741 fprintf((ret) ? stderr : stdout, "%s",
3742 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3743 " -p Load PGN file.\n"
3744 " -V Validate a game file.\n"
3745 " -S Validate and output a PGN formatted game.\n"
3746 " -R Like -S but write a reduced PGN formatted game.\n"
3747 " -t Also write custom PGN tags from config file.\n"
3748 " -E Stop processing on file parsing error (overrides config).\n"
3749 " -v Version information.\n"
3750 " -h This help text.\n");
3752 exit(ret);
3755 void cleanup_all()
3757 int i;
3759 stop_clock();
3760 free_userdata();
3761 pgn_free_all();
3762 free(config.engine_cmd);
3763 free(config.pattern);
3764 free(config.ccfile);
3765 free(config.nagfile);
3766 free(config.configfile);
3768 if (config.keys) {
3769 for (i = 0; config.keys[i]; i++)
3770 free(config.keys[i]->str);
3772 free(config.keys);
3775 if (config.einit) {
3776 for (i = 0; config.einit[i]; i++)
3777 free(config.einit[i]);
3779 free(config.einit);
3782 if (config.tag)
3783 pgn_tag_free(config.tag);
3785 if (curses_initialized) {
3786 del_panel(boardp);
3787 del_panel(historyp);
3788 del_panel(statusp);
3789 del_panel(tagp);
3790 delwin(boardw);
3791 delwin(historyw);
3792 delwin(statusw);
3793 delwin(tagw);
3795 if (enginew) {
3796 del_panel(enginep);
3797 delwin(enginew);
3799 if (enginebuf) {
3800 for (i = 0; enginebuf[i]; i++)
3801 free(enginebuf[i]);
3803 free(enginebuf);
3807 endwin();
3811 void catch_signal(int which)
3813 switch (which) {
3814 case SIGALRM:
3815 update_clocks();
3816 break;
3817 case SIGPIPE:
3818 if (which == SIGPIPE && quit)
3819 break;
3821 if (which == SIGPIPE)
3822 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
3824 cleanup_all();
3825 exit(EXIT_FAILURE);
3826 break;
3827 case SIGSTOP:
3828 savetty();
3829 break;
3830 case SIGCONT:
3831 resetty();
3832 keypad(boardw, TRUE);
3833 curs_set(0);
3834 cbreak();
3835 noecho();
3836 break;
3837 case SIGINT:
3838 case SIGTERM:
3839 quit = 1;
3840 break;
3841 default:
3842 break;
3846 void loading_progress(long total, long offset)
3848 int n = (100 * (offset / 100) / (total / 100));
3850 if (curses_initialized)
3851 update_loading_window(n);
3852 else {
3853 fprintf(stderr, "Loading... %i%% (%i games)\r", n, gtotal);
3854 fflush(stderr);
3858 static void set_defaults()
3860 set_config_defaults();
3861 filetype = NO_FILE;
3862 pgn_config_set(PGN_PROGRESS, 1024);
3863 pgn_config_set(PGN_PROGRESS_FUNC, loading_progress);
3866 int main(int argc, char *argv[])
3868 int opt;
3869 struct stat st;
3870 char buf[FILENAME_MAX];
3871 char datadir[FILENAME_MAX];
3872 int ret = EXIT_SUCCESS;
3873 int validate_only = 0, validate_and_write = 0;
3874 int write_custom_tags = 0;
3875 FILE *fp;
3876 int i = 0;
3878 if ((config.pwd = getpwuid(getuid())) == NULL)
3879 err(EXIT_FAILURE, "getpwuid()");
3881 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
3882 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
3883 config.ccfile = strdup(buf);
3884 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
3885 config.nagfile = strdup(buf);
3886 snprintf(buf, sizeof(buf), "%s/config", datadir);
3887 config.configfile = strdup(buf);
3889 if (stat(datadir, &st) == -1) {
3890 if (errno == ENOENT) {
3891 if (mkdir(datadir, 0755) == -1)
3892 err(EXIT_FAILURE, "%s", datadir);
3894 else
3895 err(EXIT_FAILURE, "%s", datadir);
3897 stat(datadir, &st);
3900 if (!S_ISDIR(st.st_mode))
3901 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
3903 set_defaults();
3905 while ((opt = getopt(argc, argv, "EVtSRhp:v")) != -1) {
3906 switch (opt) {
3907 case 't':
3908 write_custom_tags = 1;
3909 break;
3910 case 'E':
3911 i = 1;
3912 break;
3913 case 'R':
3914 pgn_config_set(PGN_REDUCED, 1);
3915 case 'S':
3916 validate_and_write = 1;
3917 case 'V':
3918 validate_only = 1;
3919 break;
3920 case 'v':
3921 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
3922 COPYRIGHT);
3923 exit(EXIT_SUCCESS);
3924 case 'p':
3925 filetype = PGN_FILE;
3926 strncpy(loadfile, optarg, sizeof(loadfile));
3927 break;
3928 case 'h':
3929 default:
3930 usage(argv[0], EXIT_SUCCESS);
3934 if ((validate_only || validate_and_write) && !*loadfile)
3935 usage(argv[0], EXIT_FAILURE);
3937 if (access(config.configfile, R_OK) == 0)
3938 parse_rcfile(config.configfile);
3940 if (i)
3941 pgn_config_set(PGN_STOP_ON_ERROR, 1);
3943 signal(SIGPIPE, catch_signal);
3944 signal(SIGCONT, catch_signal);
3945 signal(SIGSTOP, catch_signal);
3946 signal(SIGINT, catch_signal);
3947 signal(SIGALRM, catch_signal);
3948 signal(SIGTERM, catch_signal);
3950 srandom(getpid());
3952 switch (filetype) {
3953 case PGN_FILE:
3954 if ((fp = pgn_open(loadfile)) == NULL)
3955 err(EXIT_FAILURE, "%s", loadfile);
3957 ret = pgn_parse(fp);
3958 break;
3959 case FEN_FILE:
3960 //ret = parse_fen_file(loadfile);
3961 break;
3962 case EPD_FILE: // Not implemented.
3963 case NO_FILE:
3964 default:
3965 // No file specified. Empty game.
3966 ret = pgn_parse(NULL);
3967 add_custom_tags(&game[gindex].tag);
3968 break;
3971 if (validate_only || validate_and_write) {
3972 if (validate_and_write) {
3973 for (i = 0; i < gtotal; i++) {
3974 if (write_custom_tags)
3975 add_custom_tags(&game[i].tag);
3977 pgn_write(stdout, game[i]);
3981 cleanup_all();
3982 exit(ret);
3984 else if (ret == E_PGN_ERR)
3985 exit(ret);
3987 init_userdata();
3989 if (initscr() == NULL)
3990 errx(EXIT_FAILURE, "%s", E_INITCURSES);
3991 else
3992 curses_initialized = 1;
3994 if (LINES < 24 || COLS < 80) {
3995 endwin();
3996 errx(EXIT_FAILURE, "Need at least an 80x24 terminal.");
3999 if (has_colors() == TRUE && start_color() == OK)
4000 init_color_pairs();
4002 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
4003 boardp = new_panel(boardw);
4004 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
4005 COLS - HISTORY_WIDTH);
4006 historyp = new_panel(historyw);
4007 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
4008 statusp = new_panel(statusw);
4009 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
4010 tagp = new_panel(tagw);
4011 keypad(boardw, TRUE);
4012 // leaveok(boardw, TRUE);
4013 leaveok(tagw, TRUE);
4014 leaveok(statusw, TRUE);
4015 leaveok(historyw, TRUE);
4016 curs_set(0);
4017 cbreak();
4018 noecho();
4019 draw_window_decor();
4020 game_loop();
4021 cleanup_all();
4022 exit(EXIT_SUCCESS);