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>
70 static char *str_etc(const char *str
, int maxlen
, int rev
)
72 int len
= strlen(str
);
73 static char buf
[80], *p
= buf
;
76 strncpy(buf
, str
, sizeof(buf
));
85 for (i
= 0; i
< maxlen
+ 3; i
++)
86 *p
++ = buf
[(len
- maxlen
) + i
+ 3];
101 static char *real_filename(char *path
)
104 static char buf
[FILENAME_MAX
];
110 strncpy(buf
, path
, sizeof(buf
));
113 if (tmp
[strlen(tmp
) - 1] == '/') {
114 tmp
[strlen(tmp
) - 1] = 0;
118 if ((tmp
= strrchr(tmp
, '/')) == NULL
)
122 buf
[strlen(tmp
)] = '/';
128 static void update_cursor(GAME g
, int idx
, int *r
, int *c
)
132 int t
= history_total(g
.hp
);
135 * If not deincremented then r and c would be the next move.
139 if (idx
> t
|| idx
< 0 || !t
|| !g
.hp
[idx
]->move
) {
153 *r
= (g
.turn
== WHITE
) ? 8 : 1;
166 static int init_nag()
172 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
173 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.nagfile
, strerror(errno
));
178 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
179 nags
= Realloc(nags
, (i
+ 2) * sizeof(struct nag_s
));
180 nags
[i
].line
= strdup(line
);
190 char *history_edit_nag(void *arg
)
194 ITEM
**mitems
= NULL
;
200 HISTORY
*anno
= (HISTORY
*)arg
;
208 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
209 mitems
[i
++] = new_item(NONE
, NULL
);
211 for (n
= 0; nags
[n
].line
; n
++, i
++) {
212 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
213 mitems
[i
] = new_item(nags
[n
].line
, NULL
);
217 menu
= new_menu(mitems
);
218 scale_menu(menu
, &rows
, &cols
);
220 win
= newwin(rows
+ 4, cols
+ 2, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
221 set_menu_win(menu
, win
);
222 subw
= derwin(win
, rows
, cols
, 2, 1);
223 set_menu_sub(menu
, subw
);
224 set_menu_fore(menu
, A_REVERSE
);
225 set_menu_grey(menu
, A_NORMAL
);
226 set_menu_mark(menu
, NULL
);
227 set_menu_spacing(menu
, 0, 0, 0);
228 menu_opts_off(menu
, O_NONCYCLIC
|O_SHOWDESC
|O_ONEVALUE
);
230 panel
= new_panel(win
);
234 set_menu_pattern(menu
, mbuf
);
235 wbkgd(win
, CP_MESSAGE_WINDOW
);
236 draw_window_title(win
, NAG_EDIT_TITLE
, cols
+ 2, CP_HISTORY_TITLE
,
239 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
240 if (anno
->nag
[i
] && anno
->nag
[i
] <= item_count(menu
)) {
241 set_item_value(mitems
[anno
->nag
[i
]], TRUE
);
242 set_current_item(menu
, mitems
[anno
->nag
[i
]]);
252 wattron(win
, A_REVERSE
);
254 for (c
= 1; c
< (cols
+ 2) - 1; c
++)
255 mvwprintw(win
, rows
+ 2, c
, " ");
257 c
= item_index(current_item(menu
)) + 1;
259 snprintf(buf
, sizeof(buf
), "Item %i of %i (%i of %i selected) %s", c
,
260 item_count(menu
), itemcount
, MAX_PGN_NAG
, NAG_EDIT_PROMPT
);
261 draw_prompt(win
, rows
+ 2, cols
+ 2, buf
, CP_MESSAGE_PROMPT
);
263 wattroff(win
, A_REVERSE
);
266 for (i
= 0; mitems
[i
]; i
++)
267 set_item_value(mitems
[i
], FALSE
);
269 set_item_value(mitems
[0], TRUE
);
272 set_item_value(mitems
[0], FALSE
);
274 /* This nl() statement needs to be here because NL is recognized
275 * for some reason after the first selection.
287 help(NAG_EDIT_HELP
, ANYKEY
, naghelp
);
295 for (i
= item_index(current_item(menu
)) + 1; mitems
[i
]; i
++) {
296 if (item_value(mitems
[i
]) == TRUE
) {
303 for (i
= 0; mitems
[i
]; i
++) {
304 if (item_value(mitems
[i
]) == TRUE
) {
311 set_current_item(menu
, mitems
[found
]);
319 for (i
= item_index(current_item(menu
)) - 1; i
> 0; i
--) {
320 if (item_value(mitems
[i
]) == TRUE
) {
327 for (i
= item_count(menu
) - 1; i
> 0; i
--) {
328 if (item_value(mitems
[i
]) == TRUE
) {
335 set_current_item(menu
, mitems
[found
]);
338 menu_driver(menu
, REQ_FIRST_ITEM
);
341 menu_driver(menu
, REQ_LAST_ITEM
);
344 menu_driver(menu
, REQ_UP_ITEM
);
347 menu_driver(menu
, REQ_DOWN_ITEM
);
351 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
352 menu_driver(menu
, REQ_FIRST_ITEM
);
356 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
357 menu_driver(menu
, REQ_LAST_ITEM
);
360 if (item_index(current_item(menu
)) == 0 &&
361 item_value(current_item(menu
)) == FALSE
) {
366 if (item_value(current_item(menu
)) == TRUE
) {
367 set_item_value(current_item(menu
), FALSE
);
371 if (itemcount
+ 1 > MAX_PGN_NAG
)
374 set_item_value(current_item(menu
), TRUE
);
378 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
387 tmp
= menu_pattern(menu
);
389 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
390 menu_driver(menu
, REQ_CLEAR_PATTERN
);
391 menu_driver(menu
, c
);
394 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
395 menu_driver(menu
, c
);
403 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
406 for (i
= 0, n
= 0; mitems
[i
] && n
< MAX_PGN_NAG
; i
++) {
407 if (item_value(mitems
[i
]) == TRUE
)
415 for (i
= 0; mitems
[i
]; i
++)
416 free_item(mitems
[i
]);
425 static void view_nag(void *arg
)
427 HISTORY
*h
= (HISTORY
*)arg
;
429 char line
[LINE_MAX
] = {0};
432 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
439 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
443 strncat(line
, nags
[h
->nag
[i
] - 1].line
, sizeof(line
));
444 strncat(line
, "\n", sizeof(line
));
447 line
[strlen(line
) - 1] = 0;
448 message(buf
, ANYKEY
, "%s", line
);
451 void view_annotation(HISTORY h
)
453 char buf
[MAX_SAN_MOVE_LEN
+ strlen(ANNOTATION_VIEW_TITLE
) + 4];
454 int nag
= 0, comment
= 0;
456 if (h
.comment
&& h
.comment
[0])
462 if (!nag
&& !comment
)
465 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
.move
);
468 show_message(buf
, (nag
) ? "Any other key to continue" : ANYKEY
,
469 (nag
) ? "Press 'n' to view NAG" : NULL
,
470 (nag
) ? view_nag
: NULL
, (nag
) ? (void *)&h
: NULL
,
471 (nag
) ? 'n' : 0, "%s", h
.comment
);
473 show_message(buf
, "Any other key to continue", "Press 'n' to view NAG",
474 view_nag
, (void *)&h
, 'n', "%s", "No annotations for this move");
477 static void cleanup(WINDOW
*win
, WINDOW
*subw
, PANEL
*panel
, MENU
*menu
,
478 ITEM
**items
, struct d_entries
*entries
)
485 for (i
= 0; items
[i
]; i
++)
491 for (i
= 0; entries
[i
].name
; i
++) {
492 free(entries
[i
].name
);
493 free(entries
[i
].fancy
);
504 static int sort_entries(const void *s1
, const void *s2
)
506 const struct d_entries
*ss1
= s1
;
507 const struct d_entries
*ss2
= s2
;
509 return strcmp(ss1
->name
, ss2
->name
);
512 static struct d_entries
*get_directory_entries(const char *path
)
515 struct dirent
*entry
;
516 struct d_entries
*entries
= NULL
;
519 if ((dp
= opendir(path
)) == NULL
)
522 while ((entry
= readdir(dp
)) != NULL
) {
525 char tbuf
[64 + 1] = {0}; //FIXME
527 char buf
[FILENAME_MAX
];
531 if (entry
->d_name
[0] == '.' && entry
->d_name
[1] == 0)
534 snprintf(buf
, sizeof(buf
), "%s/%s", path
, entry
->d_name
);
536 if (stat(buf
, &st
) == -1)
540 entries
= Realloc(entries
, (n
+ 2) * sizeof(struct d_entries
));
541 entries
[n
].name
= strdup(buf
);
542 tmp
= real_filename(buf
);
543 len
= strlen(tmp
) + 2;
544 entries
[n
].fancy
= Malloc(len
);
545 strncpy(entries
[n
].fancy
, tmp
, len
);
547 if (S_ISDIR(st
.st_mode
))
548 entries
[n
].fancy
[len
- 2] = '/';
550 tp
= localtime(&st
.st_mtime
);
551 strftime(tbuf
, sizeof(tbuf
), "%b %d %T", tp
);
553 snprintf(entries
[n
].desc
, sizeof(entries
[n
].desc
), "%-7i %s",
556 memset(&entries
[++n
], '\0', sizeof(struct d_entries
));
560 qsort(entries
, n
, sizeof(struct d_entries
), sort_entries
);
564 char *browse_directory(void *arg
)
567 char path
[FILENAME_MAX
] = {0};
568 static char file
[FILENAME_MAX
];
569 char *oldwd
= getcwd(NULL
, 0);
571 char *inputstr
= (char *)arg
;
572 int initkey
= (inputstr
) ? inputstr
[0] : 0;
574 if (config
.savedirectory
) {
575 if ((dp
= opendir(config
.savedirectory
)) == NULL
) {
576 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
578 getcwd(path
, sizeof(path
));
582 strncpy(path
, config
.savedirectory
, sizeof(path
));
586 getcwd(path
, sizeof(path
));
592 ITEM
**mitems
= NULL
;
598 struct d_entries
*entries
= NULL
;
601 int len
= strlen(path
);
603 /* /some/path/blah/../ */
604 if (path
[len
- 1] == '.' && path
[len
- 2] == '.' &&
605 path
[len
- 3] == '/') {
607 tmp
+= strlen(path
) - 5;
610 while (*--tmp
!= '/')
619 if (path
[1] && path
[strlen(path
) - 1] == '/')
620 path
[strlen(path
) - 1] = '\0';
622 if ((entries
= get_directory_entries(path
)) == NULL
) {
623 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
627 for (i
= 0; entries
[i
].name
; i
++) {
628 mitems
= Realloc(mitems
, (idx
+ 2) * sizeof(ITEM
));
629 mitems
[idx
++] = new_item(entries
[i
].fancy
, entries
[i
].desc
);
633 menu
= new_menu(mitems
);
634 scale_menu(menu
, &rows
, &cols
);
636 if (cols
< strlen(path
))
639 if (cols
< strlen(HELP_PROMPT
))
640 cols
= strlen(HELP_PROMPT
);
642 rows
= (LINES
/ 5) * 4;
645 win
= newwin(rows
+ 4, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
646 set_menu_format(menu
, rows
, 0);
647 set_menu_win(menu
, win
);
648 subw
= derwin(win
, rows
, cols
- 2, 2, 1);
649 set_menu_sub(menu
, subw
);
650 set_menu_fore(menu
, A_REVERSE
);
651 set_menu_grey(menu
, A_NORMAL
);
652 set_menu_mark(menu
, NULL
);
653 set_menu_spacing(menu
, 2, 0, 0);
654 menu_opts_off(menu
, O_NONCYCLIC
);
656 panel
= new_panel(win
);
658 draw_window_title(win
, path
, cols
, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
659 draw_prompt(win
, rows
+ 2, cols
, HELP_PROMPT
, CP_MESSAGE_PROMPT
);
664 set_menu_pattern(menu
, mbuf
);
666 if (isgraph(initkey
)) {
667 menu_driver(menu
, initkey
);
674 /* This nl() statement needs to be here because NL is recognized
675 * for some reason after the first selection.
686 menu_driver(menu
, REQ_SCR_UPAGE
);
691 menu_driver(menu
, REQ_SCR_DPAGE
);
694 menu_driver(menu
, REQ_UP_ITEM
);
697 menu_driver(menu
, REQ_DOWN_ITEM
);
700 selected
= item_index(current_item(menu
));
704 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
709 help(BROWSER_HELP
, ANYKEY
, file_browser_help
);
712 if ((tmp
= getenv("HOME")) == NULL
) {
713 cmessage(ERROR
, ANYKEY
, "%s", E_HOME_ENV
);
717 strncpy(path
, tmp
, sizeof(path
));
718 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
722 if ((tmp
= get_input_str_clear(BROWSER_CHDIR_TITLE
, NULL
))
726 tmp
= tilde_expand(tmp
);
727 strncpy(path
, tmp
, sizeof(path
));
728 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
732 tmp
= menu_pattern(menu
);
734 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
735 menu_driver(menu
, REQ_CLEAR_PATTERN
);
736 menu_driver(menu
, c
);
739 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
740 menu_driver(menu
, c
);
748 snprintf(file
, sizeof(file
), "%s", entries
[selected
].name
);
750 if (stat(file
, &st
) == -1) {
751 cmessage(ERROR
, ANYKEY
, "%s", strerror(errno
));
752 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
756 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
758 if (S_ISDIR(st
.st_mode
)) {
759 strncpy(path
, file
, sizeof(path
));
763 if (S_ISREG(st
.st_mode
))
766 cmessage(ERROR
, ANYKEY
, "%s", E_NOTAREGFILE
);
772 return (*file
) ? file
: NULL
;
774 static int init_country_codes()
777 char line
[LINE_MAX
], *s
;
780 if ((fp
= fopen(config
.ccfile
, "r")) == NULL
) {
781 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.ccfile
, strerror(errno
));
785 while ((s
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
788 if ((tmp
= strsep(&s
, " ")) == NULL
)
797 ccodes
= Realloc(ccodes
, (cindex
+ 2) * sizeof(struct country_codes
));
798 strncpy(ccodes
[cindex
].code
, tmp
, sizeof(ccodes
[cindex
].code
));
799 strncpy(ccodes
[cindex
].country
, s
, sizeof(ccodes
[cindex
].country
));
803 memset(&ccodes
[cindex
], '\0', sizeof(struct country_codes
));
809 char *country_codes(void *arg
)
813 ITEM
**mitems
= NULL
;
821 if (init_country_codes())
825 for (n
= i
= 0; ccodes
[n
].code
[0]; n
++, i
++) {
826 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
827 mitems
[i
] = new_item(ccodes
[n
].country
, ccodes
[n
].code
);
831 menu
= new_menu(mitems
);
832 scale_menu(menu
, &rows
, &cols
);
834 if (cols
< strlen(HELP_PROMPT
) + 21)
835 cols
= strlen(HELP_PROMPT
) + 21;
837 win
= newwin(rows
+ 4, cols
+ 4, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
838 set_menu_win(menu
, win
);
839 subw
= derwin(win
, rows
, cols
+ 2, 2, 1);
840 set_menu_sub(menu
, subw
);
841 set_menu_fore(menu
, A_REVERSE
);
842 set_menu_grey(menu
, A_NORMAL
);
843 set_menu_mark(menu
, NULL
);
844 set_menu_spacing(menu
, 0, 0, 0);
845 menu_opts_off(menu
, O_NONCYCLIC
);
847 panel
= new_panel(win
);
851 set_menu_pattern(menu
, mbuf
);
852 wbkgd(win
, CP_MESSAGE_WINDOW
);
853 draw_window_title(win
, CC_TITLE
, cols
+ 4, CP_MESSAGE_TITLE
,
860 wattron(win
, A_REVERSE
);
862 for (c
= 1; c
< (cols
+ 2) - 1; c
++)
863 mvwprintw(win
, rows
+ 2, c
, " ");
865 c
= item_index(current_item(menu
)) + 1;
867 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_ITEM_STR
, c
,
868 N_OF_N_STR
, item_count(menu
), HELP_PROMPT
);
869 draw_prompt(win
, rows
+ 2, cols
+ 2, buf
, CP_MESSAGE_PROMPT
);
871 wattroff(win
, A_REVERSE
);
873 /* This nl() statement needs to be here because NL is recognized
874 * for some reason after the first selection.
884 help(CC_KEY_HELP
, ANYKEY
, cc_help
);
887 menu_driver(menu
, REQ_FIRST_ITEM
);
890 menu_driver(menu
, REQ_LAST_ITEM
);
893 menu_driver(menu
, REQ_UP_ITEM
);
896 menu_driver(menu
, REQ_DOWN_ITEM
);
900 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
901 menu_driver(menu
, REQ_FIRST_ITEM
);
906 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
907 menu_driver(menu
, REQ_LAST_ITEM
);
910 tmp
= (char *)item_description(current_item(menu
));
918 tmp
= menu_pattern(menu
);
920 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
921 menu_driver(menu
, REQ_CLEAR_PATTERN
);
922 menu_driver(menu
, c
);
925 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
926 menu_driver(menu
, c
);
937 for (i
= 0; mitems
[i
]; i
++)
938 free_item(mitems
[i
]);
946 static void add_custom_tags(TAG
**t
, unsigned char *n
)
950 for (i
= 0; i
< config
.tindex
; i
++)
951 pgn_add_tag(&(*t
), &(*n
), config
.tag
[i
].name
,
952 config
.tag
[i
].value
);
955 TAG
*edit_tags(GAME g
, BOARD b
, int edit
)
959 unsigned char data_index
= 0;
960 int n
, lastindex
= 0;
963 /* Edit the backup copy, not the original in case the save fails. */
964 for (n
= 0; n
< g
.tindex
; n
++)
965 pgn_add_tag(&data
, &data_index
, g
.tag
[n
].name
, g
.tag
[n
].value
);
970 ITEM
**mitems
= NULL
;
978 int nlen
= 0, vlen
= 0;
980 for (i
= 0; i
< data_index
; i
++) {
981 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
983 if (data
[i
].value
[0]) {
984 nlen
= strlen(data
[i
].name
);
985 vlen
= strlen(data
[i
].value
);
987 /* The +6 is for the menu padding. */
988 mitems
[i
] = new_item(data
[i
].name
,
989 (nlen
+ vlen
+ 6 >= MAX_VALUE_WIDTH
)
990 ? PRESS_ENTER
: data
[i
].value
);
993 mitems
[i
] = new_item(data
[i
].name
, UNKNOWN
);
997 menu
= new_menu(mitems
);
998 scale_menu(menu
, &rows
, &cols
);
1000 /* +14 for the extra prompt info. */
1001 if (cols
< strlen(HELP_PROMPT
) + 14)
1002 cols
= strlen(HELP_PROMPT
) + 14;
1004 win
= newwin(rows
+ 4, cols
+ 4, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
1005 set_menu_win(menu
, win
);
1006 subw
= derwin(win
, rows
, cols
+ 2, 2, 1);
1007 set_menu_sub(menu
, subw
);
1008 set_menu_fore(menu
, A_REVERSE
);
1009 set_menu_grey(menu
, A_NORMAL
);
1010 set_menu_mark(menu
, NULL
);
1011 set_menu_pad(menu
, '-');
1012 set_menu_spacing(menu
, 3, 0, 0);
1013 menu_opts_off(menu
, O_NONCYCLIC
);
1015 panel
= new_panel(win
);
1020 set_menu_pattern(menu
, mbuf
);
1021 wbkgd(win
, CP_MESSAGE_WINDOW
);
1022 draw_window_title(win
, (edit
) ? TAG_EDIT_TITLE
: TAG_VIEW_TITLE
,
1023 cols
+ 4, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
1028 char *newtag
= NULL
;
1029 unsigned char tpgn_index
= 0;
1031 if (set_current_item(menu
, mitems
[lastindex
]) != E_OK
) {
1032 lastindex
= item_count(menu
) - 1;
1036 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_TAG_STR
,
1037 item_index(current_item(menu
)) + 1, N_OF_N_STR
,
1038 item_count(menu
), HELP_PROMPT
);
1039 draw_prompt(win
, rows
+ 2, cols
+ 4, buf
, CP_MESSAGE_PROMPT
);
1048 add_custom_tags(&data
, &data_index
);
1053 help(TAG_EDIT_HELP
, ANYKEY
, pgn_edit_help
);
1055 help(TAG_VIEW_HELP
, ANYKEY
, pgn_info_help
);
1061 selected
= item_index(current_item(menu
));
1063 if (selected
<= 6) {
1064 cmessage(NULL
, ANYKEY
, "%s", E_REMOVE_STR
);
1068 for (i
= 0; i
< data_index
; i
++) {
1072 pgn_add_tag(&tmppgn
, &tpgn_index
, data
[i
].name
,
1076 pgn_tag_free(data
, data_index
);
1079 for (i
= data_index
= 0; i
< tpgn_index
; i
++) {
1080 pgn_add_tag(&data
, &data_index
, tmppgn
[i
].name
,
1084 pgn_tag_free(tmppgn
, tpgn_index
);
1091 if ((newtag
= get_input(TAG_NEW_TITLE
, NULL
, 1, 1, NULL
,
1092 NULL
, NULL
, 0, FIELD_TYPE_PGN_TAG_NAME
))
1096 newtag
[0] = toupper(newtag
[0]);
1098 if (strlen(newtag
) > MAX_VALUE_WIDTH
- 6 -
1099 strlen(PRESS_ENTER
)) {
1100 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_NAMETOOLONG
);
1104 for (i
= 0; i
< data_index
; i
++) {
1105 if (strcasecmp(data
[i
].name
, newtag
) == 0) {
1111 pgn_add_tag(&data
, &data_index
, newtag
, NULL
);
1113 selected
= data_index
- 1;
1117 menu_driver(menu
, REQ_FIRST_ITEM
);
1120 menu_driver(menu
, REQ_LAST_ITEM
);
1126 pgn_add_tag(&data
, &data_index
, "FEN", pgn_game_to_fen(g
, b
));
1127 selected
= data_index
- 1;
1132 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
1133 menu_driver(menu
, REQ_LAST_ITEM
);
1137 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
1138 menu_driver(menu
, REQ_FIRST_ITEM
);
1141 menu_driver(menu
, REQ_UP_ITEM
);
1144 menu_driver(menu
, REQ_DOWN_ITEM
);
1147 selected
= item_index(current_item(menu
));
1151 cleanup(win
, subw
, panel
, menu
, mitems
, NULL
);
1155 tmp
= menu_pattern(menu
);
1157 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
1158 menu_driver(menu
, REQ_CLEAR_PATTERN
);
1159 menu_driver(menu
, c
);
1162 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
1163 menu_driver(menu
, c
);
1169 lastindex
= item_index(current_item(menu
));
1173 lastindex
= selected
;
1174 nlen
= strlen(data
[selected
].name
) + 3;
1175 nlen
+= (edit
) ? strlen(TAG_EDIT_TAG_TITLE
) : strlen(TAG_VIEW_TAG_TITLE
);
1177 if (nlen
> MAX_VALUE_WIDTH
)
1178 snprintf(buf
, sizeof(buf
), "%s", data
[selected
].name
);
1180 snprintf(buf
, sizeof(buf
), "%s \"%s\"",
1181 (edit
) ? TAG_EDIT_TAG_TITLE
: TAG_VIEW_TAG_TITLE
,
1182 data
[selected
].name
);
1185 if (strcmp(item_description(mitems
[selected
]), UNKNOWN
) == 0)
1188 cmessage(buf
, ANYKEY
, "%s", data
[selected
].value
);
1192 if (strcmp(data
[selected
].name
, "Date") == 0) {
1193 tmp
= get_input(buf
, data
[selected
].value
, 0, 0, NULL
, NULL
, NULL
,
1194 0, FIELD_TYPE_PGN_DATE
);
1197 if (strptime(tmp
, PGN_TIME_FORMAT
, &tp
) == NULL
) {
1198 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_DATE_FMT
);
1205 else if (strcmp(data
[selected
].name
, "Site") == 0) {
1206 tmp
= get_input(buf
, data
[selected
].value
, 1, 1, CC_PROMPT
,
1207 country_codes
, NULL
, CTRL('t'), -1);
1212 else if (strcmp(data
[selected
].name
, "Round") == 0) {
1213 tmp
= get_input(buf
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1214 FIELD_TYPE_PGN_ROUND
);
1223 else if (strcmp(data
[selected
].name
, "Result") == 0) {
1224 tmp
= get_input(buf
, data
[selected
].value
, 1, 1, NULL
, NULL
, NULL
,
1231 if (item_description(mitems
[selected
]) &&
1232 strcmp(item_description(mitems
[selected
]), UNKNOWN
) == 0)
1235 tmp
= data
[selected
].value
;
1237 tmp
= get_input(buf
, tmp
, 0, 0, NULL
, NULL
, NULL
, 0, -1);
1240 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
1241 data
[selected
].value
= Realloc(data
[selected
].value
, len
);
1242 strncpy(data
[selected
].value
, (tmp
) ? tmp
: "", len
);
1245 cleanup(win
, subw
, panel
, menu
, mitems
, NULL
);
1250 pgn_tag_free(data
, data_index
);
1257 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1258 * game index number.
1260 int save_pgn(const char *filename
, int isfifo
, int saveindex
)
1265 char buf
[FILENAME_MAX
];
1268 char *command
= NULL
;
1269 int saveindex_max
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
1271 if (filename
[0] != '/' && config
.savedirectory
&& !isfifo
) {
1272 if (stat(config
.savedirectory
, &st
) == -1) {
1273 if (errno
== ENOENT
) {
1274 if (mkdir(config
.savedirectory
, 0755) == -1) {
1275 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1281 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1287 stat(config
.savedirectory
, &st
);
1289 if (!S_ISDIR(st
.st_mode
)) {
1290 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
1294 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
1299 command
= compression_cmd(filename
, 0);
1301 /* This is a hack to resume an existing game when more than one game is
1302 * available. Also resuming a saved game and a game from history.
1304 // FIXME: may not need this when a FEN tag is supported (by the engine).
1308 if (access(filename
, W_OK
) == 0) {
1309 c
= cmessage(NULL
, GAME_SAVE_OVERWRITE_PROMPT
,
1310 "%s \"%s\"", E_FILEEXISTS
, filename
);
1315 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
1333 if ((fp
= popen(command
, "w")) == NULL
) {
1334 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1339 if ((fp
= fopen(filename
, mode
)) == NULL
) {
1340 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1346 pgn_write(fp
, game
[saveindex
], isfifo
);
1348 for (i
= (saveindex
== -1) ? 0 : saveindex
; i
< saveindex_max
; i
++)
1349 pgn_write(fp
, game
[i
], isfifo
);
1357 if (!isfifo
&& saveindex
== -1)
1358 strncpy(loadfile
, filename
, sizeof(loadfile
));
1363 char *random_agony(GAME g
)
1367 char line
[LINE_MAX
];
1369 if (n
== -1 || !config
.agony
|| !curses_initialized
||
1370 (g
.mode
== MODE_HISTORY
&& !config
.historyagony
))
1374 if ((fp
= fopen(config
.agonyfile
, "r")) == NULL
) {
1376 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.agonyfile
, strerror(errno
));
1381 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
1382 agony
= Realloc(agony
, (n
+ 2) * sizeof(char *));
1383 agony
[n
++] = strdup(trim(line
));
1390 if (agony
[0] == NULL
|| !n
) {
1396 return agony
[random() % n
];
1399 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
1401 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1403 (TEST_FLAG((*g
).flags
, GF_WK_CASTLE
) || mod
) &&
1404 pgn_piece_to_int(board
[7][4].icon
) == KING
&& isupper(piece
)) {
1406 TOGGLE_FLAG((*g
).flags
, GF_WK_CASTLE
);
1409 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1411 (TEST_FLAG((*g
).flags
, GF_WQ_CASTLE
) || mod
) &&
1412 pgn_piece_to_int(board
[7][4].icon
) == KING
&& isupper(piece
)) {
1414 TOGGLE_FLAG((*g
).flags
, GF_WQ_CASTLE
);
1417 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1419 (TEST_FLAG((*g
).flags
, GF_BK_CASTLE
) || mod
) &&
1420 pgn_piece_to_int(board
[0][4].icon
) == KING
&& islower(piece
)) {
1422 TOGGLE_FLAG((*g
).flags
, GF_BK_CASTLE
);
1425 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1427 (TEST_FLAG((*g
).flags
, GF_BQ_CASTLE
) || mod
) &&
1428 pgn_piece_to_int(board
[0][4].icon
) == KING
&& islower(piece
)) {
1430 TOGGLE_FLAG((*g
).flags
, GF_BQ_CASTLE
);
1433 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1435 (mod
|| (pgn_piece_to_int(board
[7][7].icon
) == ROOK
&&
1436 TEST_FLAG((*g
).flags
, GF_WK_CASTLE
))
1438 (pgn_piece_to_int(board
[7][0].icon
) == ROOK
&&
1439 TEST_FLAG((*g
).flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
1441 if (TEST_FLAG((*g
).flags
, GF_WK_CASTLE
) ||
1442 TEST_FLAG((*g
).flags
, GF_WQ_CASTLE
))
1443 CLEAR_FLAG((*g
).flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1445 SET_FLAG((*g
).flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1449 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1451 (mod
|| (pgn_piece_to_int(board
[0][7].icon
) == ROOK
&&
1452 TEST_FLAG((*g
).flags
, GF_BK_CASTLE
))
1454 (pgn_piece_to_int(board
[0][0].icon
) == ROOK
&&
1455 TEST_FLAG((*g
).flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
1457 if (TEST_FLAG((*g
).flags
, GF_BK_CASTLE
) ||
1458 TEST_FLAG((*g
).flags
, GF_BQ_CASTLE
))
1459 CLEAR_FLAG((*g
).flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1461 SET_FLAG((*g
).flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1469 static void draw_board(GAME
*g
, int details
, int crow
, int ccol
)
1472 int bcol
= 0, brow
= 0;
1473 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
1474 int ncols
= 0, offset
= 1;
1475 unsigned coords_y
= 8;
1477 for (row
= 0; row
< maxy
; row
++) {
1480 for (col
= 0; col
< maxx
; col
++) {
1483 unsigned char piece
;
1485 if (row
== 0 || row
== maxy
- 2) {
1487 mvwaddch(boardw
, row
, col
,
1488 LINE_GRAPHIC((row
) ?
1489 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
1490 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
1491 else if (col
== maxx
- 2)
1492 mvwaddch(boardw
, row
, col
,
1493 LINE_GRAPHIC((row
) ?
1494 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
1495 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
1496 else if (!(col
% 4))
1497 mvwaddch(boardw
, row
, col
,
1498 LINE_GRAPHIC((row
) ?
1499 ACS_BTEE
| CP_BOARD_GRAPHICS
:
1500 ACS_TTEE
| CP_BOARD_GRAPHICS
));
1502 if (col
!= maxx
- 1)
1503 mvwaddch(boardw
, row
, col
,
1504 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1510 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
1511 wattron(boardw
, CP_BOARD_COORDS
);
1512 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
1513 wattroff(boardw
, CP_BOARD_COORDS
);
1517 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
1519 mvwaddch(boardw
, row
, col
,
1520 LINE_GRAPHIC((col
) ?
1521 ACS_RTEE
| CP_BOARD_GRAPHICS
:
1522 ACS_LTEE
| CP_BOARD_GRAPHICS
));
1524 mvwaddch(boardw
, row
, col
,
1525 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1530 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
1531 mvwaddch(boardw
, row
, col
,
1532 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1536 if (!(col
% 4) && row
!= maxy
- 1) {
1537 mvwaddch(boardw
, row
, col
,
1538 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
1549 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
1555 if (config
.validmoves
&& board
[brow
][bcol
].valid
) {
1556 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
1557 CP_BOARD_MOVES_BLACK
;
1560 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
1563 if (row
== ROWTOMATRIX(crow
) && col
== COLTOMATRIX(ccol
)) {
1564 attrs
= CP_BOARD_CURSOR
;
1567 if (row
== ROWTOMATRIX(sp
.row
) &&
1568 col
== COLTOMATRIX(sp
.col
)) {
1569 attrs
= CP_BOARD_SELECTED
;
1572 if (row
== maxy
- 1)
1575 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
1577 if (row
== maxy
- 1)
1578 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
1580 if (details
&& board
[row
/ 2][bcol
].enpassant
)
1583 piece
= board
[row
/ 2][bcol
].icon
;
1585 if (details
&& castling_state(g
, board
, brow
, bcol
,
1589 if ((*g
).side
== WHITE
&& isupper(piece
))
1591 else if ((*g
).side
== BLACK
&& islower(piece
))
1594 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
1596 CLEAR_FLAG(attrs
, A_BOLD
);
1597 CLEAR_FLAG(attrs
, A_REVERSE
);
1600 waddch(boardw
, ' ' | attrs
);
1606 if (col
!= maxx
- 1)
1607 mvwaddch(boardw
, row
, col
,
1608 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1616 void invalid_move(int n
, const char *m
)
1618 if (curses_initialized
)
1619 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", E_INVALID_MOVE
, m
, n
);
1621 warnx("%s: %s \"%s\" (round #%i)", loadfile
, E_INVALID_MOVE
, m
, n
);
1624 /* Convert the selected piece to SAN format and validate it. */
1625 static char *board_to_san(GAME
*g
, BOARD b
)
1627 static char str
[MAX_SAN_MOVE_LEN
+ 1], *p
;
1632 snprintf(str
, sizeof(str
), "%c%i%c%i", x_grid_chars
[sp
.col
- 1],
1633 sp
.row
, x_grid_chars
[sp
.destcol
- 1], sp
.destrow
);
1636 piece
= pgn_piece_to_int(b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
);
1638 if (piece
== PAWN
&& ((sp
.destrow
== 8 && (*g
).turn
== WHITE
) ||
1639 (sp
.destrow
== 1 && (*g
).turn
== BLACK
))) {
1640 promo
= cmessage(PROMOTION_TITLE
, PROMOTION_PROMPT
, PROMOTION_TEXT
);
1642 if (pgn_piece_to_int(promo
) == -1)
1645 p
= str
+ strlen(str
);
1646 *p
++ = toupper(promo
);
1650 memcpy(oldboard
, b
, sizeof(BOARD
));
1652 if ((p
= pgn_a2a4tosan(g
, b
, str
)) == NULL
) {
1653 cmessage(p
, ANYKEY
, "%s", E_A2A4_PARSE
);
1654 memcpy(b
, oldboard
, sizeof(BOARD
));
1658 if (pgn_validate_move(g
, b
, p
)) {
1659 invalid_move(gindex
+ 1, p
);
1660 memcpy(b
, oldboard
, sizeof(BOARD
));
1667 static int move_to_engine(GAME
*g
, BOARD b
)
1671 if ((p
= board_to_san(g
, b
)) == NULL
)
1674 sp
.row
= sp
.col
= sp
.icon
= 0;
1677 (*g
).hp
= history_add((*g
).hp
, &(*g
).hindex
, p
);
1679 SET_FLAG((*g
).flags
, GF_MODIFIED
);
1684 SEND_TO_ENGINE("%s\n", p
);
1688 static void update_clock(int n
, int *h
, int *m
, int *s
)
1691 *m
= (n
% 3600) / 60;
1692 *s
= (n
% 3600) % 60;
1697 void update_status_window(GAME g
)
1701 char tmp
[15], *engine
, *mode
;
1708 getmaxyx(statusw
, maxy
, maxx
);
1716 if (TEST_FLAG(g
.flags
, GF_DELETE
)) {
1722 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1732 if (TEST_FLAG(g
.flags
, GF_MODIFIED
)) {
1747 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1748 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1749 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1751 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1755 mode
= MODE_HISTORY_STR
;
1758 mode
= MODE_EDIT_STR
;
1761 mode
= MODE_PLAY_STR
;
1768 mvwprintw(statusw
, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR
, w
, mode
);
1770 switch (status
.engine
) {
1771 case ENGINE_THINKING
:
1772 engine
= ENGINE_THINKING_STR
;
1775 engine
= ENGINE_READY_STR
;
1777 case ENGINE_INITIALIZING
:
1778 engine
= ENGINE_INITIALIZING_STR
;
1780 case ENGINE_OFFLINE
:
1781 engine
= ENGINE_OFFLINE_STR
;
1788 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1789 wattron(statusw
, CP_STATUS_ENGINE
);
1790 mvwaddstr(statusw
, 5, 9, engine
);
1791 wattroff(statusw
, CP_STATUS_ENGINE
);
1793 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1794 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1796 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1797 tmp
[0] = toupper(tmp
[0]);
1798 update_clock(g
.moveclock
, &h
, &m
, &s
);
1799 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1800 mvwprintw(statusw
, 7, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1802 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1803 tmp
[0] = toupper(tmp
[0]);
1804 update_clock(g
.moveclock
, &h
, &m
, &s
);
1805 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1806 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1809 for (i
= 1; i
< maxx
- 4; i
++)
1810 mvwprintw(statusw
, maxy
- 2, i
, " ");
1813 status
.notify
= strdup(GAME_HELP_PROMPT
);
1815 wattron(statusw
, CP_STATUS_NOTIFY
);
1816 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1818 wattroff(statusw
, CP_STATUS_NOTIFY
);
1821 void update_history_window(GAME g
)
1823 char buf
[HISTORY_WIDTH
];
1826 int t
= history_total(g
.hp
);
1828 n
= (g
.hindex
+ 1) / 2;
1831 total
= (t
+ 1) / 2;
1836 snprintf(buf
, sizeof(buf
), "%u %s %u%s",n
, N_OF_N_STR
, total
,
1837 (movestep
== 1) ? HISTORY_MOVE_STEP
: "");
1839 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1841 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1842 HISTORY_WIDTH
- 13, buf
);
1844 h
= history_by_n(g
.hp
, g
.hindex
);
1846 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1850 if (h
&& ((h
->comment
&& h
->comment
[0]) || h
->nag
[0])) {
1851 strncat(buf
, " (v", sizeof(buf
));
1856 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1861 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1866 strncat(buf
, ")", sizeof(buf
));
1868 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1869 HISTORY_WIDTH
- 13, buf
);
1871 h
= history_by_n(g
.hp
, game
[gindex
].hindex
- 1);
1873 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1877 if (h
&& ((h
->comment
&& h
->comment
[0]) || h
->nag
[0])) {
1878 strncat(buf
, " (V", sizeof(buf
));
1883 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1888 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1893 strncat(buf
, ")", sizeof(buf
));
1895 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1896 HISTORY_WIDTH
- 13, buf
);
1899 void update_tag_window(TAG
*t
)
1902 int w
= TAG_WIDTH
- 10;
1904 for (i
= 0; i
< 7; i
++)
1905 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
].name
, w
, t
[i
].value
);
1908 void draw_prompt(WINDOW
*win
, int y
, int width
, const char *str
, chtype attr
)
1914 for (i
= 1; i
< width
- 1; i
++)
1915 mvwaddch(win
, y
, i
, ' ');
1917 mvwprintw(win
, y
, CENTERX(width
, str
), "%s", str
);
1918 wattroff(win
, attr
);
1921 void draw_window_title(WINDOW
*win
, const char *title
, int width
, chtype attr
,
1929 for (i
= 1; i
< width
- 1; i
++)
1930 mvwaddch(win
, 1, i
, ' ');
1932 mvwprintw(win
, 1, CENTERX(width
, title
), "%s", title
);
1933 wattroff(win
, attr
);
1936 wattron(win
, battr
);
1937 box(win
, ACS_VLINE
, ACS_HLINE
);
1938 wattroff(win
, battr
);
1941 void update_all(GAME g
)
1943 update_status_window(g
);
1944 update_history_window(g
);
1945 update_tag_window(g
.tag
);
1948 static void game_next_prev(GAME g
, int n
, int count
)
1954 if (gindex
+ count
> gtotal
- 1) {
1956 gindex
= gtotal
- 1;
1964 if (gindex
- count
< 0) {
1968 gindex
= gtotal
- 1;
1975 static void delete_game(int which
)
1981 for (i
= 0; i
< gtotal
; i
++) {
1982 if (i
== which
|| TEST_FLAG(game
[i
].flags
, GF_DELETE
)) {
1987 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
1988 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
1989 g
[gi
].tag
= game
[i
].tag
;
1990 g
[gi
].history
= game
[i
].history
;
1991 g
[gi
].hp
= game
[i
].hp
;
1999 if (which
+ 1 >= gtotal
)
2000 gindex
= gtotal
- 1;
2005 gindex
= gtotal
- 1;
2007 game
[gindex
].hp
= game
[gindex
].history
;
2010 static int find_move_exp(GAME g
, const char *str
, int init
, int which
,
2016 static int firstrun
= 1;
2025 if ((ret
= regcomp(&r
, str
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2026 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2027 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2034 incr
= (which
== 0) ? -(1) : 1;
2036 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
2037 if (i
== g
.hindex
- 1)
2040 if (i
> history_total(g
.hp
))
2043 i
= history_total(g
.hp
);
2046 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
2049 if (count
== ++found
) {
2054 if (ret
!= REG_NOMATCH
) {
2055 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2056 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
2065 static int toggle_delete_flag(int n
)
2069 TOGGLE_FLAG(game
[n
].flags
, GF_DELETE
);
2071 for (i
= x
= 0; i
< gtotal
; i
++) {
2072 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2077 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2078 CLEAR_FLAG(game
[n
].flags
, GF_DELETE
);
2085 static void edit_save_tags(GAME
*g
)
2090 if ((t
= edit_tags(*g
, board
, 1)) == NULL
)
2095 for (i
= 0; t
[i
].name
; i
++)
2096 pgn_add_tag(&(*g
).tag
, &(*g
).tindex
, t
[i
].name
, t
[i
].value
);
2099 SET_FLAG((*g
).flags
, GF_MODIFIED
);
2103 static int find_game_exp(char *str
, int which
, int count
)
2105 char *nstr
= NULL
, *exp
= NULL
;
2109 char buf
[255], *tmp
;
2112 int incr
= (which
== 0) ? -(1) : 1;
2114 strncpy(buf
, str
, sizeof(buf
));
2117 if (strstr(tmp
, ":") != NULL
) {
2118 nstr
= strsep(&tmp
, ":");
2120 if ((ret
= regcomp(&nexp
, nstr
,
2121 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
2122 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
2123 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2134 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2135 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
2136 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2143 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
2154 for (t
= 0; t
< game
[g
].tindex
; t
++) {
2156 if (regexec(&nexp
, game
[g
].tag
[t
].name
, 0, 0, 0) == 0) {
2157 if (regexec(&vexp
, game
[g
].tag
[t
].value
, 0, 0, 0) == 0) {
2158 if (count
== ++found
) {
2166 if (regexec(&vexp
, game
[g
].tag
[t
].value
, 0, 0, 0) == 0) {
2167 if (count
== ++found
) {
2188 void edit_board(GAME g
, BOARD b
)
2192 p
= b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
;
2193 b
[ROWTOBOARD(sp
.destrow
)][COLTOBOARD(sp
.destcol
)].icon
= p
;
2194 b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
= pgn_int_to_piece(g
.turn
, OPEN_SQUARE
);
2197 // Updates the notification line in the status window then refreshes the
2199 void update_status_notify(GAME g
, char *fmt
, ...)
2202 #ifdef HAVE_VASPRINTF
2209 if (status
.notify
) {
2210 free(status
.notify
);
2211 status
.notify
= NULL
;
2213 if (curses_initialized
)
2214 update_status_window(g
);
2221 #ifdef HAVE_VASPRINTF
2222 vasprintf(&line
, fmt
, ap
);
2224 vsnprintf(line
, sizeof(line
), fmt
, ap
);
2229 free(status
.notify
);
2231 status
.notify
= strdup(line
);
2233 #ifdef HAVE_VASPRINTF
2236 if (curses_initialized
)
2237 update_status_window(g
);
2240 static void switch_side(GAME
*g
)
2242 if ((*g
).side
== WHITE
)
2248 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
2252 if ((*g
).hp
[(*g
).hindex
]->rav
== NULL
)
2255 (*g
).rav
= Realloc((*g
).rav
, ((*g
).ravlevel
+ 1) * sizeof(RAV
));
2256 (*g
).rav
[(*g
).ravlevel
].hp
= (*g
).hp
;
2257 (*g
).rav
[(*g
).ravlevel
].flags
= (*g
).flags
;
2258 (*g
).rav
[(*g
).ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
2259 (*g
).rav
[(*g
).ravlevel
].hindex
= (*g
).hindex
;
2260 (*g
).hp
= (*g
).hp
[(*g
).hindex
]->rav
;
2266 if ((*g
).ravlevel
- 1 < 0)
2271 pgn_init_fen_board(g
, b
, (*g
).rav
[(*g
).ravlevel
].fen
);
2272 free((*g
).rav
[(*g
).ravlevel
].fen
);
2273 (*g
).hp
= (*g
).rav
[(*g
).ravlevel
].hp
;
2274 (*g
).flags
= (*g
).rav
[(*g
).ravlevel
].flags
;
2275 (*g
).hindex
= (*g
).rav
[(*g
).ravlevel
].hindex
;
2279 static void draw_window_decor()
2281 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
2282 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
2283 wbkgd(boardw
, CP_BOARD_WINDOW
);
2284 wbkgd(statusw
, CP_STATUS_WINDOW
);
2285 draw_window_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
2286 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
2287 wbkgd(tagw
, CP_TAG_WINDOW
);
2288 draw_window_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
2290 wbkgd(historyw
, CP_HISTORY_WINDOW
);
2291 draw_window_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
2292 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
2295 static void do_window_resize()
2297 if (LINES
< 24 || COLS
< 80)
2300 resizeterm(LINES
, COLS
);
2301 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
2302 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
2303 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
2304 wmove(historyw
, 0, 0);
2305 wclrtobot(historyw
);
2308 wmove(statusw
, 0, 0);
2310 draw_window_decor();
2311 update_all(game
[gindex
]);
2316 int error_recover
= 0;
2319 int crow
= 2, ccol
= 5;
2320 char moveexp
[255] = {0};
2321 char gameexp
[255] = {0};
2322 int delete_count
= 0;
2323 int markstart
= -1, markend
= -1;
2325 int board_details
= 0;
2327 gindex
= gtotal
- 1;
2328 markstart
= -1, markend
= -1;
2330 if (history_total(game
[gindex
].hp
))
2331 game
[gindex
].mode
= MODE_HISTORY
;
2333 game
[gindex
].mode
= MODE_PLAY
;
2335 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2336 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
2338 paused
= 1; //FIXME clock
2340 update_all(game
[gindex
]);
2341 update_tag_window(game
[gindex
].tag
);
2345 int i
, x
, n
= 0, len
= 0;
2348 char fdbuf[8192] = {0};
2353 char tfile
[FILENAME_MAX
];
2354 int minr
, maxr
, minc
, maxc
;
2358 if (engine_initialized
) {
2363 FD_SET(enginefd
[0], &fds
);
2365 for (i
= 0; i
< gtotal
; i
++) {
2366 if (game
[i
].sockfd
> 0) {
2367 if (game
[i
].sockfd
> n
)
2370 FD_SET(game
[i
].sockfd
, &fds
);
2374 n
= (n
> enginefd
[0]) ? n
: enginefd
[0];
2376 if ((n
= select(n
+ 1, &fds
, NULL
, NULL
, &tv
)) > 0) {
2377 if (FD_ISSET(enginefd
[0], &fds
)) {
2378 len
= read(enginefd
[0], fdbuf
, sizeof(fdbuf
));
2381 if (errno
!= EAGAIN
) {
2382 cmessage(ERROR
, ANYKEY
, "Attempt #%i. read(): %s",
2383 ++error_recover
, strerror(errno
));
2389 // FIXME engine may be associated with another
2391 parse_engine_output(&game
[gindex
], fdbuf
);
2392 update_all(game
[gindex
]);
2397 for (i
= 0; i
< gtotal
; i
++) {
2398 if (game
[i
].sockfd
<= 0)
2401 if (FD_ISSET(game
[i
].sockfd
, &fds
)) {
2402 len
= recv(game
[i
].sockfd
, fdbuf
, sizeof(fdbuf
), 0);
2405 if (errno
!= EAGAIN
) {
2406 cmessage(ERROR
, ANYKEY
,
2407 "Attempt #%i. recv(): %s",
2408 ++error_recover
, strerror(errno
));
2414 parse_ics_output(fdbuf
);
2416 update_all(game
[gindex
]);
2423 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
2432 draw_board(&game
[gindex
], board_details
, crow
, ccol
);
2433 wmove(boardw
, ROWTOMATRIX(crow
), COLTOMATRIX(ccol
));
2444 if ((c
= wgetch(boardw
)) == ERR
)
2448 if (!count
&& status
.notify
)
2449 update_status_notify(game
[gindex
], NULL
);
2456 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(board
));
2464 if (game
[gindex
].mode
== MODE_PLAY
) {
2468 if (game
[gindex
].mode
== MODE_HISTORY
) {
2469 rav_next_prev(&game
[gindex
], board
, (c
== '-') ? 0 : 1);
2470 update_all(game
[gindex
]);
2475 if (game
[gindex
].mode
== MODE_EDIT
) {
2476 if (crow
!= 6 && crow
!= 3)
2479 pgn_reset_enpassant(board
);
2480 board
[ROWTOBOARD(crow
)][COLTOBOARD(ccol
)].enpassant
= 1;
2497 if (history_total(game
[gindex
].hp
))
2503 pgn_add_tag(&game
[gindex
].tag
, &game
[gindex
].tindex
,
2504 "FEN", pgn_game_to_fen(game
[gindex
], board
));
2505 pgn_add_tag(&game
[gindex
].tag
, &game
[gindex
].tindex
,
2507 game
[gindex
].mode
= MODE_PLAY
;
2508 pgn_sort_tags(game
[gindex
]);
2511 game
[gindex
].mode
= MODE_EDIT
;
2514 if (pgn_init_fen_board(&game
[gindex
], board
, NULL
))
2520 update_all(game
[gindex
]);
2528 if (!*gameexp
|| c
== '?') {
2529 if ((tmp
= get_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
,
2530 1, 1, GAME_FIND_EXPRESSION_PROMPT
, NULL
,
2531 NULL
, 0, -1)) == NULL
)
2534 strncpy(gameexp
, tmp
, sizeof(gameexp
));
2537 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1,
2538 (count
) ? count
: 1)) == -1)
2543 if (history_total(game
[gindex
].hp
))
2544 game
[gindex
].mode
= MODE_HISTORY
;
2546 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2547 update_all(game
[gindex
]);
2548 update_tag_window(game
[gindex
].tag
);
2601 if (history_total(game
[gindex
].hp
) < 2)
2606 if (!*moveexp
|| c
== '/') {
2607 if ((tmp
= get_input(FIND_REGEXP
, moveexp
, 1, 1, NULL
,
2608 NULL
, NULL
, 0, -1)) == NULL
)
2611 strncpy(moveexp
, tmp
, sizeof(moveexp
));
2615 if ((n
= find_move_exp(game
[gindex
], moveexp
, n
,
2616 (c
== '[') ? 0 : 1, (count
) ? count
: 1)) == -1)
2619 game
[gindex
].hindex
= n
;
2620 history_update_board(&game
[gindex
], board
, game
[gindex
].hindex
);
2621 update_all(game
[gindex
]);
2624 if (game
[gindex
].hindex
== history_total(game
[gindex
].hp
))
2627 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
]);
2630 if (game
[gindex
].hindex
- 1 >= 0)
2631 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2635 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (count
) ? count
: 1);
2643 game
[gindex
].mode
= MODE_HISTORY
;
2646 if (history_total(game
[gindex
].hp
))
2647 game
[gindex
].mode
= MODE_HISTORY
;
2649 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2650 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
, &ccol
);
2651 update_all(game
[gindex
]);
2652 update_tag_window(game
[gindex
].tag
);
2655 if (game
[gindex
].mode
!= MODE_HISTORY
||
2656 history_total(game
[gindex
].hp
) < 2)
2660 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2661 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2662 game[gindex].htotal)) == NULL)
2667 if ((tmp
= get_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1,
2668 NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2671 if (!isinteger(tmp
))
2679 if (i
> (history_total(game
[gindex
].hp
) / 2) || i
< 0)
2682 game
[gindex
].hindex
= i
* 2;
2684 if (history_total(game
[gindex
].hp
))
2685 game
[gindex
].mode
= MODE_HISTORY
;
2687 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2688 update_all(game
[gindex
]);
2695 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2696 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2702 if ((tmp
= get_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
,
2703 NULL
, NULL
, 0, -1)) == NULL
)
2706 if (!isinteger(tmp
))
2714 if (--i
> gtotal
- 1 || i
< 0)
2719 if (history_total(game
[gindex
].hp
))
2720 game
[gindex
].mode
= MODE_HISTORY
;
2722 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2723 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
, &ccol
);
2724 update_all(game
[gindex
]);
2725 update_tag_window(game
[gindex
].tag
);
2732 board
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2734 board
[ROWTOBOARD(crow
)][COLTOBOARD(ccol
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2736 sp
.icon
= sp
.row
= sp
.col
= 0;
2743 if (count
&& !delete_count
) {
2746 update_status_notify(game
[gindex
], "%s (delete)",
2751 if (markstart
>= 0 && markend
>= 0) {
2752 if (markstart
> markend
) {
2754 markstart
= markend
;
2758 for (i
= markstart
; i
<= markend
; i
++) {
2759 if (toggle_delete_flag(i
))
2764 if (toggle_delete_flag(gindex
))
2768 markstart
= markend
= -1;
2769 update_status_window(game
[gindex
]);
2773 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2779 for (i
= n
= 0; i
< gtotal
; i
++) {
2780 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2785 tmp
= GAME_DELETE_GAME_TEXT
;
2788 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2792 tmp
= GAME_DELETE_ALL_TEXT
;
2795 if (config
.deleteprompt
) {
2796 if ((c
= cmessage(NULL
, YESNO
, "%s", tmp
)) != 'y')
2800 delete_game((!n
) ? gindex
: -1);
2802 if (history_total(game
[gindex
].hp
))
2803 game
[gindex
].mode
= MODE_HISTORY
;
2805 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2806 update_all(game
[gindex
]);
2807 update_tag_window(game
[gindex
].tag
);
2810 annotate
= game
[gindex
].hindex
;
2812 if (annotate
&& game
[gindex
].hp
[annotate
- 1]->move
)
2817 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_EDIT_TITLE
,
2818 game
[gindex
].hp
[annotate
]->move
);
2820 tmp
= get_input(buf
, game
[gindex
].hp
[annotate
]->comment
,
2821 0, 0, NAG_PROMPT
, history_edit_nag
,
2822 (void *)game
[gindex
].hp
[annotate
], CTRL('T'), -1);
2824 if (!tmp
&& (!game
[gindex
].hp
[annotate
]->comment
||
2825 !*game
[gindex
].hp
[annotate
]->comment
))
2827 else if (tmp
&& game
[gindex
].hp
[annotate
]->comment
) {
2828 if (strcmp(tmp
, game
[gindex
].hp
[annotate
]->comment
) == 0)
2832 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
2834 game
[gindex
].hp
[annotate
]->comment
=
2835 Realloc(game
[gindex
].hp
[annotate
]->comment
, len
);
2837 strncpy(game
[gindex
].hp
[annotate
]->comment
,
2838 (tmp
) ? tmp
: "", len
);
2840 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2841 update_all(game
[gindex
]);
2844 edit_save_tags(&game
[gindex
]);
2845 pgn_sort_tags(game
[gindex
]);
2846 update_all(game
[gindex
]);
2847 update_tag_window(game
[gindex
].tag
);
2850 if (game
[gindex
].mode
== MODE_EDIT
) {
2851 c
= message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, "%s",
2854 if (pgn_piece_to_int(c
) == -1)
2857 board
[ROWTOBOARD(crow
)][COLTOBOARD(ccol
)].icon
= c
;
2861 edit_tags(game
[gindex
], board
, 0);
2864 if (game
[gindex
].mode
== MODE_HISTORY
||
2865 status
.engine
== ENGINE_THINKING
)
2868 status
.engine
= ENGINE_THINKING
;
2869 update_status_window(game
[gindex
]);
2870 SEND_TO_ENGINE("go\n");
2873 if (game
[gindex
].mode
== MODE_HISTORY
) {
2874 if (TEST_FLAG(game
[gindex
].flags
, GF_BLACK_OPENING
)) {
2875 cmessage(NULL
, ANYKEY
, "%s", E_RESUME_BLACK
);
2879 if (game
[gindex
].hindex
!= history_total(game
[gindex
].hp
)) {
2881 if ((c
= message(NULL
, YESNO
, "%s",
2882 GAME_RESUME_HISTORY_TEXT
)) != 'y')
2887 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
2892 wtimeout(boardw
, 70);
2894 if (!noengine
&& !engine_initialized
) {
2895 if (start_chess_engine() < 0)
2903 oldhistorytotal
= history_total(game
[gindex
].hp
);
2904 game
[gindex
].mode
= MODE_PLAY
;
2905 status
.engine
= ENGINE_READY
;
2909 if (config
.engine
!= GNUCHESS
)
2910 SEND_TO_ENGINE("read %s\n", config
.fifo
);
2912 SEND_TO_ENGINE("\npgnload %s\n", config
.fifo
);
2915 update_all(game
[gindex
]);
2919 if (!history_total(game
[gindex
].hp
) || status
.engine
==
2923 wtimeout(boardw
, -1);
2925 if (history_total(game
[gindex
].hp
))
2926 game
[gindex
].mode
= MODE_HISTORY
;
2928 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2931 /* FIXME dies reading FIFO sometimes. */
2932 if (game
[gindex
].mode
!= MODE_PLAY
|| !history_total(game
[gindex
].hp
))
2935 history_previous(&game
[gindex
], board
, (count
) ? count
* 2 : 2,
2939 if (status
.engine
== CRAFTY
)
2940 SEND_TO_ENGINE("read %s\n", config
.fifo
);
2942 SEND_TO_ENGINE("\npgnload %s\n", config
.fifo
);
2945 update_history_window(game
[gindex
]);
2948 if ((tmp
= get_input(GAME_LOAD_TITLE
, NULL
, 1, 1,
2949 BROWSER_PROMPT
, browse_directory
, NULL
,
2953 tmp
= tilde_expand(tmp
);
2955 if (pgn_parse_file(tmp
))
2958 gindex
= gtotal
- 1;
2959 strncpy(loadfile
, tmp
, sizeof(loadfile
));
2961 if (history_total(game
[gindex
].hp
))
2962 game
[gindex
].mode
= MODE_HISTORY
;
2964 history_update_board(&game
[gindex
], board
, history_total(game
[gindex
].hp
));
2965 update_all(game
[gindex
]);
2966 update_tag_window(game
[gindex
].tag
);
2973 n
= message(NULL
, GAME_SAVE_MULTI_PROMPT
, "%s",
2974 GAME_SAVE_MULTI_TEXT
);
2981 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2986 if ((tmp
= get_input(GAME_SAVE_TITLE
, loadfile
, 1, 1,
2987 BROWSER_PROMPT
, browse_directory
, NULL
,
2988 '\t', -1)) == NULL
) {
2989 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2993 tmp
= tilde_expand(tmp
);
2995 if (strstr(tmp
, ".") == NULL
&& compression_cmd(tmp
, 0)
2997 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3001 if (save_pgn(tmp
, 0, x
)) {
3002 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
3006 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
3007 update_all(game
[gindex
]);
3013 n
= help(GAME_HELP_INDEX_TITLE
, GAME_HELP_INDEX_PROMPT
,
3018 help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3021 help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3024 help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3027 help(GAME_HELP_GAME_TITLE
, ANYKEY
, gamehelp
);
3039 if (cmessage(NULL
, YESNO
, "%s", GAME_NEW_PROMPT
) != 'y')
3043 game
[gindex
].mode
= MODE_PLAY
;
3048 pgn_new_game(board
);
3049 add_custom_tags(&game
[gindex
].tag
, &game
[gindex
].tindex
);
3052 pgn_parse_file(NULL
);
3053 add_custom_tags(&game
[gindex
].tag
, &game
[gindex
].tindex
);
3054 pgn_init_board(board
);
3057 game
[gindex
].mode
= MODE_PLAY
;
3058 crow
= (game
[gindex
].side
== WHITE
) ? 2 : 7;
3061 if (!noengine
&& (status
.engine
== ENGINE_OFFLINE
||
3062 engine_initialized
== 0)) {
3063 if (start_chess_engine() < 0)
3067 SEND_TO_ENGINE("\nnew\n");
3068 set_engine_defaults();
3069 status
.engine
= ENGINE_READY
;
3070 update_status_notify(game
[gindex
], NULL
);
3071 update_all(game
[gindex
]);
3072 update_tag_window(game
[gindex
].tag
);
3077 keypad(boardw
, TRUE
);
3082 if (game
[gindex
].mode
== MODE_EDIT
) {
3083 castling_state(&game
[gindex
], board
, ROWTOBOARD(crow
),
3085 board
[ROWTOBOARD(crow
)][COLTOBOARD(ccol
)].icon
, 1);
3089 if (status
.engine
== ENGINE_THINKING
)
3092 if (status
.engine
== ENGINE_OFFLINE
)
3095 if ((tmp
= get_input_str_clear(ENGINE_CMD_TITLE
, NULL
))
3097 SEND_TO_ENGINE("%s\n", tmp
);
3101 sp
.icon
= sp
.row
= sp
.col
= 0;
3102 markend
= markstart
= 0;
3106 update_status_notify(game
[gindex
], NULL
);
3109 if (config
.validmoves
)
3110 board_reset_valid_moves(board
);
3117 count
= count
* 10 + n
;
3121 update_status_notify(game
[gindex
], "Repeat %i", count
);
3124 if (game
[gindex
].mode
== MODE_HISTORY
) {
3125 history_next(&game
[gindex
], board
, (count
> 0) ?
3126 config
.jumpcount
* count
* movestep
:
3127 config
.jumpcount
* movestep
, movestep
);
3128 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
,
3130 update_all(game
[gindex
]);
3135 if (sp.icon && config.validmoves) {
3136 get_valid_cursor(board, UP, (count) ? count : 1,
3137 &crow, &ccol, minr, maxr, minc, maxc);
3154 if (game
[gindex
].mode
== MODE_HISTORY
) {
3155 history_previous(&game
[gindex
], board
, (count
) ?
3156 config
.jumpcount
* count
* movestep
:
3157 config
.jumpcount
* movestep
, movestep
);
3158 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
,
3160 update_all(game
[gindex
]);
3165 if (sp.icon && config.validmoves) {
3166 get_valid_cursor(board, DOWN, (count) ? count : 1,
3167 &crow, &ccol, minr, maxr, minc, maxc);
3175 update_status_notify(game
[gindex
], NULL
);
3185 if (game
[gindex
].mode
== MODE_HISTORY
) {
3186 history_previous(&game
[gindex
], board
, (count
) ?
3187 count
* movestep
: movestep
, movestep
);
3188 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
,
3190 update_all(game
[gindex
]);
3195 if (sp.icon && config.validmoves) {
3196 get_valid_cursor(board, LEFT, (count) ? count : 1,
3197 &crow, &ccol, minr, maxr, minc, maxc);
3214 if (game
[gindex
].mode
== MODE_HISTORY
) {
3215 history_next(&game
[gindex
], board
, (count
) ?
3216 count
* movestep
: movestep
, movestep
);
3217 update_cursor(game
[gindex
], game
[gindex
].hindex
, &crow
,
3219 update_all(game
[gindex
]);
3224 if (sp.icon && config.validmoves) {
3225 get_valid_cursor(board, RIGHT, (count) ? count : 1,
3226 &crow, &ccol, minr, maxr, minc, maxc);
3243 if (game
[gindex
].mode
== MODE_HISTORY
)
3246 if (game
[gindex
].mode
== MODE_EDIT
)
3247 pgn_switch_turn(&game
[gindex
]);
3250 SEND_TO_ENGINE("\nswitch\n");
3251 switch_side(&game
[gindex
]);
3252 update_status_window(game
[gindex
]);
3255 if (!editmode
&& game
[gindex
].mode
== MODE_HISTORY
) {
3261 update_history_window(game
[gindex
]);
3265 if (!noengine
&& (status
.engine
== ENGINE_OFFLINE
||
3266 !engine_initialized
) && !editmode
) {
3267 if (start_chess_engine() < 0) {
3275 wtimeout(boardw
, 70);
3277 if (sp
.icon
|| (!editmode
&& status
.engine
== ENGINE_THINKING
)) {
3282 sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(crow
),
3283 COLTOMATRIX(ccol
)+1) & A_CHARTEXT
;
3285 if (sp
.icon
== ' ') {
3290 if (!editmode
&& ((islower(sp
.icon
) &&
3291 game
[gindex
].turn
!= BLACK
) ||
3292 (isupper(sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
3293 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
3301 if (!editmode
&& config
.validmoves
) {
3302 board_get_valid_moves(&game
[gindex
], board
,
3303 pgn_piece_to_int(sp
.icon
), sp
.row
, sp
.col
, &minr
,
3304 &maxr
, &minc
, &maxc
);
3306 number_valid_moves(board, sp.row, sp.col);
3310 if (game
[gindex
].mode
== MODE_PLAY
)
3315 pushkey
= count
= 0;
3316 update_status_notify(game
[gindex
], NULL
);
3318 if (!editmode
&& game
[gindex
].mode
== MODE_HISTORY
)
3321 if (status
.engine
== ENGINE_THINKING
) {
3333 edit_board(game
[gindex
], board
);
3334 sp
.icon
= sp
.row
= sp
.col
= 0;
3338 if (move_to_engine(&game
[gindex
], board
)) {
3339 if (config
.validmoves
)
3340 board_reset_valid_moves(board
);
3342 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
)) {
3343 CLEAR_FLAG(game
[gindex
].flags
, GF_GAMEOVER
);
3344 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
3363 void usage(const char *pn
, int ret
)
3365 fprintf((ret
) ? stderr
: stdout
, "%s",
3366 "Usage: cboard [-hvNE] [-d <n>] [-VtRS] [-p <file>]\n"
3367 " -p Load PGN file.\n"
3368 " -V Validate a game file.\n"
3369 " -S Validate and output a PGN formatted game.\n"
3370 " -R Like -S but write a reduced PGN formatted game.\n"
3371 " -t Also write custom PGN tags from config file.\n"
3372 " -N Don't enable the chess engine (two human players).\n"
3373 " -E Stop processing on file parsing error (overrides config).\n"
3374 " -d Escape key delay in milliseconds.\n"
3375 " -v Version information.\n"
3376 " -h This help text.\n");
3381 void catch_signal(int which
)
3393 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3402 keypad(boardw
, TRUE
);
3409 static void set_defaults()
3412 set_config_defaults();
3415 int main(int argc
, char *argv
[])
3419 char buf
[FILENAME_MAX
];
3420 char datadir
[FILENAME_MAX
];
3421 int ret
= EXIT_SUCCESS
;
3422 int validate_only
= 0, validate_and_write
= 0, reduced
= 0;
3423 int write_custom_tags
= 0;
3425 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3426 err(EXIT_FAILURE
, "getpwuid()");
3428 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3429 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3430 config
.ccfile
= strdup(buf
);
3431 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3432 config
.nagfile
= strdup(buf
);
3433 snprintf(buf
, sizeof(buf
), "%s/agony.data", datadir
);
3434 config
.agonyfile
= strdup(buf
);
3435 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3436 config
.configfile
= strdup(buf
);
3437 snprintf(buf
, sizeof(buf
), "%s/fifo", datadir
);
3438 config
.fifo
= strdup(buf
);
3440 if (stat(datadir
, &st
) == -1) {
3441 if (errno
== ENOENT
) {
3442 if (mkdir(datadir
, 0755) == -1)
3443 err(EXIT_FAILURE
, "%s", datadir
);
3446 err(EXIT_FAILURE
, "%s", datadir
);
3451 if (!S_ISDIR(st
.st_mode
))
3452 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3454 if (access(config
.fifo
, R_OK
) == -1 && errno
== ENOENT
) {
3455 if (mkfifo(config
.fifo
, 0600) == -1)
3456 err(EXIT_FAILURE
, "%s", config
.fifo
);
3461 while ((opt
= getopt(argc
, argv
, "d:ENVtSRhp:v")) != -1) {
3464 ESCDELAY
= atoi(optarg
);
3467 write_custom_tags
= 1;
3470 config
.stoponerror
= 1;
3478 validate_and_write
= 1;
3483 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3487 filetype
= PGN_FILE
;
3488 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3492 usage(argv
[0], EXIT_SUCCESS
);
3496 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3497 usage(argv
[0], EXIT_FAILURE
);
3499 if (access(config
.configfile
, R_OK
) == 0)
3500 parse_rcfile(config
.configfile
);
3502 signal(SIGPIPE
, catch_signal
);
3503 signal(SIGCONT
, catch_signal
);
3504 signal(SIGSTOP
, catch_signal
);
3505 signal(SIGINT
, catch_signal
);
3511 ret
= pgn_parse_file(loadfile
);
3514 //ret = parse_fen_file(loadfile);
3516 case EPD_FILE
: // Not implemented.
3519 // No file specified. Empty game.
3520 ret
= pgn_parse_file(NULL
);
3521 add_custom_tags(&game
[gindex
].tag
, &game
[gindex
].tindex
);
3526 err(EXIT_FAILURE
, "%s", loadfile
);
3528 if (validate_only
|| validate_and_write
) {
3529 if (validate_and_write
) {
3532 for (i
= 0; i
< gtotal
; i
++) {
3533 if (write_custom_tags
)
3534 add_custom_tags(&game
[i
].tag
, &game
[i
].tindex
);
3536 pgn_write(stdout
, game
[i
], reduced
);
3546 if (initscr() == NULL
)
3547 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3549 curses_initialized
= 1;
3551 if (LINES
< 24 || COLS
< 80) {
3553 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3556 if (has_colors() == TRUE
&& start_color() == OK
)
3559 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3560 boardp
= new_panel(boardw
);
3561 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3562 COLS
- HISTORY_WIDTH
);
3563 historyp
= new_panel(historyw
);
3564 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3565 statusp
= new_panel(statusw
);
3566 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3567 tagp
= new_panel(tagw
);
3568 keypad(boardw
, TRUE
);
3569 // leaveok(boardw, TRUE);
3570 leaveok(tagw
, TRUE
);
3571 leaveok(statusw
, TRUE
);
3572 leaveok(historyw
, TRUE
);
3576 draw_window_decor();
3584 del_panel(historyp
);