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
));
155 nags
= Realloc(nags
, 2 * sizeof(char *));
160 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
161 nags
= Realloc(nags
, (i
+ 2) * sizeof(char *));
162 nags
[i
++] = strdup(line
);
169 void set_menu_vars(int c
, int rows
, int items
, int *item
, int *top
)
171 int selected
= *item
;
176 selected
= toppos
= 0;
180 toppos
= items
- rows
+ 1;
183 if (selected
- 1 < 0) {
186 toppos
= selected
- rows
+ 1;
191 if (toppos
&& selected
<= toppos
)
196 if (selected
+ 1 > items
)
197 selected
= toppos
= 0;
201 if (selected
- toppos
>= rows
)
206 toppos
= (items
> rows
) ? items
- rows
+ 1 : 0;
214 int test_nag_selected(unsigned char nag
[], int s
)
218 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
226 char *history_edit_nag(void *arg
)
233 HISTORY
*anno
= (HISTORY
*)arg
;
238 unsigned char nag
[MAX_PGN_NAG
] = {0};
245 for (i
= 1, n
= 0; nags
[i
]; i
++) {
254 rows
= (total
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : total
+ 4;
256 win
= newwin(rows
, cols
, CALCPOSY(rows
), CALCPOSX(cols
));
257 panel
= new_panel(win
);
262 wbkgd(win
, CP_MESSAGE_WINDOW
);
263 memcpy(&nag
, &anno
->nag
, sizeof(nag
));
265 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
276 draw_window_title(win
, NAG_EDIT_TITLE
, cols
, CP_HISTORY_TITLE
,
279 for (i
= toppos
, c
= 2; i
< total
&& c
< rows
- 2; i
++, c
++) {
280 if ((i
== selected
&& i
!= 0)|| test_nag_selected(nag
, i
) != -1) {
281 wattron(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
282 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
283 wattroff(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
287 mvwprintw(win
, c
, 1, "%s", (nags
[i
]) ? nags
[i
] : "none");
290 snprintf(buf
, sizeof(buf
), "NAG %i of %i (%i of %i selected) %s",
291 selected
+ 1, total
, itemcount
, MAX_PGN_NAG
, NAG_EDIT_PROMPT
);
292 draw_prompt(win
, rows
- 2, cols
, buf
, CP_MESSAGE_PROMPT
);
302 help(NAG_EDIT_HELP
, ANYKEY
, naghelp
);
308 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
312 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
319 if ((found
= test_nag_selected(nag
, selected
)) != -1) {
324 if (itemcount
+ 1 > MAX_PGN_NAG
)
327 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
350 memcpy(&anno
->nag
, &nag
, sizeof(nag
));
356 static void view_nag(void *arg
)
358 HISTORY
*h
= (HISTORY
*)arg
;
360 char line
[LINE_MAX
] = {0};
363 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
370 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
374 strncat(line
, nags
[h
->nag
[i
]], sizeof(line
));
375 strncat(line
, "\n", sizeof(line
));
378 line
[strlen(line
) - 1] = 0;
379 message(buf
, ANYKEY
, "%s", line
);
382 void view_annotation(HISTORY h
)
384 char buf
[strlen(h
.move
) + strlen(ANNOTATION_VIEW_TITLE
) + 4];
385 int nag
= 0, comment
= 0;
387 if (h
.comment
&& h
.comment
[0])
393 if (!nag
&& !comment
)
396 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
.move
);
399 show_message(buf
, (nag
) ? "Any other key to continue" : ANYKEY
,
400 (nag
) ? "Press 'n' to view NAG" : NULL
,
401 (nag
) ? view_nag
: NULL
, (nag
) ? (void *)&h
: NULL
,
402 (nag
) ? 'n' : 0, "%s", h
.comment
);
404 show_message(buf
, "Any other key to continue", "Press 'n' to view NAG",
405 view_nag
, (void *)&h
, 'n', "%s", "No annotations for this move");
408 static void cleanup(WINDOW
*win
, PANEL
*panel
, struct file_s
*files
)
413 for (i
= 0; files
[i
].name
; i
++) {
425 static int sort_files(const void *a
, const void *b
)
427 const struct file_s
*aa
= a
;
428 const struct file_s
*bb
= b
;
430 return strcmp(aa
->name
, bb
->name
);
433 char *file_browser(void *arg
)
435 char pattern
[FILENAME_MAX
];
436 static char path
[FILENAME_MAX
];
437 static char file
[FILENAME_MAX
];
440 int cursor
= curs_set(0);
443 if (config
.savedirectory
) {
444 if ((p
= word_expand(config
.savedirectory
)) == NULL
)
447 strncpy(path
, p
, sizeof(path
));
449 if (access(path
, R_OK
) == -1) {
450 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
451 getcwd(path
, sizeof(path
));
455 getcwd(path
, sizeof(path
));
460 * First find directories (including hidden) in the working directory.
461 * Then apply the config.pattern to regular files.
463 if ((p
= word_split_append(path
, '/', ".* *")) == NULL
)
466 strncpy(pattern
, p
, sizeof(pattern
));
475 int len
= strlen(path
);
478 struct file_s
*files
= NULL
;
484 if (wordexp(pattern
, &w
, x
) != 0) {
485 cmessage(ERROR
, ANYKEY
, "Error in pattern\n%s", pattern
);
489 for (i
= 0; i
< w
.we_wordc
; i
++) {
494 if (stat(w
.we_wordv
[i
], &st
) == -1)
497 if ((p
= strrchr(w
.we_wordv
[i
], '/')) != NULL
)
503 if (!S_ISDIR(st
.st_mode
))
506 if (p
[0] == '.' && p
[1] == 0)
510 if (S_ISDIR(st
.st_mode
))
515 files
= Realloc(files
, (n
+ 2) * sizeof(struct file_s
));
516 files
[n
].path
= strdup(w
.we_wordv
[i
]);
517 files
[n
].name
= Malloc(len
);
518 strncpy(files
[n
].name
, p
, len
);
520 if (S_ISDIR(st
.st_mode
))
521 files
[n
].name
[len
- 2] = '/';
523 tp
= localtime(&st
.st_mtime
);
524 strftime(tbuf
, sizeof(tbuf
), "%b %d %T", tp
);
525 snprintf(sbuf
, sizeof(sbuf
), "%i %s", (int)st
.st_size
, tbuf
);
526 files
[n
].st
= strdup(sbuf
);
527 memset(&files
[++n
], '\0', sizeof(struct file_s
));
533 if ((p
= word_split_append(path
, '/', config
.pattern
)) == NULL
)
536 strncpy(pattern
, p
, sizeof(pattern
));
542 qsort(files
, n
, sizeof(struct file_s
), sort_files
);
544 for (i
= x
= nlen
= 0; i
< n
; i
++) {
545 if (strlen(files
[i
].name
) > nlen
)
546 nlen
= strlen(files
[i
].name
);
548 if (x
< nlen
+ strlen(files
[i
].st
))
549 x
= nlen
+ strlen(files
[i
].st
);
554 if (cols
< strlen(path
))
557 if (cols
< strlen(HELP_PROMPT
))
558 cols
= strlen(HELP_PROMPT
);
564 rows
= (n
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : n
+ 4;
566 win
= newwin(rows
, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
567 wbkgd(win
, CP_MESSAGE_WINDOW
);
568 panel
= new_panel(win
);
569 draw_window_title(win
, path
, cols
, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
570 draw_prompt(win
, rows
- 2, cols
, HELP_PROMPT
, CP_MESSAGE_PROMPT
);
579 for (i
= toppos
, c
= 2; i
< n
&& c
< rows
- 2; i
++, c
++) {
581 wattron(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
582 mvwprintw(win
, c
, 1, "%-*s %-*s", nlen
, files
[i
].name
,
583 cols
- nlen
- 2 - 2, files
[i
].st
);
584 wattroff(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
588 mvwprintw(win
, c
, 1, "%-*s %-*s", nlen
, files
[i
].name
,
589 cols
- nlen
- 2 - 2, files
[i
].st
);
600 set_menu_vars(c
, rows
- 4, n
- 1, &selected
, &toppos
);
606 cleanup(win
, panel
, files
);
611 help(BROWSER_HELP
, ANYKEY
, file_browser_help
);
614 strncpy(path
, "~", sizeof(path
));
615 cleanup(win
, panel
, files
);
619 if ((tmp
= get_input_str_clear(BROWSER_CHDIR_TITLE
, NULL
))
623 if (tmp
[strlen(tmp
) - 1] == '/')
624 tmp
[strlen(tmp
) - 1] = 0;
626 strncpy(path
, tmp
, sizeof(path
));
627 cleanup(win
, panel
, files
);
636 strncpy(file
, files
[selected
].path
, sizeof(file
));
637 cleanup(win
, panel
, files
);
639 if (stat(file
, &st
) == -1) {
640 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, strerror(errno
));
644 if (S_ISDIR(st
.st_mode
)) {
645 p
= file
+ strlen(file
) - 2;
647 if (strcmp(p
, "..") == 0) {
648 p
= file
+ strlen(file
) - 3;
651 if ((p
= strrchr(file
, '/')) != NULL
)
652 file
[strlen(file
) - strlen(p
)] = 0;
655 strncpy(path
, file
, sizeof(path
));
659 if (S_ISREG(st
.st_mode
))
662 cmessage(ERROR
, ANYKEY
, "%s\n%s", file
, E_NOTAREGFILE
);
667 return (*file
) ? file
: NULL
;
670 static int init_country_codes()
673 char line
[LINE_MAX
], *s
;
676 if ((fp
= fopen(config
.ccfile
, "r")) == NULL
) {
677 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.ccfile
, strerror(errno
));
681 while ((s
= fgets(line
, sizeof(line
), fp
)) != NULL
) {
684 if ((tmp
= strsep(&s
, " ")) == NULL
)
693 ccodes
= Realloc(ccodes
, (cindex
+ 2) * sizeof(struct country_codes
));
694 strncpy(ccodes
[cindex
].code
, tmp
, sizeof(ccodes
[cindex
].code
));
695 strncpy(ccodes
[cindex
].country
, s
, sizeof(ccodes
[cindex
].country
));
699 memset(&ccodes
[cindex
], '\0', sizeof(struct country_codes
));
705 char *country_codes(void *arg
)
718 if (init_country_codes())
722 for (i
= n
= 0; ccodes
[i
].code
&& ccodes
[i
].code
[0]; i
++) {
723 n
= strlen(ccodes
[i
].code
) + strlen(ccodes
[i
].country
);
732 if (cols
< strlen(HELP_PROMPT
) + 21)
733 cols
= strlen(HELP_PROMPT
) + 21;
741 rows
= (i
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 : i
+ 4;
742 win
= newwin(rows
, cols
, CALCPOSY(rows
) - 2, CALCPOSX(cols
));
743 panel
= new_panel(win
);
748 wbkgd(win
, CP_MESSAGE_WINDOW
);
757 draw_window_title(win
, CC_TITLE
, cols
, CP_MESSAGE_TITLE
,
760 for (i
= toppos
, c
= 2; i
< total
&& c
< rows
- 2; i
++, c
++) {
762 wattron(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
763 mvwprintw(win
, c
, 1, "%3s %s", ccodes
[i
].code
,
765 wattroff(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
769 mvwprintw(win
, c
, 1, "%3s %s", ccodes
[i
].code
, ccodes
[i
].country
);
772 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_ITEM_STR
,
773 selected
+ 1, N_OF_N_STR
, total
, HELP_PROMPT
);
774 draw_prompt(win
, rows
- 2, cols
, buf
, CP_MESSAGE_PROMPT
);
780 help(CC_KEY_HELP
, ANYKEY
, cc_help
);
786 set_menu_vars(c
, rows
- 4, total
- 1, &selected
, &toppos
);
789 tmp
= ccodes
[selected
].code
;
807 static void add_custom_tags(TAG
***t
)
810 int total
= pgn_tag_total(config
.tag
);
815 for (i
= 0; i
< total
; i
++)
816 pgn_tag_add(t
, config
.tag
[i
]->name
, config
.tag
[i
]->value
);
821 TAG
**edit_tags(GAME g
, BOARD b
, int edit
)
831 /* Edit the backup copy, not the original in case the save fails. */
832 len
= pgn_tag_total(g
.tag
);
834 for (n
= 0; n
< len
; n
++)
835 pgn_tag_add(&data
, g
.tag
[n
]->name
, g
.tag
[n
]->value
);
837 data_index
= pgn_tag_total(data
);
848 data_index
= pgn_tag_total(data
);
850 for (i
= cols
= 0, n
= 4; i
< data_index
; i
++) {
851 n
= strlen(data
[i
]->name
);
857 n
+= strlen(data
[i
]->value
);
859 n
+= strlen(UNKNOWN
);
870 /* +14 for the extra prompt info. */
871 if (cols
< strlen(HELP_PROMPT
) + 14 + 2)
872 cols
= strlen(HELP_PROMPT
) + 14 + 2;
874 rows
= (data_index
+ 4 > (LINES
/ 5) * 4) ? (LINES
/ 5) * 4 :
877 win
= newwin(rows
, cols
, CALCPOSY(rows
), CALCPOSX(cols
));
878 panel
= new_panel(win
);
883 wbkgd(win
, CP_MESSAGE_WINDOW
);
884 draw_window_title(win
, (edit
) ? TAG_EDIT_TITLE
: TAG_VIEW_TITLE
,
885 cols
, CP_MESSAGE_TITLE
, CP_MESSAGE_BORDER
);
887 if (selected
>= data_index
- 1)
888 selected
= data_index
- 1;
895 for (i
= toppos
, c
= 2; i
< data_index
&& c
< rows
- 2; i
++, c
++) {
897 wattron(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
898 mvwprintw(win
, c
, 1, "%*s: %-*s", nlen
, data
[i
]->name
,
899 cols
- nlen
- 2 - 2, (data
[i
]->value
&&
900 data
[i
]->value
[0]) ? data
[i
]->value
: UNKNOWN
);
901 wattroff(win
, CP_MESSAGE_WINDOW
| A_REVERSE
);
905 mvwprintw(win
, c
, 1, "%*s: %-*s", nlen
, data
[i
]->name
,
906 cols
- nlen
- 2 - 2, (data
[i
]->value
&&
907 data
[i
]->value
[0]) ? data
[i
]->value
: UNKNOWN
);
910 snprintf(buf
, sizeof(buf
), "%s %i %s %i %s", MENU_TAG_STR
,
911 selected
+ 1, N_OF_N_STR
, data_index
, HELP_PROMPT
);
912 draw_prompt(win
, rows
- 2, cols
, buf
, CP_MESSAGE_PROMPT
);
921 add_custom_tags(&data
);
922 selected
= data_index
- 1;
923 toppos
= data_index
- (rows
- 4);
928 help(TAG_EDIT_HELP
, ANYKEY
, pgn_edit_help
);
930 help(TAG_VIEW_HELP
, ANYKEY
, pgn_info_help
);
937 cmessage(NULL
, ANYKEY
, "%s", E_REMOVE_STR
);
941 data_index
= pgn_tag_total(data
);
943 for (i
= 0; i
< data_index
; i
++) {
947 pgn_tag_add(&tmppgn
, data
[i
]->name
, data
[i
]->value
);
953 for (i
= 0; tmppgn
[i
]; i
++)
954 pgn_tag_add(&data
, tmppgn
[i
]->name
, tmppgn
[i
]->value
);
956 pgn_tag_free(tmppgn
);
958 if (selected
>= data_index
)
959 selected
= data_index
- 1;
961 if (selected
> rows
- 5)
962 toppos
= selected
- (rows
- 5);
964 toppos
-= (toppos
) ? 1 : 0;
972 if ((newtag
= get_input(TAG_NEW_TITLE
, NULL
, 1, 1, NULL
,
973 NULL
, NULL
, 0, FIELD_TYPE_PGN_TAG_NAME
))
977 newtag
[0] = toupper(newtag
[0]);
979 if (strlen(newtag
) > MAX_VALUE_WIDTH
- 6 -
980 strlen(PRESS_ENTER
)) {
981 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_NAMETOOLONG
);
985 for (i
= 0; i
< data_index
; i
++) {
986 if (strcasecmp(data
[i
]->name
, newtag
) == 0) {
992 pgn_tag_add(&data
, newtag
, NULL
);
993 data_index
= pgn_tag_total(data
);
994 selected
= data_index
- 1;
995 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1003 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1010 pgn_tag_add(&data
, "FEN", pgn_game_to_fen(g
, b
));
1011 data_index
= pgn_tag_total(data
);
1012 selected
= data_index
- 1;
1013 set_menu_vars(c
, rows
- 4, data_index
- 1, &selected
,
1031 nlen
= strlen(data
[selected
]->name
) + 2;
1032 nlen
+= (edit
) ? strlen(TAG_EDIT_TAG_TITLE
) : strlen(TAG_VIEW_TAG_TITLE
);
1034 if (nlen
> MAX_VALUE_WIDTH
)
1035 snprintf(buf
, sizeof(buf
), "%s", data
[selected
]->name
);
1037 snprintf(buf
, sizeof(buf
), "%s \"%s\"",
1038 (edit
) ? TAG_EDIT_TAG_TITLE
: TAG_VIEW_TAG_TITLE
,
1039 data
[selected
]->name
);
1042 if (!data
[selected
]->value
)
1045 cmessage(buf
, ANYKEY
, "%s", data
[selected
]->value
);
1049 if (strcmp(data
[selected
]->name
, "Date") == 0) {
1050 tmp
= get_input(buf
, data
[selected
]->value
, 0, 0, NULL
, NULL
, NULL
,
1051 0, FIELD_TYPE_PGN_DATE
);
1054 if (strptime(tmp
, PGN_TIME_FORMAT
, &tp
) == NULL
) {
1055 cmessage(ERROR
, ANYKEY
, "%s", E_TAG_DATE_FMT
);
1062 else if (strcmp(data
[selected
]->name
, "Site") == 0) {
1063 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, CC_PROMPT
,
1064 country_codes
, NULL
, CTRL('t'), -1);
1069 else if (strcmp(data
[selected
]->name
, "Round") == 0) {
1070 tmp
= get_input(buf
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1071 FIELD_TYPE_PGN_ROUND
);
1080 else if (strcmp(data
[selected
]->name
, "Result") == 0) {
1081 tmp
= get_input(buf
, data
[selected
]->value
, 1, 1, NULL
, NULL
, NULL
,
1088 tmp
= (data
[selected
]->value
) ? data
[selected
]->value
: NULL
;
1089 tmp
= get_input(buf
, tmp
, 0, 0, NULL
, NULL
, NULL
, 0, -1);
1092 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
1093 data
[selected
]->value
= Realloc(data
[selected
]->value
, len
);
1094 strncpy(data
[selected
]->value
, (tmp
) ? tmp
: "", len
);
1110 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
1111 * game index number.
1113 int save_pgn(const char *filename
, int isfifo
, int saveindex
)
1118 char buf
[FILENAME_MAX
];
1121 char *command
= NULL
;
1122 int saveindex_max
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
1124 if (filename
[0] != '/' && config
.savedirectory
&& !isfifo
) {
1125 if (stat(config
.savedirectory
, &st
) == -1) {
1126 if (errno
== ENOENT
) {
1127 if (mkdir(config
.savedirectory
, 0755) == -1) {
1128 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1134 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
1140 stat(config
.savedirectory
, &st
);
1142 if (!S_ISDIR(st
.st_mode
)) {
1143 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
1147 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
1151 /* This is a hack to resume an existing game when more than one game is
1152 * available. Also resuming a saved game and a game from history.
1154 // FIXME: may not need this when a FEN tag is supported (by the engine).
1158 if (access(filename
, W_OK
) == 0) {
1159 c
= cmessage(NULL
, GAME_SAVE_OVERWRITE_PROMPT
,
1160 "%s \"%s\"", E_FILEEXISTS
, filename
);
1164 if (pgn_is_compressed(filename
)) {
1165 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
1183 if ((fp
= popen(command
, "w")) == NULL
) {
1184 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1189 if ((fp
= fopen(filename
, mode
)) == NULL
) {
1190 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
1196 pgn_write(fp
, game
[saveindex
]);
1198 for (i
= (saveindex
== -1) ? 0 : saveindex
; i
< saveindex_max
; i
++)
1199 pgn_write(fp
, game
[i
]);
1207 if (!isfifo
&& saveindex
== -1)
1208 strncpy(loadfile
, filename
, sizeof(loadfile
));
1213 char *random_agony(GAME g
)
1217 char line
[LINE_MAX
];
1219 if (n
== -1 || !config
.agony
|| !curses_initialized
||
1220 (g
.mode
== MODE_HISTORY
&& !config
.historyagony
))
1224 if ((fp
= fopen(config
.agonyfile
, "r")) == NULL
) {
1226 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.agonyfile
, strerror(errno
));
1231 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
1232 agony
= Realloc(agony
, (n
+ 2) * sizeof(char *));
1233 agony
[n
++] = strdup(trim(line
));
1240 if (agony
[0] == NULL
|| !n
) {
1246 return agony
[random() % n
];
1249 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
1251 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1253 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
1254 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1256 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
1259 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1261 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
1262 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
1264 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
1267 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
1269 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
1270 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1272 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
1275 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
1277 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
1278 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
1280 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
1283 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1285 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
1286 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
1288 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
1289 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
1291 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
1292 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
1293 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1295 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
1299 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
1301 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
1302 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
1304 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
1305 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
1307 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
1308 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
1309 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1311 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
1319 static void draw_board(GAME
*g
, int details
)
1322 int bcol
= 0, brow
= 0;
1323 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
1324 int ncols
= 0, offset
= 1;
1325 unsigned coords_y
= 8;
1327 if (g
->mode
!= MODE_PLAY
&& g
->mode
!= MODE_EDIT
)
1328 update_cursor(*g
, g
->hindex
);
1330 for (row
= 0; row
< maxy
; row
++) {
1333 for (col
= 0; col
< maxx
; col
++) {
1336 unsigned char piece
;
1338 if (row
== 0 || row
== maxy
- 2) {
1340 mvwaddch(boardw
, row
, col
,
1341 LINE_GRAPHIC((row
) ?
1342 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
1343 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
1344 else if (col
== maxx
- 2)
1345 mvwaddch(boardw
, row
, col
,
1346 LINE_GRAPHIC((row
) ?
1347 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
1348 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
1349 else if (!(col
% 4))
1350 mvwaddch(boardw
, row
, col
,
1351 LINE_GRAPHIC((row
) ?
1352 ACS_BTEE
| CP_BOARD_GRAPHICS
:
1353 ACS_TTEE
| CP_BOARD_GRAPHICS
));
1355 if (col
!= maxx
- 1)
1356 mvwaddch(boardw
, row
, col
,
1357 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1363 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
1364 wattron(boardw
, CP_BOARD_COORDS
);
1365 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
1366 wattroff(boardw
, CP_BOARD_COORDS
);
1370 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
1372 mvwaddch(boardw
, row
, col
,
1373 LINE_GRAPHIC((col
) ?
1374 ACS_RTEE
| CP_BOARD_GRAPHICS
:
1375 ACS_LTEE
| CP_BOARD_GRAPHICS
));
1377 mvwaddch(boardw
, row
, col
,
1378 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1383 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
1384 mvwaddch(boardw
, row
, col
,
1385 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
1389 if (!(col
% 4) && row
!= maxy
- 1) {
1390 mvwaddch(boardw
, row
, col
,
1391 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
1402 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
1408 if (config
.validmoves
&& g
->b
[brow
][bcol
].valid
) {
1409 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
1410 CP_BOARD_MOVES_BLACK
;
1413 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
1416 if (row
== ROWTOMATRIX(c_row
) && col
==
1417 COLTOMATRIX(c_col
)) {
1418 attrs
= CP_BOARD_CURSOR
;
1421 if (row
== ROWTOMATRIX(sp
.row
) &&
1422 col
== COLTOMATRIX(sp
.col
)) {
1423 attrs
= CP_BOARD_SELECTED
;
1426 if (row
== maxy
- 1)
1429 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
1431 if (row
== maxy
- 1)
1432 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
1434 if (details
&& g
->b
[row
/ 2][bcol
].enpassant
)
1437 piece
= g
->b
[row
/ 2][bcol
].icon
;
1439 if (details
&& castling_state(g
, g
->b
, brow
, bcol
,
1443 if (g
->side
== WHITE
&& isupper(piece
))
1445 else if (g
->side
== BLACK
&& islower(piece
))
1448 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
1450 CLEAR_FLAG(attrs
, A_BOLD
);
1451 CLEAR_FLAG(attrs
, A_REVERSE
);
1454 waddch(boardw
, ' ' | attrs
);
1460 if (col
!= maxx
- 1)
1461 mvwaddch(boardw
, row
, col
,
1462 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
1470 void invalid_move(int n
, const char *m
)
1472 if (curses_initialized
)
1473 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", E_INVALID_MOVE
, m
, n
);
1475 warnx("%s: %s \"%s\" (round #%i)", loadfile
, E_INVALID_MOVE
, m
, n
);
1478 /* Convert the selected piece to SAN format and validate it. */
1479 static char *board_to_san(GAME
*g
, BOARD b
)
1481 static char str
[MAX_SAN_MOVE_LEN
+ 1], *p
;
1484 struct userdata_s
*d
= g
->data
;
1486 snprintf(str
, sizeof(str
), "%c%i%c%i", x_grid_chars
[sp
.col
- 1],
1487 sp
.row
, x_grid_chars
[sp
.destcol
- 1], sp
.destrow
);
1490 piece
= pgn_piece_to_int(b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
);
1492 if (piece
== PAWN
&& ((sp
.destrow
== 8 && g
->turn
== WHITE
) ||
1493 (sp
.destrow
== 1 && g
->turn
== BLACK
))) {
1494 promo
= cmessage(PROMOTION_TITLE
, PROMOTION_PROMPT
, PROMOTION_TEXT
);
1496 if (pgn_piece_to_int(promo
) == -1)
1499 p
= str
+ strlen(str
);
1500 *p
++ = toupper(promo
);
1506 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
1507 if (pgn_validate_move(g
, b
, &p
)) {
1508 invalid_move(gindex
+ 1, p
);
1515 if (pgn_validate_only(g
, b
, &p
)) {
1516 invalid_move(gindex
+ 1, p
);
1523 static int move_to_engine(GAME
*g
, BOARD b
)
1526 struct userdata_s
*d
= g
->data
;
1528 if ((p
= board_to_san(g
, b
)) == NULL
)
1531 sp
.row
= sp
.col
= sp
.icon
= 0;
1533 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
1534 pgn_history_add(g
, p
);
1536 SET_FLAG(g
->flags
, GF_MODIFIED
);
1541 send_to_engine(g
, "%s\n", p
);
1545 static void update_clock(int n
, int *h
, int *m
, int *s
)
1548 *m
= (n
% 3600) / 60;
1549 *s
= (n
% 3600) % 60;
1554 void update_status_window(GAME g
)
1558 char tmp
[15], *engine
, *mode
;
1564 struct userdata_s
*d
= g
.data
;
1566 getmaxyx(statusw
, maxy
, maxx
);
1574 if (TEST_FLAG(g
.flags
, GF_DELETE
)) {
1580 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1590 if (TEST_FLAG(g
.flags
, GF_MODIFIED
)) {
1605 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1606 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1607 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1609 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1613 mode
= MODE_HISTORY_STR
;
1616 mode
= MODE_EDIT_STR
;
1619 mode
= MODE_PLAY_STR
;
1626 snprintf(buf
, len
- 1, "%*s %s", 7, STATUS_MODE_STR
, mode
);
1628 if (g
.mode
== MODE_PLAY
) {
1629 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1630 strncat(buf
, " (human/human)", len
- 1);
1631 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1632 strncat(buf
, " (engine/engine)", len
- 1);
1634 strncat(buf
, " (human/engine)", len
- 1);
1637 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1640 switch (d
->engine
->status
) {
1641 case ENGINE_THINKING
:
1642 engine
= ENGINE_PONDER_STR
;
1645 engine
= ENGINE_READY_STR
;
1647 case ENGINE_INITIALIZING
:
1648 engine
= ENGINE_INITIALIZING_STR
;
1650 case ENGINE_OFFLINE
:
1651 engine
= ENGINE_OFFLINE_STR
;
1659 engine
= ENGINE_OFFLINE_STR
;
1661 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1662 wattron(statusw
, CP_STATUS_ENGINE
);
1663 mvwaddstr(statusw
, 5, 9, engine
);
1664 wattroff(statusw
, CP_STATUS_ENGINE
);
1666 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1667 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1669 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1670 tmp
[0] = toupper(tmp
[0]);
1671 update_clock(g
.moveclock
, &h
, &m
, &s
);
1672 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1673 mvwprintw(statusw
, 7, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1675 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1676 tmp
[0] = toupper(tmp
[0]);
1677 update_clock(g
.moveclock
, &h
, &m
, &s
);
1678 snprintf(buf
, len
, "%.2i:%.2i:%.2i", h
, m
, s
);
1679 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, buf
);
1682 for (i
= 1; i
< maxx
- 4; i
++)
1683 mvwprintw(statusw
, maxy
- 2, i
, " ");
1686 status
.notify
= strdup(GAME_HELP_PROMPT
);
1688 wattron(statusw
, CP_STATUS_NOTIFY
);
1689 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1691 wattroff(statusw
, CP_STATUS_NOTIFY
);
1694 void update_history_window(GAME g
)
1696 char buf
[HISTORY_WIDTH
- 1];
1699 int t
= pgn_history_total(g
.hp
);
1701 n
= (g
.hindex
+ 1) / 2;
1704 total
= (t
+ 1) / 2;
1709 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1710 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
1712 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1714 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1715 HISTORY_WIDTH
- 13, buf
);
1717 h
= pgn_history_by_n(g
.hp
, g
.hindex
);
1718 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1721 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1722 strncat(buf
, " (v", sizeof(buf
));
1727 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1732 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1737 strncat(buf
, ")", sizeof(buf
));
1739 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1740 HISTORY_WIDTH
- 13, buf
);
1742 h
= pgn_history_by_n(g
.hp
, game
[gindex
].hindex
- 1);
1743 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1746 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1747 strncat(buf
, " (V", sizeof(buf
));
1752 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1757 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1762 strncat(buf
, ")", sizeof(buf
));
1764 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1765 HISTORY_WIDTH
- 13, buf
);
1768 void update_tag_window(TAG
**t
)
1771 int w
= TAG_WIDTH
- 10;
1773 for (i
= 0; i
< 7; i
++)
1774 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
]->name
, w
, t
[i
]->value
);
1777 void draw_prompt(WINDOW
*win
, int y
, int width
, const char *str
, chtype attr
)
1783 for (i
= 1; i
< width
- 1; i
++)
1784 mvwaddch(win
, y
, i
, ' ');
1786 mvwprintw(win
, y
, CENTERX(width
, str
), "%s", str
);
1787 wattroff(win
, attr
);
1790 void draw_window_title(WINDOW
*win
, const char *title
, int width
, chtype attr
,
1798 for (i
= 1; i
< width
- 1; i
++)
1799 mvwaddch(win
, 1, i
, ' ');
1801 mvwprintw(win
, 1, CENTERX(width
, title
), "%s", title
);
1802 wattroff(win
, attr
);
1805 wattron(win
, battr
);
1806 box(win
, ACS_VLINE
, ACS_HLINE
);
1807 wattroff(win
, battr
);
1810 void append_enginebuf(char *line
)
1815 for (i
= 0; enginebuf
[i
]; i
++);
1817 if (i
>= LINES
- 3) {
1820 for (i
= 0; enginebuf
[i
+1]; i
++)
1821 enginebuf
[i
] = enginebuf
[i
+1];
1823 enginebuf
[i
] = strdup(line
);
1826 enginebuf
= Realloc(enginebuf
, (i
+ 2) * sizeof(char *));
1827 enginebuf
[i
++] = strdup(line
);
1828 enginebuf
[i
] = NULL
;
1832 void update_engine_window()
1839 wmove(enginew
, 0, 0);
1843 for (i
= 0; enginebuf
[i
]; i
++)
1844 mvwprintw(enginew
, i
+ 2, 1, "%s", enginebuf
[i
]);
1847 draw_window_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1851 void toggle_engine_window()
1854 enginew
= newwin(LINES
, COLS
, 0, 0);
1855 enginep
= new_panel(enginew
);
1856 draw_window_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1858 hide_panel(enginep
);
1861 if (panel_hidden(enginep
)) {
1862 update_engine_window();
1867 hide_panel(enginep
);
1878 void update_all(GAME g
)
1880 update_status_window(g
);
1881 update_history_window(g
);
1882 update_tag_window(g
.tag
);
1883 update_engine_window();
1886 static void game_next_prev(GAME g
, int n
, int count
)
1892 if (gindex
+ count
> gtotal
- 1) {
1894 gindex
= gtotal
- 1;
1902 if (gindex
- count
< 0) {
1906 gindex
= gtotal
- 1;
1913 static void delete_game(int which
)
1919 for (i
= 0; i
< gtotal
; i
++) {
1920 if (i
== which
|| TEST_FLAG(game
[i
].flags
, GF_DELETE
)) {
1925 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
1926 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
1927 g
[gi
].tag
= game
[i
].tag
;
1928 g
[gi
].history
= game
[i
].history
;
1929 g
[gi
].hp
= game
[i
].hp
;
1937 if (which
+ 1 >= gtotal
)
1938 gindex
= gtotal
- 1;
1943 gindex
= gtotal
- 1;
1945 game
[gindex
].hp
= game
[gindex
].history
;
1948 static int find_move_exp(GAME g
, const char *str
, int init
, int which
,
1954 static int firstrun
= 1;
1963 if ((ret
= regcomp(&r
, str
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
1964 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
1965 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
1972 incr
= (which
== 0) ? -1 : 1;
1974 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
1975 if (i
== g
.hindex
- 1)
1978 if (i
>= pgn_history_total(g
.hp
))
1981 i
= pgn_history_total(g
.hp
) - 1;
1984 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
1987 if (count
== ++found
) {
1992 if (ret
!= REG_NOMATCH
) {
1993 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
1994 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
2003 static int toggle_delete_flag(int n
)
2007 TOGGLE_FLAG(game
[n
].flags
, GF_DELETE
);
2009 for (i
= x
= 0; i
< gtotal
; i
++) {
2010 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2015 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2016 CLEAR_FLAG(game
[n
].flags
, GF_DELETE
);
2023 static void edit_save_tags(GAME
*g
)
2027 if ((t
= edit_tags(*g
, g
->b
, 1)) == NULL
)
2030 pgn_tag_free(g
->tag
);
2032 SET_FLAG(g
->flags
, GF_MODIFIED
);
2033 pgn_tag_sort(g
->tag
);
2036 static int find_game_exp(char *str
, int which
, int count
)
2038 char *nstr
= NULL
, *exp
= NULL
;
2042 char buf
[255], *tmp
;
2045 int incr
= (which
== 0) ? -(1) : 1;
2047 strncpy(buf
, str
, sizeof(buf
));
2050 if (strstr(tmp
, ":") != NULL
) {
2051 nstr
= strsep(&tmp
, ":");
2053 if ((ret
= regcomp(&nexp
, nstr
,
2054 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
2055 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
2056 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2067 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2068 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
2069 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2076 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
2087 for (t
= 0; game
[g
].tag
[t
]; t
++) {
2089 if (regexec(&nexp
, game
[g
].tag
[t
]->name
, 0, 0, 0) == 0) {
2090 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2091 if (count
== ++found
) {
2099 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
2100 if (count
== ++found
) {
2122 * Updates the notification line in the status window then refreshes the
2125 void update_status_notify(GAME g
, char *fmt
, ...)
2128 #ifdef HAVE_VASPRINTF
2135 if (status
.notify
) {
2136 free(status
.notify
);
2137 status
.notify
= NULL
;
2139 if (curses_initialized
)
2140 update_status_window(g
);
2147 #ifdef HAVE_VASPRINTF
2148 vasprintf(&line
, fmt
, ap
);
2150 vsnprintf(line
, sizeof(line
), fmt
, ap
);
2155 free(status
.notify
);
2157 status
.notify
= strdup(line
);
2159 #ifdef HAVE_VASPRINTF
2162 if (curses_initialized
)
2163 update_status_window(g
);
2166 static void switch_side(GAME
*g
)
2168 g
->side
= (g
->side
== WHITE
) ? BLACK
: WHITE
;
2171 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
2175 if (g
->hp
[g
->hindex
]->rav
== NULL
)
2178 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
2179 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
2180 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
2181 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
2182 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
2183 g
->hp
= g
->hp
[g
->hindex
]->rav
;
2189 if (g
->ravlevel
- 1 < 0)
2194 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
2195 free(g
->rav
[g
->ravlevel
].fen
);
2196 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
2197 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
2198 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
2202 static void draw_window_decor()
2204 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
2205 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
2206 wbkgd(boardw
, CP_BOARD_WINDOW
);
2207 wbkgd(statusw
, CP_STATUS_WINDOW
);
2208 draw_window_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
2209 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
2210 wbkgd(tagw
, CP_TAG_WINDOW
);
2211 draw_window_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
2213 wbkgd(historyw
, CP_HISTORY_WINDOW
);
2214 draw_window_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
2215 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
2218 static void do_window_resize()
2220 if (LINES
< 24 || COLS
< 80)
2223 resizeterm(LINES
, COLS
);
2224 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
2225 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
2226 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
2227 wmove(historyw
, 0, 0);
2228 wclrtobot(historyw
);
2231 wmove(statusw
, 0, 0);
2233 draw_window_decor();
2234 update_all(game
[gindex
]);
2237 static void historymode_keys(chtype
);
2238 static int playmode_keys(chtype c
)
2240 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
2241 int editmode
= (game
[gindex
].mode
== MODE_EDIT
) ? 1 : 0;
2245 struct userdata_s
*d
= game
[gindex
].data
;
2249 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
2251 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2252 pgn_history_total(game
[gindex
].hp
)) {
2253 pgn_tag_add(&game
[gindex
].tag
, "FEN",
2254 pgn_game_to_fen(game
[gindex
], game
[gindex
].b
));
2255 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2257 if (start_chess_engine(&game
[gindex
]) <= 0) {
2258 send_to_engine(&game
[gindex
], "setboard %s\n",
2259 game
[gindex
].tag
[x
]->value
);
2260 d
->engine
->status
= ENGINE_READY
;
2264 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2267 d
->engine
->status
= ENGINE_READY
;
2269 update_all(game
[gindex
]);
2275 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2276 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
2278 if (d
->engine
&& !TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
2279 d
->engine
->status
= ENGINE_READY
;
2281 update_all(game
[gindex
]);
2287 if (d
->engine
->status
== ENGINE_OFFLINE
)
2290 x
= d
->engine
->status
;
2292 if ((tmp
= get_input_str_clear(ENGINE_CMD_TITLE
, NULL
)) != NULL
)
2293 send_to_engine(&game
[gindex
], "%s\n", tmp
);
2294 d
->engine
->status
= x
;
2298 pushkey
= keycount
= 0;
2299 update_status_notify(game
[gindex
], NULL
);
2301 if (!editmode
&& !TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2302 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
)) {
2314 p
= game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
;
2315 game
[gindex
].b
[ROWTOBOARD(sp
.destrow
)][COLTOBOARD(sp
.destcol
)].icon
= p
;
2316 game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
=
2317 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2318 sp
.icon
= sp
.row
= sp
.col
= 0;
2322 if (move_to_engine(&game
[gindex
], game
[gindex
].b
)) {
2323 if (config
.validmoves
)
2324 pgn_reset_valid_moves(game
[gindex
].b
);
2326 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
)) {
2327 CLEAR_FLAG(game
[gindex
].flags
, GF_GAMEOVER
);
2328 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2334 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2335 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
2336 if (start_chess_engine(&game
[gindex
])) {
2341 x
= pgn_tag_find(game
[gindex
].tag
, "FEN");
2342 w
= pgn_tag_find(game
[gindex
].tag
, "SetUp");
2344 if ((w
>= 0 && x
>= 0 && atoi(game
[gindex
].tag
[w
]->value
) == 1)
2345 || (x
>= 0 && w
== -1)) {
2346 send_to_engine(&game
[gindex
], "setboard %s\n",
2347 game
[gindex
].tag
[x
]->value
);
2348 d
->engine
->status
= ENGINE_READY
;
2352 if (sp
.icon
|| (!editmode
&& d
->engine
&&
2353 d
->engine
->status
== ENGINE_THINKING
)) {
2358 sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(c_row
),
2359 COLTOMATRIX(c_col
)+1) & A_CHARTEXT
;
2361 if (sp
.icon
== ' ') {
2366 if (!editmode
&& ((islower(sp
.icon
) && game
[gindex
].turn
!= BLACK
)
2367 || (isupper(sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
2368 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
2376 if (!editmode
&& config
.validmoves
)
2377 pgn_get_valid_moves(&game
[gindex
], game
[gindex
].b
, sp
.row
,
2383 send_to_engine(&game
[gindex
], "\nswitch\n");
2384 switch_side(&game
[gindex
]);
2385 update_status_window(game
[gindex
]);
2388 if (!pgn_history_total(game
[gindex
].hp
))
2391 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2392 send_to_engine(&game
[gindex
], "remove\n");
2393 d
->engine
->status
= ENGINE_READY
;
2396 game
[gindex
].hindex
-= 2;
2397 pgn_history_free(game
[gindex
].hp
, game
[gindex
].hindex
);
2398 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
);
2399 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
2400 game
[gindex
].hindex
);
2401 update_history_window(game
[gindex
]);
2404 historymode_keys(c
);
2407 board_details
= (board_details
) ? 0 : 1;
2410 paused
= (paused
) ? 0 : 1;
2413 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
)
2414 start_chess_engine(&game
[gindex
]);
2416 send_to_engine(&game
[gindex
], "go\n");
2423 for (x
= 0; config
.keys
[x
]; x
++) {
2424 if (config
.keys
[x
]->c
== c
) {
2425 send_to_engine(&game
[gindex
], "%s\n",
2426 config
.keys
[x
]->str
);
2427 d
->engine
->status
= ENGINE_READY
;
2438 static void editmode_keys(chtype c
)
2448 game
[gindex
].b
[ROWTOBOARD(sp
.row
)][COLTOBOARD(sp
.col
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2450 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
= pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2452 sp
.icon
= sp
.row
= sp
.col
= 0;
2455 pgn_switch_turn(&game
[gindex
]);
2456 switch_side(&game
[gindex
]);
2457 update_all(game
[gindex
]);
2460 castling_state(&game
[gindex
], game
[gindex
].b
, ROWTOBOARD(c_row
),
2462 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
, 1);
2465 c
= message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, "%s",
2468 if (pgn_piece_to_int(c
) == -1)
2471 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].icon
= c
;
2474 if (c_row
== 6 || c_row
== 3) {
2475 pgn_reset_enpassant(game
[gindex
].b
);
2476 game
[gindex
].b
[ROWTOBOARD(c_row
)][COLTOBOARD(c_col
)].enpassant
= 1;
2484 static void historymode_keys(chtype c
)
2488 static char moveexp
[255] = {0};
2492 movestep
= (movestep
== 1) ? 2 : 1;
2493 update_history_window(game
[gindex
]);
2496 pgn_history_next(&game
[gindex
], game
[gindex
].b
, (keycount
> 0) ?
2497 config
.jumpcount
* keycount
* movestep
:
2498 config
.jumpcount
* movestep
);
2499 update_all(game
[gindex
]);
2502 pgn_history_prev(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2503 config
.jumpcount
* keycount
* movestep
:
2504 config
.jumpcount
* movestep
);
2505 update_all(game
[gindex
]);
2508 pgn_history_prev(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2509 keycount
* movestep
: movestep
);
2510 update_all(game
[gindex
]);
2513 pgn_history_next(&game
[gindex
], game
[gindex
].b
, (keycount
) ?
2514 keycount
* movestep
: movestep
);
2515 update_all(game
[gindex
]);
2518 n
= game
[gindex
].hindex
;
2520 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
2526 snprintf(buf
, COLS
- 1, "%s \"%s\"", ANNOTATION_EDIT_TITLE
,
2527 game
[gindex
].hp
[n
]->move
);
2529 tmp
= get_input(buf
, game
[gindex
].hp
[n
]->comment
, 0, 0, NAG_PROMPT
,
2530 history_edit_nag
, (void *)game
[gindex
].hp
[n
], CTRL('T'),
2534 if (!tmp
&& (!game
[gindex
].hp
[n
]->comment
||
2535 !*game
[gindex
].hp
[n
]->comment
))
2537 else if (tmp
&& game
[gindex
].hp
[n
]->comment
) {
2538 if (strcmp(tmp
, game
[gindex
].hp
[n
]->comment
) == 0)
2542 len
= (tmp
) ? strlen(tmp
) + 1 : 1;
2543 game
[gindex
].hp
[n
]->comment
= Realloc(game
[gindex
].hp
[n
]->comment
,
2545 strncpy(game
[gindex
].hp
[n
]->comment
, (tmp
) ? tmp
: "", len
);
2546 SET_FLAG(game
[gindex
].flags
, GF_MODIFIED
);
2547 update_all(game
[gindex
]);
2552 if (pgn_history_total(game
[gindex
].hp
) < 2)
2557 if (!*moveexp
|| c
== '/') {
2558 if ((tmp
= get_input(FIND_REGEXP
, moveexp
, 1, 1, NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2561 strncpy(moveexp
, tmp
, sizeof(moveexp
));
2565 if ((n
= find_move_exp(game
[gindex
], moveexp
, n
,
2566 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1))
2570 game
[gindex
].hindex
= n
;
2571 pgn_board_update(&game
[gindex
], game
[gindex
].b
, game
[gindex
].hindex
);
2572 update_all(game
[gindex
]);
2575 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
]);
2578 if (game
[gindex
].hindex
- 1 >= 0)
2579 view_annotation(*game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2583 rav_next_prev(&game
[gindex
], game
[gindex
].b
, (c
== '-') ? 0 : 1);
2584 update_all(game
[gindex
]);
2587 if (pgn_history_total(game
[gindex
].hp
) < 2)
2590 /* FIXME field validation
2591 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
2592 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
2593 game[gindex].htotal)) == NULL)
2598 if ((tmp
= get_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1,
2599 NULL
, NULL
, NULL
, 0, -1)) == NULL
)
2602 if (!isinteger(tmp
))
2610 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
2613 game
[gindex
].hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2614 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
2615 game
[gindex
].hindex
);
2616 update_all(game
[gindex
]);
2623 static void cleanup_all_games()
2627 for (i
= 0; i
< gtotal
; i
++) {
2628 struct userdata_s
*d
;
2631 stop_engine(&game
[i
]);
2634 game
[i
].data
= NULL
;
2639 void update_loading_window()
2642 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
2643 loadingp
= new_panel(loadingw
);
2644 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
2647 wmove(loadingw
, 0, 0);
2648 wclrtobot(loadingw
);
2649 wattron(loadingw
, CP_MESSAGE_BORDER
);
2650 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
2651 wattroff(loadingw
, CP_MESSAGE_BORDER
);
2652 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
2653 11 + strlen(itoa(gtotal
))), "Loading... %i", gtotal
);
2658 void init_userdata()
2662 for (i
= 0; i
< gtotal
; i
++) {
2663 struct userdata_s
*d
= NULL
;
2665 d
= Calloc(1, sizeof(struct userdata_s
));
2671 // Global and other keys.
2672 static int globalkeys(chtype c
)
2674 static char gameexp
[255] = {0};
2678 char tfile
[FILENAME_MAX
];
2679 struct userdata_s
*d
= game
[gindex
].data
;
2683 toggle_engine_window();
2686 cmessage("ABOUT", ANYKEY
, "%s\n%s with %i colors and %i "
2687 "color pairs\nCopyright 2002-2006 %s", PACKAGE_STRING
,
2688 curses_version(), COLORS
, COLOR_PAIRS
, PACKAGE_BUGREPORT
);
2691 if (game
[gindex
].mode
!= MODE_HISTORY
) {
2692 if (!pgn_history_total(game
[gindex
].hp
) ||
2693 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2696 game
[gindex
].mode
= MODE_HISTORY
;
2697 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2698 update_all(game
[gindex
]);
2703 if (TEST_FLAG(game
[gindex
].flags
, GF_BLACK_OPENING
)) {
2704 cmessage(NULL
, ANYKEY
, "%s", E_RESUME_BLACK
);
2708 // FIXME Resuming from previous history could append to a RAV.
2709 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
2711 if ((c
= message(NULL
, YESNO
, "%s",
2712 GAME_RESUME_HISTORY_TEXT
)) != 'y')
2717 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
2721 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2722 d
->engine
->status
== ENGINE_OFFLINE
)) {
2723 if (start_chess_engine(&game
[gindex
]) < 0)
2731 oldhistorytotal
= pgn_history_total(game
[gindex
].hp
);
2732 game
[gindex
].mode
= MODE_PLAY
;
2733 update_all(game
[gindex
]);
2737 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
2741 markend
= delete_count
;
2746 if (game
[gindex
].mode
!= MODE_EDIT
) {
2747 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2749 update_all(game
[gindex
]);
2750 update_tag_window(game
[gindex
].tag
);
2758 if (!*gameexp
|| c
== '?') {
2759 if ((tmp
= get_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
,
2760 1, 1, GAME_FIND_EXPRESSION_PROMPT
, NULL
,
2761 NULL
, 0, -1)) == NULL
)
2764 strncpy(gameexp
, tmp
, sizeof(gameexp
));
2767 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1, (keycount
)
2774 if (pgn_history_total(game
[gindex
].hp
))
2775 game
[gindex
].mode
= MODE_HISTORY
;
2777 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2778 update_all(game
[gindex
]);
2779 update_tag_window(game
[gindex
].tag
);
2785 /* FIXME field validation
2786 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
2787 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
2793 if ((tmp
= get_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
,
2794 NULL
, NULL
, 0, -1)) == NULL
)
2797 if (!isinteger(tmp
))
2805 if (--i
> gtotal
- 1 || i
< 0)
2809 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2810 update_all(game
[gindex
]);
2811 update_tag_window(game
[gindex
].tag
);
2819 if (keycount
&& !delete_count
) {
2821 delete_count
= keycount
;
2822 update_status_notify(game
[gindex
], "%s (delete)",
2827 if (markstart
>= 0 && markend
>= 0) {
2828 if (markstart
> markend
) {
2830 markstart
= markend
;
2834 for (i
= markstart
; i
<= markend
; i
++) {
2835 if (toggle_delete_flag(i
))
2840 if (toggle_delete_flag(gindex
))
2844 markstart
= markend
= -1;
2845 update_status_window(game
[gindex
]);
2849 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2855 for (i
= n
= 0; i
< gtotal
; i
++) {
2856 if (TEST_FLAG(game
[i
].flags
, GF_DELETE
))
2861 tmp
= GAME_DELETE_GAME_TEXT
;
2864 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2868 tmp
= GAME_DELETE_ALL_TEXT
;
2871 if (config
.deleteprompt
) {
2872 if ((c
= cmessage(NULL
, YESNO
, "%s", tmp
)) != 'y')
2876 delete_game((!n
) ? gindex
: -1);
2878 if (pgn_history_total(game
[gindex
].hp
))
2879 game
[gindex
].mode
= MODE_HISTORY
;
2881 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2882 update_all(game
[gindex
]);
2883 update_tag_window(game
[gindex
].tag
);
2886 edit_save_tags(&game
[gindex
]);
2887 update_all(game
[gindex
]);
2888 update_tag_window(game
[gindex
].tag
);
2891 edit_tags(game
[gindex
], game
[gindex
].b
, 0);
2894 if ((tmp
= get_input(GAME_LOAD_TITLE
, NULL
, 1, 1,
2895 BROWSER_PROMPT
, file_browser
, NULL
, '\t',
2899 if ((tmp
= word_expand(tmp
)) == NULL
)
2902 if ((fp
= pgn_open(tmp
)) == NULL
) {
2903 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
2910 del_panel(loadingp
);
2915 strncpy(loadfile
, tmp
, sizeof(loadfile
));
2917 if (pgn_history_total(game
[gindex
].hp
))
2918 game
[gindex
].mode
= MODE_HISTORY
;
2920 pgn_board_update(&game
[gindex
], game
[gindex
].b
, pgn_history_total(game
[gindex
].hp
));
2921 update_all(game
[gindex
]);
2922 update_tag_window(game
[gindex
].tag
);
2929 n
= message(NULL
, GAME_SAVE_MULTI_PROMPT
, "%s",
2930 GAME_SAVE_MULTI_TEXT
);
2937 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2942 if ((tmp
= get_input(GAME_SAVE_TITLE
, loadfile
, 1, 1,
2943 BROWSER_PROMPT
, file_browser
, NULL
,
2944 '\t', -1)) == NULL
) {
2945 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2949 if ((tmp
= word_expand(tmp
)) == NULL
)
2952 if (pgn_is_compressed(tmp
)) {
2953 p
= tmp
+ strlen(tmp
) - 1;
2955 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
2957 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2962 if ((p
= strchr(tmp
, '.')) != NULL
) {
2963 if (strcmp(p
, ".pgn") != 0) {
2964 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2969 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2974 if (save_pgn(tmp
, 0, i
)) {
2975 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
2979 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
2980 update_all(game
[gindex
]);
2985 switch (game
[gindex
].mode
) {
2987 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
2990 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
2993 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
2999 while (c
== KEY_F(1)) {
3000 c
= help(GAME_HELP_INDEX_TITLE
, GAME_HELP_INDEX_PROMPT
,
3005 c
= help(GAME_HELP_HISTORY_TITLE
, ANYKEY
, historyhelp
);
3008 c
= help(GAME_HELP_PLAY_TITLE
, ANYKEY
, playhelp
);
3011 c
= help(GAME_HELP_EDIT_TITLE
, ANYKEY
, edithelp
);
3014 c
= help(GAME_HELP_GAME_TITLE
, ANYKEY
, gamehelp
);
3025 if (cmessage(NULL
, YESNO
, "%s", GAME_NEW_PROMPT
) != 'y')
3031 add_custom_tags(&game
[gindex
].tag
);
3032 d
= Calloc(1, sizeof(struct userdata_s
));
3033 game
[gindex
].data
= d
;
3036 cleanup_all_games();
3038 add_custom_tags(&game
[gindex
].tag
);
3039 pgn_board_init(game
[gindex
].b
);
3040 d
= Calloc(1, sizeof(struct userdata_s
));
3041 game
[gindex
].data
= d
;
3044 game
[gindex
].mode
= MODE_PLAY
;
3045 c_row
= (game
[gindex
].side
== WHITE
) ? 2 : 7;
3047 update_status_notify(game
[gindex
], NULL
);
3048 update_all(game
[gindex
]);
3049 update_tag_window(game
[gindex
].tag
);
3053 keypad(boardw
, TRUE
);
3057 sp
.icon
= sp
.row
= sp
.col
= 0;
3058 markend
= markstart
= 0;
3062 update_status_notify(game
[gindex
], NULL
);
3065 if (config
.validmoves
)
3066 pgn_reset_valid_moves(game
[gindex
].b
);
3073 keycount
= keycount
* 10 + n
;
3077 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3080 if (game
[gindex
].mode
== MODE_HISTORY
)
3095 if (game
[gindex
].mode
== MODE_HISTORY
)
3101 update_status_notify(game
[gindex
], NULL
);
3111 if (game
[gindex
].mode
== MODE_HISTORY
)
3126 if (game
[gindex
].mode
== MODE_HISTORY
)
3141 if (game
[gindex
].mode
!= MODE_EDIT
&& game
[gindex
].mode
!=
3145 // Don't edit a running game (for now).
3146 if (pgn_history_total(game
[gindex
].hp
))
3149 if (game
[gindex
].mode
!= MODE_EDIT
) {
3150 pgn_board_init_fen(&game
[gindex
], game
[gindex
].b
, NULL
);
3152 game
[gindex
].mode
= MODE_EDIT
;
3153 update_all(game
[gindex
]);
3158 pgn_tag_add(&game
[gindex
].tag
, "FEN",
3159 pgn_game_to_fen(game
[gindex
], game
[gindex
].b
));
3160 pgn_tag_add(&game
[gindex
].tag
, "SetUp", "1");
3161 pgn_tag_sort(game
[gindex
].tag
);
3162 game
[gindex
].mode
= MODE_PLAY
;
3163 update_all(game
[gindex
]);
3173 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(game
[gindex
].b
));
3186 int error_recover
= 0;
3188 c_row
= 2, c_col
= 5;
3189 gindex
= gtotal
- 1;
3191 if (pgn_history_total(game
[gindex
].hp
))
3192 game
[gindex
].mode
= MODE_HISTORY
;
3194 game
[gindex
].mode
= MODE_PLAY
;
3196 if (game
[gindex
].mode
== MODE_HISTORY
) {
3197 pgn_board_update(&game
[gindex
], game
[gindex
].b
,
3198 pgn_history_total(game
[gindex
].hp
));
3201 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
3203 paused
= 1; //FIXME clock
3205 update_all(game
[gindex
]);
3206 update_tag_window(game
[gindex
].tag
);
3207 wtimeout(boardw
, 70);
3212 char fdbuf
[8192] = {0};
3214 struct timeval tv
= {0, 0};
3216 struct userdata_s
*d
= NULL
;
3220 for (i
= 0; i
< gtotal
; i
++) {
3224 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
3225 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
3226 n
= d
->engine
->fd
[ENGINE_IN_FD
];
3228 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
3234 if ((n
= select(n
+ 1, &rfds
, NULL
, NULL
, &tv
)) > 0) {
3235 for (i
= 0; i
< gtotal
; i
++) {
3239 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3240 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3244 if (errno
!= EAGAIN
) {
3245 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3247 waitpid(d
->engine
->pid
, &n
, 0);
3255 parse_engine_output(&game
[i
], fdbuf
);
3256 update_all(game
[gindex
]);
3265 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3272 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
) && game
[gindex
].mode
3274 game
[gindex
].mode
= MODE_HISTORY
;
3275 update_all(game
[gindex
]);
3279 draw_board(&game
[gindex
], board_details
);
3280 wmove(boardw
, ROWTOMATRIX(c_row
), COLTOMATRIX(c_col
));
3290 if ((c
= wgetch(boardw
)) == ERR
)
3294 if (!keycount
&& status
.notify
)
3295 update_status_notify(game
[gindex
], NULL
);
3298 if ((n
= globalkeys(c
)) == 1) {
3305 switch (game
[gindex
].mode
) {
3310 if (playmode_keys(c
))
3314 historymode_keys(c
);
3324 void usage(const char *pn
, int ret
)
3326 fprintf((ret
) ? stderr
: stdout
, "%s",
3327 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3328 " -p Load PGN file.\n"
3329 " -V Validate a game file.\n"
3330 " -S Validate and output a PGN formatted game.\n"
3331 " -R Like -S but write a reduced PGN formatted game.\n"
3332 " -t Also write custom PGN tags from config file.\n"
3333 " -E Stop processing on file parsing error (overrides config).\n"
3334 " -v Version information.\n"
3335 " -h This help text.\n");
3344 cleanup_all_games();
3347 del_panel(historyp
);
3360 for (i
= 0; enginebuf
[i
]; i
++)
3370 void catch_signal(int which
)
3375 if (which
== SIGPIPE
&& quit
)
3378 if (which
== SIGPIPE
)
3379 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3389 keypad(boardw
, TRUE
);
3395 if (curses_initialized
) {
3396 update_loading_window(game
[gindex
]);
3400 fprintf(stderr
, "Loading... %i\r", gtotal
);
3408 static void set_defaults()
3411 set_config_defaults();
3414 int main(int argc
, char *argv
[])
3418 char buf
[FILENAME_MAX
];
3419 char datadir
[FILENAME_MAX
];
3420 int ret
= EXIT_SUCCESS
;
3421 int validate_only
= 0, validate_and_write
= 0, reduced
= 0;
3422 int write_custom_tags
= 0;
3426 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3427 err(EXIT_FAILURE
, "getpwuid()");
3429 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3430 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3431 config
.ccfile
= strdup(buf
);
3432 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3433 config
.nagfile
= strdup(buf
);
3434 snprintf(buf
, sizeof(buf
), "%s/agony.data", datadir
);
3435 config
.agonyfile
= strdup(buf
);
3436 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3437 config
.configfile
= strdup(buf
);
3438 snprintf(buf
, sizeof(buf
), "%s/fifo", datadir
);
3439 config
.fifo
= strdup(buf
);
3441 if (stat(datadir
, &st
) == -1) {
3442 if (errno
== ENOENT
) {
3443 if (mkdir(datadir
, 0755) == -1)
3444 err(EXIT_FAILURE
, "%s", datadir
);
3447 err(EXIT_FAILURE
, "%s", datadir
);
3452 if (!S_ISDIR(st
.st_mode
))
3453 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3455 if (access(config
.fifo
, R_OK
) == -1 && errno
== ENOENT
) {
3456 if (mkfifo(config
.fifo
, 0600) == -1)
3457 err(EXIT_FAILURE
, "%s", config
.fifo
);
3462 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3465 write_custom_tags
= 1;
3468 config
.stoponerror
= 1;
3473 validate_and_write
= 1;
3478 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3482 filetype
= PGN_FILE
;
3483 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3487 usage(argv
[0], EXIT_SUCCESS
);
3491 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3492 usage(argv
[0], EXIT_FAILURE
);
3494 if (access(config
.configfile
, R_OK
) == 0)
3495 parse_rcfile(config
.configfile
);
3497 signal(SIGPIPE
, catch_signal
);
3498 signal(SIGCONT
, catch_signal
);
3499 signal(SIGSTOP
, catch_signal
);
3500 signal(SIGINT
, catch_signal
);
3501 signal(SIGUSR1
, catch_signal
);
3507 if ((fp
= pgn_open(loadfile
)) == NULL
)
3508 err(EXIT_FAILURE
, "%s", loadfile
);
3510 ret
= pgn_parse(fp
);
3513 //ret = parse_fen_file(loadfile);
3515 case EPD_FILE
: // Not implemented.
3518 // No file specified. Empty game.
3519 ret
= pgn_parse(NULL
);
3520 add_custom_tags(&game
[gindex
].tag
);
3524 if (validate_only
|| validate_and_write
) {
3525 if (validate_and_write
) {
3526 for (i
= 0; i
< gtotal
; i
++) {
3527 if (write_custom_tags
)
3528 add_custom_tags(&game
[i
].tag
);
3530 pgn_write(stdout
, game
[i
]);
3542 if (initscr() == NULL
)
3543 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3545 curses_initialized
= 1;
3547 if (LINES
< 24 || COLS
< 80) {
3549 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3552 if (has_colors() == TRUE
&& start_color() == OK
)
3555 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3556 boardp
= new_panel(boardw
);
3557 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3558 COLS
- HISTORY_WIDTH
);
3559 historyp
= new_panel(historyw
);
3560 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3561 statusp
= new_panel(statusw
);
3562 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3563 tagp
= new_panel(tagw
);
3564 keypad(boardw
, TRUE
);
3565 // leaveok(boardw, TRUE);
3566 leaveok(tagw
, TRUE
);
3567 leaveok(statusw
, TRUE
);
3568 leaveok(historyw
, TRUE
);
3572 draw_window_decor();