1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@luxsci.net>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #include <sys/types.h>
25 #include <sys/socket.h>
39 #ifdef HAVE_SYS_WAIT_H
77 static char *str_etc(const char *str
, int maxlen
, int rev
)
80 static char buf
[80], *p
= buf
;
87 strncpy(buf
, str
, sizeof(buf
));
96 for (i
= 0; i
< maxlen
+ 3; i
++)
97 *p
++ = buf
[(len
- maxlen
) + i
+ 3];
100 p
= buf
+ maxlen
- 4;
112 void update_cursor(GAME g
, int idx
)
116 int t
= pgn_history_total(g
.hp
);
117 struct userdata_s
*d
= g
.data
;
120 * If not deincremented then r and c would be the next move.
124 if (idx
> t
|| idx
< 0 || !t
|| !g
.hp
[idx
]->move
) {
125 d
->c_row
= 2, d
->c_col
= 5;
138 d
->c_row
= (g
.turn
== WHITE
) ? 1 : 8;
147 d
->c_row
= RANKTOINT(*p
--);
148 d
->c_col
= FILETOINT(*p
);
151 static int init_nag()
157 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
158 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.nagfile
, strerror(errno
));
162 nags
= Realloc(nags
, (i
+2) * sizeof(char *));
163 nags
[i
++] = strdup("None");
167 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
168 nags
= Realloc(nags
, (i
+ 2) * sizeof(char *));
169 nags
[i
++] = strdup(line
);
177 void edit_nag_toggle_item(struct menu_input_s
*m
)
179 struct input_s
*in
= m
->data
;
180 struct input_data_s
*id
= in
->data
;
181 HISTORY
*h
= id
->data
;
184 if (m
->selected
== 0) {
185 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
188 for (i
= 0; m
->items
[i
]; i
++)
189 m
->items
[i
]->selected
= 0;
194 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
195 if (h
->nag
[i
] == m
->selected
)
196 h
->nag
[i
] = m
->selected
= 0;
199 h
->nag
[i
] = m
->selected
;
206 void edit_nag_save(struct menu_input_s
*m
)
211 void edit_nag_help(struct menu_input_s
*m
)
213 message(NAG_EDIT_HELP
, ANYKEY
, "%s", naghelp
);
216 struct menu_item_s
**get_nag_items(WIN
*win
)
219 struct menu_input_s
*m
= win
->data
;
220 struct input_s
*in
= m
->data
;
221 struct input_data_s
*id
= in
->data
;
222 struct menu_item_s
**items
= m
->items
;
223 HISTORY
*h
= id
->data
;
226 for (i
= 0; items
[i
]; i
++)
230 for (i
= 0; nags
[i
]; i
++) {
231 items
= Realloc(items
, (i
+2) * sizeof(struct menu_item_s
*));
232 items
[i
] = Malloc(sizeof(struct menu_item_s
));
233 items
[i
]->name
= nags
[i
];
234 items
[i
]->value
= NULL
;
236 for (n
= 0; n
< MAX_PGN_NAG
; n
++) {
237 if (h
->nag
[n
] == i
) {
238 items
[i
]->selected
= 1;
245 items
[i
]->selected
= 0;
254 void edit_nag(void *arg
)
256 struct menu_key_s
**keys
= NULL
;
263 add_menu_key(&keys
, ' ', edit_nag_toggle_item
);
264 add_menu_key(&keys
, CTRL('x'), edit_nag_save
);
265 add_menu_key(&keys
, KEY_F(1), edit_nag_help
);
266 construct_menu(0, 0, -1, -1, NAG_EDIT_TITLE
, 1, get_nag_items
, keys
, arg
,
271 static void *view_nag(void *arg
)
273 HISTORY
*h
= (HISTORY
*)arg
;
275 char line
[LINE_MAX
] = {0};
278 snprintf(buf
, sizeof(buf
), "Viewing NAG for \"%s\"", h
->move
);
285 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
289 strncat(line
, nags
[h
->nag
[i
]], sizeof(line
));
290 strncat(line
, "\n", sizeof(line
));
293 line
[strlen(line
) - 1] = 0;
294 message(buf
, ANYKEY
, "%s", line
);
298 void view_annotation(HISTORY
*h
)
300 char buf
[MAX_SAN_MOVE_LEN
+ strlen(ANNOTATION_VIEW_TITLE
) + 4];
301 int nag
= 0, comment
= 0;
306 if (h
->comment
&& h
->comment
[0])
312 if (!nag
&& !comment
)
315 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_VIEW_TITLE
, h
->move
);
318 construct_message(buf
, (nag
) ? "Any other key to continue" : ANYKEY
, 0,
319 (nag
) ? "Press 'n' to view NAG" : NULL
,
320 (nag
) ? view_nag
: NULL
, (nag
) ? h
: NULL
, NULL
,
321 (nag
) ? 'n' : 0, "%s", h
->comment
);
323 construct_message(buf
, "Any other key to continue", 0,
324 "Press 'n' to view NAG", view_nag
, h
, NULL
, 'n',
325 "%s", "No annotations for this move");
328 static int sort_files(const void *a
, const void *b
)
330 struct file_s
**aa
= (struct file_s
**)a
;
331 struct file_s
**bb
= (struct file_s
**)b
;
333 return strcmp((*aa
)->name
, (*bb
)->name
);
336 void free_file_browser()
343 for (i
= 0; files
[i
]; i
++) {
344 free(files
[i
]->name
);
345 free(files
[i
]->path
);
354 struct menu_item_s
**get_file_items(WIN
*win
)
356 struct menu_input_s
*m
= win
->data
;
357 struct input_s
*in
= m
->data
;
358 char *path
= in
->arg
;
359 struct menu_item_s
**items
= m
->items
;
371 * First find directories (including hidden) in the working directory.
372 * Then apply the config.pattern to regular files.
374 if ((p
= word_split_append(path
, '/', ".* *")) == NULL
)
377 strncpy(pattern
, p
, sizeof(pattern
));
380 for (i
= 0; items
[i
]; i
++)
385 items
= m
->items
= NULL
;
390 if (wordexp(pattern
, &w
, x
) != 0) {
391 cmessage(ERROR
, ANYKEY
, "Error in pattern\n%s", pattern
);
395 for (i
= 0; i
< w
.we_wordc
; i
++) {
400 if (stat(w
.we_wordv
[i
], &st
) == -1)
403 if ((p
= strrchr(w
.we_wordv
[i
], '/')) != NULL
)
409 if (!S_ISDIR(st
.st_mode
))
412 if (p
[0] == '.' && p
[1] == 0)
416 if (S_ISDIR(st
.st_mode
))
420 files
= Realloc(files
, (n
+ 2) * sizeof(struct file_s
*));
421 files
[n
] = Malloc(sizeof(struct file_s
));
422 files
[n
]->path
= strdup(w
.we_wordv
[i
]);
424 files
[n
]->name
= Malloc(len
);
425 strcpy(files
[n
]->name
, p
);
427 if (S_ISDIR(st
.st_mode
)) {
428 files
[n
]->name
[len
- 2] = '/';
429 files
[n
]->name
[len
- 1] = 0;
430 p
= files
[n
]->path
+ strlen(files
[n
]->path
) - 1;
432 if (*p
== '.' && *(p
- 1) == '.' && *(p
- 2) == '/') {
436 if (strlen(files
[n
]->path
)) {
443 if (!strlen(files
[n
]->path
)) {
451 tp
= localtime(&st
.st_mtime
);
452 strftime(tbuf
, sizeof(tbuf
), "%b %d %T", tp
);
453 snprintf(sbuf
, sizeof(sbuf
), "%9i %s", (int)st
.st_size
, tbuf
);
454 files
[n
]->st
= strdup(sbuf
);
463 qsort(files
, n
, sizeof(struct file_s
*), sort_files
);
465 if ((p
= word_split_append(path
, '/', config
.pattern
)) == NULL
)
468 strncpy(pattern
, p
, sizeof(pattern
));
474 qsort(files
+ d
, i
, sizeof(struct file_s
*), sort_files
);
476 for (i
= 0; files
[i
]; i
++) {
477 items
= Realloc(items
, (i
+2) * sizeof(struct menu_item_s
*));
478 items
[i
] = Malloc(sizeof(struct menu_item_s
));
479 items
[i
]->name
= files
[i
]->name
;
480 items
[i
]->value
= files
[i
]->st
;
481 items
[i
]->selected
= 0;
487 m
->title
= strdup(path
);
493 void file_browser_help(struct menu_input_s
*m
)
495 message(BROWSER_HELP
, ANYKEY
, "%s", filebrowser_help
);
498 void file_browser_select(struct menu_input_s
*m
)
500 struct input_s
*in
= m
->data
;
501 char *path
= in
->arg
;
504 if (stat(files
[m
->selected
]->path
, &st
) == -1) {
505 message(ERROR
, ANYKEY
, "%s", strerror(errno
));
509 if (S_ISDIR(st
.st_mode
)) {
510 if (access(files
[m
->selected
]->path
, R_OK
) != 0) {
511 cmessage(files
[m
->selected
]->path
, ANYKEY
, "%s", strerror(errno
));
516 path
= strdup(files
[m
->selected
]->path
);
522 strncpy(in
->buf
, files
[m
->selected
]->path
, sizeof(in
->buf
));
523 set_field_buffer(in
->fields
[0], 0, in
->buf
);
527 void file_browser_finalize(WIN
*win
)
529 struct input_s
*in
= win
->data
;
535 void file_browser_home(struct menu_input_s
*m
)
537 struct input_s
*in
= m
->data
;
538 char *path
= in
->arg
;
542 pw
= getpwuid(getuid());
543 path
= strdup(pw
->pw_dir
);
548 void file_browser_abort(struct menu_input_s
*m
)
553 void file_browser(void *arg
)
555 struct menu_key_s
**keys
= NULL
;
556 struct input_s
*in
= arg
;
558 char path
[FILENAME_MAX
];
560 if (config
.savedirectory
) {
561 if ((p
= word_expand(config
.savedirectory
)) == NULL
)
564 strncpy(path
, p
, sizeof(path
));
566 if (access(path
, R_OK
) == -1) {
567 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
568 getcwd(path
, sizeof(path
));
572 getcwd(path
, sizeof(path
));
574 in
->arg
= strdup(path
);
575 add_menu_key(&keys
, '\n', file_browser_select
);
576 add_menu_key(&keys
, KEY_F(1), file_browser_help
);
577 add_menu_key(&keys
, '~', file_browser_home
);
578 add_menu_key(&keys
, KEY_ESCAPE
, file_browser_abort
);
579 construct_menu(LINES
- 4, 0, -1, -1, NULL
, 0, get_file_items
, keys
, in
,
580 file_browser_finalize
);
584 void do_game_write(char *filename
, char *mode
, int start
, int end
)
586 char *command
= NULL
;
589 struct userdata_s
*d
;
592 if ((fp
= popen(command
, "w")) == NULL
) {
593 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
598 if ((fp
= fopen(filename
, mode
)) == NULL
) {
599 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
604 for (i
= (start
== -1) ? 0 : start
; i
< end
; i
++) {
606 pgn_write(fp
, game
[i
]);
607 CLEAR_FLAG(d
->flags
, CF_MODIFIED
);
616 strncpy(loadfile
, filename
, sizeof(loadfile
));
618 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
619 update_all(game
[gindex
]);
623 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
624 update_all(game
[gindex
]);
634 void do_save_game_overwrite_confirm(WIN
*win
)
637 struct save_game_s
*s
= win
->data
;
641 if (pgn_is_compressed(s
->filename
) == E_PGN_OK
) {
642 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
655 do_game_write(s
->filename
, mode
, s
->start
, s
->end
);
662 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
665 // FIXME command (compression)
666 void save_pgn(char *filename
, int saveindex
)
668 char buf
[FILENAME_MAX
];
670 int end
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
671 struct save_game_s
*s
;
673 if (filename
[0] != '/' && config
.savedirectory
) {
674 if (stat(config
.savedirectory
, &st
) == -1) {
675 if (errno
== ENOENT
) {
676 if (mkdir(config
.savedirectory
, 0755) == -1) {
677 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
683 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
689 stat(config
.savedirectory
, &st
);
691 if (!S_ISDIR(st
.st_mode
)) {
692 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
696 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
700 if (access(filename
, W_OK
) == 0) {
701 s
= Malloc(sizeof(struct save_game_s
));
702 s
->filename
= strdup(filename
);
703 s
->start
= saveindex
;
705 construct_message(NULL
, GAME_SAVE_OVERWRITE_PROMPT
, 1, NULL
, NULL
,
706 s
, do_save_game_overwrite_confirm
, 0, "%s \"%s\"",
707 E_FILEEXISTS
, filename
);
711 do_game_write(filename
, "a", saveindex
, end
);
714 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
716 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
718 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
719 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
721 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
724 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
726 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
727 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
729 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
732 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
734 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
735 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
737 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
740 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
742 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
743 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
745 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
748 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
750 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
751 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
753 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
754 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
756 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
757 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
758 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
760 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
764 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
766 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
767 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
769 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
770 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
772 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
773 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
774 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
776 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
784 static void draw_board(GAME
*g
)
787 int bcol
= 0, brow
= 0;
788 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
789 int ncols
= 0, offset
= 1;
790 unsigned coords_y
= 8;
791 struct userdata_s
*d
= g
->data
;
793 if (d
->mode
!= MODE_PLAY
&& d
->mode
!= MODE_EDIT
)
794 update_cursor(*g
, g
->hindex
);
796 for (row
= 0; row
< maxy
; row
++) {
799 for (col
= 0; col
< maxx
; col
++) {
804 if (row
== 0 || row
== maxy
- 2) {
806 mvwaddch(boardw
, row
, col
,
808 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
809 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
810 else if (col
== maxx
- 2)
811 mvwaddch(boardw
, row
, col
,
813 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
814 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
816 mvwaddch(boardw
, row
, col
,
818 ACS_BTEE
| CP_BOARD_GRAPHICS
:
819 ACS_TTEE
| CP_BOARD_GRAPHICS
));
822 mvwaddch(boardw
, row
, col
,
823 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
829 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
830 wattron(boardw
, CP_BOARD_COORDS
);
831 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
832 wattroff(boardw
, CP_BOARD_COORDS
);
836 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
838 mvwaddch(boardw
, row
, col
,
840 ACS_RTEE
| CP_BOARD_GRAPHICS
:
841 ACS_LTEE
| CP_BOARD_GRAPHICS
));
843 mvwaddch(boardw
, row
, col
,
844 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
849 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
850 mvwaddch(boardw
, row
, col
,
851 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
855 if (!(col
% 4) && row
!= maxy
- 1) {
856 mvwaddch(boardw
, row
, col
,
857 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
868 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
874 if (config
.validmoves
&& d
->b
[brow
][bcol
].valid
) {
875 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
876 CP_BOARD_MOVES_BLACK
;
879 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
882 if (row
== ROWTOMATRIX(d
->c_row
) && col
==
883 COLTOMATRIX(d
->c_col
)) {
884 attrs
= CP_BOARD_CURSOR
;
887 if (row
== ROWTOMATRIX(d
->sp
.srow
) &&
888 col
== COLTOMATRIX(d
->sp
.scol
)) {
889 attrs
= CP_BOARD_SELECTED
;
895 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
898 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
900 if (config
.details
&& d
->b
[row
/ 2][bcol
].enpassant
)
903 piece
= d
->b
[row
/ 2][bcol
].icon
;
905 if (config
.details
&& castling_state(g
, d
->b
, brow
,
909 if (g
->side
== WHITE
&& isupper(piece
))
911 else if (g
->side
== BLACK
&& islower(piece
))
914 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
916 CLEAR_FLAG(attrs
, A_BOLD
);
917 CLEAR_FLAG(attrs
, A_REVERSE
);
920 waddch(boardw
, ' ' | attrs
);
927 mvwaddch(boardw
, row
, col
,
928 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
935 mvwaddch(boardw
, maxy
- 1, maxx
- 2, (config
.details
) ? '!' : ' ');
938 void invalid_move(int n
, int e
, const char *m
)
940 if (curses_initialized
)
941 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", (e
== E_PGN_AMBIGUOUS
)
942 ? E_AMBIGUOUS
: E_INVALID_MOVE
, m
, n
);
944 warnx("%s: %s \"%s\" (round #%i)", loadfile
, (e
== E_PGN_AMBIGUOUS
)
945 ? E_AMBIGUOUS
: E_INVALID_MOVE
, m
, n
);
948 static void update_clock(GAME
*g
, struct itimerval it
)
950 struct userdata_s
*d
= g
->data
;
953 if (g
->turn
== WHITE
) {
954 d
->wc
.tv_sec
+= it
.it_value
.tv_sec
;
955 d
->wc
.tv_usec
+= it
.it_value
.tv_usec
;
957 if (d
->wc
.tv_usec
> 1000000 - 1) {
958 d
->wc
.tv_sec
+= d
->wc
.tv_usec
/ 1000000;
959 d
->wc
.tv_usec
= d
->wc
.tv_usec
% 1000000;
963 d
->bc
.tv_sec
+= it
.it_value
.tv_sec
;
964 d
->bc
.tv_usec
+= it
.it_value
.tv_usec
;
966 if (d
->bc
.tv_usec
> 1000000 - 1) {
967 d
->bc
.tv_sec
+= d
->bc
.tv_usec
/ 1000000;
968 d
->bc
.tv_usec
= d
->bc
.tv_usec
% 1000000;
972 d
->elapsed
= d
->wc
.tv_sec
+ d
->bc
.tv_sec
;
973 n
= d
->wc
.tv_usec
+ d
->bc
.tv_usec
;
974 d
->elapsed
+= (n
> 1000000 - 1) ? n
/ 1000000 : 0;
976 if (TEST_FLAG(d
->flags
, CF_CLOCK
)) {
977 if (d
->elapsed
>= d
->limit
) {
978 SET_FLAG(g
->flags
, GF_GAMEOVER
);
979 pgn_tag_add(&g
->tag
, "Result", "1/2-1/2");
984 void do_validate_move(char *m
)
986 struct userdata_s
*d
= game
[gindex
].data
;
989 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
990 if ((n
= pgn_parse_move(&game
[gindex
], d
->b
, &m
)) != E_PGN_OK
) {
991 invalid_move(d
->n
+ 1, n
, m
);
996 pgn_history_add(&game
[gindex
], m
);
997 pgn_switch_turn(&game
[gindex
]);
1000 if ((n
= pgn_validate_move(&game
[gindex
], d
->b
, &m
)) != E_PGN_OK
) {
1001 invalid_move(d
->n
+ 1, n
, m
);
1006 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "%s\n", m
);
1009 d
->sp
.srow
= d
->sp
.scol
= d
->sp
.icon
= 0;
1011 if (config
.validmoves
)
1012 pgn_reset_valid_moves(d
->b
);
1014 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
1015 d
->mode
= MODE_HISTORY
;
1017 SET_FLAG(d
->flags
, CF_MODIFIED
);
1024 void do_promotion_piece_finalize(WIN
*win
)
1026 char *p
, *str
= win
->data
;
1028 if (pgn_piece_to_int(win
->c
) == -1)
1031 p
= str
+ strlen(str
);
1032 *p
++ = toupper(win
->c
);
1034 do_validate_move(str
);
1037 static void move_to_engine(GAME
*g
)
1039 struct userdata_s
*d
= g
->data
;
1043 if (config
.validmoves
&&
1044 !d
->b
[RANKTOBOARD(d
->sp
.row
)][FILETOBOARD(d
->sp
.col
)].valid
)
1047 str
= Malloc(MAX_SAN_MOVE_LEN
+ 1);
1048 snprintf(str
, MAX_SAN_MOVE_LEN
+ 1, "%c%i%c%i",
1049 x_grid_chars
[d
->sp
.scol
- 1],
1050 d
->sp
.srow
, x_grid_chars
[d
->sp
.col
- 1], d
->sp
.row
);
1052 piece
= pgn_piece_to_int(d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
);
1054 if (piece
== PAWN
&& (d
->sp
.row
== 8 || d
->sp
.row
== 1)) {
1055 construct_message(PROMOTION_TITLE
, PROMOTION_PROMPT
, 1, NULL
, NULL
,
1056 str
, do_promotion_piece_finalize
, 0, "%s", PROMOTION_TEXT
);
1060 do_validate_move(str
);
1063 static char *clock_to_char(long n
)
1065 static char buf
[16];
1066 int h
= 0, m
= 0, s
= 0;
1069 m
= (n
% 3600) / 60;
1070 s
= (n
% 3600) % 60;
1071 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i", h
, m
, s
);
1075 static char *timeval_to_char(struct timeval t
)
1077 static char buf
[16];
1078 int h
= 0, m
= 0, s
= 0;
1082 m
= (n
% 3600) / 60;
1083 s
= (n
% 3600) % 60;
1084 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i.%.2i", h
, m
, s
,
1085 (int)t
.tv_usec
/ 10000);
1089 void update_status_window(GAME g
)
1093 char tmp
[15], *engine
, *mode
;
1098 struct userdata_s
*d
= g
.data
;
1100 getmaxyx(statusw
, maxy
, maxx
);
1108 if (TEST_FLAG(d
->flags
, CF_DELETE
)) {
1114 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1124 if (TEST_FLAG(d
->flags
, CF_MODIFIED
)) {
1139 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1140 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1141 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1143 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1147 mode
= MODE_HISTORY_STR
;
1150 mode
= MODE_EDIT_STR
;
1153 mode
= MODE_PLAY_STR
;
1160 snprintf(buf
, len
- 1, "%*s %s", 7, STATUS_MODE_STR
, mode
);
1162 if (d
->mode
== MODE_PLAY
) {
1163 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1164 strncat(buf
, " (human/human)", len
- 1);
1165 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1166 strncat(buf
, " (engine/engine)", len
- 1);
1168 strncat(buf
, " (human/engine)", len
- 1);
1171 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1174 switch (d
->engine
->status
) {
1175 case ENGINE_THINKING
:
1176 engine
= ENGINE_PONDER_STR
;
1179 engine
= ENGINE_READY_STR
;
1181 case ENGINE_INITIALIZING
:
1182 engine
= ENGINE_INITIALIZING_STR
;
1184 case ENGINE_OFFLINE
:
1185 engine
= ENGINE_OFFLINE_STR
;
1193 engine
= ENGINE_OFFLINE_STR
;
1195 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1196 wattron(statusw
, CP_STATUS_ENGINE
);
1197 mvwaddstr(statusw
, 5, 9, engine
);
1198 wattroff(statusw
, CP_STATUS_ENGINE
);
1200 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1201 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1203 mvwprintw(statusw
, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR
, w
,
1204 clock_to_char((TEST_FLAG(d
->flags
, CF_CLOCK
)) ?
1205 d
->limit
- d
->elapsed
: 0));
1207 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1208 tmp
[0] = toupper(tmp
[0]);
1209 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, timeval_to_char(d
->wc
));
1211 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1212 tmp
[0] = toupper(tmp
[0]);
1213 mvwprintw(statusw
, 9, 1, "%*s: %-*s", 6, tmp
, w
, timeval_to_char(d
->bc
));
1216 for (i
= 1; i
< maxx
- 4; i
++)
1217 mvwprintw(statusw
, maxy
- 2, i
, " ");
1220 status
.notify
= strdup(GAME_HELP_PROMPT
);
1222 wattron(statusw
, CP_STATUS_NOTIFY
);
1223 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1225 wattroff(statusw
, CP_STATUS_NOTIFY
);
1228 void update_history_window(GAME g
)
1230 char buf
[HISTORY_WIDTH
- 1];
1233 int t
= pgn_history_total(g
.hp
);
1235 n
= (g
.hindex
+ 1) / 2;
1238 total
= (t
+ 1) / 2;
1243 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1244 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
1246 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1248 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1249 HISTORY_WIDTH
- 13, buf
);
1251 h
= pgn_history_by_n(g
.hp
, g
.hindex
);
1252 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1255 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1256 strncat(buf
, " (v", sizeof(buf
));
1261 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1266 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1271 strncat(buf
, ")", sizeof(buf
));
1273 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1274 HISTORY_WIDTH
- 13, buf
);
1276 h
= pgn_history_by_n(g
.hp
, g
.hindex
- 1);
1277 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1280 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1281 strncat(buf
, " (V", sizeof(buf
));
1286 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1291 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1296 strncat(buf
, ")", sizeof(buf
));
1298 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1299 HISTORY_WIDTH
- 13, buf
);
1302 void update_tag_window(TAG
**t
)
1305 int w
= TAG_WIDTH
- 10;
1307 for (i
= 0; i
< 7; i
++)
1308 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
]->name
, w
,
1309 str_etc(t
[i
]->value
, w
, 0));
1312 void append_enginebuf(char *line
)
1317 for (i
= 0; enginebuf
[i
]; i
++);
1319 if (i
>= LINES
- 3) {
1322 for (i
= 0; enginebuf
[i
+1]; i
++)
1323 enginebuf
[i
] = enginebuf
[i
+1];
1325 enginebuf
[i
] = strdup(line
);
1328 enginebuf
= Realloc(enginebuf
, (i
+ 2) * sizeof(char *));
1329 enginebuf
[i
++] = strdup(line
);
1330 enginebuf
[i
] = NULL
;
1334 void update_engine_window()
1341 wmove(enginew
, 0, 0);
1345 for (i
= 0; enginebuf
[i
]; i
++)
1346 mvwprintw(enginew
, i
+ 2, 1, "%s", enginebuf
[i
]);
1349 window_draw_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1353 void toggle_engine_window()
1356 enginew
= newwin(LINES
, COLS
, 0, 0);
1357 enginep
= new_panel(enginew
);
1358 window_draw_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1360 hide_panel(enginep
);
1363 if (panel_hidden(enginep
)) {
1364 update_engine_window();
1369 hide_panel(enginep
);
1380 void update_all(GAME g
)
1382 update_status_window(g
);
1383 update_history_window(g
);
1384 update_tag_window(g
.tag
);
1385 update_engine_window();
1388 static void game_next_prev(GAME g
, int n
, int count
)
1394 if (gindex
+ count
> gtotal
- 1) {
1396 gindex
= gtotal
- 1;
1404 if (gindex
- count
< 0) {
1408 gindex
= gtotal
- 1;
1415 static void delete_game(int which
)
1420 struct userdata_s
*d
;
1422 for (i
= 0; i
< gtotal
; i
++) {
1425 if (i
== which
|| TEST_FLAG(d
->flags
, CF_DELETE
)) {
1430 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
1431 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
1432 g
[gi
].tag
= game
[i
].tag
;
1433 g
[gi
].history
= game
[i
].history
;
1434 g
[gi
].hp
= game
[i
].hp
;
1442 if (which
+ 1 >= gtotal
)
1443 gindex
= gtotal
- 1;
1448 gindex
= gtotal
- 1;
1450 game
[gindex
].hp
= game
[gindex
].history
;
1454 * FIXME find across multiple games.
1456 static int find_move_exp(GAME g
, regex_t r
, int which
, int count
)
1464 incr
= (which
== 0) ? -1 : 1;
1466 for (i
= g
.hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
1467 if (i
== g
.hindex
- 1)
1470 if (i
>= pgn_history_total(g
.hp
))
1473 i
= pgn_history_total(g
.hp
) - 1;
1476 ret
= regexec(&r
, g
.hp
[i
]->move
, 0, 0, 0);
1479 if (count
== ++found
) {
1484 if (ret
!= REG_NOMATCH
) {
1485 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
1486 cmessage(E_REGEXEC_TITLE
, ANYKEY
, "%s", errbuf
);
1495 static int toggle_delete_flag(int n
)
1498 struct userdata_s
*d
= game
[n
].data
;
1500 TOGGLE_FLAG(d
->flags
, CF_DELETE
);
1502 update_all(game
[gindex
]);
1504 for (i
= x
= 0; i
< gtotal
; i
++) {
1507 if (TEST_FLAG(d
->flags
, CF_DELETE
))
1512 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
1514 CLEAR_FLAG(d
->flags
, CF_DELETE
);
1521 static int find_game_exp(char *str
, int which
, int count
)
1523 char *nstr
= NULL
, *exp
= NULL
;
1527 char buf
[255], *tmp
;
1530 int incr
= (which
== 0) ? -(1) : 1;
1532 strncpy(buf
, str
, sizeof(buf
));
1535 if (strstr(tmp
, ":") != NULL
) {
1536 nstr
= strsep(&tmp
, ":");
1538 if ((ret
= regcomp(&nexp
, nstr
,
1539 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
1540 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
1541 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
1552 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
1553 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
1554 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
1561 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
1572 for (t
= 0; game
[g
].tag
[t
]; t
++) {
1574 if (regexec(&nexp
, game
[g
].tag
[t
]->name
, 0, 0, 0) == 0) {
1575 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
1576 if (count
== ++found
) {
1584 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
1585 if (count
== ++found
) {
1607 * Updates the notification line in the status window then refreshes the
1610 void update_status_notify(GAME g
, char *fmt
, ...)
1613 #ifdef HAVE_VASPRINTF
1620 if (status
.notify
) {
1621 free(status
.notify
);
1622 status
.notify
= NULL
;
1624 if (curses_initialized
)
1625 update_status_window(g
);
1632 #ifdef HAVE_VASPRINTF
1633 vasprintf(&line
, fmt
, ap
);
1635 vsnprintf(line
, sizeof(line
), fmt
, ap
);
1640 free(status
.notify
);
1642 status
.notify
= strdup(line
);
1644 #ifdef HAVE_VASPRINTF
1647 if (curses_initialized
)
1648 update_status_window(g
);
1651 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
1655 if ((!g
->ravlevel
&& g
->hp
[g
->hindex
- 1]->rav
== NULL
) ||
1656 (g
->ravlevel
&& g
->hp
[g
->hindex
]->rav
== NULL
))
1659 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
1660 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1661 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
1662 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
1663 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1664 g
->hp
= (!g
->ravlevel
) ? g
->hp
[g
->hindex
- 1]->rav
: g
->hp
[g
->hindex
]->rav
;
1667 pgn_board_update(g
, b
, g
->hindex
+ 1);
1671 if (g
->ravlevel
- 1 < 0)
1676 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
1677 free(g
->rav
[g
->ravlevel
].fen
);
1678 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
1679 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
1680 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
1684 static void draw_window_decor()
1686 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
1687 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
1688 wbkgd(boardw
, CP_BOARD_WINDOW
);
1689 wbkgd(statusw
, CP_STATUS_WINDOW
);
1690 window_draw_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
1691 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
1692 wbkgd(tagw
, CP_TAG_WINDOW
);
1693 window_draw_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
1695 wbkgd(historyw
, CP_HISTORY_WINDOW
);
1696 window_draw_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
1697 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
1700 static void do_window_resize()
1702 if (LINES
< 24 || COLS
< 80)
1705 resizeterm(LINES
, COLS
);
1706 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
1707 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
1708 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
1709 wmove(historyw
, 0, 0);
1710 wclrtobot(historyw
);
1713 wmove(statusw
, 0, 0);
1715 draw_window_decor();
1716 update_all(game
[gindex
]);
1721 memset(&clock_timer
, 0, sizeof(struct itimerval
));
1722 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1727 if (clock_timer
.it_interval
.tv_usec
)
1730 clock_timer
.it_value
.tv_sec
= 0;
1731 clock_timer
.it_value
.tv_usec
= 100000;
1732 clock_timer
.it_interval
.tv_sec
= 0;
1733 clock_timer
.it_interval
.tv_usec
= 100000;
1734 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1737 static void update_clocks()
1740 struct userdata_s
*d
;
1741 struct itimerval it
;
1743 getitimer(ITIMER_REAL
, &it
);
1745 for (i
= 0; i
< gtotal
; i
++) {
1748 if (d
->mode
== MODE_PLAY
) {
1749 if (d
->paused
== 1 || TEST_FLAG(d
->flags
, CF_NEW
))
1751 else if (d
->paused
== -1) {
1752 if (game
[i
].side
== game
[i
].turn
) {
1758 update_clock(&game
[i
], it
);
1763 static int init_chess_engine(GAME
*g
)
1765 struct userdata_s
*d
= g
->data
;
1768 if (start_chess_engine(g
) > 0) {
1773 x
= pgn_tag_find(g
->tag
, "FEN");
1774 w
= pgn_tag_find(g
->tag
, "SetUp");
1776 if ((w
>= 0 && x
>= 0 && atoi(g
->tag
[w
]->value
) == 1) ||
1777 (x
>= 0 && w
== -1))
1778 add_engine_command(g
, ENGINE_READY
, "setboard %s\n", g
->tag
[x
]->value
);
1780 add_engine_command(g
, ENGINE_READY
, "setboard %s\n",
1781 pgn_game_to_fen(*g
, d
->b
));
1786 static int parse_clock_input(struct userdata_s
*d
, char *str
)
1808 if (!t
&& *p
!= ' ')
1842 CLEAR_FLAG(d
->flags
, CF_CLOCK
);
1845 SET_FLAG(d
->flags
, CF_CLOCK
);
1850 d
->limit
= (n
<= d
->elapsed
) ? d
->elapsed
+ n
: n
;
1856 void do_clock_input_finalize(WIN
*win
)
1858 struct userdata_s
*d
= game
[gindex
].data
;
1859 struct input_data_s
*in
= win
->data
;
1864 if (parse_clock_input(d
, in
->str
))
1865 cmessage(ERROR
, ANYKEY
, "Invalid time specification");
1871 void do_engine_command_finalize(WIN
*win
)
1873 struct userdata_s
*d
= game
[gindex
].data
;
1874 struct input_data_s
*in
= win
->data
;
1882 x
= d
->engine
->status
;
1883 send_to_engine(&game
[gindex
], -1, "%s\n", in
->str
);
1884 d
->engine
->status
= x
;
1889 static void historymode_keys(chtype
);
1890 static int playmode_keys(chtype c
)
1892 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
1893 struct userdata_s
*d
= game
[gindex
].data
;
1894 int editmode
= (d
->mode
== MODE_EDIT
) ? 1 : 0;
1897 struct input_data_s
*in
;
1901 in
= Calloc(1, sizeof(struct input_data_s
));
1902 in
->efunc
= do_clock_input_finalize
;
1903 construct_input(CLOCK_TITLE
, NULL
, 1, 1, CLOCK_HELP
, NULL
, NULL
, 0,
1907 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
1909 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
1910 pgn_history_total(game
[gindex
].hp
)) {
1911 if (init_chess_engine(&game
[gindex
]))
1915 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
1918 d
->engine
->status
= ENGINE_READY
;
1920 update_all(game
[gindex
]);
1926 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
1927 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
1929 if (d
->engine
&& TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
1930 pgn_board_update(&game
[gindex
], d
->b
,
1931 pgn_history_total(game
[gindex
].hp
));
1932 add_engine_command(&game
[gindex
], ENGINE_READY
,
1933 "setboard %s\n", pgn_game_to_fen(game
[gindex
], d
->b
));
1936 update_all(game
[gindex
]);
1942 if (d
->engine
->status
== ENGINE_OFFLINE
)
1945 x
= d
->engine
->status
;
1946 in
= Calloc(1, sizeof(struct input_data_s
));
1947 in
->efunc
= do_engine_command_finalize
;
1948 construct_input(ENGINE_CMD_TITLE
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1953 pushkey
= keycount
= 0;
1954 update_status_notify(game
[gindex
], NULL
);
1956 if (!editmode
&& !TEST_FLAG(d
->flags
, CF_HUMAN
) &&
1957 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
)) {
1965 d
->sp
.row
= d
->c_row
;
1966 d
->sp
.col
= d
->c_col
;
1969 p
= d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
;
1970 d
->b
[RANKTOBOARD(d
->sp
.row
)][FILETOBOARD(d
->sp
.col
)].icon
= p
;
1971 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
1972 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
1973 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
1977 move_to_engine(&game
[gindex
]);
1980 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
1981 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
1982 if (init_chess_engine(&game
[gindex
]))
1986 if (d
->sp
.icon
|| (!editmode
&& d
->engine
&&
1987 d
->engine
->status
== ENGINE_THINKING
)) {
1992 d
->sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(d
->c_row
),
1993 COLTOMATRIX(d
->c_col
)+1) & A_CHARTEXT
;
1995 if (d
->sp
.icon
== ' ') {
2000 if (!editmode
&& ((islower(d
->sp
.icon
) && game
[gindex
].turn
!= BLACK
)
2001 || (isupper(d
->sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
2002 if (pgn_history_total(game
[gindex
].hp
)) {
2003 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
2008 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != E_PGN_ERR
)
2011 add_engine_command(&game
[gindex
], ENGINE_READY
, "black\n");
2012 pgn_switch_turn(&game
[gindex
]);
2014 if (game
[gindex
].side
!= BLACK
)
2015 pgn_switch_side(&game
[gindex
]);
2019 d
->sp
.srow
= d
->c_row
;
2020 d
->sp
.scol
= d
->c_col
;
2022 if (!editmode
&& config
.validmoves
)
2023 pgn_find_valid_moves(game
[gindex
], d
->b
, d
->sp
.scol
, d
->sp
.srow
);
2026 CLEAR_FLAG(d
->flags
, CF_NEW
);
2032 pgn_switch_side(&game
[gindex
]);
2033 pgn_switch_turn(&game
[gindex
]);
2034 add_engine_command(&game
[gindex
], -1,
2035 (game
[gindex
].side
== WHITE
) ? "white\n" : "black\n");
2036 update_status_window(game
[gindex
]);
2039 if (!pgn_history_total(game
[gindex
].hp
))
2042 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2043 add_engine_command(&game
[gindex
], ENGINE_READY
, "remove\n");
2044 d
->engine
->status
= ENGINE_READY
;
2047 game
[gindex
].hindex
-= 2;
2048 pgn_history_free(game
[gindex
].hp
, game
[gindex
].hindex
);
2049 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
);
2050 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2051 update_history_window(game
[gindex
]);
2054 historymode_keys(c
);
2057 config
.details
= (config
.details
) ? 0 : 1;
2060 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && game
[gindex
].turn
!=
2061 game
[gindex
].side
) {
2066 d
->paused
= (d
->paused
) ? 0 : 1;
2069 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
2072 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
) {
2073 if (init_chess_engine(&game
[gindex
]))
2077 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "go\n");
2084 for (x
= 0; config
.keys
[x
]; x
++) {
2085 if (config
.keys
[x
]->c
== c
) {
2086 switch (config
.keys
[x
]->type
) {
2088 add_engine_command(&game
[gindex
], -1, "%s\n",
2089 config
.keys
[x
]->str
);
2095 add_engine_command(&game
[gindex
], -1,
2096 "%s %i\n", config
.keys
[x
]->str
, keycount
);
2103 for (w
= 0; w
< keycount
; w
++)
2104 add_engine_command(&game
[gindex
], -1,
2105 "%s\n", config
.keys
[x
]->str
);
2112 update_status_notify(game
[gindex
], NULL
);
2120 void do_edit_insert_finalize(WIN
*win
)
2122 struct userdata_s
*d
= win
->data
;
2124 if (pgn_piece_to_int(win
->c
) == -1)
2127 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
= win
->c
;
2130 static void editmode_keys(chtype c
)
2132 struct userdata_s
*d
= game
[gindex
].data
;
2142 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
2143 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2145 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
=
2146 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2148 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2151 pgn_switch_turn(&game
[gindex
]);
2152 update_all(game
[gindex
]);
2155 castling_state(&game
[gindex
], d
->b
, RANKTOBOARD(d
->c_row
),
2156 FILETOBOARD(d
->c_col
),
2157 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
, 1);
2160 construct_message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, 0, NULL
, NULL
,
2161 d
->b
, do_edit_insert_finalize
, 0, "%s", GAME_EDIT_TEXT
);
2164 if (d
->c_row
== 6 || d
->c_row
== 3) {
2165 pgn_reset_enpassant(d
->b
);
2166 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].enpassant
= 1;
2174 void do_annotate_finalize(WIN
*win
)
2176 struct userdata_s
*d
= game
[gindex
].data
;
2177 struct input_data_s
*in
= win
->data
;
2178 HISTORY
*h
= in
->data
;
2188 len
= strlen(in
->str
) + 1;
2189 h
->comment
= Realloc(h
->comment
, len
);
2190 strncpy(h
->comment
, in
->str
, len
);
2195 SET_FLAG(d
->flags
, CF_MODIFIED
);
2196 update_all(game
[gindex
]);
2199 void do_find_move_exp_finalize(int init
, int c
)
2202 struct userdata_s
*d
= game
[gindex
].data
;
2203 static int firstrun
;
2208 if (init
|| !firstrun
) {
2212 if ((ret
= regcomp(&r
, moveexp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2213 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2214 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2221 if ((n
= find_move_exp(game
[gindex
], r
,
2222 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1)) == -1)
2225 game
[gindex
].hindex
= n
;
2226 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2227 update_all(game
[gindex
]);
2230 void do_find_move_exp(WIN
*win
)
2232 struct input_data_s
*in
= win
->data
;
2237 strncpy(moveexp
, in
->str
, sizeof(moveexp
));
2242 do_find_move_exp_finalize(1, c
);
2250 void do_move_jump_finalize(int n
)
2252 struct userdata_s
*d
= game
[gindex
].data
;
2254 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
2258 update_status_notify(game
[gindex
], NULL
);
2259 game
[gindex
].hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2260 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2261 update_all(game
[gindex
]);
2264 void do_move_jump(WIN
*win
)
2266 struct input_data_s
*in
= win
->data
;
2268 if (!in
->str
|| !isinteger(in
->str
)) {
2276 do_move_jump_finalize(atoi(in
->str
));
2281 struct history_menu_s
{
2289 void get_history_data(HISTORY
**hp
, struct history_menu_s
***menu
, int m
,
2293 int t
= pgn_history_total(hp
);
2294 char buf
[MAX_SAN_MOVE_LEN
+ 3];
2296 struct history_menu_s
**hmenu
= *menu
;
2299 for (n
= 0; hmenu
[n
]; n
++);
2303 for (i
= 0; i
< t
; i
++) {
2304 hmenu
= Realloc(hmenu
, (n
+ 2) * sizeof(struct history_menu_s
*));
2305 hmenu
[n
] = Malloc(sizeof(struct history_menu_s
));
2306 snprintf(buf
, sizeof(buf
), "%c%s", (turn
== WHITE
) ? 'W' : 'B',
2308 hmenu
[n
]->line
= strdup(buf
);
2309 hmenu
[n
]->hindex
= i
;
2310 hmenu
[n
]->indent
= 0;
2311 hmenu
[n
]->ravlevel
= depth
;
2312 hmenu
[n
]->move
= (n
&& depth
> hmenu
[n
-1]->ravlevel
) ? m
++ : m
;
2318 get_history_data(hp
[i
]->rav
, &hmenu
, m
, turn
);
2319 for (n
= 0; hmenu
[n
]; n
++);
2326 turn
= (turn
== WHITE
) ? BLACK
: WHITE
;
2332 void history_draw_update(struct menu_input_s
*m
)
2335 struct userdata_s
*d
= g
->data
;
2337 g
->hindex
= m
->selected
+ 1;
2338 update_cursor(*g
, m
->selected
);
2339 pgn_board_update(g
, d
->b
, m
->selected
+ 1);
2342 struct menu_item_s
**get_history_items(WIN
*win
)
2344 struct menu_input_s
*m
= win
->data
;
2346 struct userdata_s
*d
= g
->data
;
2347 struct history_menu_s
**hm
= d
->data
;
2348 struct menu_item_s
**items
= m
->items
;
2352 get_history_data(g
->history
, &hm
, 0,
2353 TEST_FLAG(g
->flags
, GF_BLACK_OPENING
));
2354 m
->selected
= g
->hindex
- 1;
2356 if (m
->selected
< 0)
2359 m
->draw_exit_func
= history_draw_update
;
2365 for (i
= 0; items
[i
]; i
++)
2372 for (i
= 0; hm
[i
]; i
++) {
2373 items
= Realloc(items
, (i
+2) * sizeof(struct menu_item_s
*));
2374 items
[i
] = Malloc(sizeof(struct menu_item_s
));
2375 items
[i
]->name
= hm
[i
]->line
;
2376 items
[i
]->value
= NULL
;
2377 items
[i
]->selected
= 0;
2388 void history_menu_quit(struct menu_input_s
*m
)
2393 void history_menu_exit(WIN
*win
)
2395 GAME
*g
= win
->data
;
2396 struct userdata_s
*d
= g
->data
;
2397 struct history_menu_s
**hm
= d
->data
;
2403 for (i
= 0; hm
[i
]; i
++) {
2413 void history_menu_next(struct menu_input_s
*m
)
2416 struct userdata_s
*d
= g
->data
;
2417 struct history_menu_s
**hm
= d
->data
;
2420 for (t
= 0; hm
[t
]; t
++);
2422 if (m
->selected
+ 1 == t
)
2425 n
= hm
[m
->selected
+ 1]->hindex
;
2432 void history_menu_prev(struct menu_input_s
*m
)
2435 struct userdata_s
*d
= g
->data
;
2436 struct history_menu_s
**hm
= d
->data
;
2439 for (t
= 0; hm
[t
]; t
++);
2441 if (m
->selected
- 1 < 0)
2444 n
= hm
[m
->selected
- 1]->hindex
;
2450 void history_menu_help(struct menu_input_s
*m
)
2452 message("History Menu Help", ANYKEY
, "%s", history_menu_help_str
);
2455 void do_annotate_move(HISTORY
*hp
)
2458 struct input_data_s
*in
;
2460 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_EDIT_TITLE
, hp
->move
);
2461 in
= Calloc(1, sizeof(struct input_data_s
));
2463 in
->efunc
= do_annotate_finalize
;
2464 construct_input(buf
, hp
->comment
, MAX_PGN_LINE_LEN
/ INPUT_WIDTH
, 0,
2465 NAG_PROMPT
, edit_nag
, NULL
, CTRL('T'), in
, -1);
2468 void history_menu_annotate(struct menu_input_s
*m
)
2473 do_annotate_move(g
->history
[m
->selected
]);
2476 void history_menu(GAME
*g
)
2478 struct menu_key_s
**keys
= NULL
;
2480 add_menu_key(&keys
, KEY_ESCAPE
, history_menu_quit
);
2481 add_menu_key(&keys
, KEY_UP
, history_menu_prev
);
2482 add_menu_key(&keys
, KEY_DOWN
, history_menu_next
);
2483 add_menu_key(&keys
, KEY_F(1), history_menu_help
);
2484 add_menu_key(&keys
, 'a', history_menu_annotate
);
2485 construct_menu(LINES
, TAG_WIDTH
, 0, 0, "Move History Tree", 1,
2486 get_history_items
, keys
, g
, history_menu_exit
);
2489 static void historymode_keys(chtype c
)
2492 struct userdata_s
*d
= game
[gindex
].data
;
2493 struct input_data_s
*in
;
2498 history_menu(&game
[gindex
]);
2501 config
.details
= (config
.details
) ? 0 : 1;
2504 movestep
= (movestep
== 1) ? 2 : 1;
2505 update_history_window(game
[gindex
]);
2508 pgn_history_next(&game
[gindex
], d
->b
, (keycount
> 0) ?
2509 config
.jumpcount
* keycount
* movestep
:
2510 config
.jumpcount
* movestep
);
2511 update_all(game
[gindex
]);
2514 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2515 config
.jumpcount
* keycount
* movestep
:
2516 config
.jumpcount
* movestep
);
2517 update_all(game
[gindex
]);
2520 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2521 keycount
* movestep
: movestep
);
2522 update_all(game
[gindex
]);
2525 pgn_history_next(&game
[gindex
], d
->b
, (keycount
) ?
2526 keycount
* movestep
: movestep
);
2527 update_all(game
[gindex
]);
2530 n
= game
[gindex
].hindex
;
2532 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
2537 do_annotate_move(game
[gindex
].hp
[n
]);
2542 if (pgn_history_total(game
[gindex
].hp
) < 2)
2545 in
= Calloc(1, sizeof(struct input_data_s
));
2546 p
= Malloc(sizeof(int));
2549 in
->efunc
= do_find_move_exp
;
2551 if (!*moveexp
|| c
== '/') {
2552 construct_input(FIND_REGEXP
, moveexp
, 1, 0, NULL
, NULL
, NULL
,
2557 do_find_move_exp_finalize(0, c
);
2560 view_annotation(game
[gindex
].hp
[game
[gindex
].hindex
]);
2563 if (game
[gindex
].hindex
- 1 >= 0)
2564 view_annotation(game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2568 rav_next_prev(&game
[gindex
], d
->b
, (c
== '-') ? 0 : 1);
2569 update_all(game
[gindex
]);
2572 if (pgn_history_total(game
[gindex
].hp
) < 2)
2576 in
= Calloc(1, sizeof(struct input_data_s
));
2577 in
->efunc
= do_move_jump
;
2579 construct_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1, NULL
,
2580 NULL
, NULL
, 0, in
, 0);
2584 do_move_jump_finalize(keycount
);
2591 static void free_userdata()
2595 for (i
= 0; i
< gtotal
; i
++) {
2596 struct userdata_s
*d
;
2602 stop_engine(&game
[i
]);
2607 game
[i
].data
= NULL
;
2612 void update_loading_window(int n
)
2615 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
2616 loadingp
= new_panel(loadingw
);
2617 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
2620 wmove(loadingw
, 0, 0);
2621 wclrtobot(loadingw
);
2622 wattron(loadingw
, CP_MESSAGE_BORDER
);
2623 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
2624 wattroff(loadingw
, CP_MESSAGE_BORDER
);
2625 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
2626 11 + strlen(itoa(gtotal
))), "Loading... %i%% (%i games)", n
,
2631 static void init_userdata_once(GAME
*g
, int n
)
2633 struct userdata_s
*d
= NULL
;
2635 d
= Calloc(1, sizeof(struct userdata_s
));
2637 d
->c_row
= 2, d
->c_col
= 5;
2638 SET_FLAG(d
->flags
, CF_NEW
);
2641 if (pgn_board_init_fen(g
, d
->b
, NULL
) != E_PGN_OK
)
2642 pgn_board_init(d
->b
);
2645 void init_userdata()
2649 for (i
= 0; i
< gtotal
; i
++)
2650 init_userdata_once(&game
[i
], i
);
2653 void fix_marks(int *start
, int *end
)
2657 *start
= (*start
< 0) ? 0 : *start
;
2658 *end
= (*end
< 0) ? 0 : *end
;
2660 if (*start
> *end
) {
2666 *end
= (*end
> gtotal
) ? gtotal
: *end
;
2669 void do_new_game_finalize(GAME
*g
)
2671 struct userdata_s
*d
= g
->data
;
2673 d
->mode
= MODE_PLAY
;
2674 update_status_notify(*g
, NULL
);
2678 void do_new_game_from_scratch(WIN
*win
)
2680 if (tolower(win
->c
) != 'y')
2686 add_custom_tags(&game
[gindex
].tag
);
2689 do_new_game_finalize(&game
[gindex
]);
2695 add_custom_tags(&game
[gindex
].tag
);
2696 init_userdata_once(&game
[gindex
], gindex
);
2697 do_new_game_finalize(&game
[gindex
]);
2700 void do_game_delete_finalize(int n
)
2702 struct userdata_s
*d
;
2704 delete_game((!n
) ? gindex
: -1);
2705 d
= game
[gindex
].data
;
2706 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2707 update_all(game
[gindex
]);
2710 void do_game_delete_confirm(WIN
*win
)
2714 if (tolower(win
->c
) != 'y') {
2720 n
= (int *)win
->data
;
2721 do_game_delete_finalize(*n
);
2725 void do_game_delete()
2729 struct userdata_s
*d
;
2733 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2739 for (i
= n
= 0; i
< gtotal
; i
++) {
2742 if (TEST_FLAG(d
->flags
, CF_DELETE
))
2747 tmp
= GAME_DELETE_GAME_TEXT
;
2750 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2754 tmp
= GAME_DELETE_ALL_TEXT
;
2757 if (config
.deleteprompt
) {
2758 p
= Malloc(sizeof(int));
2760 construct_message(NULL
, YESNO
, 1, NULL
, NULL
, p
,
2761 do_game_delete_confirm
, 0, tmp
);
2765 do_game_delete_finalize(n
);
2768 void do_history_mode_finalize(struct userdata_s
*d
)
2771 d
->mode
= MODE_PLAY
;
2772 update_all(game
[gindex
]);
2775 void do_history_mode_confirm(WIN
*win
)
2777 struct userdata_s
*d
= game
[gindex
].data
;
2782 pgn_history_free(game
[gindex
].hp
,
2783 game
[gindex
].hindex
);
2784 pgn_board_update(&game
[gindex
], d
->b
,
2785 pgn_history_total(game
[gindex
].hp
));
2790 if (pgn_history_rav_new(&game
[gindex
], d
->b
,
2791 game
[gindex
].hindex
) != E_PGN_OK
)
2800 if (!TEST_FLAG(d
->flags
, CF_HUMAN
))
2801 add_engine_command(&game
[gindex
], ENGINE_READY
,
2802 "setboard %s\n", pgn_game_to_fen(game
[gindex
], d
->b
));
2804 do_history_mode_finalize(d
);
2807 void do_find_game_exp_finalize(int c
)
2809 struct userdata_s
*d
= game
[gindex
].data
;
2812 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1,
2813 (keycount
) ? keycount
: 1)) == -1)
2817 d
= game
[gindex
].data
;
2819 if (pgn_history_total(game
[gindex
].hp
))
2820 d
->mode
= MODE_HISTORY
;
2822 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2823 update_all(game
[gindex
]);
2826 void do_find_game_exp(WIN
*win
)
2828 struct input_data_s
*in
= win
->data
;
2833 strncpy(gameexp
, in
->str
, sizeof(gameexp
));
2838 do_find_game_exp_finalize(c
);
2846 void do_game_jump_finalize(int n
)
2848 struct userdata_s
*d
;
2850 if (--n
> gtotal
- 1 || n
< 0)
2854 d
= game
[gindex
].data
;
2855 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2856 update_status_notify(game
[gindex
], NULL
);
2857 update_all(game
[gindex
]);
2860 void do_game_jump(WIN
*win
)
2862 struct input_data_s
*in
= win
->data
;
2864 if (!in
->str
|| !isinteger(in
->str
)) {
2872 do_game_jump_finalize(atoi(in
->str
));
2877 void do_load_file(WIN
*win
)
2880 struct input_data_s
*in
= win
->data
;
2881 char *tmp
= in
->str
;
2882 struct userdata_s
*d
;
2889 if ((tmp
= word_expand(tmp
)) == NULL
)
2892 if ((fp
= pgn_open(tmp
)) == NULL
) {
2893 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
2900 * FIXME what is the game state after a parse error?
2902 if (pgn_parse(fp
) == E_PGN_ERR
) {
2903 del_panel(loadingp
);
2908 update_all(game
[gindex
]);
2912 del_panel(loadingp
);
2917 strncpy(loadfile
, tmp
, sizeof(loadfile
));
2918 d
= game
[gindex
].data
;
2920 if (pgn_history_total(game
[gindex
].hp
))
2921 d
->mode
= MODE_HISTORY
;
2923 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2924 update_all(game
[gindex
]);
2933 void do_game_save(WIN
*win
)
2935 struct input_data_s
*in
= win
->data
;
2938 char *tmp
= in
->str
;
2939 char tfile
[FILENAME_MAX
];
2942 if (!tmp
|| (tmp
= word_expand(tmp
)) == NULL
)
2945 if (pgn_is_compressed(tmp
)) {
2946 p
= tmp
+ strlen(tmp
) - 1;
2948 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
2950 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2955 if ((p
= strchr(tmp
, '.')) != NULL
) {
2956 if (strcmp(p
, ".pgn") != 0) {
2957 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2962 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2977 void do_get_game_save_input(int n
)
2979 struct input_data_s
*in
= Calloc(1, sizeof(struct input_data_s
));
2980 int *p
= Malloc(sizeof(int));
2982 in
->efunc
= do_game_save
;
2986 construct_input(GAME_SAVE_TITLE
, loadfile
, 1, 1, BROWSER_PROMPT
,
2987 file_browser
, NULL
, '\t', in
, -1);
2990 void do_game_save_multi_confirm(WIN
*win
)
2996 else if (win
->c
== 'a')
2999 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
3003 do_get_game_save_input(i
);
3006 void do_more_help(WIN
*);
3007 void do_main_help(WIN
*win
)
3012 construct_message(GAME_HELP_PLAY_TITLE
, ANYKEY
, 0,
3013 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3017 construct_message(GAME_HELP_HISTORY_TITLE
, ANYKEY
, 0,
3018 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3022 construct_message(GAME_HELP_EDIT_TITLE
, ANYKEY
, 0,
3023 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3027 construct_message(GAME_HELP_INDEX_TITLE
, ANYKEY
, 0,
3028 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3036 void do_more_help(WIN
*win
)
3038 if (win
->c
== KEY_F(1))
3039 construct_message(GAME_HELP_INDEX_PROMPT
, ANYKEY
, 0, NULL
, NULL
, NULL
,
3040 do_main_help
, 0, "%s", mainhelp
);
3043 // Global and other keys.
3044 static int globalkeys(chtype c
)
3048 struct userdata_s
*d
= game
[gindex
].data
;
3049 struct input_data_s
*in
;
3053 toggle_engine_window();
3056 cmessage("ABOUT", ANYKEY
, "%s (%s)\nUsing %s with %i colors "
3057 "and %i color pairs\nCopyright 2002-2006 %s",
3058 PACKAGE_STRING
, pgn_version(), curses_version(), COLORS
,
3059 COLOR_PAIRS
, PACKAGE_BUGREPORT
);
3062 if (d
->mode
!= MODE_HISTORY
) {
3063 if (!pgn_history_total(game
[gindex
].hp
) ||
3064 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
3067 d
->mode
= MODE_HISTORY
;
3068 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3069 update_all(game
[gindex
]);
3073 // FIXME Resuming from previous history could append to a RAV.
3074 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
3076 construct_message(NULL
, "(r)esume or abort", 0,
3077 NULL
, NULL
, NULL
, do_history_mode_confirm
, 0,
3078 "%s", GAME_RESUME_HISTORY_TEXT
);
3083 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
3087 do_history_mode_finalize(d
);
3091 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
3093 d
= game
[gindex
].data
;
3097 markend
= markstart
+ delete_count
;
3101 markend
= markstart
- delete_count
;
3102 delete_count
= -1; // to fix gindex in the other direction
3106 fix_marks(&markstart
, &markend
);
3109 if (d
->mode
!= MODE_EDIT
)
3110 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
3112 update_status_notify(game
[gindex
], NULL
);
3113 update_all(game
[gindex
]);
3121 in
= Calloc(1, sizeof(struct input_data_s
));
3122 p
= Malloc(sizeof(int));
3125 in
->efunc
= do_find_game_exp
;
3127 if (!*gameexp
|| c
== '?') {
3128 construct_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
, 1, 0,
3129 GAME_FIND_EXPRESSION_PROMPT
, NULL
, NULL
, 0, in
, -1);
3133 do_find_game_exp_finalize(c
);
3139 in
= Calloc(1, sizeof(struct input_data_s
));
3140 in
->efunc
= do_game_jump
;
3143 construct_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
, NULL
, NULL
,
3148 do_game_jump_finalize(keycount
);
3156 if (keycount
&& delete_count
== 0) {
3158 delete_count
= keycount
;
3159 update_status_notify(game
[gindex
], "%s (delete)",
3164 if (markstart
>= 0 && markend
>= 0) {
3165 for (i
= markstart
; i
< markend
; i
++) {
3166 if (toggle_delete_flag(i
)) {
3167 update_all(game
[gindex
]);
3172 gindex
= (delete_count
< 0) ? markstart
: i
- 1;
3173 update_all(game
[gindex
]);
3176 if (toggle_delete_flag(gindex
))
3180 markstart
= markend
= -1;
3182 update_status_window(game
[gindex
]);
3188 edit_tags(game
[gindex
], d
->b
, 1);
3191 edit_tags(game
[gindex
], d
->b
, 0);
3194 in
= Calloc(1, sizeof(struct input_data_s
));
3195 in
->efunc
= do_load_file
;
3196 construct_input(GAME_LOAD_TITLE
, NULL
, 1, 1, BROWSER_PROMPT
,
3197 file_browser
, NULL
, '\t', in
, -1);
3202 construct_message(NULL
, GAME_SAVE_MULTI_PROMPT
, 1, NULL
,
3203 NULL
, NULL
, do_game_save_multi_confirm
, 0, "%s",
3204 GAME_SAVE_MULTI_TEXT
);
3208 do_get_game_save_input(-1);
3213 construct_message(GAME_HELP_PLAY_TITLE
, ANYKEY
, 0,
3214 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3218 construct_message(GAME_HELP_HISTORY_TITLE
, ANYKEY
, 0,
3219 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3223 construct_message(GAME_HELP_EDIT_TITLE
, ANYKEY
, 0,
3224 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3236 construct_message(NULL
, YESNO
, 1, NULL
, NULL
, NULL
,
3237 do_new_game_from_scratch
, 0, "%s", GAME_NEW_PROMPT
);
3241 keypad(boardw
, TRUE
);
3245 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
3246 markend
= markstart
= 0;
3250 update_status_notify(game
[gindex
], NULL
);
3253 if (config
.validmoves
)
3254 pgn_reset_valid_moves(d
->b
);
3261 keycount
= keycount
* 10 + n
;
3265 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3268 if (d
->mode
== MODE_HISTORY
)
3272 d
->c_row
+= keycount
;
3283 if (d
->mode
== MODE_HISTORY
)
3287 d
->c_row
-= keycount
;
3289 update_status_notify(game
[gindex
], NULL
);
3299 if (d
->mode
== MODE_HISTORY
)
3303 d
->c_col
-= keycount
;
3314 if (d
->mode
== MODE_HISTORY
)
3318 d
->c_col
+= keycount
;
3329 if (d
->mode
!= MODE_EDIT
&& d
->mode
!=
3333 // Don't edit a running game (for now).
3334 if (pgn_history_total(game
[gindex
].hp
))
3337 if (d
->mode
!= MODE_EDIT
) {
3338 pgn_board_init_fen(&game
[gindex
], d
->b
, NULL
);
3340 d
->mode
= MODE_EDIT
;
3341 update_all(game
[gindex
]);
3346 pgn_tag_add(&game
[gindex
].tag
, "FEN",
3347 pgn_game_to_fen(game
[gindex
], d
->b
));
3348 pgn_tag_add(&game
[gindex
].tag
, "SetUp", "1");
3349 pgn_tag_sort(game
[gindex
].tag
);
3350 d
->mode
= MODE_PLAY
;
3351 update_all(game
[gindex
]);
3361 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(d
->b
));
3374 int error_recover
= 0;
3375 struct userdata_s
*d
= game
[gindex
].data
;
3377 gindex
= gtotal
- 1;
3379 if (pgn_history_total(game
[gindex
].hp
))
3380 d
->mode
= MODE_HISTORY
;
3382 d
->mode
= MODE_PLAY
;
3384 if (d
->mode
== MODE_HISTORY
) {
3385 pgn_board_update(&game
[gindex
], d
->b
,
3386 pgn_history_total(game
[gindex
].hp
));
3389 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
3392 update_all(game
[gindex
]);
3393 update_tag_window(game
[gindex
].tag
);
3394 wtimeout(boardw
, 70);
3399 char fdbuf
[8192] = {0};
3401 struct timeval tv
= {0, 0};
3408 for (i
= 0; i
< gtotal
; i
++) {
3412 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
3413 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
3414 n
= d
->engine
->fd
[ENGINE_IN_FD
];
3416 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
3419 if (d
->engine
->fd
[ENGINE_OUT_FD
] > 2) {
3420 if (d
->engine
->fd
[ENGINE_OUT_FD
] > n
)
3421 n
= d
->engine
->fd
[ENGINE_OUT_FD
];
3423 FD_SET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
);
3429 if ((n
= select(n
+ 1, &rfds
, &wfds
, NULL
, &tv
)) > 0) {
3430 for (i
= 0; i
< gtotal
; i
++) {
3434 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3435 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3439 if (errno
!= EAGAIN
) {
3440 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3442 waitpid(d
->engine
->pid
, &n
, 0);
3450 parse_engine_output(&game
[i
], fdbuf
);
3454 if (FD_ISSET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
)) {
3455 if (d
->engine
->queue
)
3456 send_engine_command(&game
[i
]);
3463 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3468 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
3469 d
->mode
= MODE_HISTORY
;
3471 d
= game
[gindex
].data
;
3473 draw_board(&game
[gindex
]);
3474 update_all(game
[gindex
]);
3475 wmove(boardw
, ROWTOMATRIX(d
->c_row
), COLTOMATRIX(d
->c_col
));
3479 * Finds the top level window in the window stack so we know what
3480 * window the wgetch()ed key belongs to.
3483 for (i
= 0; wins
[i
]; i
++);
3486 wtimeout(wp
, WINDOW_TIMEOUT
);
3495 if ((c
= wgetch(wp
)) == ERR
)
3505 * Run the function associated with the window. When the
3506 * function returns 0 win->efunc is ran (if not NULL) with
3507 * win as the one and only parameter. Then the window is
3510 * The exit function may create another window which will
3511 * mess up the window stack when window_destroy() is called.
3512 * So don't destory the window until the top window is
3513 * destroyable. See window_destroy().
3515 if ((*win
->func
)(win
) == 0) {
3520 window_destroy(win
);
3527 if (!keycount
&& status
.notify
)
3528 update_status_notify(game
[gindex
], NULL
);
3530 if ((n
= globalkeys(c
)) == 1) {
3542 if (playmode_keys(c
))
3546 historymode_keys(c
);
3556 void usage(const char *pn
, int ret
)
3558 fprintf((ret
) ? stderr
: stdout
, "%s",
3559 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3560 " -p Load PGN file.\n"
3561 " -V Validate a game file.\n"
3562 " -S Validate and output a PGN formatted game.\n"
3563 " -R Like -S but write a reduced PGN formatted game.\n"
3564 " -t Also write custom PGN tags from config file.\n"
3565 " -E Stop processing on file parsing error (overrides config).\n"
3566 " -v Version information.\n"
3567 " -h This help text.\n");
3579 free(config
.engine_cmd
);
3580 free(config
.pattern
);
3581 free(config
.ccfile
);
3582 free(config
.nagfile
);
3583 free(config
.configfile
);
3586 for (i
= 0; config
.keys
[i
]; i
++)
3587 free(config
.keys
[i
]->str
);
3593 for (i
= 0; config
.einit
[i
]; i
++)
3594 free(config
.einit
[i
]);
3600 pgn_tag_free(config
.tag
);
3602 if (curses_initialized
) {
3604 del_panel(historyp
);
3617 for (i
= 0; enginebuf
[i
]; i
++)
3628 void catch_signal(int which
)
3635 if (which
== SIGPIPE
&& quit
)
3638 if (which
== SIGPIPE
)
3639 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3649 keypad(boardw
, TRUE
);
3663 void loading_progress(long total
, long offset
)
3665 int n
= (100 * (offset
/ 100) / (total
/ 100));
3667 if (curses_initialized
)
3668 update_loading_window(n
);
3670 fprintf(stderr
, "Loading... %i%% (%i games)\r", n
, gtotal
);
3675 static void set_defaults()
3677 set_config_defaults();
3679 pgn_config_set(PGN_PROGRESS
, 1024);
3680 pgn_config_set(PGN_PROGRESS_FUNC
, loading_progress
);
3683 int main(int argc
, char *argv
[])
3687 char buf
[FILENAME_MAX
];
3688 char datadir
[FILENAME_MAX
];
3689 int ret
= EXIT_SUCCESS
;
3690 int validate_only
= 0, validate_and_write
= 0;
3691 int write_custom_tags
= 0;
3695 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3696 err(EXIT_FAILURE
, "getpwuid()");
3698 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3699 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3700 config
.ccfile
= strdup(buf
);
3701 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3702 config
.nagfile
= strdup(buf
);
3703 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3704 config
.configfile
= strdup(buf
);
3706 if (stat(datadir
, &st
) == -1) {
3707 if (errno
== ENOENT
) {
3708 if (mkdir(datadir
, 0755) == -1)
3709 err(EXIT_FAILURE
, "%s", datadir
);
3712 err(EXIT_FAILURE
, "%s", datadir
);
3717 if (!S_ISDIR(st
.st_mode
))
3718 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3722 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3725 write_custom_tags
= 1;
3731 pgn_config_set(PGN_REDUCED
, 1);
3733 validate_and_write
= 1;
3738 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3742 filetype
= PGN_FILE
;
3743 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3747 usage(argv
[0], EXIT_SUCCESS
);
3751 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3752 usage(argv
[0], EXIT_FAILURE
);
3754 if (access(config
.configfile
, R_OK
) == 0)
3755 parse_rcfile(config
.configfile
);
3758 pgn_config_set(PGN_STOP_ON_ERROR
, 1);
3760 signal(SIGPIPE
, catch_signal
);
3761 signal(SIGCONT
, catch_signal
);
3762 signal(SIGSTOP
, catch_signal
);
3763 signal(SIGINT
, catch_signal
);
3764 signal(SIGALRM
, catch_signal
);
3765 signal(SIGTERM
, catch_signal
);
3771 if ((fp
= pgn_open(loadfile
)) == NULL
)
3772 err(EXIT_FAILURE
, "%s", loadfile
);
3774 ret
= pgn_parse(fp
);
3777 //ret = parse_fen_file(loadfile);
3779 case EPD_FILE
: // Not implemented.
3782 // No file specified. Empty game.
3783 ret
= pgn_parse(NULL
);
3784 add_custom_tags(&game
[gindex
].tag
);
3788 if (validate_only
|| validate_and_write
) {
3789 if (validate_and_write
) {
3790 for (i
= 0; i
< gtotal
; i
++) {
3791 if (write_custom_tags
)
3792 add_custom_tags(&game
[i
].tag
);
3794 pgn_write(stdout
, game
[i
]);
3801 else if (ret
== E_PGN_ERR
)
3806 if (initscr() == NULL
)
3807 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3809 curses_initialized
= 1;
3811 if (LINES
< 24 || COLS
< 80) {
3813 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3816 if (has_colors() == TRUE
&& start_color() == OK
)
3819 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3820 boardp
= new_panel(boardw
);
3821 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3822 COLS
- HISTORY_WIDTH
);
3823 historyp
= new_panel(historyw
);
3824 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3825 statusp
= new_panel(statusw
);
3826 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3827 tagp
= new_panel(tagw
);
3828 keypad(boardw
, TRUE
);
3829 // leaveok(boardw, TRUE);
3830 leaveok(tagw
, TRUE
);
3831 leaveok(statusw
, TRUE
);
3832 leaveok(historyw
, TRUE
);
3836 draw_window_decor();