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>
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
);
113 * If not deincremented then r and c would be the next move.
117 if (idx
> t
|| idx
< 0 || !t
|| !g
.hp
[idx
]->move
) {
118 c_row
= 2, c_col
= 5;
131 c_row
= (g
.turn
== WHITE
) ? 1 : 8;
140 c_row
= ROWTOINT(*p
--);
141 c_col
= COLTOINT(*p
);
144 static int init_nag()
150 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
151 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.nagfile
, strerror(errno
));
156 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
157 nags
= Realloc(nags
, (i
+ 2) * sizeof(struct nag_s
));
158 nags
[i
].line
= strdup(line
);
168 char *history_edit_nag(void *arg
)
172 ITEM
**mitems
= NULL
;
178 HISTORY
*anno
= (HISTORY
*)arg
;
186 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
187 mitems
[i
++] = new_item(NONE
, NULL
);
189 for (n
= 0; nags
[n
].line
; n
++, i
++) {
190 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
191 mitems
[i
] = new_item(nags
[n
].line
, NULL
);
195 menu
= new_menu(mitems
);
196 scale_menu(menu
, &rows
, &cols
);
198 win
= newwin(rows
+ 4, cols
+ 2, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
199 set_menu_win(menu
, win
);
200 subw
= derwin(win
, rows
, cols
, 2, 1);
201 set_menu_sub(menu
, subw
);
202 set_menu_fore(menu
, A_REVERSE
);
203 set_menu_grey(menu
, A_NORMAL
);
204 set_menu_mark(menu
, NULL
);
205 set_menu_spacing(menu
, 0, 0, 0);
206 menu_opts_off(menu
, O_NONCYCLIC
|O_SHOWDESC
|O_ONEVALUE
);
208 panel
= new_panel(win
);
212 set_menu_pattern(menu
, mbuf
);
213 wbkgd(win
, CP_MESSAGE_WINDOW
);
214 draw_window_title(win
, NAG_EDIT_TITLE
, cols
+ 2, CP_HISTORY_TITLE
,
217 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
218 if (anno
->nag
[i
] && anno
->nag
[i
] <= item_count(menu
)) {
219 set_item_value(mitems
[anno
->nag
[i
]], TRUE
);
220 set_current_item(menu
, mitems
[anno
->nag
[i
]]);
230 wattron(win
, A_REVERSE
);
232 for (c
= 1; c
< (cols
+ 2) - 1; c
++)
233 mvwprintw(win
, rows
+ 2, c
, " ");
235 c
= item_index(current_item(menu
)) + 1;
237 snprintf(buf
, sizeof(buf
), "Item %i of %i (%i of %i selected) %s", c
,
238 item_count(menu
), itemcount
, MAX_PGN_NAG
, NAG_EDIT_PROMPT
);
239 draw_prompt(win
, rows
+ 2, cols
+ 2, buf
, CP_MESSAGE_PROMPT
);
241 wattroff(win
, A_REVERSE
);
244 for (i
= 0; mitems
[i
]; i
++)
245 set_item_value(mitems
[i
], FALSE
);
247 set_item_value(mitems
[0], TRUE
);
250 set_item_value(mitems
[0], FALSE
);
252 /* This nl() statement needs to be here because NL is recognized
253 * for some reason after the first selection.
263 help(NAG_EDIT_HELP
, ANYKEY
, naghelp
);
271 for (i
= item_index(current_item(menu
)) + 1; mitems
[i
]; i
++) {
272 if (item_value(mitems
[i
]) == TRUE
) {
279 for (i
= 0; mitems
[i
]; i
++) {
280 if (item_value(mitems
[i
]) == TRUE
) {
287 set_current_item(menu
, mitems
[found
]);
295 for (i
= item_index(current_item(menu
)) - 1; i
> 0; i
--) {
296 if (item_value(mitems
[i
]) == TRUE
) {
303 for (i
= item_count(menu
) - 1; i
> 0; i
--) {
304 if (item_value(mitems
[i
]) == TRUE
) {
311 set_current_item(menu
, mitems
[found
]);
314 menu_driver(menu
, REQ_FIRST_ITEM
);
317 menu_driver(menu
, REQ_LAST_ITEM
);
320 menu_driver(menu
, REQ_UP_ITEM
);
323 menu_driver(menu
, REQ_DOWN_ITEM
);
327 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
328 menu_driver(menu
, REQ_FIRST_ITEM
);
332 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
333 menu_driver(menu
, REQ_LAST_ITEM
);
336 if (item_index(current_item(menu
)) == 0 &&
337 item_value(current_item(menu
)) == FALSE
) {
342 if (item_value(current_item(menu
)) == TRUE
) {
343 set_item_value(current_item(menu
), FALSE
);
347 if (itemcount
+ 1 > MAX_PGN_NAG
)
350 set_item_value(current_item(menu
), TRUE
);
354 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
363 tmp
= menu_pattern(menu
);
365 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
366 menu_driver(menu
, REQ_CLEAR_PATTERN
);
367 menu_driver(menu
, c
);
370 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
371 menu_driver(menu
, c
);
379 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
382 for (i
= 0, n
= 0; mitems
[i
] && n
< MAX_PGN_NAG
; i
++) {
383 if (item_value(mitems
[i
]) == TRUE
)
391 for (i
= 0; mitems
[i
]; i
++)
392 free_item(mitems
[i
]);
401 static void view_nag(void *arg
)
403 HISTORY
*h
= (HISTORY
*)arg
;
405 char line
[LINE_MAX
] = {0};
408 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
415 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
419 strncat(line
, nags
[h
->nag
[i
] - 1].line
, sizeof(line
));
420 strncat(line
, "\n", sizeof(line
));
423 line
[strlen(line
) - 1] = 0;
424 message(buf
, ANYKEY
, "%s", line
);
427 void view_annotation(HISTORY h
)
429 char buf
[strlen(h
.move
) + strlen(ANNOTATION_VIEW_TITLE
) + 4];
430 int nag
= 0, comment
= 0;
432 if (h
.comment
&& h
.comment
[0])
438 if (!nag
&& !comment
)
441 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
.move
);
444 show_message(buf
, (nag
) ? "Any other key to continue" : ANYKEY
,
445 (nag
) ? "Press 'n' to view NAG" : NULL
,
446 (nag
) ? view_nag
: NULL
, (nag
) ? (void *)&h
: NULL
,
447 (nag
) ? 'n' : 0, "%s", h
.comment
);
449 show_message(buf
, "Any other key to continue", "Press 'n' to view NAG",
450 view_nag
, (void *)&h
, 'n', "%s", "No annotations for this move");
453 static void cleanup(WINDOW
*win
, WINDOW
*subw
, PANEL
*panel
, MENU
*menu
,
454 ITEM
**items
, struct d_entries
*entries
)
461 for (i
= 0; items
[i
]; i
++)
467 for (i
= 0; entries
[i
].name
; i
++) {
468 free(entries
[i
].name
);
469 free(entries
[i
].fancy
);
480 static int sort_entries(const void *s1
, const void *s2
)
482 const struct d_entries
*ss1
= s1
;
483 const struct d_entries
*ss2
= s2
;
485 return strcmp(ss1
->name
, ss2
->name
);
488 char *browse_directory(void *arg
)
490 char *inputstr
= (char *)arg
;
491 int initkey
= (inputstr
) ? inputstr
[0] : 0;
492 char pattern
[FILENAME_MAX
];
493 static char path
[FILENAME_MAX
];
494 static char file
[FILENAME_MAX
];
499 if (config
.savedirectory
) {
500 if ((p
= word_expand(config
.savedirectory
)) == NULL
)
503 strncpy(path
, p
, sizeof(path
));
505 if (access(path
, R_OK
) == -1) {
506 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
507 getcwd(path
, sizeof(path
));
511 getcwd(path
, sizeof(path
));
516 * First find directories (including hidden) in the working directory.
517 * Then apply the config.pattern to regular files.
519 if ((p
= word_split_append(path
, '/', ".* *")) == NULL
)
522 strncpy(pattern
, p
, sizeof(pattern
));
527 ITEM
**mitems
= NULL
;
534 int len
= strlen(path
);
537 struct d_entries
*entries
= NULL
;
542 if (wordexp(pattern
, &w
, x
) != 0) {
543 cmessage(ERROR
, ANYKEY
, "Error in pattern\n%s", pattern
);
547 for (i
= 0; i
< w
.we_wordc
; i
++) {
551 if (stat(w
.we_wordv
[i
], &st
) == -1)
554 if ((p
= strrchr(w
.we_wordv
[i
], '/')) != NULL
)
560 if (!S_ISDIR(st
.st_mode
))
563 if (p
[0] == '.' && p
[1] == 0)
567 if (S_ISDIR(st
.st_mode
))
572 entries
= Realloc(entries
, (n
+ 2) * sizeof(struct d_entries
));
573 entries
[n
].name
= strdup(w
.we_wordv
[i
]);
574 entries
[n
].fancy
= Malloc(len
);
575 strncpy(entries
[n
].fancy
, p
, len
);
577 if (S_ISDIR(st
.st_mode
))
578 entries
[n
].fancy
[len
- 2] = '/';
580 tp
= localtime(&st
.st_mtime
);
581 strftime(tbuf
, sizeof(tbuf
), "%b %d %T", tp
);
583 snprintf(entries
[n
].desc
, sizeof(entries
[n
].desc
), "%-7i %s",
584 (int)st
.st_size
, tbuf
);
586 memset(&entries
[++n
], '\0', sizeof(struct d_entries
));
592 if ((p
= word_split_append(path
, '/', config
.pattern
)) == NULL
)
595 strncpy(pattern
, p
, sizeof(pattern
));
601 qsort(entries
, n
, sizeof(struct d_entries
), sort_entries
);
603 for (i
= 0; i
< n
; i
++) {
604 mitems
= Realloc(mitems
, (idx
+ 2) * sizeof(ITEM
));
605 mitems
[idx
++] = new_item(entries
[i
].fancy
, entries
[i
].desc
);
609 menu
= new_menu(mitems
);
610 scale_menu(menu
, &rows
, &cols
);
612 if (cols
< strlen(path
))
615 if (cols
< strlen(HELP_PROMPT
))
616 cols
= strlen(HELP_PROMPT
);
618 rows
= (LINES
/ 5) * 4;
621 win
= newwin(rows
+ 4, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
622 set_menu_format(menu
, rows
, 0);
623 set_menu_win(menu
, win
);
624 subw
= derwin(win
, rows
, cols
- 2, 2, 1);
625 set_menu_sub(menu
, subw
);
626 set_menu_fore(menu
, A_REVERSE
);
627 set_menu_grey(menu
, A_NORMAL
);
628 set_menu_mark(menu
, NULL
);
629 set_menu_spacing(menu
, 2, 0, 0);
630 menu_opts_off(menu
, O_NONCYCLIC
);
632 panel
= new_panel(win
);
634 draw_window_title(win
, path
, cols
, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
635 draw_prompt(win
, rows
+ 2, cols
, HELP_PROMPT
, CP_MESSAGE_PROMPT
);
640 set_menu_pattern(menu
, mbuf
);
642 if (isgraph(initkey
)) {
643 menu_driver(menu
, initkey
);
650 /* This nl() statement needs to be here because NL is recognized
651 * for some reason after the first selection.
660 menu_driver(menu
, REQ_SCR_UPAGE
);
665 menu_driver(menu
, REQ_SCR_DPAGE
);
668 menu_driver(menu
, REQ_UP_ITEM
);
671 menu_driver(menu
, REQ_DOWN_ITEM
);
674 selected
= item_index(current_item(menu
));
678 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
683 help(BROWSER_HELP
, ANYKEY
, file_browser_help
);
686 strncpy(path
, "~/", sizeof(path
));
687 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
691 if ((tmp
= get_input_str_clear(BROWSER_CHDIR_TITLE
, NULL
))
695 strncpy(path
, tmp
, sizeof(path
));
696 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
700 tmp
= menu_pattern(menu
);
702 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
703 menu_driver(menu
, REQ_CLEAR_PATTERN
);
704 menu_driver(menu
, c
);
707 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
708 menu_driver(menu
, c
);
716 strncpy(file
, entries
[selected
].name
, sizeof(file
));
717 cleanup(win
, subw
, panel
, menu
, mitems
, entries
);
719 if (stat(file
, &st
) == -1) {
720 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, strerror(errno
));
724 if (S_ISDIR(st
.st_mode
)) {
725 p
= file
+ strlen(file
) - 2;
727 if (strcmp(p
, "..") == 0) {
728 p
= file
+ strlen(file
) - 3;
731 if ((p
= strrchr(file
, '/')) != NULL
)
732 file
[strlen(file
) - strlen(p
)] = 0;
735 strncpy(path
, file
, sizeof(path
));
739 if (S_ISREG(st
.st_mode
))
742 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, E_NOTAREGFILE
);
746 return (*file
) ? file
: NULL
;
748 static int init_country_codes()
751 char line
[LINE_MAX
], *s
;
754 if ((fp
= fopen(config
.ccfile
, "r")) == NULL
) {
755 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.ccfile
, strerror(errno
));
759 while ((s
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
762 if ((tmp
= strsep(&s
, " ")) == NULL
)
771 ccodes
= Realloc(ccodes
, (cindex
+ 2) * sizeof(struct country_codes
));
772 strncpy(ccodes
[cindex
].code
, tmp
, sizeof(ccodes
[cindex
].code
));
773 strncpy(ccodes
[cindex
].country
, s
, sizeof(ccodes
[cindex
].country
));
777 memset(&ccodes
[cindex
], '\0', sizeof(struct country_codes
));
783 char *country_codes(void *arg
)
787 ITEM
**mitems
= NULL
;
795 if (init_country_codes())
799 for (n
= i
= 0; ccodes
[n
].code
[0]; n
++, i
++) {
800 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
801 mitems
[i
] = new_item(ccodes
[n
].country
, ccodes
[n
].code
);
805 menu
= new_menu(mitems
);
806 scale_menu(menu
, &rows
, &cols
);
808 if (cols
< strlen(HELP_PROMPT
) + 21)
809 cols
= strlen(HELP_PROMPT
) + 21;
811 win
= newwin(rows
+ 4, cols
+ 4, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
812 set_menu_win(menu
, win
);
813 subw
= derwin(win
, rows
, cols
+ 2, 2, 1);
814 set_menu_sub(menu
, subw
);
815 set_menu_fore(menu
, A_REVERSE
);
816 set_menu_grey(menu
, A_NORMAL
);
817 set_menu_mark(menu
, NULL
);
818 set_menu_spacing(menu
, 0, 0, 0);
819 menu_opts_off(menu
, O_NONCYCLIC
);
821 panel
= new_panel(win
);
825 set_menu_pattern(menu
, mbuf
);
826 wbkgd(win
, CP_MESSAGE_WINDOW
);
827 draw_window_title(win
, CC_TITLE
, cols
+ 4, CP_MESSAGE_TITLE
,
834 wattron(win
, A_REVERSE
);
836 for (c
= 1; c
< (cols
+ 2) - 1; c
++)
837 mvwprintw(win
, rows
+ 2, c
, " ");
839 c
= item_index(current_item(menu
)) + 1;
841 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_ITEM_STR
, c
,
842 N_OF_N_STR
, item_count(menu
), HELP_PROMPT
);
843 draw_prompt(win
, rows
+ 2, cols
+ 2, buf
, CP_MESSAGE_PROMPT
);
845 wattroff(win
, A_REVERSE
);
847 /* This nl() statement needs to be here because NL is recognized
848 * for some reason after the first selection.
856 help(CC_KEY_HELP
, ANYKEY
, cc_help
);
859 menu_driver(menu
, REQ_FIRST_ITEM
);
862 menu_driver(menu
, REQ_LAST_ITEM
);
865 menu_driver(menu
, REQ_UP_ITEM
);
868 menu_driver(menu
, REQ_DOWN_ITEM
);
872 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
873 menu_driver(menu
, REQ_FIRST_ITEM
);
878 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
879 menu_driver(menu
, REQ_LAST_ITEM
);
882 tmp
= (char *)item_description(current_item(menu
));
890 tmp
= menu_pattern(menu
);
892 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
893 menu_driver(menu
, REQ_CLEAR_PATTERN
);
894 menu_driver(menu
, c
);
897 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
898 menu_driver(menu
, c
);
909 for (i
= 0; mitems
[i
]; i
++)
910 free_item(mitems
[i
]);
918 static void add_custom_tags(TAG
***t
)
925 for (i
= 0; config
.tag
[i
]; i
++)
926 pgn_tag_add(t
, config
.tag
[i
]->name
, config
.tag
[i
]->value
);
931 TAG
**edit_tags(GAME g
, BOARD b
, int edit
)
935 unsigned char data_index
= 0;
936 int n
, lastindex
= 0;
939 /* Edit the backup copy, not the original in case the save fails. */
940 for (n
= 0; g
.tag
[n
]; n
++)
941 pgn_tag_add(&data
, g
.tag
[n
]->name
, g
.tag
[n
]->value
);
943 data_index
= pgn_tag_total(data
);
948 ITEM
**mitems
= NULL
;
956 int nlen
= 0, vlen
= 0;
958 data_index
= pgn_tag_total(data
);
960 for (i
= 0; i
< data_index
; i
++) {
961 mitems
= Realloc(mitems
, (i
+ 2) * sizeof(ITEM
));
963 if (data
[i
]->value
) {
964 nlen
= strlen(data
[i
]->name
);
965 vlen
= strlen(data
[i
]->value
);
967 /* The +6 is for the menu padding. */
968 mitems
[i
] = new_item(data
[i
]->name
,
969 (nlen
+ vlen
+ 6 >= MAX_VALUE_WIDTH
)
970 ? PRESS_ENTER
: data
[i
]->value
);
973 mitems
[i
] = new_item(data
[i
]->name
, UNKNOWN
);
977 menu
= new_menu(mitems
);
978 scale_menu(menu
, &rows
, &cols
);
980 /* +14 for the extra prompt info. */
981 if (cols
< strlen(HELP_PROMPT
) + 14)
982 cols
= strlen(HELP_PROMPT
) + 14;
984 win
= newwin(rows
+ 4, cols
+ 4, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
985 set_menu_win(menu
, win
);
986 subw
= derwin(win
, rows
, cols
+ 2, 2, 1);
987 set_menu_sub(menu
, subw
);
988 set_menu_fore(menu
, A_REVERSE
);
989 set_menu_grey(menu
, A_NORMAL
);
990 set_menu_mark(menu
, NULL
);
991 set_menu_pad(menu
, '-');
992 set_menu_spacing(menu
, 3, 0, 0);
993 menu_opts_off(menu
, O_NONCYCLIC
);
995 panel
= new_panel(win
);
1000 set_menu_pattern(menu
, mbuf
);
1001 wbkgd(win
, CP_MESSAGE_WINDOW
);
1002 draw_window_title(win
, (edit
) ? TAG_EDIT_TITLE
: TAG_VIEW_TITLE
,
1003 cols
+ 4, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
1007 TAG
**tmppgn
= NULL
;
1008 char *newtag
= NULL
;
1010 if (set_current_item(menu
, mitems
[lastindex
]) != E_OK
) {
1011 lastindex
= item_count(menu
) - 1;
1015 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_TAG_STR
,
1016 item_index(current_item(menu
)) + 1, N_OF_N_STR
,
1017 item_count(menu
), HELP_PROMPT
);
1018 draw_prompt(win
, rows
+ 2, cols
+ 4, buf
, CP_MESSAGE_PROMPT
);
1024 add_custom_tags(&data
);
1029 help(TAG_EDIT_HELP
, ANYKEY
, pgn_edit_help
);
1031 help(TAG_VIEW_HELP
, ANYKEY
, pgn_info_help
);
1037 selected
= item_index(current_item(menu
));
1039 if (selected
<= 6) {
1040 cmessage(NULL
, ANYKEY
, "%s", E_REMOVE_STR
);
1044 data_index
= pgn_tag_total(data
);
1046 for (i
= 0; i
< data_index
; i
++) {
1050 pgn_tag_add(&tmppgn
, data
[i
]->name
, data
[i
]->value
);
1056 for (i
= 0; tmppgn
[i
]; i
++)
1057 pgn_tag_add(&data
, tmppgn
[i
]->name
, tmppgn
[i
]->value
);
1059 pgn_tag_free(tmppgn
);
1066 if ((newtag
= get_input(TAG_NEW_TITLE
, NULL
, 1, 1, NULL
,
1067 NULL
, NULL
, 0, FIELD_TYPE_PGN_TAG_NAME
))
1071 newtag
[0] = toupper(newtag
[0]);
1073 if (strlen(newtag
) > MAX_VALUE_WIDTH
- 6 -
1074 strlen(PRESS_ENTER
)) {
1075 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_NAMETOOLONG
);
1079 for (i
= 0; i
< data_index
; i
++) {
1080 if (strcasecmp(data
[i
]->name
, newtag
) == 0) {
1086 pgn_tag_add(&data
, newtag
, NULL
);
1087 data_index
= pgn_tag_total(data
);
1088 selected
= data_index
- 1;
1092 menu_driver(menu
, REQ_FIRST_ITEM
);
1095 menu_driver(menu
, REQ_LAST_ITEM
);
1101 pgn_tag_add(&data
, "FEN", pgn_game_to_fen(g
, b
));
1102 data_index
= pgn_tag_total(data
);
1103 selected
= data_index
- 1;
1108 if (menu_driver(menu
, REQ_SCR_DPAGE
) == E_REQUEST_DENIED
)
1109 menu_driver(menu
, REQ_LAST_ITEM
);
1113 if (menu_driver(menu
, REQ_SCR_UPAGE
) == E_REQUEST_DENIED
)
1114 menu_driver(menu
, REQ_FIRST_ITEM
);
1117 menu_driver(menu
, REQ_UP_ITEM
);
1120 menu_driver(menu
, REQ_DOWN_ITEM
);
1123 selected
= item_index(current_item(menu
));
1127 cleanup(win
, subw
, panel
, menu
, mitems
, NULL
);
1131 tmp
= menu_pattern(menu
);
1133 if (tmp
&& tmp
[strlen(tmp
) - 1] != c
) {
1134 menu_driver(menu
, REQ_CLEAR_PATTERN
);
1135 menu_driver(menu
, c
);
1138 if (menu_driver(menu
, REQ_NEXT_MATCH
) == E_NO_MATCH
)
1139 menu_driver(menu
, c
);
1145 lastindex
= item_index(current_item(menu
));
1149 lastindex
= selected
;
1150 nlen
= strlen(data
[selected
]->name
) + 3;
1151 nlen
+= (edit
) ? strlen(TAG_EDIT_TAG_TITLE
) : strlen(TAG_VIEW_TAG_TITLE
);
1153 if (nlen
> MAX_VALUE_WIDTH
)
1154 snprintf(buf
, sizeof(buf
), "%s", data
[selected
]->name
);
1156 snprintf(buf
, sizeof(buf
), "%s \"%s\"",
1157 (edit
) ? TAG_EDIT_TAG_TITLE
: TAG_VIEW_TAG_TITLE
,
1158 data
[selected
]->name
);
1161 if (strcmp(item_description(mitems
[selected
]), UNKNOWN
) == 0)
1164 cmessage(buf
, ANYKEY
, "%s", data
[selected
]->value
);
1168 if (strcmp(data
[selected
]->name
, "Date") == 0) {
1169 tmp
= get_input(buf
, data
[selected
]->value
, 0, 0, NULL
, NULL
, NULL
,
1170 0, FIELD_TYPE_PGN_DATE
);
1173 if (strptime(tmp
, PGN_TIME_FORMAT
, &tp
) == NULL
) {
1174 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_DATE_FMT
);
1181 else if (strcmp(data
[selected
]->name
, "Site") == 0) {
1182 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, CC_PROMPT
,
1183 country_codes
, NULL
, CTRL('t'), -1);
1188 else if (strcmp(data
[selected
]->name
, "Round") == 0) {
1189 tmp
= get_input(buf
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1190 FIELD_TYPE_PGN_ROUND
);
1199 else if (strcmp(data
[selected
]->name
, "Result") == 0) {
1200 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, NULL
, NULL
, NULL
,
1207 if (item_description(mitems
[selected
]) &&
1208 strcmp(item_description(mitems
[selected
]), UNKNOWN
) == 0)
1211 tmp
= data
[selected
]->value
;
1213 tmp
= get_input(buf
, tmp
, 0, 0, NULL
, NULL
, NULL
, 0, -1);
1216 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
1217 data
[selected
]->value
= Realloc(data
[selected
]->value
, len
);
1218 strncpy(data
[selected
]->value
, (tmp
) ? tmp
: "", len
);
1221 cleanup(win
, subw
, panel
, menu
, mitems
, NULL
);
1233 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1234 * game index number.
1236 int save_pgn(const char *filename
, int isfifo
, int saveindex
)
1241 char buf
[FILENAME_MAX
];
1244 char *command
= NULL
;
1245 int saveindex_max
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
1247 if (filename
[0] != '/' && config
.savedirectory
&& !isfifo
) {
1248 if (stat(config
.savedirectory
, &st
) == -1) {
1249 if (errno
== ENOENT
) {
1250 if (mkdir(config
.savedirectory
, 0755) == -1) {
1251 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1257 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1263 stat(config
.savedirectory
, &st
);
1265 if (!S_ISDIR(st
.st_mode
)) {
1266 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
1270 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
1274 /* This is a hack to resume an existing game when more than one game is
1275 * available. Also resuming a saved game and a game from history.
1277 // FIXME: may not need this when a FEN tag is supported (by the engine).
1281 if (access(filename
, W_OK
) == 0) {
1282 c
= cmessage(NULL
, GAME_SAVE_OVERWRITE_PROMPT
,
1283 "%s \"%s\"", E_FILEEXISTS
, filename
);
1287 if (pgn_is_compressed(filename
)) {
1288 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
1306 if ((fp
= popen(command
, "w")) == NULL
) {
1307 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1312 if ((fp
= fopen(filename
, mode
)) == NULL
) {
1313 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1319 pgn_write(fp
, game
[saveindex
]);
1321 for (i
= (saveindex
== -1) ? 0 : saveindex
; i
< saveindex_max
; i
++)
1322 pgn_write(fp
, game
[i
]);
1330 if (!isfifo
&& saveindex
== -1)
1331 strncpy(loadfile
, filename
, sizeof(loadfile
));
1336 char *random_agony(GAME g
)
1340 char line
[LINE_MAX
];
1342 if (n
== -1 || !config
.agony
|| !curses_initialized
||
1343 (g
.mode
== MODE_HISTORY
&& !config
.historyagony
))
1347 if ((fp
= fopen(config
.agonyfile
, "r")) == NULL
) {
1349 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.agonyfile
, strerror(errno
));
1354 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
1355 agony
= Realloc(agony
, (n
+ 2) * sizeof(char *));
1356 agony
[n
++] = strdup(trim(line
));
1363 if (agony
[0] == NULL
|| !n
) {
1369 return agony
[random() % n
];
1372 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
1374 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1376 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
1377 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1379 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
1382 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1384 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
1385 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1387 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
1390 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1392 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
1393 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1395 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
1398 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1400 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
1401 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1403 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
1406 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1408 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
1409 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
1411 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
1412 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
1414 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
1415 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
1416 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1418 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1422 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1424 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
1425 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
1427 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
1428 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
1430 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
1431 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
1432 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1434 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1442 static void draw_board(GAME
*g
, int details
)
1445 int bcol
= 0, brow
= 0;
1446 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
1447 int ncols
= 0, offset
= 1;
1448 unsigned coords_y
= 8;
1450 if (g
->mode
!= MODE_PLAY
&& g
->mode
!= MODE_EDIT
)
1451 update_cursor(*g
, g
->hindex
);
1453 for (row
= 0; row
< maxy
; row
++) {
1456 for (col
= 0; col
< maxx
; col
++) {
1459 unsigned char piece
;
1461 if (row
== 0 || row
== maxy
- 2) {
1463 mvwaddch(boardw
, row
, col
,
1464 LINE_GRAPHIC((row
) ?
1465 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
1466 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
1467 else if (col
== maxx
- 2)
1468 mvwaddch(boardw
, row
, col
,
1469 LINE_GRAPHIC((row
) ?
1470 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
1471 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
1472 else if (!(col
% 4))
1473 mvwaddch(boardw
, row
, col
,
1474 LINE_GRAPHIC((row
) ?
1475 ACS_BTEE
| CP_BOARD_GRAPHICS
:
1476 ACS_TTEE
| CP_BOARD_GRAPHICS
));
1478 if (col
!= maxx
- 1)
1479 mvwaddch(boardw
, row
, col
,
1480 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1486 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
1487 wattron(boardw
, CP_BOARD_COORDS
);
1488 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
1489 wattroff(boardw
, CP_BOARD_COORDS
);
1493 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
1495 mvwaddch(boardw
, row
, col
,
1496 LINE_GRAPHIC((col
) ?
1497 ACS_RTEE
| CP_BOARD_GRAPHICS
:
1498 ACS_LTEE
| CP_BOARD_GRAPHICS
));
1500 mvwaddch(boardw
, row
, col
,
1501 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1506 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
1507 mvwaddch(boardw
, row
, col
,
1508 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1512 if (!(col
% 4) && row
!= maxy
- 1) {
1513 mvwaddch(boardw
, row
, col
,
1514 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
1525 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
1531 if (config
.validmoves
&& g
->b
[brow
][bcol
].valid
) {
1532 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
1533 CP_BOARD_MOVES_BLACK
;
1536 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
1539 if (row
== ROWTOMATRIX(c_row
) && col
==
1540 COLTOMATRIX(c_col
)) {
1541 attrs
= CP_BOARD_CURSOR
;
1544 if (row
== ROWTOMATRIX(sp
.row
) &&
1545 col
== COLTOMATRIX(sp
.col
)) {
1546 attrs
= CP_BOARD_SELECTED
;
1549 if (row
== maxy
- 1)
1552 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
1554 if (row
== maxy
- 1)
1555 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
1557 if (details
&& g
->b
[row
/ 2][bcol
].enpassant
)
1560 piece
= g
->b
[row
/ 2][bcol
].icon
;
1562 if (details
&& castling_state(g
, g
->b
, brow
, bcol
,
1566 if (g
->side
== WHITE
&& isupper(piece
))
1568 else if (g
->side
== BLACK
&& islower(piece
))
1571 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
1573 CLEAR_FLAG(attrs
, A_BOLD
);
1574 CLEAR_FLAG(attrs
, A_REVERSE
);
1577 waddch(boardw
, ' ' | attrs
);
1583 if (col
!= maxx
- 1)
1584 mvwaddch(boardw
, row
, col
,
1585 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1593 void invalid_move(int n
, const char *m
)
1595 if (curses_initialized
)
1596 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", E_INVALID_MOVE
, m
, n
);
1598 warnx("%s: %s \"%s\" (round #%i)", loadfile
, E_INVALID_MOVE
, m
, n
);
1601 /* Convert the selected piece to SAN format and validate it. */
1602 static char *board_to_san(GAME
*g
, BOARD b
)
1604 static char str
[MAX_SAN_MOVE_LEN
+ 1], *p
;
1609 snprintf(str
, sizeof(str
), "%c%i%c%i", x_grid_chars
[sp
.col
- 1],
1610 sp
.row
, x_grid_chars
[sp
.destcol
- 1], sp
.destrow
);
1613 piece
= pgn_piece_to_int(b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
);
1615 if (piece
== PAWN
&& ((sp
.destrow
== 8 && g
->turn
== WHITE
) ||
1616 (sp
.destrow
== 1 && g
->turn
== BLACK
))) {
1617 promo
= cmessage(PROMOTION_TITLE
, PROMOTION_PROMPT
, PROMOTION_TEXT
);
1619 if (pgn_piece_to_int(promo
) == -1)
1622 p
= str
+ strlen(str
);
1623 *p
++ = toupper(promo
);
1627 memcpy(oldboard
, b
, sizeof(BOARD
));
1630 if (pgn_validate_move(g
, b
, &p
)) {
1631 invalid_move(gindex
+ 1, p
);
1632 memcpy(b
, oldboard
, sizeof(BOARD
));
1639 static int move_to_engine(GAME
*g
, BOARD b
)
1642 struct userdata_s
*d
= g
->data
;
1644 if ((p
= board_to_san(g
, b
)) == NULL
)
1647 sp
.row
= sp
.col
= sp
.icon
= 0;
1649 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
1650 pgn_history_add(g
, p
);
1652 SET_FLAG(g
->flags
, GF_MODIFIED
);
1657 send_to_engine(g
, "%s\n", p
);
1661 static void update_clock(int n
, int *h
, int *m
, int *s
)
1664 *m
= (n
% 3600) / 60;
1665 *s
= (n
% 3600) % 60;
1670 void update_status_window(GAME g
)
1674 char tmp
[15], *engine
, *mode
;
1680 struct userdata_s
*d
= g
.data
;
1682 getmaxyx(statusw
, maxy
, maxx
);
1690 if (TEST_FLAG(g
.flags
, GF_DELETE
)) {
1696 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1706 if (TEST_FLAG(g
.flags
, GF_MODIFIED
)) {
1721 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1722 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1723 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1725 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1729 mode
= MODE_HISTORY_STR
;
1732 mode
= MODE_EDIT_STR
;
1735 mode
= MODE_PLAY_STR
;
1742 snprintf(buf
, len
- 1, "%*s %s", 7, STATUS_MODE_STR
, mode
);
1744 if (g
.mode
== MODE_PLAY
) {
1745 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1746 strncat(buf
, " (human/human)", len
- 1);
1747 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1748 strncat(buf
, " (engine/engine)", len
- 1);
1750 strncat(buf
, " (human/engine)", len
- 1);
1753 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1756 switch (d
->engine
->status
) {
1757 case ENGINE_THINKING
:
1758 engine
= ENGINE_PONDER_STR
;
1761 engine
= ENGINE_READY_STR
;
1763 case ENGINE_INITIALIZING
:
1764 engine
= ENGINE_INITIALIZING_STR
;
1766 case ENGINE_OFFLINE
:
1767 engine
= ENGINE_OFFLINE_STR
;
1775 engine
= ENGINE_OFFLINE_STR
;
1777 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1778 wattron(statusw
, CP_STATUS_ENGINE
);
1779 mvwaddstr(statusw
, 5, 9, engine
);
1780 wattroff(statusw
, CP_STATUS_ENGINE
);
1782 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1783 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1785 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1786 tmp
[0] = toupper(tmp
[0]);
1787 update_clock(g
.moveclock
, &h
, &m
, &s
);
1788 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1789 mvwprintw(statusw
, 7, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1791 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1792 tmp
[0] = toupper(tmp
[0]);
1793 update_clock(g
.moveclock
, &h
, &m
, &s
);
1794 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1795 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1798 for (i
= 1; i
< maxx
- 4; i
++)
1799 mvwprintw(statusw
, maxy
- 2, i
, " ");
1802 status
.notify
= strdup(GAME_HELP_PROMPT
);
1804 wattron(statusw
, CP_STATUS_NOTIFY
);
1805 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1807 wattroff(statusw
, CP_STATUS_NOTIFY
);
1810 void update_history_window(GAME g
)
1812 char buf
[HISTORY_WIDTH
- 1];
1815 int t
= pgn_history_total(g
.hp
);
1817 n
= (g
.hindex
+ 1) / 2;
1820 total
= (t
+ 1) / 2;
1825 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1826 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
1828 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1830 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1831 HISTORY_WIDTH
- 13, buf
);
1833 h
= pgn_history_by_n(g
.hp
, g
.hindex
);
1834 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1837 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1838 strncat(buf
, " (v", sizeof(buf
));
1843 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1848 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1853 strncat(buf
, ")", sizeof(buf
));
1855 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1856 HISTORY_WIDTH
- 13, buf
);
1858 h
= pgn_history_by_n(g
.hp
, game
[gindex
].hindex
- 1);
1859 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1862 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1863 strncat(buf
, " (V", sizeof(buf
));
1868 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1873 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1878 strncat(buf
, ")", sizeof(buf
));
1880 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1881 HISTORY_WIDTH
- 13, buf
);
1884 void update_tag_window(TAG
**t
)
1887 int w
= TAG_WIDTH
- 10;
1889 for (i
= 0; i
< 7; i
++)
1890 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
]->name
, w
, t
[i
]->value
);
1893 void draw_prompt(WINDOW
*win
, int y
, int width
, const char *str
, chtype attr
)
1899 for (i
= 1; i
< width
- 1; i
++)
1900 mvwaddch(win
, y
, i
, ' ');
1902 mvwprintw(win
, y
, CENTERX(width
, str
), "%s", str
);
1903 wattroff(win
, attr
);
1906 void draw_window_title(WINDOW
*win
, const char *title
, int width
, chtype attr
,
1914 for (i
= 1; i
< width
- 1; i
++)
1915 mvwaddch(win
, 1, i
, ' ');
1917 mvwprintw(win
, 1, CENTERX(width
, title
), "%s", title
);
1918 wattroff(win
, attr
);
1921 wattron(win
, battr
);
1922 box(win
, ACS_VLINE
, ACS_HLINE
);
1923 wattroff(win
, battr
);
1932 void update_all(GAME g
)
1934 update_status_window(g
);
1935 update_history_window(g
);
1936 update_tag_window(g
.tag
);
1939 static void game_next_prev(GAME g
, int n
, int count
)
1945 if (gindex
+ count
> gtotal
- 1) {
1947 gindex
= gtotal
- 1;
1955 if (gindex
- count
< 0) {
1959 gindex
= gtotal
- 1;
1966 static void delete_game(int which
)
1972 for (i
= 0; i
< gtotal
; i
++) {
1973 if (i
== which
|| TEST_FLAG(game
[i
].flags
, GF_DELETE
)) {
1978 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
1979 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
1980 g
[gi
].tag
= game
[i
].tag
;
1981 g
[gi
].history
= game
[i
].history
;
1982 g
[gi
].hp
= game
[i
].hp
;
1990 if (which
+ 1 >= gtotal
)
1991 gindex
= gtotal
- 1;
1996 gindex
= gtotal
- 1;
1998 game
[gindex
].hp
= game
[gindex
].history
;
2001 static int find_move_exp(GAME g
, const char *str
, int init
, int which
,
2007 static int firstrun
= 1;
2016 if ((ret
= regcomp(&r
, str
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2017 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2018 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2025 incr
= (which
== 0) ? -1 : 1;
2027 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
2028 if (i
== g
.hindex
- 1)
2031 if (i
>= pgn_history_total(g
.hp
))
2034 i
= pgn_history_total(g
.hp
) - 1;
2037 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
2040 if (count
== ++found
) {
2045 if (ret
!= REG_NOMATCH
) {
2046 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2047 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
2056 static int toggle_delete_flag(int n
)
2060 TOGGLE_FLAG(game
[n
].flags
, GF_DELETE
);
2062 for (i
= x
= 0; i
< gtotal
; i
++) {
2063 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2068 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2069 CLEAR_FLAG(game
[n
].flags
, GF_DELETE
);
2076 static void edit_save_tags(GAME
*g
)
2080 if ((t
= edit_tags(*g
, g
->b
, 1)) == NULL
)
2083 pgn_tag_free(g
->tag
);
2085 SET_FLAG(g
->flags
, GF_MODIFIED
);
2086 pgn_tag_sort(g
->tag
);
2089 static int find_game_exp(char *str
, int which
, int count
)
2091 char *nstr
= NULL
, *exp
= NULL
;
2095 char buf
[255], *tmp
;
2098 int incr
= (which
== 0) ? -(1) : 1;
2100 strncpy(buf
, str
, sizeof(buf
));
2103 if (strstr(tmp
, ":") != NULL
) {
2104 nstr
= strsep(&tmp
, ":");
2106 if ((ret
= regcomp(&nexp
, nstr
,
2107 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
2108 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
2109 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2120 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2121 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
2122 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2129 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
2140 for (t
= 0; game
[g
].tag
[t
]; t
++) {
2142 if (regexec(&nexp
, game
[g
].tag
[t
]->name
, 0, 0, 0) == 0) {
2143 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2144 if (count
== ++found
) {
2152 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2153 if (count
== ++found
) {
2175 * Updates the notification line in the status window then refreshes the
2178 void update_status_notify(GAME g
, char *fmt
, ...)
2181 #ifdef HAVE_VASPRINTF
2188 if (status
.notify
) {
2189 free(status
.notify
);
2190 status
.notify
= NULL
;
2192 if (curses_initialized
)
2193 update_status_window(g
);
2200 #ifdef HAVE_VASPRINTF
2201 vasprintf(&line
, fmt
, ap
);
2203 vsnprintf(line
, sizeof(line
), fmt
, ap
);
2208 free(status
.notify
);
2210 status
.notify
= strdup(line
);
2212 #ifdef HAVE_VASPRINTF
2215 if (curses_initialized
)
2216 update_status_window(g
);
2219 static void switch_side(GAME
*g
)
2221 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
2224 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
2228 if (g
->hp
[g
->hindex
]->rav
== NULL
)
2231 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
2232 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
2233 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
2234 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
2235 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
2236 g
->hp
= g
->hp
[g
->hindex
]->rav
;
2242 if (g
->ravlevel
- 1 < 0)
2247 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
2248 free(g
->rav
[g
->ravlevel
].fen
);
2249 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
2250 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
2251 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
2255 static void draw_window_decor()
2257 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
2258 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
2259 wbkgd(boardw
, CP_BOARD_WINDOW
);
2260 wbkgd(statusw
, CP_STATUS_WINDOW
);
2261 draw_window_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
2262 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
2263 wbkgd(tagw
, CP_TAG_WINDOW
);
2264 draw_window_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
2266 wbkgd(historyw
, CP_HISTORY_WINDOW
);
2267 draw_window_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
2268 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
2271 static void do_window_resize()
2273 if (LINES
< 24 || COLS
< 80)
2276 resizeterm(LINES
, COLS
);
2277 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
2278 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
2279 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
2280 wmove(historyw
, 0, 0);
2281 wclrtobot(historyw
);
2284 wmove(statusw
, 0, 0);
2286 draw_window_decor();
2287 update_all(game
[gindex
]);
2290 static void historymode_keys(chtype
);
2291 static int playmode_keys(chtype c
)
2293 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2294 int editmode
= (game
[gindex
].mode
== MODE_EDIT
) ? 1 : 0;
2298 struct userdata_s
*d
= game
[gindex
].data
;
2302 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
2304 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2305 pgn_history_total(game
[gindex
].hp
)) {
2306 pgn_tag_add(&game
[gindex
].tag
, "FEN",
2307 pgn_game_to_fen(game
[gindex
], game
[gindex
].b
));
2308 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2310 if (start_chess_engine(&game
[gindex
]) <= 0) {
2311 send_to_engine(&game
[gindex
], "setboard %s\n",
2312 game
[gindex
].tag
[x
]->value
);
2313 d
->engine
->status
= ENGINE_READY
;
2317 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2318 update_all(game
[gindex
]);
2324 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2325 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
2326 update_all(game
[gindex
]);
2332 if (d
->engine
->status
== ENGINE_OFFLINE
)
2335 x
= d
->engine
->status
;
2337 if ((tmp
= get_input_str_clear(ENGINE_CMD_TITLE
, NULL
)) != NULL
)
2338 send_to_engine(&game
[gindex
], "%s\n", tmp
);
2339 d
->engine
->status
= x
;
2343 pushkey
= keycount
= 0;
2344 update_status_notify(game
[gindex
], NULL
);
2346 if (!editmode
&& !TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2347 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
)) {
2359 p
= game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
;
2360 game
[gindex
].b
[ROWTOBOARD(sp
.destrow
)][COLTOBOARD(sp
.destcol
)].icon
= p
;
2361 game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
=
2362 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2363 sp
.icon
= sp
.row
= sp
.col
= 0;
2367 if (move_to_engine(&game
[gindex
], game
[gindex
].b
)) {
2368 if (config
.validmoves
)
2369 pgn_reset_valid_moves(game
[gindex
].b
);
2371 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
)) {
2372 CLEAR_FLAG(game
[gindex
].flags
, GF_GAMEOVER
);
2373 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2379 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2380 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
2381 if (start_chess_engine(&game
[gindex
])) {
2386 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2387 w
= pgn_tag_find(game
[gindex
].tag
, "SetUp");
2389 if ((w
>= 0 && x
>= 0 && atoi(game
[gindex
].tag
[w
]->value
) == 1)
2390 || (x
>= 0 && w
== -1)) {
2391 send_to_engine(&game
[gindex
], "setboard %s\n",
2392 game
[gindex
].tag
[x
]->value
);
2393 d
->engine
->status
= ENGINE_READY
;
2397 if (sp
.icon
|| (!editmode
&& d
->engine
&&
2398 d
->engine
->status
== ENGINE_THINKING
)) {
2403 sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(c_row
),
2404 COLTOMATRIX(c_col
)+1) & A_CHARTEXT
;
2406 if (sp
.icon
== ' ') {
2411 if (!editmode
&& ((islower(sp
.icon
) && game
[gindex
].turn
!= BLACK
)
2412 || (isupper(sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
2413 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
2421 if (!editmode
&& config
.validmoves
)
2422 pgn_get_valid_moves(&game
[gindex
], game
[gindex
].b
, sp
.row
,
2428 send_to_engine(&game
[gindex
], "\nswitch\n");
2429 switch_side(&game
[gindex
]);
2430 update_status_window(game
[gindex
]);
2433 if (!pgn_history_total(game
[gindex
].hp
))
2436 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2437 send_to_engine(&game
[gindex
], "remove\n");
2438 d
->engine
->status
= ENGINE_READY
;
2441 game
[gindex
].hindex
-= 2;
2442 pgn_history_free(game
[gindex
].hp
, game
[gindex
].hindex
);
2443 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
);
2444 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
2445 game
[gindex
].hindex
);
2446 update_history_window(game
[gindex
]);
2449 historymode_keys(c
);
2452 board_details
= (board_details
) ? 0 : 1;
2455 paused
= (paused
) ? 0 : 1;
2458 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
2459 start_chess_engine(&game
[gindex
]);
2461 send_to_engine(&game
[gindex
], "go\n");
2470 static void editmode_keys(chtype c
)
2480 game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2482 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2484 sp
.icon
= sp
.row
= sp
.col
= 0;
2487 pgn_switch_turn(&game
[gindex
]);
2488 switch_side(&game
[gindex
]);
2489 update_all(game
[gindex
]);
2492 castling_state(&game
[gindex
], game
[gindex
].b
, ROWTOBOARD(c_row
),
2494 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
, 1);
2497 c
= message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, "%s",
2500 if (pgn_piece_to_int(c
) == -1)
2503 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
= c
;
2506 if (c_row
== 6 || c_row
== 3) {
2507 pgn_reset_enpassant(game
[gindex
].b
);
2508 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].enpassant
= 1;
2516 static void historymode_keys(chtype c
)
2520 static char moveexp
[255] = {0};
2524 movestep
= (movestep
== 1) ? 2 : 1;
2525 update_history_window(game
[gindex
]);
2528 pgn_history_next(&game
[gindex
], game
[gindex
].b
, (keycount
> 0) ?
2529 config
.jumpcount
* keycount
* movestep
:
2530 config
.jumpcount
* movestep
);
2531 update_all(game
[gindex
]);
2534 pgn_history_prev(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2535 config
.jumpcount
* keycount
* movestep
:
2536 config
.jumpcount
* movestep
);
2537 update_all(game
[gindex
]);
2540 pgn_history_prev(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2541 keycount
* movestep
: movestep
);
2542 update_all(game
[gindex
]);
2545 pgn_history_next(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2546 keycount
* movestep
: movestep
);
2547 update_all(game
[gindex
]);
2550 n
= game
[gindex
].hindex
;
2552 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
2558 snprintf(buf
, COLS
- 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE
,
2559 game
[gindex
].hp
[n
]->move
);
2561 tmp
= get_input(buf
, game
[gindex
].hp
[n
]->comment
, 0, 0, NAG_PROMPT
,
2562 history_edit_nag
, (void *)game
[gindex
].hp
[n
], CTRL('T'),
2566 if (!tmp
&& (!game
[gindex
].hp
[n
]->comment
||
2567 !*game
[gindex
].hp
[n
]->comment
))
2569 else if (tmp
&& game
[gindex
].hp
[n
]->comment
) {
2570 if (strcmp(tmp
, game
[gindex
].hp
[n
]->comment
) == 0)
2574 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
2575 game
[gindex
].hp
[n
]->comment
= Realloc(game
[gindex
].hp
[n
]->comment
,
2577 strncpy(game
[gindex
].hp
[n
]->comment
, (tmp
) ? tmp
: "", len
);
2578 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2579 update_all(game
[gindex
]);
2584 if (pgn_history_total(game
[gindex
].hp
) < 2)
2589 if (!*moveexp
|| c
== '/') {
2590 if ((tmp
= get_input(FIND_REGEXP
, moveexp
, 1, 1, NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2593 strncpy(moveexp
, tmp
, sizeof(moveexp
));
2597 if ((n
= find_move_exp(game
[gindex
], moveexp
, n
,
2598 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1))
2602 game
[gindex
].hindex
= n
;
2603 pgn_board_update(&game
[gindex
], game
[gindex
].b
, game
[gindex
].hindex
);
2604 update_all(game
[gindex
]);
2607 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
]);
2610 if (game
[gindex
].hindex
- 1 >= 0)
2611 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2615 rav_next_prev(&game
[gindex
], game
[gindex
].b
, (c
== '-') ? 0 : 1);
2616 update_all(game
[gindex
]);
2619 if (pgn_history_total(game
[gindex
].hp
) < 2)
2622 /* FIXME field validation
2623 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2624 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2625 game[gindex].htotal)) == NULL)
2630 if ((tmp
= get_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1,
2631 NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2634 if (!isinteger(tmp
))
2642 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
2645 game
[gindex
].hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2646 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
2647 game
[gindex
].hindex
);
2648 update_all(game
[gindex
]);
2655 static void cleanup_all_games()
2659 for (i
= 0; i
< gtotal
; i
++) {
2660 struct userdata_s
*d
;
2663 stop_engine(&game
[i
]);
2666 game
[i
].data
= NULL
;
2671 void update_loading_window()
2674 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
2675 loadingp
= new_panel(loadingw
);
2676 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
2679 wmove(loadingw
, 0, 0);
2680 wclrtobot(loadingw
);
2681 wattron(loadingw
, CP_MESSAGE_BORDER
);
2682 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
2683 wattroff(loadingw
, CP_MESSAGE_BORDER
);
2684 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
2685 11 + strlen(itoa(gtotal
))), "Loading... %i", gtotal
);
2690 void init_userdata()
2694 for (i
= 0; i
< gtotal
; i
++) {
2695 struct userdata_s
*d
= NULL
;
2697 d
= Calloc(1, sizeof(struct userdata_s
));
2702 // Global and other keys.
2703 static int globalkeys(chtype c
)
2705 static char gameexp
[255] = {0};
2709 char tfile
[FILENAME_MAX
];
2710 struct userdata_s
*d
= game
[gindex
].data
;
2714 cmessage("ABOUT", ANYKEY
, "%s\n%s with %i colors and %i "
2715 "color pairs\nCopyright 2002-2006 %s", PACKAGE_STRING
,
2716 curses_version(), COLORS
, COLOR_PAIRS
, PACKAGE_BUGREPORT
);
2719 if (game
[gindex
].mode
!= MODE_HISTORY
) {
2720 if (!pgn_history_total(game
[gindex
].hp
) ||
2721 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2724 game
[gindex
].mode
= MODE_HISTORY
;
2725 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2726 update_all(game
[gindex
]);
2731 if (TEST_FLAG(game
[gindex
].flags
, GF_BLACK_OPENING
)) {
2732 cmessage(NULL
, ANYKEY
, "%s", E_RESUME_BLACK
);
2736 // FIXME Resuming from previous history could append to a RAV.
2737 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
2739 if ((c
= message(NULL
, YESNO
, "%s",
2740 GAME_RESUME_HISTORY_TEXT
)) != 'y')
2745 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
2749 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2750 d
->engine
->status
== ENGINE_OFFLINE
)) {
2751 if (start_chess_engine(&game
[gindex
]) < 0)
2759 oldhistorytotal
= pgn_history_total(game
[gindex
].hp
);
2760 game
[gindex
].mode
= MODE_PLAY
;
2761 update_all(game
[gindex
]);
2765 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
2769 markend
= delete_count
;
2774 if (game
[gindex
].mode
!= MODE_EDIT
) {
2775 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2777 update_all(game
[gindex
]);
2778 update_tag_window(game
[gindex
].tag
);
2780 // Not sure whether to keep these.
2781 case '!': c_row
= 1; return 1;
2782 case '@': c_row
= 2; return 1;
2783 case '#': c_row
= 3; return 1;
2784 case '$': c_row
= 4; return 1;
2785 case '%': c_row
= 5; return 1;
2786 case '^': c_row
= 6; return 1;
2787 case '&': c_row
= 7; return 1;
2788 case '*': c_row
= 8; return 1;
2789 case 'A': c_col
= 1; return 1;
2790 case 'B': c_col
= 2; return 1;
2791 case 'C': c_col
= 3; return 1;
2792 case 'D': c_col
= 4; return 1;
2793 case 'E': c_col
= 5; return 1;
2794 case 'F': c_col
= 6; return 1;
2795 case 'G': c_col
= 7; return 1;
2796 case 'H': c_col
= 8; return 1;
2803 if (!*gameexp
|| c
== '?') {
2804 if ((tmp
= get_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
,
2805 1, 1, GAME_FIND_EXPRESSION_PROMPT
, NULL
,
2806 NULL
, 0, -1)) == NULL
)
2809 strncpy(gameexp
, tmp
, sizeof(gameexp
));
2812 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1, (keycount
)
2819 if (pgn_history_total(game
[gindex
].hp
))
2820 game
[gindex
].mode
= MODE_HISTORY
;
2822 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2823 update_all(game
[gindex
]);
2824 update_tag_window(game
[gindex
].tag
);
2830 /* FIXME field validation
2831 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2832 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2838 if ((tmp
= get_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
,
2839 NULL
, NULL
, 0, -1)) == NULL
)
2842 if (!isinteger(tmp
))
2850 if (--i
> gtotal
- 1 || i
< 0)
2854 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2855 update_all(game
[gindex
]);
2856 update_tag_window(game
[gindex
].tag
);
2864 if (keycount
&& !delete_count
) {
2866 delete_count
= keycount
;
2867 update_status_notify(game
[gindex
], "%s (delete)",
2872 if (markstart
>= 0 && markend
>= 0) {
2873 if (markstart
> markend
) {
2875 markstart
= markend
;
2879 for (i
= markstart
; i
<= markend
; i
++) {
2880 if (toggle_delete_flag(i
))
2885 if (toggle_delete_flag(gindex
))
2889 markstart
= markend
= -1;
2890 update_status_window(game
[gindex
]);
2894 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2900 for (i
= n
= 0; i
< gtotal
; i
++) {
2901 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2906 tmp
= GAME_DELETE_GAME_TEXT
;
2909 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2913 tmp
= GAME_DELETE_ALL_TEXT
;
2916 if (config
.deleteprompt
) {
2917 if ((c
= cmessage(NULL
, YESNO
, "%s", tmp
)) != 'y')
2921 delete_game((!n
) ? gindex
: -1);
2923 if (pgn_history_total(game
[gindex
].hp
))
2924 game
[gindex
].mode
= MODE_HISTORY
;
2926 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2927 update_all(game
[gindex
]);
2928 update_tag_window(game
[gindex
].tag
);
2931 edit_save_tags(&game
[gindex
]);
2932 update_all(game
[gindex
]);
2933 update_tag_window(game
[gindex
].tag
);
2936 edit_tags(game
[gindex
], game
[gindex
].b
, 0);
2939 if ((tmp
= get_input(GAME_LOAD_TITLE
, NULL
, 1, 1,
2940 BROWSER_PROMPT
, browse_directory
, NULL
, '\t',
2944 if ((tmp
= word_expand(tmp
)) == NULL
)
2947 if ((fp
= pgn_open(tmp
)) == NULL
) {
2948 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
2955 del_panel(loadingp
);
2960 strncpy(loadfile
, tmp
, sizeof(loadfile
));
2962 if (pgn_history_total(game
[gindex
].hp
))
2963 game
[gindex
].mode
= MODE_HISTORY
;
2965 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2966 update_all(game
[gindex
]);
2967 update_tag_window(game
[gindex
].tag
);
2974 n
= message(NULL
, GAME_SAVE_MULTI_PROMPT
, "%s",
2975 GAME_SAVE_MULTI_TEXT
);
2982 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2987 if ((tmp
= get_input(GAME_SAVE_TITLE
, loadfile
, 1, 1,
2988 BROWSER_PROMPT
, browse_directory
, NULL
,
2989 '\t', -1)) == NULL
) {
2990 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2994 if ((tmp
= word_expand(tmp
)) == NULL
)
2997 if (pgn_is_compressed(tmp
)) {
2998 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3002 if ((p
= strchr(tmp
, '.')) != NULL
) {
3003 if (strcmp(p
, ".pgn") != 0) {
3004 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3009 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3014 if (save_pgn(tmp
, 0, i
)) {
3015 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
3019 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
3020 update_all(game
[gindex
]);
3025 switch (game
[gindex
].mode
) {
3027 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3030 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3033 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3039 while (c
== KEY_F(1)) {
3040 c
= help(GAME_HELP_INDEX_TITLE
, GAME_HELP_INDEX_PROMPT
,
3045 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3048 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3051 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3054 c
= help(GAME_HELP_GAME_TITLE
, ANYKEY
, gamehelp
);
3065 if (cmessage(NULL
, YESNO
, "%s", GAME_NEW_PROMPT
) != 'y')
3071 add_custom_tags(&game
[gindex
].tag
);
3072 d
= Calloc(1, sizeof(struct userdata_s
));
3073 game
[gindex
].data
= d
;
3076 cleanup_all_games();
3078 add_custom_tags(&game
[gindex
].tag
);
3079 pgn_board_init(game
[gindex
].b
);
3080 d
= Calloc(1, sizeof(struct userdata_s
));
3081 game
[gindex
].data
= d
;
3084 game
[gindex
].mode
= MODE_PLAY
;
3085 c_row
= (game
[gindex
].side
== WHITE
) ? 2 : 7;
3087 update_status_notify(game
[gindex
], NULL
);
3088 update_all(game
[gindex
]);
3089 update_tag_window(game
[gindex
].tag
);
3093 keypad(boardw
, TRUE
);
3097 sp
.icon
= sp
.row
= sp
.col
= 0;
3098 markend
= markstart
= 0;
3102 update_status_notify(game
[gindex
], NULL
);
3105 if (config
.validmoves
)
3106 pgn_reset_valid_moves(game
[gindex
].b
);
3113 keycount
= keycount
* 10 + n
;
3117 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3120 if (game
[gindex
].mode
== MODE_HISTORY
)
3135 if (game
[gindex
].mode
== MODE_HISTORY
)
3141 update_status_notify(game
[gindex
], NULL
);
3151 if (game
[gindex
].mode
== MODE_HISTORY
)
3166 if (game
[gindex
].mode
== MODE_HISTORY
)
3181 if (game
[gindex
].mode
!= MODE_EDIT
&& game
[gindex
].mode
!=
3185 // Don't edit a running game (for now).
3186 if (pgn_history_total(game
[gindex
].hp
))
3189 if (game
[gindex
].mode
!= MODE_EDIT
) {
3190 pgn_board_init_fen(&game
[gindex
], game
[gindex
].b
, NULL
);
3192 game
[gindex
].mode
= MODE_EDIT
;
3193 update_all(game
[gindex
]);
3198 pgn_tag_add(&game
[gindex
].tag
, "FEN",
3199 pgn_game_to_fen(game
[gindex
], game
[gindex
].b
));
3200 pgn_tag_add(&game
[gindex
].tag
, "SetUp", "1");
3201 pgn_tag_sort(game
[gindex
].tag
);
3202 game
[gindex
].mode
= MODE_PLAY
;
3203 update_all(game
[gindex
]);
3213 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(game
[gindex
].b
));
3226 int error_recover
= 0;
3228 c_row
= 2, c_col
= 5;
3229 gindex
= gtotal
- 1;
3231 if (pgn_history_total(game
[gindex
].hp
))
3232 game
[gindex
].mode
= MODE_HISTORY
;
3234 game
[gindex
].mode
= MODE_PLAY
;
3236 if (game
[gindex
].mode
== MODE_HISTORY
) {
3237 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
3238 pgn_history_total(game
[gindex
].hp
));
3241 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
3243 paused
= 1; //FIXME clock
3245 update_all(game
[gindex
]);
3246 update_tag_window(game
[gindex
].tag
);
3247 wtimeout(boardw
, 70);
3252 char fdbuf
[8192] = {0};
3254 struct timeval tv
= {0, 0};
3256 struct userdata_s
*d
= NULL
;
3260 for (i
= 0; i
< gtotal
; i
++) {
3264 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
3265 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
3266 n
= d
->engine
->fd
[ENGINE_IN_FD
];
3268 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
3274 if ((n
= select(n
+ 1, &rfds
, NULL
, NULL
, &tv
)) > 0) {
3275 for (i
= 0; i
< gtotal
; i
++) {
3279 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3280 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3284 if (errno
!= EAGAIN
) {
3285 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3287 waitpid(d
->engine
->pid
, &n
, 0);
3295 parse_engine_output(&game
[i
], fdbuf
);
3296 update_all(game
[gindex
]);
3305 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3312 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
) && game
[gindex
].mode
3314 game
[gindex
].mode
= MODE_HISTORY
;
3315 update_all(game
[gindex
]);
3319 draw_board(&game
[gindex
], board_details
);
3320 wmove(boardw
, ROWTOMATRIX(c_row
), COLTOMATRIX(c_col
));
3330 if ((c
= wgetch(boardw
)) == ERR
)
3334 if (!keycount
&& status
.notify
)
3335 update_status_notify(game
[gindex
], NULL
);
3338 if ((n
= globalkeys(c
)) == 1) {
3345 switch (game
[gindex
].mode
) {
3350 if (playmode_keys(c
))
3354 historymode_keys(c
);
3364 void usage(const char *pn
, int ret
)
3366 fprintf((ret
) ? stderr
: stdout
, "%s",
3367 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3368 " -p Load PGN file.\n"
3369 " -V Validate a game file.\n"
3370 " -S Validate and output a PGN formatted game.\n"
3371 " -R Like -S but write a reduced PGN formatted game.\n"
3372 " -t Also write custom PGN tags from config file.\n"
3373 " -E Stop processing on file parsing error (overrides config).\n"
3374 " -v Version information.\n"
3375 " -h This help text.\n");
3382 cleanup_all_games();
3385 del_panel(historyp
);
3395 void catch_signal(int which
)
3400 if (which
== SIGPIPE
&& quit
)
3403 if (which
== SIGPIPE
)
3404 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3414 keypad(boardw
, TRUE
);
3420 if (curses_initialized
) {
3421 update_loading_window(game
[gindex
]);
3425 fprintf(stderr
, "Loading... %i\r", gtotal
);
3433 static void set_defaults()
3436 set_config_defaults();
3439 int main(int argc
, char *argv
[])
3443 char buf
[FILENAME_MAX
];
3444 char datadir
[FILENAME_MAX
];
3445 int ret
= EXIT_SUCCESS
;
3446 int validate_only
= 0, validate_and_write
= 0, reduced
= 0;
3447 int write_custom_tags
= 0;
3451 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3452 err(EXIT_FAILURE
, "getpwuid()");
3454 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3455 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3456 config
.ccfile
= strdup(buf
);
3457 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3458 config
.nagfile
= strdup(buf
);
3459 snprintf(buf
, sizeof(buf
), "%s/agony.data", datadir
);
3460 config
.agonyfile
= strdup(buf
);
3461 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3462 config
.configfile
= strdup(buf
);
3463 snprintf(buf
, sizeof(buf
), "%s/fifo", datadir
);
3464 config
.fifo
= strdup(buf
);
3466 if (stat(datadir
, &st
) == -1) {
3467 if (errno
== ENOENT
) {
3468 if (mkdir(datadir
, 0755) == -1)
3469 err(EXIT_FAILURE
, "%s", datadir
);
3472 err(EXIT_FAILURE
, "%s", datadir
);
3477 if (!S_ISDIR(st
.st_mode
))
3478 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3480 if (access(config
.fifo
, R_OK
) == -1 && errno
== ENOENT
) {
3481 if (mkfifo(config
.fifo
, 0600) == -1)
3482 err(EXIT_FAILURE
, "%s", config
.fifo
);
3487 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3490 write_custom_tags
= 1;
3493 config
.stoponerror
= 1;
3498 validate_and_write
= 1;
3503 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3507 filetype
= PGN_FILE
;
3508 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3512 usage(argv
[0], EXIT_SUCCESS
);
3516 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3517 usage(argv
[0], EXIT_FAILURE
);
3519 if (access(config
.configfile
, R_OK
) == 0)
3520 parse_rcfile(config
.configfile
);
3522 signal(SIGPIPE
, catch_signal
);
3523 signal(SIGCONT
, catch_signal
);
3524 signal(SIGSTOP
, catch_signal
);
3525 signal(SIGINT
, catch_signal
);
3526 signal(SIGUSR1
, catch_signal
);
3532 if ((fp
= pgn_open(loadfile
)) == NULL
)
3533 err(EXIT_FAILURE
, "%s", loadfile
);
3535 ret
= pgn_parse(fp
);
3538 //ret = parse_fen_file(loadfile);
3540 case EPD_FILE
: // Not implemented.
3543 // No file specified. Empty game.
3544 ret
= pgn_parse(NULL
);
3545 add_custom_tags(&game
[gindex
].tag
);
3549 if (validate_only
|| validate_and_write
) {
3550 if (validate_and_write
) {
3551 for (i
= 0; i
< gtotal
; i
++) {
3552 if (write_custom_tags
)
3553 add_custom_tags(&game
[i
].tag
);
3555 pgn_write(stdout
, game
[i
]);
3567 if (initscr() == NULL
)
3568 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3570 curses_initialized
= 1;
3572 if (LINES
< 24 || COLS
< 80) {
3574 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3577 if (has_colors() == TRUE
&& start_color() == OK
)
3580 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3581 boardp
= new_panel(boardw
);
3582 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3583 COLS
- HISTORY_WIDTH
);
3584 historyp
= new_panel(historyw
);
3585 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3586 statusp
= new_panel(statusw
);
3587 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3588 tagp
= new_panel(tagw
);
3589 keypad(boardw
, TRUE
);
3590 // leaveok(boardw, TRUE);
3591 leaveok(tagw
, TRUE
);
3592 leaveok(statusw
, TRUE
);
3593 leaveok(historyw
, TRUE
);
3597 draw_window_decor();