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>
71 static char *str_etc(const char *str
, int maxlen
, int rev
)
73 int len
= strlen(str
);
74 static char buf
[80], *p
= buf
;
77 strncpy(buf
, str
, sizeof(buf
));
86 for (i
= 0; i
< maxlen
+ 3; i
++)
87 *p
++ = buf
[(len
- maxlen
) + i
+ 3];
102 void update_cursor(GAME g
, int idx
)
106 int t
= pgn_history_total(g
.hp
);
107 struct userdata_s
*d
= g
.data
;
110 * If not deincremented then r and c would be the next move.
114 if (idx
> t
|| idx
< 0 || !t
|| !g
.hp
[idx
]->move
) {
115 d
->c_row
= 2, d
->c_col
= 5;
128 d
->c_row
= (g
.turn
== WHITE
) ? 1 : 8;
137 d
->c_row
= ROWTOINT(*p
--);
138 d
->c_col
= COLTOINT(*p
);
141 static int init_nag()
147 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
148 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.nagfile
, strerror(errno
));
152 nags
= Realloc(nags
, 2 * sizeof(char *));
157 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
158 nags
= Realloc(nags
, (i
+ 2) * sizeof(char *));
159 nags
[i
++] = strdup(line
);
166 void set_menu_vars(int c
, int rows
, int items
, int *item
, int *top
)
168 int selected
= *item
;
173 selected
= toppos
= 0;
177 toppos
= items
- rows
+ 1;
180 if (selected
- 1 < 0) {
183 toppos
= selected
- rows
+ 1;
188 if (toppos
&& selected
<= toppos
)
193 if (selected
+ 1 > items
)
194 selected
= toppos
= 0;
198 if (selected
- toppos
>= rows
)
208 toppos
= selected
- rows
+ 1;
216 if (selected
> items
)
219 toppos
= selected
- rows
+ 1;
225 toppos
= selected
- rows
+ 1;
236 int test_nag_selected(unsigned char nag
[], int s
)
240 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
248 char *history_edit_nag(void *arg
)
255 HISTORY
*anno
= (HISTORY
*)arg
;
260 unsigned char nag
[MAX_PGN_NAG
] = {0};
261 char menubuf
[64] = {0}, *mp
= menubuf
;
268 for (i
= 1, n
= 0; nags
[i
]; i
++) {
277 rows
= (total
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : total
+ 4;
279 win
= newwin(rows
, cols
, CALCPOSY(rows
), CALCPOSX(cols
));
280 panel
= new_panel(win
);
286 memcpy(&nag
, &anno
->nag
, sizeof(nag
));
288 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
299 draw_window_title(win
, NAG_EDIT_TITLE
, cols
, CP_INPUT_TITLE
,
302 for (i
= toppos
, c
= 2; i
< total
&& c
< rows
- 2; i
++, c
++) {
304 wattron(win
, CP_MENU_HIGHLIGHT
);
305 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
306 wattroff(win
, CP_MENU_HIGHLIGHT
);
310 if (test_nag_selected(nag
, i
) != -1) {
311 wattron(win
, CP_MENU_SELECTED
);
312 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
313 wattroff(win
, CP_MENU_SELECTED
);
317 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
320 snprintf(buf
, sizeof(buf
), "NAG %i of %i (%i of %i selected) %s",
321 selected
+ 1, total
, itemcount
, MAX_PGN_NAG
, NAG_EDIT_PROMPT
);
322 draw_prompt(win
, rows
- 2, cols
, buf
, CP_INPUT_PROMPT
);
332 help(NAG_EDIT_HELP
, ANYKEY
, naghelp
);
340 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
346 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
353 if ((found
= test_nag_selected(nag
, selected
)) != -1) {
358 if (itemcount
+ 1 > MAX_PGN_NAG
)
361 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
379 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
388 for (i
= 0; i
< total
; i
++) {
392 if (strncasecmp(menubuf
, nags
[i
], strlen(menubuf
)) == 0) {
403 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
409 memcpy(&anno
->nag
, &nag
, sizeof(nag
));
415 static void view_nag(void *arg
)
417 HISTORY
*h
= (HISTORY
*)arg
;
419 char line
[LINE_MAX
] = {0};
422 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
429 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
433 strncat(line
, nags
[h
->nag
[i
]], sizeof(line
));
434 strncat(line
, "\n", sizeof(line
));
437 line
[strlen(line
) - 1] = 0;
438 message(buf
, ANYKEY
, "%s", line
);
441 void view_annotation(HISTORY h
)
443 char buf
[strlen(h
.move
) + strlen(ANNOTATION_VIEW_TITLE
) + 4];
444 int nag
= 0, comment
= 0;
446 if (h
.comment
&& h
.comment
[0])
452 if (!nag
&& !comment
)
455 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
.move
);
458 show_message(buf
, (nag
) ? "Any other key to continue" : ANYKEY
,
459 (nag
) ? "Press 'n' to view NAG" : NULL
,
460 (nag
) ? view_nag
: NULL
, (nag
) ? (void *)&h
: NULL
,
461 (nag
) ? 'n' : 0, "%s", h
.comment
);
463 show_message(buf
, "Any other key to continue", "Press 'n' to view NAG",
464 view_nag
, (void *)&h
, 'n', "%s", "No annotations for this move");
467 static void cleanup(WINDOW
*win
, PANEL
*panel
, struct file_s
*files
)
472 for (i
= 0; files
[i
].name
; i
++) {
485 static int sort_files(const void *a
, const void *b
)
487 const struct file_s
*aa
= a
;
488 const struct file_s
*bb
= b
;
490 return strcmp(aa
->name
, bb
->name
);
493 char *file_browser(void *arg
)
495 char pattern
[FILENAME_MAX
];
496 static char path
[FILENAME_MAX
];
497 static char file
[FILENAME_MAX
];
500 int cursor
= curs_set(0);
501 char menubuf
[64] = {0}, *mp
= menubuf
;
504 if (config
.savedirectory
) {
505 if ((p
= word_expand(config
.savedirectory
)) == NULL
)
508 strncpy(path
, p
, sizeof(path
));
510 if (access(path
, R_OK
) == -1) {
511 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
512 getcwd(path
, sizeof(path
));
516 getcwd(path
, sizeof(path
));
521 * First find directories (including hidden) in the working directory.
522 * Then apply the config.pattern to regular files.
524 if ((p
= word_split_append(path
, '/', ".* *")) == NULL
)
527 strncpy(pattern
, p
, sizeof(pattern
));
536 int len
= strlen(path
);
539 struct file_s
*files
= NULL
;
545 if (wordexp(pattern
, &w
, x
) != 0) {
546 cmessage(ERROR
, ANYKEY
, "Error in pattern\n%s", pattern
);
550 for (i
= 0; i
< w
.we_wordc
; i
++) {
555 if (stat(w
.we_wordv
[i
], &st
) == -1)
558 if ((p
= strrchr(w
.we_wordv
[i
], '/')) != NULL
)
564 if (!S_ISDIR(st
.st_mode
))
567 if (p
[0] == '.' && p
[1] == 0)
571 if (S_ISDIR(st
.st_mode
))
576 files
= Realloc(files
, (n
+ 2) * sizeof(struct file_s
));
577 files
[n
].path
= strdup(w
.we_wordv
[i
]);
578 files
[n
].name
= Malloc(len
);
579 strncpy(files
[n
].name
, p
, len
);
581 if (S_ISDIR(st
.st_mode
))
582 files
[n
].name
[len
- 2] = '/';
584 tp
= localtime(&st
.st_mtime
);
585 strftime(tbuf
, sizeof(tbuf
), "%b %d %T", tp
);
586 snprintf(sbuf
, sizeof(sbuf
), "%9i %s", (int)st
.st_size
, tbuf
);
587 files
[n
].st
= strdup(sbuf
);
588 memset(&files
[++n
], '\0', sizeof(struct file_s
));
594 if ((p
= word_split_append(path
, '/', config
.pattern
)) == NULL
)
597 strncpy(pattern
, p
, sizeof(pattern
));
603 qsort(files
, n
, sizeof(struct file_s
), sort_files
);
605 for (i
= x
= nlen
= 0; i
< n
; i
++) {
606 if (strlen(files
[i
].name
) > nlen
)
607 nlen
= strlen(files
[i
].name
);
609 if (x
< nlen
+ strlen(files
[i
].st
))
610 x
= nlen
+ strlen(files
[i
].st
);
615 if (cols
< strlen(path
))
618 if (cols
< strlen(HELP_PROMPT
))
619 cols
= strlen(HELP_PROMPT
);
625 rows
= (n
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : n
+ 4;
627 win
= newwin(rows
, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
629 panel
= new_panel(win
);
630 draw_window_title(win
, path
, cols
, CP_INPUT_TITLE
, CP_INPUT_BORDER
);
631 draw_prompt(win
, rows
- 2, cols
, HELP_PROMPT
, CP_INPUT_PROMPT
);
640 for (i
= toppos
, c
= 2; i
< n
&& c
< rows
- 2; i
++, c
++) {
642 wattron(win
, CP_MENU_HIGHLIGHT
);
643 mvwprintw(win
, c
, 1, "%-*s %-*s", nlen
, files
[i
].name
,
644 cols
- nlen
- 2 - 1, files
[i
].st
);
645 wattroff(win
, CP_MENU_HIGHLIGHT
);
649 mvwprintw(win
, c
, 1, "%-*s %-*s", nlen
, files
[i
].name
,
650 cols
- nlen
- 2 - 1, files
[i
].st
);
663 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
671 cleanup(win
, panel
, files
);
676 help(BROWSER_HELP
, ANYKEY
, file_browser_help
);
679 strncpy(path
, "~", sizeof(path
));
680 cleanup(win
, panel
, files
);
684 if ((tmp
= get_input_str_clear(BROWSER_CHDIR_TITLE
, NULL
))
688 if (tmp
[strlen(tmp
) - 1] == '/')
689 tmp
[strlen(tmp
) - 1] = 0;
691 strncpy(path
, tmp
, sizeof(path
));
692 cleanup(win
, panel
, files
);
696 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
705 for (i
= 0; i
< n
; i
++) {
706 if (strncasecmp(menubuf
, files
[i
].name
,
707 strlen(menubuf
)) == 0) {
718 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
726 strncpy(file
, files
[selected
].path
, sizeof(file
));
727 cleanup(win
, panel
, files
);
729 if (stat(file
, &st
) == -1) {
730 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, strerror(errno
));
734 if (S_ISDIR(st
.st_mode
)) {
735 p
= file
+ strlen(file
) - 2;
737 if (strcmp(p
, "..") == 0) {
738 p
= file
+ strlen(file
) - 3;
741 if ((p
= strrchr(file
, '/')) != NULL
)
742 file
[strlen(file
) - strlen(p
)] = 0;
745 strncpy(path
, file
, sizeof(path
));
749 if (S_ISREG(st
.st_mode
))
752 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, E_NOTAREGFILE
);
757 return (*file
) ? file
: NULL
;
760 static int init_country_codes()
763 char line
[LINE_MAX
], *s
;
766 if ((fp
= fopen(config
.ccfile
, "r")) == NULL
) {
767 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.ccfile
, strerror(errno
));
771 while ((s
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
774 if ((tmp
= strsep(&s
, " ")) == NULL
)
783 ccodes
= Realloc(ccodes
, (cindex
+ 2) * sizeof(struct country_codes
));
784 strncpy(ccodes
[cindex
].code
, tmp
, sizeof(ccodes
[cindex
].code
));
785 strncpy(ccodes
[cindex
].country
, s
, sizeof(ccodes
[cindex
].country
));
789 memset(&ccodes
[cindex
], '\0', sizeof(struct country_codes
));
795 char *country_codes(void *arg
)
806 char menubuf
[64] = {0}, *mp
= menubuf
;
809 if (init_country_codes())
813 for (i
= n
= 0; ccodes
[i
].code
&& ccodes
[i
].code
[0]; i
++) {
814 n
= strlen(ccodes
[i
].code
) + strlen(ccodes
[i
].country
);
823 if (cols
< strlen(HELP_PROMPT
) + 21)
824 cols
= strlen(HELP_PROMPT
) + 21;
832 rows
= (i
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : i
+ 4;
833 win
= newwin(rows
, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
834 panel
= new_panel(win
);
848 draw_window_title(win
, CC_TITLE
, cols
, CP_INPUT_TITLE
, CP_INPUT_BORDER
);
850 for (i
= toppos
, c
= 2; i
< total
&& c
< rows
- 2; i
++, c
++) {
852 wattron(win
, CP_MENU_HIGHLIGHT
);
853 mvwprintw(win
, c
, 1, "%3s %s", ccodes
[i
].code
,
855 wattroff(win
, CP_MENU_HIGHLIGHT
);
859 mvwprintw(win
, c
, 1, "%3s %s", ccodes
[i
].code
, ccodes
[i
].country
);
862 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_ITEM_STR
,
863 selected
+ 1, N_OF_N_STR
, total
, HELP_PROMPT
);
864 draw_prompt(win
, rows
- 2, cols
, buf
, CP_INPUT_PROMPT
);
870 help(CC_KEY_HELP
, ANYKEY
, cc_help
);
878 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
883 tmp
= ccodes
[selected
].code
;
891 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
900 for (i
= 0; i
< total
; i
++) {
901 if (strncasecmp(menubuf
, ccodes
[i
].code
,
902 strlen(menubuf
)) == 0) {
913 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
924 static void add_custom_tags(TAG
***t
)
927 int total
= pgn_tag_total(config
.tag
);
932 for (i
= 0; i
< total
; i
++)
933 pgn_tag_add(t
, config
.tag
[i
]->name
, config
.tag
[i
]->value
);
938 TAG
**edit_tags(GAME g
, BOARD b
, int edit
)
947 char menubuf
[64] = {0}, *mp
= menubuf
;
949 /* Edit the backup copy, not the original in case the save fails. */
950 len
= pgn_tag_total(g
.tag
);
952 for (n
= 0; n
< len
; n
++)
953 pgn_tag_add(&data
, g
.tag
[n
]->name
, g
.tag
[n
]->value
);
955 data_index
= pgn_tag_total(data
);
966 data_index
= pgn_tag_total(data
);
968 for (i
= cols
= 0, n
= 4; i
< data_index
; i
++) {
969 n
= strlen(data
[i
]->name
);
975 n
+= strlen(data
[i
]->value
);
977 n
+= strlen(UNKNOWN
);
988 /* +14 for the extra prompt info. */
989 if (cols
< strlen(HELP_PROMPT
) + 14 + 2)
990 cols
= strlen(HELP_PROMPT
) + 14 + 2;
992 rows
= (data_index
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 :
995 win
= newwin(rows
, cols
, CALCPOSY(rows
), CALCPOSX(cols
));
996 panel
= new_panel(win
);
1001 wbkgd(win
, CP_MENU
);
1002 draw_window_title(win
, (edit
) ? TAG_EDIT_TITLE
: TAG_VIEW_TITLE
,
1003 cols
, (edit
) ? CP_INPUT_TITLE
: CP_MESSAGE_TITLE
,
1004 (edit
) ? CP_INPUT_BORDER
: CP_MESSAGE_BORDER
);
1006 if (selected
>= data_index
- 1)
1007 selected
= data_index
- 1;
1011 TAG
**tmppgn
= NULL
;
1012 char *newtag
= NULL
;
1014 for (i
= toppos
, c
= 2; i
< data_index
&& c
< rows
- 2; i
++, c
++) {
1015 if (i
== selected
) {
1016 wattron(win
, CP_MENU_HIGHLIGHT
);
1017 mvwprintw(win
, c
, 1, "%*s: %-*s", nlen
, data
[i
]->name
,
1018 cols
- nlen
- 2 - 2, (data
[i
]->value
&&
1019 data
[i
]->value
[0]) ? data
[i
]->value
: UNKNOWN
);
1020 wattroff(win
, CP_MENU_HIGHLIGHT
);
1024 mvwprintw(win
, c
, 1, "%*s: %-*s", nlen
, data
[i
]->name
,
1025 cols
- nlen
- 2 - 2, (data
[i
]->value
&&
1026 data
[i
]->value
[0]) ? data
[i
]->value
: UNKNOWN
);
1029 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_TAG_STR
,
1030 selected
+ 1, N_OF_N_STR
, data_index
, HELP_PROMPT
);
1031 draw_prompt(win
, rows
- 2, cols
, buf
,
1032 (edit
) ? CP_INPUT_PROMPT
: CP_MESSAGE_PROMPT
);
1041 add_custom_tags(&data
);
1042 selected
= data_index
- 1;
1043 toppos
= data_index
- (rows
- 4);
1048 help(TAG_EDIT_HELP
, ANYKEY
, pgn_edit_help
);
1050 help(TAG_VIEW_HELP
, ANYKEY
, pgn_info_help
);
1056 if (selected
<= 6) {
1057 cmessage(NULL
, ANYKEY
, "%s", E_REMOVE_STR
);
1061 data_index
= pgn_tag_total(data
);
1063 for (i
= 0; i
< data_index
; i
++) {
1067 pgn_tag_add(&tmppgn
, data
[i
]->name
, data
[i
]->value
);
1073 for (i
= 0; tmppgn
[i
]; i
++)
1074 pgn_tag_add(&data
, tmppgn
[i
]->name
, tmppgn
[i
]->value
);
1076 pgn_tag_free(tmppgn
);
1078 if (selected
>= data_index
)
1079 selected
= data_index
- 1;
1081 if (selected
> rows
- 5)
1082 toppos
= selected
- (rows
- 5);
1084 toppos
-= (toppos
) ? 1 : 0;
1092 if ((newtag
= get_input(TAG_NEW_TITLE
, NULL
, 1, 1, NULL
,
1093 NULL
, NULL
, 0, FIELD_TYPE_PGN_TAG_NAME
))
1097 newtag
[0] = toupper(newtag
[0]);
1099 if (strlen(newtag
) > MAX_VALUE_WIDTH
- 6 -
1100 strlen(PRESS_ENTER
)) {
1101 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_NAMETOOLONG
);
1105 for (i
= 0; i
< data_index
; i
++) {
1106 if (strcasecmp(data
[i
]->name
, newtag
) == 0) {
1112 pgn_tag_add(&data
, newtag
, NULL
);
1113 data_index
= pgn_tag_total(data
);
1114 selected
= data_index
- 1;
1115 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1125 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1134 pgn_tag_add(&data
, "FEN", pgn_game_to_fen(g
, b
));
1135 data_index
= pgn_tag_total(data
);
1136 selected
= data_index
- 1;
1137 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1150 if (strlen(menubuf
) + 1 > sizeof(menubuf
) - 1) {
1159 for (i
= 0; i
< data_index
; i
++) {
1160 if (strncasecmp(menubuf
, data
[i
]->name
, strlen(menubuf
))
1167 if (n
== selected
) {
1172 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1179 nlen
= strlen(data
[selected
]->name
) + 2;
1180 nlen
+= (edit
) ? strlen(TAG_EDIT_TAG_TITLE
) : strlen(TAG_VIEW_TAG_TITLE
);
1182 if (nlen
> MAX_VALUE_WIDTH
)
1183 snprintf(buf
, sizeof(buf
), "%s", data
[selected
]->name
);
1185 snprintf(buf
, sizeof(buf
), "%s \"%s\"",
1186 (edit
) ? TAG_EDIT_TAG_TITLE
: TAG_VIEW_TAG_TITLE
,
1187 data
[selected
]->name
);
1190 if (!data
[selected
]->value
)
1193 cmessage(buf
, ANYKEY
, "%s", data
[selected
]->value
);
1197 if (strcmp(data
[selected
]->name
, "Date") == 0) {
1198 tmp
= get_input(buf
, data
[selected
]->value
, 0, 0, NULL
, NULL
, NULL
,
1199 0, FIELD_TYPE_PGN_DATE
);
1202 if (strptime(tmp
, PGN_TIME_FORMAT
, &tp
) == NULL
) {
1203 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_DATE_FMT
);
1210 else if (strcmp(data
[selected
]->name
, "Site") == 0) {
1211 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, CC_PROMPT
,
1212 country_codes
, NULL
, CTRL('t'), -1);
1217 else if (strcmp(data
[selected
]->name
, "Round") == 0) {
1218 tmp
= get_input(buf
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1219 FIELD_TYPE_PGN_ROUND
);
1228 else if (strcmp(data
[selected
]->name
, "Result") == 0) {
1229 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, NULL
, NULL
, NULL
,
1236 tmp
= (data
[selected
]->value
) ? data
[selected
]->value
: NULL
;
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
);
1260 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1261 * game index number.
1263 int save_pgn(const char *filename
, int saveindex
)
1268 char buf
[FILENAME_MAX
];
1271 char *command
= NULL
;
1272 int saveindex_max
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
1274 if (filename
[0] != '/' && config
.savedirectory
) {
1275 if (stat(config
.savedirectory
, &st
) == -1) {
1276 if (errno
== ENOENT
) {
1277 if (mkdir(config
.savedirectory
, 0755) == -1) {
1278 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1284 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1290 stat(config
.savedirectory
, &st
);
1292 if (!S_ISDIR(st
.st_mode
)) {
1293 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
1297 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
1301 if (access(filename
, W_OK
) == 0) {
1302 c
= cmessage(NULL
, GAME_SAVE_OVERWRITE_PROMPT
,
1303 "%s \"%s\"", E_FILEEXISTS
, filename
);
1307 if (pgn_is_compressed(filename
) == E_PGN_OK
) {
1308 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
1325 if ((fp
= popen(command
, "w")) == NULL
) {
1326 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1331 if ((fp
= fopen(filename
, mode
)) == NULL
) {
1332 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1337 for (i
= (saveindex
== -1) ? 0 : saveindex
; i
< saveindex_max
; i
++)
1338 pgn_write(fp
, game
[i
]);
1345 if (saveindex
== -1)
1346 strncpy(loadfile
, filename
, sizeof(loadfile
));
1351 char *random_agony(GAME g
)
1355 char line
[LINE_MAX
];
1357 if (n
== -1 || !config
.agony
|| !curses_initialized
||
1358 (g
.mode
== MODE_HISTORY
&& !config
.historyagony
))
1362 if ((fp
= fopen(config
.agonyfile
, "r")) == NULL
) {
1364 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.agonyfile
, strerror(errno
));
1369 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
1370 agony
= Realloc(agony
, (n
+ 2) * sizeof(char *));
1371 agony
[n
++] = strdup(trim(line
));
1378 if (agony
[0] == NULL
|| !n
) {
1384 return agony
[random() % n
];
1387 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
1389 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1391 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
1392 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1394 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
1397 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1399 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
1400 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1402 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
1405 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1407 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
1408 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1410 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
1413 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1415 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
1416 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1418 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
1421 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1423 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
1424 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
1426 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
1427 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
1429 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
1430 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
1431 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1433 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1437 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1439 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
1440 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
1442 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
1443 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
1445 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
1446 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
1447 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1449 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1457 static void draw_board(GAME
*g
, int details
)
1460 int bcol
= 0, brow
= 0;
1461 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
1462 int ncols
= 0, offset
= 1;
1463 unsigned coords_y
= 8;
1464 struct userdata_s
*d
= g
->data
;
1466 if (g
->mode
!= MODE_PLAY
&& g
->mode
!= MODE_EDIT
)
1467 update_cursor(*g
, g
->hindex
);
1469 for (row
= 0; row
< maxy
; row
++) {
1472 for (col
= 0; col
< maxx
; col
++) {
1475 unsigned char piece
;
1477 if (row
== 0 || row
== maxy
- 2) {
1479 mvwaddch(boardw
, row
, col
,
1480 LINE_GRAPHIC((row
) ?
1481 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
1482 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
1483 else if (col
== maxx
- 2)
1484 mvwaddch(boardw
, row
, col
,
1485 LINE_GRAPHIC((row
) ?
1486 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
1487 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
1488 else if (!(col
% 4))
1489 mvwaddch(boardw
, row
, col
,
1490 LINE_GRAPHIC((row
) ?
1491 ACS_BTEE
| CP_BOARD_GRAPHICS
:
1492 ACS_TTEE
| CP_BOARD_GRAPHICS
));
1494 if (col
!= maxx
- 1)
1495 mvwaddch(boardw
, row
, col
,
1496 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1502 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
1503 wattron(boardw
, CP_BOARD_COORDS
);
1504 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
1505 wattroff(boardw
, CP_BOARD_COORDS
);
1509 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
1511 mvwaddch(boardw
, row
, col
,
1512 LINE_GRAPHIC((col
) ?
1513 ACS_RTEE
| CP_BOARD_GRAPHICS
:
1514 ACS_LTEE
| CP_BOARD_GRAPHICS
));
1516 mvwaddch(boardw
, row
, col
,
1517 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1522 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
1523 mvwaddch(boardw
, row
, col
,
1524 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1528 if (!(col
% 4) && row
!= maxy
- 1) {
1529 mvwaddch(boardw
, row
, col
,
1530 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
1541 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
1547 if (config
.validmoves
&& d
->b
[brow
][bcol
].valid
) {
1548 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
1549 CP_BOARD_MOVES_BLACK
;
1552 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
1555 if (row
== ROWTOMATRIX(d
->c_row
) && col
==
1556 COLTOMATRIX(d
->c_col
)) {
1557 attrs
= CP_BOARD_CURSOR
;
1560 if (row
== ROWTOMATRIX(sp
.row
) &&
1561 col
== COLTOMATRIX(sp
.col
)) {
1562 attrs
= CP_BOARD_SELECTED
;
1565 if (row
== maxy
- 1)
1568 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
1570 if (row
== maxy
- 1)
1571 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
1573 if (details
&& d
->b
[row
/ 2][bcol
].enpassant
)
1576 piece
= d
->b
[row
/ 2][bcol
].icon
;
1578 if (details
&& castling_state(g
, d
->b
, brow
, bcol
,
1582 if (g
->side
== WHITE
&& isupper(piece
))
1584 else if (g
->side
== BLACK
&& islower(piece
))
1587 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
1589 CLEAR_FLAG(attrs
, A_BOLD
);
1590 CLEAR_FLAG(attrs
, A_REVERSE
);
1593 waddch(boardw
, ' ' | attrs
);
1599 if (col
!= maxx
- 1)
1600 mvwaddch(boardw
, row
, col
,
1601 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1609 void invalid_move(int n
, const char *m
)
1611 if (curses_initialized
)
1612 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", E_INVALID_MOVE
, m
, n
);
1614 warnx("%s: %s \"%s\" (round #%i)", loadfile
, E_INVALID_MOVE
, m
, n
);
1617 /* Convert the selected piece to SAN format and validate it. */
1618 static char *board_to_san(GAME
*g
, BOARD b
)
1620 static char str
[MAX_SAN_MOVE_LEN
+ 1], *p
;
1623 struct userdata_s
*d
= g
->data
;
1625 snprintf(str
, sizeof(str
), "%c%i%c%i", x_grid_chars
[sp
.col
- 1],
1626 sp
.row
, x_grid_chars
[sp
.destcol
- 1], sp
.destrow
);
1629 piece
= pgn_piece_to_int(b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
);
1631 if (piece
== PAWN
&& ((sp
.destrow
== 8 && g
->turn
== WHITE
) ||
1632 (sp
.destrow
== 1 && g
->turn
== BLACK
))) {
1633 promo
= cmessage(PROMOTION_TITLE
, PROMOTION_PROMPT
, PROMOTION_TEXT
);
1635 if (pgn_piece_to_int(promo
) == -1)
1638 p
= str
+ strlen(str
);
1639 *p
++ = toupper(promo
);
1645 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
1646 if (pgn_parse_move(g
, b
, &p
) != E_PGN_OK
) {
1647 invalid_move(gindex
+ 1, p
);
1654 if (pgn_validate_move(g
, b
, &p
) != E_PGN_OK
) {
1655 invalid_move(gindex
+ 1, p
);
1662 static int move_to_engine(GAME
*g
, BOARD b
)
1665 struct userdata_s
*d
= g
->data
;
1667 if ((p
= board_to_san(g
, b
)) == NULL
)
1670 sp
.row
= sp
.col
= sp
.icon
= 0;
1672 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
1673 pgn_history_add(g
, p
);
1675 SET_FLAG(g
->flags
, GF_MODIFIED
);
1680 add_engine_command(g
, ENGINE_THINKING
, "%s\n", p
);
1684 static void update_clock(int n
, int *h
, int *m
, int *s
)
1687 *m
= (n
% 3600) / 60;
1688 *s
= (n
% 3600) % 60;
1693 void update_status_window(GAME g
)
1697 char tmp
[15], *engine
, *mode
;
1703 struct userdata_s
*d
= g
.data
;
1705 getmaxyx(statusw
, maxy
, maxx
);
1713 if (TEST_FLAG(g
.flags
, GF_DELETE
)) {
1719 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1729 if (TEST_FLAG(g
.flags
, GF_MODIFIED
)) {
1744 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1745 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1746 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1748 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1752 mode
= MODE_HISTORY_STR
;
1755 mode
= MODE_EDIT_STR
;
1758 mode
= MODE_PLAY_STR
;
1765 snprintf(buf
, len
- 1, "%*s %s", 7, STATUS_MODE_STR
, mode
);
1767 if (g
.mode
== MODE_PLAY
) {
1768 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1769 strncat(buf
, " (human/human)", len
- 1);
1770 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1771 strncat(buf
, " (engine/engine)", len
- 1);
1773 strncat(buf
, " (human/engine)", len
- 1);
1776 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1779 switch (d
->engine
->status
) {
1780 case ENGINE_THINKING
:
1781 engine
= ENGINE_PONDER_STR
;
1784 engine
= ENGINE_READY_STR
;
1786 case ENGINE_INITIALIZING
:
1787 engine
= ENGINE_INITIALIZING_STR
;
1789 case ENGINE_OFFLINE
:
1790 engine
= ENGINE_OFFLINE_STR
;
1798 engine
= ENGINE_OFFLINE_STR
;
1800 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1801 wattron(statusw
, CP_STATUS_ENGINE
);
1802 mvwaddstr(statusw
, 5, 9, engine
);
1803 wattroff(statusw
, CP_STATUS_ENGINE
);
1805 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1806 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1808 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1809 tmp
[0] = toupper(tmp
[0]);
1810 update_clock(g
.moveclock
, &h
, &m
, &s
);
1811 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1812 mvwprintw(statusw
, 7, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1814 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1815 tmp
[0] = toupper(tmp
[0]);
1816 update_clock(g
.moveclock
, &h
, &m
, &s
);
1817 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1818 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1821 for (i
= 1; i
< maxx
- 4; i
++)
1822 mvwprintw(statusw
, maxy
- 2, i
, " ");
1825 status
.notify
= strdup(GAME_HELP_PROMPT
);
1827 wattron(statusw
, CP_STATUS_NOTIFY
);
1828 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1830 wattroff(statusw
, CP_STATUS_NOTIFY
);
1833 void update_history_window(GAME g
)
1835 char buf
[HISTORY_WIDTH
- 1];
1838 int t
= pgn_history_total(g
.hp
);
1840 n
= (g
.hindex
+ 1) / 2;
1843 total
= (t
+ 1) / 2;
1848 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1849 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
1851 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1853 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1854 HISTORY_WIDTH
- 13, buf
);
1856 h
= pgn_history_by_n(g
.hp
, g
.hindex
);
1857 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1860 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1861 strncat(buf
, " (v", sizeof(buf
));
1866 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1871 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1876 strncat(buf
, ")", sizeof(buf
));
1878 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1879 HISTORY_WIDTH
- 13, buf
);
1881 h
= pgn_history_by_n(g
.hp
, game
[gindex
].hindex
- 1);
1882 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1885 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1886 strncat(buf
, " (V", sizeof(buf
));
1891 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1896 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1901 strncat(buf
, ")", sizeof(buf
));
1903 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1904 HISTORY_WIDTH
- 13, buf
);
1907 void update_tag_window(TAG
**t
)
1910 int w
= TAG_WIDTH
- 10;
1912 for (i
= 0; i
< 7; i
++)
1913 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
]->name
, w
, t
[i
]->value
);
1916 void draw_prompt(WINDOW
*win
, int y
, int width
, const char *str
, chtype attr
)
1922 for (i
= 1; i
< width
- 1; i
++)
1923 mvwaddch(win
, y
, i
, ' ');
1925 mvwprintw(win
, y
, CENTERX(width
, str
), "%s", str
);
1926 wattroff(win
, attr
);
1929 void draw_window_title(WINDOW
*win
, const char *title
, int width
, chtype attr
,
1937 for (i
= 1; i
< width
- 1; i
++)
1938 mvwaddch(win
, 1, i
, ' ');
1940 mvwprintw(win
, 1, CENTERX(width
, title
), "%s", title
);
1941 wattroff(win
, attr
);
1944 wattron(win
, battr
);
1945 box(win
, ACS_VLINE
, ACS_HLINE
);
1946 wattroff(win
, battr
);
1949 void append_enginebuf(char *line
)
1954 for (i
= 0; enginebuf
[i
]; i
++);
1956 if (i
>= LINES
- 3) {
1959 for (i
= 0; enginebuf
[i
+1]; i
++)
1960 enginebuf
[i
] = enginebuf
[i
+1];
1962 enginebuf
[i
] = strdup(line
);
1965 enginebuf
= Realloc(enginebuf
, (i
+ 2) * sizeof(char *));
1966 enginebuf
[i
++] = strdup(line
);
1967 enginebuf
[i
] = NULL
;
1971 void update_engine_window()
1978 wmove(enginew
, 0, 0);
1982 for (i
= 0; enginebuf
[i
]; i
++)
1983 mvwprintw(enginew
, i
+ 2, 1, "%s", enginebuf
[i
]);
1986 draw_window_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1990 void toggle_engine_window()
1993 enginew
= newwin(LINES
, COLS
, 0, 0);
1994 enginep
= new_panel(enginew
);
1995 draw_window_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1997 hide_panel(enginep
);
2000 if (panel_hidden(enginep
)) {
2001 update_engine_window();
2006 hide_panel(enginep
);
2017 void update_all(GAME g
)
2019 update_status_window(g
);
2020 update_history_window(g
);
2021 update_tag_window(g
.tag
);
2022 update_engine_window();
2025 static void game_next_prev(GAME g
, int n
, int count
)
2031 if (gindex
+ count
> gtotal
- 1) {
2033 gindex
= gtotal
- 1;
2041 if (gindex
- count
< 0) {
2045 gindex
= gtotal
- 1;
2052 static void delete_game(int which
)
2058 for (i
= 0; i
< gtotal
; i
++) {
2059 if (i
== which
|| TEST_FLAG(game
[i
].flags
, GF_DELETE
)) {
2064 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
2065 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
2066 g
[gi
].tag
= game
[i
].tag
;
2067 g
[gi
].history
= game
[i
].history
;
2068 g
[gi
].hp
= game
[i
].hp
;
2076 if (which
+ 1 >= gtotal
)
2077 gindex
= gtotal
- 1;
2082 gindex
= gtotal
- 1;
2084 game
[gindex
].hp
= game
[gindex
].history
;
2087 static int find_move_exp(GAME g
, const char *str
, int init
, int which
,
2093 static int firstrun
= 1;
2102 if ((ret
= regcomp(&r
, str
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2103 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2104 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2111 incr
= (which
== 0) ? -1 : 1;
2113 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
2114 if (i
== g
.hindex
- 1)
2117 if (i
>= pgn_history_total(g
.hp
))
2120 i
= pgn_history_total(g
.hp
) - 1;
2123 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
2126 if (count
== ++found
) {
2131 if (ret
!= REG_NOMATCH
) {
2132 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2133 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
2142 static int toggle_delete_flag(int n
)
2146 TOGGLE_FLAG(game
[n
].flags
, GF_DELETE
);
2148 update_all(game
[gindex
]);
2150 for (i
= x
= 0; i
< gtotal
; i
++) {
2151 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2156 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2157 CLEAR_FLAG(game
[n
].flags
, GF_DELETE
);
2164 static void edit_save_tags(GAME
*g
)
2167 struct userdata_s
*d
= g
->data
;
2169 if ((t
= edit_tags(*g
, d
->b
, 1)) == NULL
)
2172 pgn_tag_free(g
->tag
);
2174 SET_FLAG(g
->flags
, GF_MODIFIED
);
2175 pgn_tag_sort(g
->tag
);
2178 static int find_game_exp(char *str
, int which
, int count
)
2180 char *nstr
= NULL
, *exp
= NULL
;
2184 char buf
[255], *tmp
;
2187 int incr
= (which
== 0) ? -(1) : 1;
2189 strncpy(buf
, str
, sizeof(buf
));
2192 if (strstr(tmp
, ":") != NULL
) {
2193 nstr
= strsep(&tmp
, ":");
2195 if ((ret
= regcomp(&nexp
, nstr
,
2196 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
2197 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
2198 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2209 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2210 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
2211 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2218 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
2229 for (t
= 0; game
[g
].tag
[t
]; t
++) {
2231 if (regexec(&nexp
, game
[g
].tag
[t
]->name
, 0, 0, 0) == 0) {
2232 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2233 if (count
== ++found
) {
2241 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2242 if (count
== ++found
) {
2264 * Updates the notification line in the status window then refreshes the
2267 void update_status_notify(GAME g
, char *fmt
, ...)
2270 #ifdef HAVE_VASPRINTF
2277 if (status
.notify
) {
2278 free(status
.notify
);
2279 status
.notify
= NULL
;
2281 if (curses_initialized
)
2282 update_status_window(g
);
2289 #ifdef HAVE_VASPRINTF
2290 vasprintf(&line
, fmt
, ap
);
2292 vsnprintf(line
, sizeof(line
), fmt
, ap
);
2297 free(status
.notify
);
2299 status
.notify
= strdup(line
);
2301 #ifdef HAVE_VASPRINTF
2304 if (curses_initialized
)
2305 update_status_window(g
);
2308 static void switch_side(GAME
*g
)
2310 int i
= pgn_tag_find(g
->tag
, "White");
2311 int n
= pgn_tag_find(g
->tag
, "Black");
2312 char *w
= g
->tag
[i
]->value
;
2314 g
->tag
[i
]->value
= g
->tag
[n
]->value
;
2315 g
->tag
[n
]->value
= w
;
2316 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
2317 update_tag_window(g
->tag
);
2320 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
2324 if (g
->hp
[g
->hindex
]->rav
== NULL
)
2327 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
2328 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
2329 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
2330 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
2331 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
2332 g
->hp
= g
->hp
[g
->hindex
]->rav
;
2338 if (g
->ravlevel
- 1 < 0)
2343 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
2344 free(g
->rav
[g
->ravlevel
].fen
);
2345 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
2346 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
2347 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
2351 static void draw_window_decor()
2353 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
2354 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
2355 wbkgd(boardw
, CP_BOARD_WINDOW
);
2356 wbkgd(statusw
, CP_STATUS_WINDOW
);
2357 draw_window_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
2358 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
2359 wbkgd(tagw
, CP_TAG_WINDOW
);
2360 draw_window_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
2362 wbkgd(historyw
, CP_HISTORY_WINDOW
);
2363 draw_window_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
2364 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
2367 static void do_window_resize()
2369 if (LINES
< 24 || COLS
< 80)
2372 resizeterm(LINES
, COLS
);
2373 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
2374 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
2375 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
2376 wmove(historyw
, 0, 0);
2377 wclrtobot(historyw
);
2380 wmove(statusw
, 0, 0);
2382 draw_window_decor();
2383 update_all(game
[gindex
]);
2386 void add_engine_command(GAME
*g
, int s
, char *fmt
, ...)
2390 struct userdata_s
*d
= g
->data
;
2397 q
= d
->engine
->queue
;
2400 for (i
= 0; q
[i
]; i
++);
2402 q
= Realloc(q
, (i
+ 2) * sizeof(struct queue_s
*));
2404 #ifdef HAVE_VASPRINTF
2405 vasprintf(&line
, fmt
, ap
);
2407 line
= Malloc(LINE_MAX
+ 1);
2408 vsnprintf(line
, LINE_MAX
, fmt
, ap
);
2411 q
[i
] = Malloc(sizeof(struct queue_s
));
2413 q
[i
++]->status
= (s
== -1) ? d
->engine
->status
: s
;
2415 d
->engine
->queue
= q
;
2418 static void historymode_keys(chtype
);
2419 static int playmode_keys(chtype c
)
2421 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2422 int editmode
= (game
[gindex
].mode
== MODE_EDIT
) ? 1 : 0;
2426 struct userdata_s
*d
= game
[gindex
].data
;
2430 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
2432 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2433 pgn_history_total(game
[gindex
].hp
)) {
2434 pgn_tag_add(&game
[gindex
].tag
, "FEN",
2435 pgn_game_to_fen(game
[gindex
], d
->b
));
2436 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2438 if (start_chess_engine(&game
[gindex
]) <= 0) {
2439 add_engine_command(&game
[gindex
], ENGINE_READY
,
2440 "setboard %s\n", game
[gindex
].tag
[x
]->value
);
2441 d
->engine
->status
= ENGINE_READY
;
2445 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2448 d
->engine
->status
= ENGINE_READY
;
2450 update_all(game
[gindex
]);
2456 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2457 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
2459 if (d
->engine
&& !TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
2460 add_engine_command(&game
[gindex
], ENGINE_READY
, "new\n");
2461 pgn_board_update(&game
[gindex
], d
->b
,
2462 pgn_history_total(game
[gindex
].hp
));
2463 add_engine_command(&game
[gindex
], ENGINE_READY
,
2464 "setboard %s\n", pgn_game_to_fen(game
[gindex
], d
->b
));
2467 update_all(game
[gindex
]);
2473 if (d
->engine
->status
== ENGINE_OFFLINE
)
2476 x
= d
->engine
->status
;
2478 if ((tmp
= get_input_str_clear(ENGINE_CMD_TITLE
, NULL
)) != NULL
)
2479 send_to_engine(&game
[gindex
], ENGINE_READY
, "%s\n", tmp
);
2480 d
->engine
->status
= x
;
2484 pushkey
= keycount
= 0;
2485 update_status_notify(game
[gindex
], NULL
);
2487 if (!editmode
&& !TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2488 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
)) {
2496 sp
.destrow
= d
->c_row
;
2497 sp
.destcol
= d
->c_col
;
2500 p
= d
->b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
;
2501 d
->b
[ROWTOBOARD(sp
.destrow
)][COLTOBOARD(sp
.destcol
)].icon
= p
;
2502 d
->b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
=
2503 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2504 sp
.icon
= sp
.row
= sp
.col
= 0;
2508 if (move_to_engine(&game
[gindex
], d
->b
)) {
2509 if (config
.validmoves
)
2510 pgn_reset_valid_moves(d
->b
);
2512 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
)) {
2513 CLEAR_FLAG(game
[gindex
].flags
, GF_GAMEOVER
);
2514 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2520 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2521 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
2522 if (start_chess_engine(&game
[gindex
])) {
2527 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2528 w
= pgn_tag_find(game
[gindex
].tag
, "SetUp");
2530 if ((w
>= 0 && x
>= 0 && atoi(game
[gindex
].tag
[w
]->value
) == 1)
2531 || (x
>= 0 && w
== -1)) {
2532 add_engine_command(&game
[gindex
], ENGINE_READY
,
2533 "setboard %s\n", game
[gindex
].tag
[x
]->value
);
2537 if (sp
.icon
|| (!editmode
&& d
->engine
&&
2538 d
->engine
->status
== ENGINE_THINKING
)) {
2543 sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(d
->c_row
),
2544 COLTOMATRIX(d
->c_col
)+1) & A_CHARTEXT
;
2546 if (sp
.icon
== ' ') {
2551 if (!editmode
&& ((islower(sp
.icon
) && game
[gindex
].turn
!= BLACK
)
2552 || (isupper(sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
2553 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
2561 if (!editmode
&& config
.validmoves
)
2562 pgn_find_valid_moves(game
[gindex
], d
->b
, sp
.col
, sp
.row
);
2567 add_engine_command(&game
[gindex
], ENGINE_READY
, "\nswitch\n");
2568 switch_side(&game
[gindex
]);
2569 update_status_window(game
[gindex
]);
2572 if (!pgn_history_total(game
[gindex
].hp
))
2575 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2576 add_engine_command(&game
[gindex
], ENGINE_READY
, "remove\n");
2577 d
->engine
->status
= ENGINE_READY
;
2580 game
[gindex
].hindex
-= 2;
2581 pgn_history_free(game
[gindex
].hp
, game
[gindex
].hindex
);
2582 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
);
2583 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2584 update_history_window(game
[gindex
]);
2587 historymode_keys(c
);
2590 board_details
= (board_details
) ? 0 : 1;
2593 paused
= (paused
) ? 0 : 1;
2596 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
2597 start_chess_engine(&game
[gindex
]);
2599 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "go\n");
2606 for (x
= 0; config
.keys
[x
]; x
++) {
2607 if (config
.keys
[x
]->c
== c
) {
2608 switch (config
.keys
[x
]->type
) {
2610 add_engine_command(&game
[gindex
], -1, "%s\n",
2611 config
.keys
[x
]->str
);
2617 add_engine_command(&game
[gindex
], -1,
2618 "%s %i\n", config
.keys
[x
]->str
, keycount
);
2625 for (w
= 0; w
< keycount
; w
++)
2626 add_engine_command(&game
[gindex
], -1,
2627 "%s\n", config
.keys
[x
]->str
);
2634 update_status_notify(game
[gindex
], NULL
);
2642 static void editmode_keys(chtype c
)
2644 struct userdata_s
*d
= game
[gindex
].data
;
2654 d
->b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
=
2655 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2657 d
->b
[ROWTOBOARD(d
->c_row
)][COLTOBOARD(d
->c_col
)].icon
=
2658 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2660 sp
.icon
= sp
.row
= sp
.col
= 0;
2663 pgn_switch_turn(&game
[gindex
]);
2664 switch_side(&game
[gindex
]);
2665 update_all(game
[gindex
]);
2668 castling_state(&game
[gindex
], d
->b
, ROWTOBOARD(d
->c_row
),
2669 COLTOBOARD(d
->c_col
),
2670 d
->b
[ROWTOBOARD(d
->c_row
)][COLTOBOARD(d
->c_col
)].icon
, 1);
2673 c
= message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, "%s",
2676 if (pgn_piece_to_int(c
) == -1)
2679 d
->b
[ROWTOBOARD(d
->c_row
)][COLTOBOARD(d
->c_col
)].icon
= c
;
2682 if (d
->c_row
== 6 || d
->c_row
== 3) {
2683 pgn_reset_enpassant(d
->b
);
2684 d
->b
[ROWTOBOARD(d
->c_row
)][COLTOBOARD(d
->c_col
)].enpassant
= 1;
2692 static void historymode_keys(chtype c
)
2696 static char moveexp
[255] = {0};
2697 struct userdata_s
*d
= game
[gindex
].data
;
2701 board_details
= (board_details
) ? 0 : 1;
2704 movestep
= (movestep
== 1) ? 2 : 1;
2705 update_history_window(game
[gindex
]);
2708 pgn_history_next(&game
[gindex
], d
->b
, (keycount
> 0) ?
2709 config
.jumpcount
* keycount
* movestep
:
2710 config
.jumpcount
* movestep
);
2711 update_all(game
[gindex
]);
2714 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2715 config
.jumpcount
* keycount
* movestep
:
2716 config
.jumpcount
* movestep
);
2717 update_all(game
[gindex
]);
2720 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2721 keycount
* movestep
: movestep
);
2722 update_all(game
[gindex
]);
2725 pgn_history_next(&game
[gindex
], d
->b
, (keycount
) ?
2726 keycount
* movestep
: movestep
);
2727 update_all(game
[gindex
]);
2730 n
= game
[gindex
].hindex
;
2732 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
2738 snprintf(buf
, COLS
- 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE
,
2739 game
[gindex
].hp
[n
]->move
);
2741 tmp
= get_input(buf
, game
[gindex
].hp
[n
]->comment
, 0, 0, NAG_PROMPT
,
2742 history_edit_nag
, (void *)game
[gindex
].hp
[n
], CTRL('T'),
2746 if (!tmp
&& (!game
[gindex
].hp
[n
]->comment
||
2747 !*game
[gindex
].hp
[n
]->comment
))
2749 else if (tmp
&& game
[gindex
].hp
[n
]->comment
) {
2750 if (strcmp(tmp
, game
[gindex
].hp
[n
]->comment
) == 0)
2754 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
2755 game
[gindex
].hp
[n
]->comment
= Realloc(game
[gindex
].hp
[n
]->comment
,
2757 strncpy(game
[gindex
].hp
[n
]->comment
, (tmp
) ? tmp
: "", len
);
2758 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2759 update_all(game
[gindex
]);
2764 if (pgn_history_total(game
[gindex
].hp
) < 2)
2769 if (!*moveexp
|| c
== '/') {
2770 if ((tmp
= get_input(FIND_REGEXP
, moveexp
, 1, 1, NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2773 strncpy(moveexp
, tmp
, sizeof(moveexp
));
2777 if ((n
= find_move_exp(game
[gindex
], moveexp
, n
,
2778 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1))
2782 game
[gindex
].hindex
= n
;
2783 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2784 update_all(game
[gindex
]);
2787 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
]);
2790 if (game
[gindex
].hindex
- 1 >= 0)
2791 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2795 rav_next_prev(&game
[gindex
], d
->b
, (c
== '-') ? 0 : 1);
2796 update_all(game
[gindex
]);
2799 if (pgn_history_total(game
[gindex
].hp
) < 2)
2802 /* FIXME field validation
2803 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2804 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2805 game[gindex].htotal)) == NULL)
2810 if ((tmp
= get_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1,
2811 NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2814 if (!isinteger(tmp
))
2822 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
2825 game
[gindex
].hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2826 pgn_board_update(&game
[gindex
], d
->b
,
2827 game
[gindex
].hindex
);
2828 update_all(game
[gindex
]);
2835 static void free_userdata()
2839 for (i
= 0; i
< gtotal
; i
++) {
2840 struct userdata_s
*d
;
2846 stop_engine(&game
[i
]);
2851 game
[i
].data
= NULL
;
2856 void update_loading_window()
2859 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
2860 loadingp
= new_panel(loadingw
);
2861 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
2864 wmove(loadingw
, 0, 0);
2865 wclrtobot(loadingw
);
2866 wattron(loadingw
, CP_MESSAGE_BORDER
);
2867 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
2868 wattroff(loadingw
, CP_MESSAGE_BORDER
);
2869 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
2870 11 + strlen(itoa(gtotal
))), "Loading... %i", gtotal
);
2875 void init_userdata()
2879 for (i
= 0; i
< gtotal
; i
++) {
2880 struct userdata_s
*d
= NULL
;
2882 d
= Calloc(1, sizeof(struct userdata_s
));
2885 pgn_board_init(d
->b
);
2889 void fix_marks(int *start
, int *end
)
2893 *start
= (*start
< 0) ? 0 : *start
;
2894 *end
= (*end
< 0) ? 0 : *end
;
2896 if (*start
> *end
) {
2902 *end
= (*end
> gtotal
) ? gtotal
: *end
;
2905 // Global and other keys.
2906 static int globalkeys(chtype c
)
2908 static char gameexp
[255] = {0};
2912 char tfile
[FILENAME_MAX
];
2913 struct userdata_s
*d
= game
[gindex
].data
;
2917 toggle_engine_window();
2920 cmessage("ABOUT", ANYKEY
, "%s (%s)\nUsing %s with %i colors "
2921 "and %i color pairs\nCopyright 2002-2006 %s",
2922 PACKAGE_STRING
, pgn_version(), curses_version(), COLORS
,
2923 COLOR_PAIRS
, PACKAGE_BUGREPORT
);
2926 if (game
[gindex
].mode
!= MODE_HISTORY
) {
2927 if (!pgn_history_total(game
[gindex
].hp
) ||
2928 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2931 game
[gindex
].mode
= MODE_HISTORY
;
2932 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2933 update_all(game
[gindex
]);
2938 if (TEST_FLAG(game
[gindex
].flags
, GF_BLACK_OPENING
)) {
2939 cmessage(NULL
, ANYKEY
, "%s", E_RESUME_BLACK
);
2943 // FIXME Resuming from previous history could append to a RAV.
2944 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
2946 if ((c
= message(NULL
, YESNO
, "%s",
2947 GAME_RESUME_HISTORY_TEXT
)) != 'y')
2952 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
2957 if (!TEST_FLAG(d->flags, CF_HUMAN) && (!d->engine ||
2958 d->engine->status == ENGINE_OFFLINE)) {
2959 if (start_chess_engine(&game[gindex]) < 0)
2962 send_to_engine(&game[gindex], "setboard %s\n",
2963 pgn_game_to_fen(game[gindex], d->b));
2964 d->engine->status = ENGINE_READY;
2971 oldhistorytotal
= pgn_history_total(game
[gindex
].hp
);
2972 game
[gindex
].mode
= MODE_PLAY
;
2973 update_all(game
[gindex
]);
2977 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
2979 d
= game
[gindex
].data
;
2983 markend
= markstart
+ delete_count
;
2987 markend
= markstart
- delete_count
;
2988 delete_count
= -1; // to fix gindex in the other direction
2992 fix_marks(&markstart
, &markend
);
2995 if (game
[gindex
].mode
!= MODE_EDIT
)
2996 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2998 update_status_notify(game
[gindex
], NULL
);
2999 update_all(game
[gindex
]);
3007 if (!*gameexp
|| c
== '?') {
3008 if ((tmp
= get_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
,
3009 1, 1, GAME_FIND_EXPRESSION_PROMPT
, NULL
,
3010 NULL
, 0, -1)) == NULL
)
3013 strncpy(gameexp
, tmp
, sizeof(gameexp
));
3016 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1, (keycount
)
3022 d
= game
[gindex
].data
;
3024 if (pgn_history_total(game
[gindex
].hp
))
3025 game
[gindex
].mode
= MODE_HISTORY
;
3027 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3028 update_all(game
[gindex
]);
3034 /* FIXME field validation
3035 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
3036 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
3042 if ((tmp
= get_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
,
3043 NULL
, NULL
, 0, -1)) == NULL
)
3046 if (!isinteger(tmp
))
3054 if (--i
> gtotal
- 1 || i
< 0)
3058 d
= game
[gindex
].data
;
3059 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3060 update_status_notify(game
[gindex
], NULL
);
3061 update_all(game
[gindex
]);
3069 if (keycount
&& delete_count
== 0) {
3071 delete_count
= keycount
;
3072 update_status_notify(game
[gindex
], "%s (delete)",
3077 if (markstart
>= 0 && markend
>= 0) {
3078 for (i
= markstart
; i
< markend
; i
++) {
3079 if (toggle_delete_flag(i
)) {
3080 update_all(game
[gindex
]);
3085 gindex
= (delete_count
< 0) ? markstart
: i
- 1;
3086 update_all(game
[gindex
]);
3089 if (toggle_delete_flag(gindex
))
3093 markstart
= markend
= -1;
3095 update_status_window(game
[gindex
]);
3099 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
3105 for (i
= n
= 0; i
< gtotal
; i
++) {
3106 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
3111 tmp
= GAME_DELETE_GAME_TEXT
;
3114 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
3118 tmp
= GAME_DELETE_ALL_TEXT
;
3121 if (config
.deleteprompt
) {
3122 if ((c
= cmessage(NULL
, YESNO
, "%s", tmp
)) != 'y')
3126 delete_game((!n
) ? gindex
: -1);
3128 if (pgn_history_total(game
[gindex
].hp
))
3129 game
[gindex
].mode
= MODE_HISTORY
;
3131 d
= game
[gindex
].data
;
3132 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3133 update_all(game
[gindex
]);
3136 edit_save_tags(&game
[gindex
]);
3137 update_all(game
[gindex
]);
3140 edit_tags(game
[gindex
], d
->b
, 0);
3143 if ((tmp
= get_input(GAME_LOAD_TITLE
, NULL
, 1, 1,
3144 BROWSER_PROMPT
, file_browser
, NULL
, '\t',
3148 if ((tmp
= word_expand(tmp
)) == NULL
)
3151 if ((fp
= pgn_open(tmp
)) == NULL
) {
3152 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
3158 if (pgn_parse(fp
)) {
3160 update_all(game
[gindex
]);
3164 del_panel(loadingp
);
3169 strncpy(loadfile
, tmp
, sizeof(loadfile
));
3171 if (pgn_history_total(game
[gindex
].hp
))
3172 game
[gindex
].mode
= MODE_HISTORY
;
3174 d
= game
[gindex
].data
;
3175 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3176 update_all(game
[gindex
]);
3183 n
= message(NULL
, GAME_SAVE_MULTI_PROMPT
, "%s",
3184 GAME_SAVE_MULTI_TEXT
);
3191 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
3196 if ((tmp
= get_input(GAME_SAVE_TITLE
, loadfile
, 1, 1,
3197 BROWSER_PROMPT
, file_browser
, NULL
,
3198 '\t', -1)) == NULL
) {
3199 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
3203 if ((tmp
= word_expand(tmp
)) == NULL
)
3206 if (pgn_is_compressed(tmp
)) {
3207 p
= tmp
+ strlen(tmp
) - 1;
3209 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
3211 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3216 if ((p
= strchr(tmp
, '.')) != NULL
) {
3217 if (strcmp(p
, ".pgn") != 0) {
3218 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3223 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3228 if (save_pgn(tmp
, i
)) {
3229 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
3233 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
3234 update_all(game
[gindex
]);
3239 switch (game
[gindex
].mode
) {
3241 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3244 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3247 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3253 while (c
== KEY_F(1)) {
3254 c
= help(GAME_HELP_INDEX_TITLE
, GAME_HELP_INDEX_PROMPT
,
3259 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3262 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3265 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3268 c
= help(GAME_HELP_GAME_TITLE
, ANYKEY
, gamehelp
);
3279 if (cmessage(NULL
, YESNO
, "%s", GAME_NEW_PROMPT
) != 'y')
3285 add_custom_tags(&game
[gindex
].tag
);
3286 d
= Calloc(1, sizeof(struct userdata_s
));
3287 pgn_board_init(d
->b
);
3288 game
[gindex
].data
= d
;
3293 add_custom_tags(&game
[gindex
].tag
);
3294 d
= Calloc(1, sizeof(struct userdata_s
));
3295 pgn_board_init(d
->b
);
3296 game
[gindex
].data
= d
;
3299 game
[gindex
].mode
= MODE_PLAY
;
3300 d
->c_row
= (game
[gindex
].side
== WHITE
) ? 2 : 7;
3302 update_status_notify(game
[gindex
], NULL
);
3303 update_all(game
[gindex
]);
3307 keypad(boardw
, TRUE
);
3311 sp
.icon
= sp
.row
= sp
.col
= 0;
3312 markend
= markstart
= 0;
3316 update_status_notify(game
[gindex
], NULL
);
3319 if (config
.validmoves
)
3320 pgn_reset_valid_moves(d
->b
);
3327 keycount
= keycount
* 10 + n
;
3331 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3334 if (game
[gindex
].mode
== MODE_HISTORY
)
3338 d
->c_row
+= keycount
;
3349 if (game
[gindex
].mode
== MODE_HISTORY
)
3353 d
->c_row
-= keycount
;
3355 update_status_notify(game
[gindex
], NULL
);
3365 if (game
[gindex
].mode
== MODE_HISTORY
)
3369 d
->c_col
-= keycount
;
3380 if (game
[gindex
].mode
== MODE_HISTORY
)
3384 d
->c_col
+= keycount
;
3395 if (game
[gindex
].mode
!= MODE_EDIT
&& game
[gindex
].mode
!=
3399 // Don't edit a running game (for now).
3400 if (pgn_history_total(game
[gindex
].hp
))
3403 if (game
[gindex
].mode
!= MODE_EDIT
) {
3404 pgn_board_init_fen(&game
[gindex
], d
->b
, NULL
);
3406 game
[gindex
].mode
= MODE_EDIT
;
3407 update_all(game
[gindex
]);
3412 pgn_tag_add(&game
[gindex
].tag
, "FEN",
3413 pgn_game_to_fen(game
[gindex
], d
->b
));
3414 pgn_tag_add(&game
[gindex
].tag
, "SetUp", "1");
3415 pgn_tag_sort(game
[gindex
].tag
);
3416 game
[gindex
].mode
= MODE_PLAY
;
3417 update_all(game
[gindex
]);
3427 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(d
->b
));
3438 void send_engine_command(GAME
*g
)
3440 struct userdata_s
*d
= g
->data
;
3441 struct queue_s
**q
= d
->engine
->queue
;
3444 if (!d
->engine
|| !d
->engine
->queue
)
3450 if (send_to_engine(g
, q
[0]->status
, "%s", q
[0]->line
) == 0) {
3451 if (d
->n
== gindex
) {
3452 d
->engine
->status
= ENGINE_THINKING
;
3453 update_status_window(*g
);
3461 for (i
= 0; q
[i
]; i
++) {
3473 int error_recover
= 0;
3474 struct userdata_s
*d
= game
[gindex
].data
;
3476 d
->c_row
= 2, d
->c_col
= 5;
3477 gindex
= gtotal
- 1;
3479 if (pgn_history_total(game
[gindex
].hp
))
3480 game
[gindex
].mode
= MODE_HISTORY
;
3482 game
[gindex
].mode
= MODE_PLAY
;
3484 if (game
[gindex
].mode
== MODE_HISTORY
) {
3485 pgn_board_update(&game
[gindex
], d
->b
,
3486 pgn_history_total(game
[gindex
].hp
));
3489 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
3491 paused
= 1; //FIXME clock
3493 update_all(game
[gindex
]);
3494 update_tag_window(game
[gindex
].tag
);
3495 wtimeout(boardw
, 70);
3500 char fdbuf
[8192] = {0};
3502 struct timeval tv
= {0, 0};
3507 for (i
= 0; i
< gtotal
; i
++) {
3511 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
3512 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
3513 n
= d
->engine
->fd
[ENGINE_IN_FD
];
3515 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
3521 if ((n
= select(n
+ 1, &rfds
, NULL
, NULL
, &tv
)) > 0) {
3522 for (i
= 0; i
< gtotal
; i
++) {
3526 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3527 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3531 if (errno
!= EAGAIN
) {
3532 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3534 waitpid(d
->engine
->pid
, &n
, 0);
3542 parse_engine_output(&game
[i
], fdbuf
);
3544 if (d
->engine
->queue
)
3545 send_engine_command(&game
[i
]);
3554 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3557 for (i
= 0; i
< gtotal
; i
++) {
3560 if (d
->engine
&& d
->engine
->queue
) {
3561 send_engine_command(&game
[i
]);
3568 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
) && game
[gindex
].mode
3570 game
[gindex
].mode
= MODE_HISTORY
;
3572 d
= game
[gindex
].data
;
3574 draw_board(&game
[gindex
], board_details
);
3575 update_all(game
[gindex
]);
3576 wmove(boardw
, ROWTOMATRIX(d
->c_row
), COLTOMATRIX(d
->c_col
));
3586 if ((c
= wgetch(boardw
)) == ERR
)
3590 if (!keycount
&& status
.notify
)
3591 update_status_notify(game
[gindex
], NULL
);
3593 if ((n
= globalkeys(c
)) == 1) {
3600 switch (game
[gindex
].mode
) {
3605 if (playmode_keys(c
))
3609 historymode_keys(c
);
3619 void usage(const char *pn
, int ret
)
3621 fprintf((ret
) ? stderr
: stdout
, "%s",
3622 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3623 " -p Load PGN file.\n"
3624 " -V Validate a game file.\n"
3625 " -S Validate and output a PGN formatted game.\n"
3626 " -R Like -S but write a reduced PGN formatted game.\n"
3627 " -t Also write custom PGN tags from config file.\n"
3628 " -E Stop processing on file parsing error (overrides config).\n"
3629 " -v Version information.\n"
3630 " -h This help text.\n");
3641 free(config
.engine_cmd
);
3642 free(config
.pattern
);
3645 for (i
= 0; config
.einit
[i
]; i
++)
3646 free(config
.einit
[i
]);
3652 pgn_tag_free(config
.tag
);
3654 if (curses_initialized
) {
3656 del_panel(historyp
);
3669 for (i
= 0; enginebuf
[i
]; i
++)
3680 void catch_signal(int which
)
3685 if (which
== SIGPIPE
&& quit
)
3688 if (which
== SIGPIPE
)
3689 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3699 keypad(boardw
, TRUE
);
3705 if (curses_initialized
) {
3706 update_loading_window(game
[gindex
]);
3710 fprintf(stderr
, "Loading... %i\r", gtotal
);
3718 static void set_defaults()
3721 set_config_defaults();
3724 int main(int argc
, char *argv
[])
3728 char buf
[FILENAME_MAX
];
3729 char datadir
[FILENAME_MAX
];
3730 int ret
= EXIT_SUCCESS
;
3731 int validate_only
= 0, validate_and_write
= 0;
3732 int write_custom_tags
= 0;
3736 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3737 err(EXIT_FAILURE
, "getpwuid()");
3739 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3740 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3741 config
.ccfile
= strdup(buf
);
3742 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3743 config
.nagfile
= strdup(buf
);
3744 snprintf(buf
, sizeof(buf
), "%s/agony.data", datadir
);
3745 config
.agonyfile
= strdup(buf
);
3746 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3747 config
.configfile
= strdup(buf
);
3749 if (stat(datadir
, &st
) == -1) {
3750 if (errno
== ENOENT
) {
3751 if (mkdir(datadir
, 0755) == -1)
3752 err(EXIT_FAILURE
, "%s", datadir
);
3755 err(EXIT_FAILURE
, "%s", datadir
);
3760 if (!S_ISDIR(st
.st_mode
))
3761 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3765 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3768 write_custom_tags
= 1;
3771 pgn_config_set(PGN_STOP_ON_ERROR
, 1);
3774 pgn_config_set(PGN_REDUCED
, 1);
3776 validate_and_write
= 1;
3781 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3785 filetype
= PGN_FILE
;
3786 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3790 usage(argv
[0], EXIT_SUCCESS
);
3794 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3795 usage(argv
[0], EXIT_FAILURE
);
3797 if (access(config
.configfile
, R_OK
) == 0)
3798 parse_rcfile(config
.configfile
);
3800 signal(SIGPIPE
, catch_signal
);
3801 signal(SIGCONT
, catch_signal
);
3802 signal(SIGSTOP
, catch_signal
);
3803 signal(SIGINT
, catch_signal
);
3804 signal(SIGUSR1
, catch_signal
);
3810 if ((fp
= pgn_open(loadfile
)) == NULL
)
3811 err(EXIT_FAILURE
, "%s", loadfile
);
3813 ret
= pgn_parse(fp
);
3816 //ret = parse_fen_file(loadfile);
3818 case EPD_FILE
: // Not implemented.
3821 // No file specified. Empty game.
3822 ret
= pgn_parse(NULL
);
3823 add_custom_tags(&game
[gindex
].tag
);
3827 if (validate_only
|| validate_and_write
) {
3828 if (validate_and_write
) {
3829 for (i
= 0; i
< gtotal
; i
++) {
3830 if (write_custom_tags
)
3831 add_custom_tags(&game
[i
].tag
);
3833 pgn_write(stdout
, game
[i
]);
3845 if (initscr() == NULL
)
3846 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3848 curses_initialized
= 1;
3850 if (LINES
< 24 || COLS
< 80) {
3852 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3855 if (has_colors() == TRUE
&& start_color() == OK
)
3858 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3859 boardp
= new_panel(boardw
);
3860 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3861 COLS
- HISTORY_WIDTH
);
3862 historyp
= new_panel(historyw
);
3863 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3864 statusp
= new_panel(statusw
);
3865 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3866 tagp
= new_panel(tagw
);
3867 keypad(boardw
, TRUE
);
3868 // leaveok(boardw, TRUE);
3869 leaveok(tagw
, TRUE
);
3870 leaveok(statusw
, TRUE
);
3871 leaveok(historyw
, TRUE
);
3875 draw_window_decor();