1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
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
23 #include <sys/types.h>
25 #include <sys/socket.h>
39 #ifdef HAVE_SYS_WAIT_H
75 static char *str_etc(const char *str
, int maxlen
, int rev
)
77 int len
= strlen(str
);
78 static char buf
[80], *p
= buf
;
81 strncpy(buf
, str
, sizeof(buf
));
90 for (i
= 0; i
< maxlen
+ 3; i
++)
91 *p
++ = buf
[(len
- maxlen
) + i
+ 3];
106 void update_cursor(GAME g
, int idx
)
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.
118 if (idx
> t
|| idx
< 0 || !t
|| !g
.hp
[idx
]->move
) {
119 d
->c_row
= 2, d
->c_col
= 5;
132 d
->c_row
= (g
.turn
== WHITE
) ? 1 : 8;
141 d
->c_row
= RANKTOINT(*p
--);
142 d
->c_col
= FILETOINT(*p
);
145 static int init_nag()
151 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
152 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.nagfile
, strerror(errno
));
156 nags
= Realloc(nags
, 2 * sizeof(char *));
161 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
162 nags
= Realloc(nags
, (i
+ 2) * sizeof(char *));
163 nags
[i
++] = strdup(line
);
170 void set_menu_vars(int c
, int rows
, int items
, int *item
, int *top
)
172 int selected
= *item
;
177 selected
= toppos
= 0;
181 toppos
= items
- rows
+ 1;
184 if (selected
- 1 < 0) {
187 toppos
= selected
- rows
+ 1;
192 if (toppos
&& selected
<= toppos
)
197 if (selected
+ 1 > items
)
198 selected
= toppos
= 0;
202 if (selected
- toppos
>= rows
)
212 toppos
= selected
- rows
+ 1;
220 if (selected
> items
)
223 toppos
= selected
- rows
+ 1;
229 if (selected
== (LINES
/ 5) * 4 - 4)
231 else if (selected
<= rows
)
234 toppos
= selected
- rows
+ 1;
245 int test_nag_selected(unsigned char nag
[], int s
)
249 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
257 char *history_edit_nag(void *arg
)
264 HISTORY
*anno
= (HISTORY
*)arg
;
269 unsigned char nag
[MAX_PGN_NAG
] = {0};
270 char menubuf
[64] = {0}, *mp
= menubuf
;
277 for (i
= 1, n
= 0; nags
[i
]; i
++) {
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
);
295 memcpy(&nag
, &anno
->nag
, sizeof(nag
));
297 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
308 draw_window_title(win
, NAG_EDIT_TITLE
, cols
, CP_INPUT_TITLE
,
311 for (i
= toppos
, c
= 2; i
< total
&& c
< rows
- 2; i
++, c
++) {
313 wattron(win
, CP_MENU_HIGHLIGHT
);
314 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
315 wattroff(win
, CP_MENU_HIGHLIGHT
);
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
);
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
);
341 help(NAG_EDIT_HELP
, ANYKEY
, naghelp
);
349 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
355 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
362 if ((found
= test_nag_selected(nag
, selected
)) != -1) {
367 if (itemcount
+ 1 > MAX_PGN_NAG
)
370 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
388 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
397 for (i
= 0; i
< total
; i
++) {
401 if (strncasecmp(menubuf
, nags
[i
], strlen(menubuf
)) == 0) {
412 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
418 memcpy(&anno
->nag
, &nag
, sizeof(nag
));
424 static void view_nag(void *arg
)
426 HISTORY
*h
= (HISTORY
*)arg
;
428 char line
[LINE_MAX
] = {0};
431 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
438 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
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])
461 if (!nag
&& !comment
)
464 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
.move
);
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
);
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
)
481 for (i
= 0; files
[i
].name
; i
++) {
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
];
509 int cursor
= curs_set(0);
510 char menubuf
[64] = {0}, *mp
= menubuf
;
513 if (config
.savedirectory
) {
514 if ((p
= word_expand(config
.savedirectory
)) == 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
));
525 getcwd(path
, sizeof(path
));
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
)
536 strncpy(pattern
, p
, sizeof(pattern
));
545 int len
= strlen(path
);
548 struct file_s
*files
= NULL
;
554 if (wordexp(pattern
, &w
, x
) != 0) {
555 cmessage(ERROR
, ANYKEY
, "Error in pattern\n%s", pattern
);
559 for (i
= 0; i
< w
.we_wordc
; i
++) {
564 if (stat(w
.we_wordv
[i
], &st
) == -1)
567 if ((p
= strrchr(w
.we_wordv
[i
], '/')) != NULL
)
573 if (!S_ISDIR(st
.st_mode
))
576 if (p
[0] == '.' && p
[1] == 0)
580 if (S_ISDIR(st
.st_mode
))
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
));
603 if ((p
= word_split_append(path
, '/', config
.pattern
)) == NULL
)
606 strncpy(pattern
, p
, sizeof(pattern
));
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
);
624 if (cols
< strlen(path
))
627 if (cols
< strlen(HELP_PROMPT
))
628 cols
= strlen(HELP_PROMPT
);
634 rows
= (n
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : n
+ 4;
636 win
= newwin(rows
, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
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
);
649 for (i
= toppos
, c
= 2; i
< n
&& c
< rows
- 2; i
++, c
++) {
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
);
658 mvwprintw(win
, c
, 1, "%-*s %-*s", nlen
, files
[i
].name
,
659 cols
- nlen
- 2 - 1, files
[i
].st
);
672 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
680 cleanup(win
, panel
, files
);
685 help(BROWSER_HELP
, ANYKEY
, file_browser_help
);
688 strncpy(path
, "~", sizeof(path
));
689 cleanup(win
, panel
, files
);
693 if ((tmp
= get_input_str_clear(BROWSER_CHDIR_TITLE
, NULL
))
697 if (tmp
[strlen(tmp
) - 1] == '/')
698 tmp
[strlen(tmp
) - 1] = 0;
700 strncpy(path
, tmp
, sizeof(path
));
701 cleanup(win
, panel
, files
);
705 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
714 for (i
= 0; i
< n
; i
++) {
715 if (strncasecmp(menubuf
, files
[i
].name
,
716 strlen(menubuf
)) == 0) {
727 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
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
));
743 if (S_ISDIR(st
.st_mode
)) {
744 p
= file
+ strlen(file
) - 2;
746 if (strcmp(p
, "..") == 0) {
747 p
= file
+ strlen(file
) - 3;
750 if ((p
= strrchr(file
, '/')) != NULL
)
751 file
[strlen(file
) - strlen(p
)] = 0;
754 strncpy(path
, file
, sizeof(path
));
758 if (S_ISREG(st
.st_mode
))
761 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, E_NOTAREGFILE
);
766 return (*file
) ? file
: NULL
;
769 static int init_country_codes()
772 char line
[LINE_MAX
], *s
;
775 if ((fp
= fopen(config
.ccfile
, "r")) == NULL
) {
776 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.ccfile
, strerror(errno
));
780 while ((s
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
783 if ((tmp
= strsep(&s
, " ")) == NULL
)
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
));
798 memset(&ccodes
[cindex
], '\0', sizeof(struct country_codes
));
804 char *country_codes(void *arg
)
815 char menubuf
[64] = {0}, *mp
= menubuf
;
818 if (init_country_codes())
822 for (i
= n
= 0; ccodes
[i
].code
&& ccodes
[i
].code
[0]; i
++) {
823 n
= strlen(ccodes
[i
].code
) + strlen(ccodes
[i
].country
);
832 if (cols
< strlen(HELP_PROMPT
) + 21)
833 cols
= strlen(HELP_PROMPT
) + 21;
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
);
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
++) {
861 wattron(win
, CP_MENU_HIGHLIGHT
);
862 mvwprintw(win
, c
, 1, "%3s %s", ccodes
[i
].code
,
864 wattroff(win
, CP_MENU_HIGHLIGHT
);
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
);
879 help(CC_KEY_HELP
, ANYKEY
, cc_help
);
887 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
892 tmp
= ccodes
[selected
].code
;
900 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
909 for (i
= 0; i
< total
; i
++) {
910 if (strncasecmp(menubuf
, ccodes
[i
].code
,
911 strlen(menubuf
)) == 0) {
922 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
933 static void add_custom_tags(TAG
***t
)
936 int total
= pgn_tag_total(config
.tag
);
941 for (i
= 0; i
< total
; i
++)
942 pgn_tag_add(t
, config
.tag
[i
]->name
, config
.tag
[i
]->value
);
947 TAG
**edit_tags(GAME g
, BOARD b
, int edit
)
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
);
975 data_index
= pgn_tag_total(data
);
977 for (i
= cols
= 0, n
= 4; i
< data_index
; i
++) {
978 n
= strlen(data
[i
]->name
);
984 n
+= strlen(data
[i
]->value
);
986 n
+= strlen(UNKNOWN
);
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 :
1004 win
= newwin(rows
, cols
, CALCPOSY(rows
), CALCPOSX(cols
));
1005 panel
= new_panel(win
);
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;
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
);
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
);
1050 add_custom_tags(&data
);
1051 selected
= data_index
- 1;
1052 toppos
= data_index
- (rows
- 4);
1057 help(TAG_EDIT_HELP
, ANYKEY
, pgn_edit_help
);
1059 help(TAG_VIEW_HELP
, ANYKEY
, pgn_info_help
);
1065 if (selected
<= 6) {
1066 cmessage(NULL
, ANYKEY
, "%s", E_REMOVE_STR
);
1070 data_index
= pgn_tag_total(data
);
1072 for (i
= 0; i
< data_index
; i
++) {
1076 pgn_tag_add(&tmppgn
, data
[i
]->name
, data
[i
]->value
);
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;
1090 if (selected
> rows
- 4)
1091 toppos
= selected
- (rows
- 4);
1093 toppos
-= (toppos
) ? 1 : 0;
1102 if ((newtag
= get_input(TAG_NEW_TITLE
, NULL
, 1, 1, NULL
,
1103 NULL
, NULL
, 0, FIELD_TYPE_PGN_TAG_NAME
))
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
);
1115 for (i
= 0; i
< data_index
; i
++) {
1116 if (strcasecmp(data
[i
]->name
, newtag
) == 0) {
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
,
1135 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
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
,
1165 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
1174 for (i
= 0; i
< data_index
; i
++) {
1175 if (strncasecmp(menubuf
, data
[i
]->name
, strlen(menubuf
))
1182 if (n
== selected
) {
1187 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
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
);
1200 snprintf(buf
, sizeof(buf
), "%s \"%s\"",
1201 (edit
) ? TAG_EDIT_TAG_TITLE
: TAG_VIEW_TAG_TITLE
,
1202 data
[selected
]->name
);
1205 if (!data
[selected
]->value
)
1208 cmessage(buf
, ANYKEY
, "%s", data
[selected
]->value
);
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
);
1217 if (strptime(tmp
, PGN_TIME_FORMAT
, &tp
) == NULL
) {
1218 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_DATE_FMT
);
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);
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
);
1243 else if (strcmp(data
[selected
]->name
, "Result") == 0) {
1244 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, NULL
, NULL
, NULL
,
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
);
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
)
1283 char buf
[FILENAME_MAX
];
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
,
1300 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1306 stat(config
.savedirectory
, &st
);
1308 if (!S_ISDIR(st
.st_mode
)) {
1309 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
1313 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
1317 if (access(filename
, W_OK
) == 0) {
1318 c
= cmessage(NULL
, GAME_SAVE_OVERWRITE_PROMPT
,
1319 "%s \"%s\"", E_FILEEXISTS
, filename
);
1323 if (pgn_is_compressed(filename
) == E_PGN_OK
) {
1324 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
1341 if ((fp
= popen(command
, "w")) == NULL
) {
1342 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1347 if ((fp
= fopen(filename
, mode
)) == NULL
) {
1348 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1353 for (i
= (saveindex
== -1) ? 0 : saveindex
; i
< saveindex_max
; i
++) {
1355 pgn_write(fp
, game
[i
]);
1356 CLEAR_FLAG(d
->flags
, CF_MODIFIED
);
1364 if (saveindex
== -1)
1365 strncpy(loadfile
, filename
, sizeof(loadfile
));
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
1374 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
1375 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1377 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
1380 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1382 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
1383 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1385 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
1388 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1390 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
1391 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1393 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
1396 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1398 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
1399 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1401 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
1404 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
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
)) {
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
);
1416 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1420 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
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
)) {
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
);
1432 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1440 static void draw_board(GAME
*g
)
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
++) {
1455 for (col
= 0; col
< maxx
; col
++) {
1458 unsigned char piece
;
1460 if (row
== 0 || row
== maxy
- 2) {
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
));
1477 if (col
!= maxx
- 1)
1478 mvwaddch(boardw
, row
, col
,
1479 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
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
);
1492 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
1494 mvwaddch(boardw
, row
, col
,
1495 LINE_GRAPHIC((col
) ?
1496 ACS_RTEE
| CP_BOARD_GRAPHICS
:
1497 ACS_LTEE
| CP_BOARD_GRAPHICS
));
1499 mvwaddch(boardw
, row
, col
,
1500 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1505 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
1506 mvwaddch(boardw
, row
, col
,
1507 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1511 if (!(col
% 4) && row
!= maxy
- 1) {
1512 mvwaddch(boardw
, row
, col
,
1513 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
1524 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
1530 if (config
.validmoves
&& d
->b
[brow
][bcol
].valid
) {
1531 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
1532 CP_BOARD_MOVES_BLACK
;
1535 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
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)
1551 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
1553 if (row
== maxy
- 1)
1554 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
1556 if (config
.details
&& d
->b
[row
/ 2][bcol
].enpassant
)
1559 piece
= d
->b
[row
/ 2][bcol
].icon
;
1561 if (config
.details
&& castling_state(g
, d
->b
, brow
,
1565 if (g
->side
== WHITE
&& isupper(piece
))
1567 else if (g
->side
== BLACK
&& islower(piece
))
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
);
1582 if (col
!= maxx
- 1)
1583 mvwaddch(boardw
, row
, col
,
1584 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
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
);
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
;
1610 struct userdata_s
*d
= g
->data
;
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
);
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)
1626 p
= str
+ strlen(str
);
1627 *p
++ = toupper(promo
);
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
);
1642 if ((n
= pgn_validate_move(g
, b
, &p
)) != E_PGN_OK
) {
1643 invalid_move(d
->n
+ 1, n
, p
);
1650 static void update_clock(GAME
*g
, struct itimerval it
)
1652 struct userdata_s
*d
= g
->data
;
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;
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
)
1689 struct userdata_s
*d
= g
->data
;
1691 if ((p
= board_to_san(g
, b
)) == NULL
)
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
);
1702 SET_FLAG(d
->flags
, CF_MODIFIED
);
1707 add_engine_command(g
, ENGINE_THINKING
, "%s\n", p
);
1711 static char *clock_to_char(long n
)
1713 static char buf
[16];
1714 int h
= 0, m
= 0, s
= 0;
1717 m
= (n
% 3600) / 60;
1718 s
= (n
% 3600) % 60;
1719 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i", h
, m
, s
);
1723 static char *timeval_to_char(struct timeval t
)
1725 static char buf
[16];
1726 int h
= 0, m
= 0, s
= 0;
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);
1737 void update_status_window(GAME g
)
1741 char tmp
[15], *engine
, *mode
;
1746 struct userdata_s
*d
= g
.data
;
1748 getmaxyx(statusw
, maxy
, maxx
);
1756 if (TEST_FLAG(d
->flags
, CF_DELETE
)) {
1762 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1772 if (TEST_FLAG(d
->flags
, CF_MODIFIED
)) {
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
,
1791 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1795 mode
= MODE_HISTORY_STR
;
1798 mode
= MODE_EDIT_STR
;
1801 mode
= MODE_PLAY_STR
;
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);
1816 strncat(buf
, " (human/engine)", len
- 1);
1819 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1822 switch (d
->engine
->status
) {
1823 case ENGINE_THINKING
:
1824 engine
= ENGINE_PONDER_STR
;
1827 engine
= ENGINE_READY_STR
;
1829 case ENGINE_INITIALIZING
:
1830 engine
= ENGINE_INITIALIZING_STR
;
1832 case ENGINE_OFFLINE
:
1833 engine
= ENGINE_OFFLINE_STR
;
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
));
1864 for (i
= 1; i
< maxx
- 4; i
++)
1865 mvwprintw(statusw
, maxy
- 2, i
, " ");
1868 status
.notify
= strdup(GAME_HELP_PROMPT
);
1870 wattron(statusw
, CP_STATUS_NOTIFY
);
1871 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1873 wattroff(statusw
, CP_STATUS_NOTIFY
);
1876 void update_history_window(GAME g
)
1878 char buf
[HISTORY_WIDTH
- 1];
1881 int t
= pgn_history_total(g
.hp
);
1883 n
= (g
.hindex
+ 1) / 2;
1886 total
= (t
+ 1) / 2;
1891 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1892 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
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
);
1903 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1904 strncat(buf
, " (v", sizeof(buf
));
1909 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1914 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
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
);
1928 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1929 strncat(buf
, " (V", sizeof(buf
));
1934 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1939 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
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
)
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
)
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
,
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
)
1998 for (i
= 0; enginebuf
[i
]; i
++);
2000 if (i
>= LINES
- 3) {
2003 for (i
= 0; enginebuf
[i
+1]; i
++)
2004 enginebuf
[i
] = enginebuf
[i
+1];
2006 enginebuf
[i
] = strdup(line
);
2009 enginebuf
= Realloc(enginebuf
, (i
+ 2) * sizeof(char *));
2010 enginebuf
[i
++] = strdup(line
);
2011 enginebuf
[i
] = NULL
;
2015 void update_engine_window()
2022 wmove(enginew
, 0, 0);
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
,
2034 void toggle_engine_window()
2037 enginew
= newwin(LINES
, COLS
, 0, 0);
2038 enginep
= new_panel(enginew
);
2039 draw_window_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
2041 hide_panel(enginep
);
2044 if (panel_hidden(enginep
)) {
2045 update_engine_window();
2050 hide_panel(enginep
);
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
)
2075 if (gindex
+ count
> gtotal
- 1) {
2077 gindex
= gtotal
- 1;
2085 if (gindex
- count
< 0) {
2089 gindex
= gtotal
- 1;
2096 static void delete_game(int which
)
2101 struct userdata_s
*d
;
2103 for (i
= 0; i
< gtotal
; i
++) {
2106 if (i
== which
|| TEST_FLAG(d
->flags
, CF_DELETE
)) {
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
;
2123 if (which
+ 1 >= gtotal
)
2124 gindex
= gtotal
- 1;
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
,
2140 static int firstrun
= 1;
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
);
2158 incr
= (which
== 0) ? -1 : 1;
2160 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
2161 if (i
== g
.hindex
- 1)
2164 if (i
>= pgn_history_total(g
.hp
))
2167 i
= pgn_history_total(g
.hp
) - 1;
2170 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
2173 if (count
== ++found
) {
2178 if (ret
!= REG_NOMATCH
) {
2179 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2180 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
2189 static int toggle_delete_flag(int n
)
2192 struct userdata_s
*d
= game
[n
].data
;
2194 TOGGLE_FLAG(d
->flags
, CF_DELETE
);
2196 update_all(game
[gindex
]);
2198 for (i
= x
= 0; i
< gtotal
; i
++) {
2201 if (TEST_FLAG(d
->flags
, CF_DELETE
))
2206 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2208 CLEAR_FLAG(d
->flags
, CF_DELETE
);
2215 static void edit_save_tags(GAME
*g
)
2218 struct userdata_s
*d
= g
->data
;
2220 if ((t
= edit_tags(*g
, d
->b
, 1)) == NULL
)
2223 pgn_tag_free(g
->tag
);
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
;
2235 char buf
[255], *tmp
;
2238 int incr
= (which
== 0) ? -(1) : 1;
2240 strncpy(buf
, str
, sizeof(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
);
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
);
2269 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
2280 for (t
= 0; game
[g
].tag
[t
]; t
++) {
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
) {
2292 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2293 if (count
== ++found
) {
2315 * Updates the notification line in the status window then refreshes the
2318 void update_status_notify(GAME g
, char *fmt
, ...)
2321 #ifdef HAVE_VASPRINTF
2328 if (status
.notify
) {
2329 free(status
.notify
);
2330 status
.notify
= NULL
;
2332 if (curses_initialized
)
2333 update_status_window(g
);
2340 #ifdef HAVE_VASPRINTF
2341 vasprintf(&line
, fmt
, ap
);
2343 vsnprintf(line
, sizeof(line
), fmt
, ap
);
2348 free(status
.notify
);
2350 status
.notify
= strdup(line
);
2352 #ifdef HAVE_VASPRINTF
2355 if (curses_initialized
)
2356 update_status_window(g
);
2359 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
2363 if ((!g
->ravlevel
&& g
->hp
[g
->hindex
- 1]->rav
== NULL
) ||
2364 (g
->ravlevel
&& g
->hp
[g
->hindex
]->rav
== NULL
))
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
;
2375 pgn_board_update(g
, b
, g
->hindex
+ 1);
2379 if (g
->ravlevel
- 1 < 0)
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
;
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
,
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)
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
);
2421 wmove(statusw
, 0, 0);
2423 draw_window_decor();
2424 update_all(game
[gindex
]);
2429 memset(&clock_timer
, 0, sizeof(struct itimerval
));
2430 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
2435 if (clock_timer
.it_interval
.tv_usec
)
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()
2448 struct userdata_s
*d
;
2449 struct itimerval it
;
2451 getitimer(ITIMER_REAL
, &it
);
2453 for (i
= 0; i
< gtotal
; i
++) {
2456 if (d
->mode
== MODE_PLAY
) {
2457 if (d
->paused
== 1 || TEST_FLAG(d
->flags
, CF_NEW
))
2459 else if (d
->paused
== -1) {
2460 if (game
[i
].side
== game
[i
].turn
) {
2466 update_clock(&game
[i
], it
);
2471 static int init_chess_engine(GAME
*g
)
2473 struct userdata_s
*d
= g
->data
;
2476 if (start_chess_engine(g
) > 0) {
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
);
2488 add_engine_command(g
, ENGINE_READY
, "setboard %s\n",
2489 pgn_game_to_fen(*g
, d
->b
));
2494 static int parse_clock_input(struct userdata_s
*d
, char *str
)
2516 if (!t
&& *p
!= ' ')
2550 CLEAR_FLAG(d
->flags
, CF_CLOCK
);
2553 SET_FLAG(d
->flags
, CF_CLOCK
);
2558 d
->limit
= (n
<= d
->elapsed
) ? d
->elapsed
+ n
: n
;
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;
2576 if ((tmp
= get_input(CLOCK_TITLE
, NULL
, 1, 1, CLOCK_HELP
, NULL
,
2577 NULL
, 0, -1)) == NULL
)
2580 if (parse_clock_input(d
, tmp
))
2581 cmessage(ERROR
, ANYKEY
, "Invalid time specification");
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
]))
2592 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2595 d
->engine
->status
= ENGINE_READY
;
2597 update_all(game
[gindex
]);
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
]);
2619 if (d
->engine
->status
== ENGINE_OFFLINE
)
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
;
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
)) {
2642 d
->sp
.row
= d
->c_row
;
2643 d
->sp
.col
= d
->c_col
;
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;
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
);
2668 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2669 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
2670 if (init_chess_engine(&game
[gindex
]))
2674 if (d
->sp
.icon
|| (!editmode
&& d
->engine
&&
2675 d
->engine
->status
== ENGINE_THINKING
)) {
2680 d
->sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(d
->c_row
),
2681 COLTOMATRIX(d
->c_col
)+1) & A_CHARTEXT
;
2683 if (d
->sp
.icon
== ' ') {
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
);
2696 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != E_PGN_ERR
)
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
);
2714 CLEAR_FLAG(d
->flags
, CF_NEW
);
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
]);
2727 if (!pgn_history_total(game
[gindex
].hp
))
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
]);
2742 historymode_keys(c
);
2745 config
.details
= (config
.details
) ? 0 : 1;
2748 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && game
[gindex
].turn
!=
2749 game
[gindex
].side
) {
2754 d
->paused
= (d
->paused
) ? 0 : 1;
2757 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
2760 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
) {
2761 if (init_chess_engine(&game
[gindex
]))
2765 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "go\n");
2772 for (x
= 0; config
.keys
[x
]; x
++) {
2773 if (config
.keys
[x
]->c
== c
) {
2774 switch (config
.keys
[x
]->type
) {
2776 add_engine_command(&game
[gindex
], -1, "%s\n",
2777 config
.keys
[x
]->str
);
2783 add_engine_command(&game
[gindex
], -1,
2784 "%s %i\n", config
.keys
[x
]->str
, keycount
);
2791 for (w
= 0; w
< keycount
; w
++)
2792 add_engine_command(&game
[gindex
], -1,
2793 "%s\n", config
.keys
[x
]->str
);
2800 update_status_notify(game
[gindex
], NULL
);
2808 static void editmode_keys(chtype c
)
2810 struct userdata_s
*d
= game
[gindex
].data
;
2820 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
2821 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
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;
2829 pgn_switch_turn(&game
[gindex
]);
2830 update_all(game
[gindex
]);
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);
2838 c
= message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, "%s",
2841 if (pgn_piece_to_int(c
) == -1)
2844 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
= c
;
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;
2857 static void historymode_keys(chtype c
)
2861 static char moveexp
[255] = {0};
2862 struct userdata_s
*d
= game
[gindex
].data
;
2866 config
.details
= (config
.details
) ? 0 : 1;
2869 movestep
= (movestep
== 1) ? 2 : 1;
2870 update_history_window(game
[gindex
]);
2873 pgn_history_next(&game
[gindex
], d
->b
, (keycount
> 0) ?
2874 config
.jumpcount
* keycount
* movestep
:
2875 config
.jumpcount
* movestep
);
2876 update_all(game
[gindex
]);
2879 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2880 config
.jumpcount
* keycount
* movestep
:
2881 config
.jumpcount
* movestep
);
2882 update_all(game
[gindex
]);
2885 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2886 keycount
* movestep
: movestep
);
2887 update_all(game
[gindex
]);
2890 pgn_history_next(&game
[gindex
], d
->b
, (keycount
) ?
2891 keycount
* movestep
: movestep
);
2892 update_all(game
[gindex
]);
2895 n
= game
[gindex
].hindex
;
2897 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
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'),
2911 if (!tmp
&& (!game
[gindex
].hp
[n
]->comment
||
2912 !*game
[gindex
].hp
[n
]->comment
))
2914 else if (tmp
&& game
[gindex
].hp
[n
]->comment
) {
2915 if (strcmp(tmp
, game
[gindex
].hp
[n
]->comment
) == 0)
2919 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
2920 game
[gindex
].hp
[n
]->comment
= Realloc(game
[gindex
].hp
[n
]->comment
,
2922 strncpy(game
[gindex
].hp
[n
]->comment
, (tmp
) ? tmp
: "", len
);
2923 SET_FLAG(d
->flags
, CF_MODIFIED
);
2924 update_all(game
[gindex
]);
2929 if (pgn_history_total(game
[gindex
].hp
) < 2)
2934 if (!*moveexp
|| c
== '/') {
2935 if ((tmp
= get_input(FIND_REGEXP
, moveexp
, 1, 1, NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2938 strncpy(moveexp
, tmp
, sizeof(moveexp
));
2942 if ((n
= find_move_exp(game
[gindex
], moveexp
, n
,
2943 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1))
2947 game
[gindex
].hindex
= n
;
2948 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2949 update_all(game
[gindex
]);
2952 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
]);
2955 if (game
[gindex
].hindex
- 1 >= 0)
2956 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2960 rav_next_prev(&game
[gindex
], d
->b
, (c
== '-') ? 0 : 1);
2961 update_all(game
[gindex
]);
2964 if (pgn_history_total(game
[gindex
].hp
) < 2)
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)
2975 if ((tmp
= get_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1,
2976 NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2979 if (!isinteger(tmp
))
2987 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
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
]);
3002 static void free_userdata()
3006 for (i
= 0; i
< gtotal
; i
++) {
3007 struct userdata_s
*d
;
3013 stop_engine(&game
[i
]);
3018 game
[i
].data
= NULL
;
3023 void update_loading_window(int n
)
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
,
3042 static void init_userdata_once(GAME
*g
, int n
)
3044 struct userdata_s
*d
= NULL
;
3046 d
= Calloc(1, sizeof(struct userdata_s
));
3048 d
->c_row
= 2, d
->c_col
= 5;
3049 SET_FLAG(d
->flags
, CF_NEW
);
3052 if (pgn_board_init_fen(g
, d
->b
, NULL
) != E_PGN_OK
)
3053 pgn_board_init(d
->b
);
3056 void init_userdata()
3060 for (i
= 0; i
< gtotal
; i
++)
3061 init_userdata_once(&game
[i
], i
);
3064 void fix_marks(int *start
, int *end
)
3068 *start
= (*start
< 0) ? 0 : *start
;
3069 *end
= (*end
< 0) ? 0 : *end
;
3071 if (*start
> *end
) {
3077 *end
= (*end
> gtotal
) ? gtotal
: *end
;
3080 // Global and other keys.
3081 static int globalkeys(chtype c
)
3083 static char gameexp
[255] = {0};
3087 char tfile
[FILENAME_MAX
];
3088 struct userdata_s
*d
= game
[gindex
].data
;
3092 toggle_engine_window();
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
);
3101 if (d
->mode
!= MODE_HISTORY
) {
3102 if (!pgn_history_total(game
[gindex
].hp
) ||
3103 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
3106 d
->mode
= MODE_HISTORY
;
3107 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3108 update_all(game
[gindex
]);
3112 // FIXME Resuming from previous history could append to a RAV.
3113 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
3115 if ((c
= message(NULL
, YESNO
, "%s",
3116 GAME_RESUME_HISTORY_TEXT
)) != 'y')
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
],
3130 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
3135 d
->mode
= MODE_PLAY
;
3136 update_all(game
[gindex
]);
3140 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
3142 d
= game
[gindex
].data
;
3146 markend
= markstart
+ delete_count
;
3150 markend
= markstart
- delete_count
;
3151 delete_count
= -1; // to fix gindex in the other direction
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
]);
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
)
3176 strncpy(gameexp
, tmp
, sizeof(gameexp
));
3179 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1, (keycount
)
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
]);
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))
3205 if ((tmp
= get_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
,
3206 NULL
, NULL
, 0, -1)) == NULL
)
3209 if (!isinteger(tmp
))
3217 if (--i
> gtotal
- 1 || i
< 0)
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
]);
3232 if (keycount
&& delete_count
== 0) {
3234 delete_count
= keycount
;
3235 update_status_notify(game
[gindex
], "%s (delete)",
3240 if (markstart
>= 0 && markend
>= 0) {
3241 for (i
= markstart
; i
< markend
; i
++) {
3242 if (toggle_delete_flag(i
)) {
3243 update_all(game
[gindex
]);
3248 gindex
= (delete_count
< 0) ? markstart
: i
- 1;
3249 update_all(game
[gindex
]);
3252 if (toggle_delete_flag(gindex
))
3256 markstart
= markend
= -1;
3258 update_status_window(game
[gindex
]);
3262 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
3268 for (i
= n
= 0; i
< gtotal
; i
++) {
3271 if (TEST_FLAG(d
->flags
, CF_DELETE
))
3276 tmp
= GAME_DELETE_GAME_TEXT
;
3279 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
3283 tmp
= GAME_DELETE_ALL_TEXT
;
3286 if (config
.deleteprompt
) {
3287 if ((c
= cmessage(NULL
, YESNO
, "%s", tmp
)) != 'y')
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
]);
3297 edit_save_tags(&game
[gindex
]);
3298 update_all(game
[gindex
]);
3301 edit_tags(game
[gindex
], d
->b
, 0);
3304 if ((tmp
= get_input(GAME_LOAD_TITLE
, NULL
, 1, 1,
3305 BROWSER_PROMPT
, file_browser
, NULL
, '\t',
3309 if ((tmp
= word_expand(tmp
)) == NULL
)
3312 if ((fp
= pgn_open(tmp
)) == NULL
) {
3313 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
3319 if (pgn_parse(fp
) == E_PGN_ERR
) {
3320 del_panel(loadingp
);
3325 update_all(game
[gindex
]);
3329 del_panel(loadingp
);
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
]);
3348 n
= message(NULL
, GAME_SAVE_MULTI_PROMPT
, "%s",
3349 GAME_SAVE_MULTI_TEXT
);
3356 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
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
);
3368 if ((tmp
= word_expand(tmp
)) == NULL
)
3371 if (pgn_is_compressed(tmp
)) {
3372 p
= tmp
+ strlen(tmp
) - 1;
3374 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
3376 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3381 if ((p
= strchr(tmp
, '.')) != NULL
) {
3382 if (strcmp(p
, ".pgn") != 0) {
3383 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3388 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3393 if (save_pgn(tmp
, i
)) {
3394 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
3398 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
3399 update_all(game
[gindex
]);
3406 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3409 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3412 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3418 while (c
== KEY_F(1)) {
3419 c
= help(GAME_HELP_INDEX_TITLE
, GAME_HELP_INDEX_PROMPT
,
3424 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3427 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3430 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3433 c
= help(GAME_HELP_GAME_TITLE
, ANYKEY
, gamehelp
);
3444 if (cmessage(NULL
, YESNO
, "%s", GAME_NEW_PROMPT
) != 'y')
3450 add_custom_tags(&game
[gindex
].tag
);
3451 init_userdata_once(&game
[gindex
], gindex
);
3457 add_custom_tags(&game
[gindex
].tag
);
3462 d
->mode
= MODE_PLAY
;
3463 update_status_notify(game
[gindex
], NULL
);
3464 update_all(game
[gindex
]);
3468 keypad(boardw
, TRUE
);
3472 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
3473 markend
= markstart
= 0;
3477 update_status_notify(game
[gindex
], NULL
);
3480 if (config
.validmoves
)
3481 pgn_reset_valid_moves(d
->b
);
3488 keycount
= keycount
* 10 + n
;
3492 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3495 if (d
->mode
== MODE_HISTORY
)
3499 d
->c_row
+= keycount
;
3510 if (d
->mode
== MODE_HISTORY
)
3514 d
->c_row
-= keycount
;
3516 update_status_notify(game
[gindex
], NULL
);
3526 if (d
->mode
== MODE_HISTORY
)
3530 d
->c_col
-= keycount
;
3541 if (d
->mode
== MODE_HISTORY
)
3545 d
->c_col
+= keycount
;
3556 if (d
->mode
!= MODE_EDIT
&& d
->mode
!=
3560 // Don't edit a running game (for now).
3561 if (pgn_history_total(game
[gindex
].hp
))
3564 if (d
->mode
!= MODE_EDIT
) {
3565 pgn_board_init_fen(&game
[gindex
], d
->b
, NULL
);
3567 d
->mode
= MODE_EDIT
;
3568 update_all(game
[gindex
]);
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
]);
3588 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(d
->b
));
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
;
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
);
3619 update_all(game
[gindex
]);
3620 update_tag_window(game
[gindex
].tag
);
3621 wtimeout(boardw
, 70);
3626 char fdbuf
[8192] = {0};
3628 struct timeval tv
= {0, 0};
3633 for (i
= 0; i
< gtotal
; i
++) {
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
);
3654 if ((n
= select(n
+ 1, &rfds
, &wfds
, NULL
, &tv
)) > 0) {
3655 for (i
= 0; i
< gtotal
; i
++) {
3659 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3660 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3664 if (errno
!= EAGAIN
) {
3665 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3667 waitpid(d
->engine
->pid
, &n
, 0);
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
]);
3688 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3693 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
3694 d
->mode
= MODE_HISTORY
;
3696 d
= game
[gindex
].data
;
3698 draw_board(&game
[gindex
]);
3699 update_all(game
[gindex
]);
3700 wmove(boardw
, ROWTOMATRIX(d
->c_row
), COLTOMATRIX(d
->c_col
));
3706 if ((c
= wgetch(boardw
)) == ERR
)
3710 if (!keycount
&& status
.notify
)
3711 update_status_notify(game
[gindex
], NULL
);
3713 if ((n
= globalkeys(c
)) == 1) {
3725 if (playmode_keys(c
))
3729 historymode_keys(c
);
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");
3762 free(config
.engine_cmd
);
3763 free(config
.pattern
);
3764 free(config
.ccfile
);
3765 free(config
.nagfile
);
3766 free(config
.configfile
);
3769 for (i
= 0; config
.keys
[i
]; i
++)
3770 free(config
.keys
[i
]->str
);
3776 for (i
= 0; config
.einit
[i
]; i
++)
3777 free(config
.einit
[i
]);
3783 pgn_tag_free(config
.tag
);
3785 if (curses_initialized
) {
3787 del_panel(historyp
);
3800 for (i
= 0; enginebuf
[i
]; i
++)
3811 void catch_signal(int which
)
3818 if (which
== SIGPIPE
&& quit
)
3821 if (which
== SIGPIPE
)
3822 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3832 keypad(boardw
, TRUE
);
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
);
3853 fprintf(stderr
, "Loading... %i%% (%i games)\r", n
, gtotal
);
3858 static void set_defaults()
3860 set_config_defaults();
3862 pgn_config_set(PGN_PROGRESS
, 1024);
3863 pgn_config_set(PGN_PROGRESS_FUNC
, loading_progress
);
3866 int main(int argc
, char *argv
[])
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;
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
);
3895 err(EXIT_FAILURE
, "%s", datadir
);
3900 if (!S_ISDIR(st
.st_mode
))
3901 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3905 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3908 write_custom_tags
= 1;
3914 pgn_config_set(PGN_REDUCED
, 1);
3916 validate_and_write
= 1;
3921 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3925 filetype
= PGN_FILE
;
3926 strncpy(loadfile
, optarg
, sizeof(loadfile
));
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
);
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
);
3954 if ((fp
= pgn_open(loadfile
)) == NULL
)
3955 err(EXIT_FAILURE
, "%s", loadfile
);
3957 ret
= pgn_parse(fp
);
3960 //ret = parse_fen_file(loadfile);
3962 case EPD_FILE
: // Not implemented.
3965 // No file specified. Empty game.
3966 ret
= pgn_parse(NULL
);
3967 add_custom_tags(&game
[gindex
].tag
);
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
]);
3984 else if (ret
== E_PGN_ERR
)
3989 if (initscr() == NULL
)
3990 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3992 curses_initialized
= 1;
3994 if (LINES
< 24 || COLS
< 80) {
3996 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3999 if (has_colors() == TRUE
&& start_color() == OK
)
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
);
4019 draw_window_decor();