1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
3 Copyright (C) 2002-2013 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
26 #include <sys/types.h>
28 #include <sys/socket.h>
31 #include <ncursesw/panel.h>
44 #ifdef HAVE_SYS_WAIT_H
69 #include "filebrowser.h"
79 #define COPYRIGHT "Copyright (C) 2002-2013 " PACKAGE_BUGREPORT
80 #define LINE_GRAPHIC(c) ((!config.linegraphics) ? ' ' : c)
81 #define ROWTOMATRIX(r) ((8 - r) * 2 + 2 - 1)
82 #define COLTOMATRIX(c) ((c == 1) ? 1 : c * 4 - 3)
83 #define BOARD_HEIGHT 18
84 #define BOARD_WIDTH 34
85 #define STATUS_HEIGHT 12
86 #define STATUS_WIDTH (COLS - BOARD_WIDTH)
87 #define TAG_HEIGHT LINES - STATUS_HEIGHT - 1
88 #define TAG_WIDTH (COLS - BOARD_WIDTH)
89 #define HISTORY_HEIGHT (LINES - BOARD_HEIGHT)
90 #define HISTORY_WIDTH (COLS - STATUS_WIDTH)
91 #define MAX_VALUE_WIDTH (COLS - 8)
97 static WINDOW
*boardw
;
101 static WINDOW
*statusw
;
102 static PANEL
*statusp
;
103 static WINDOW
*historyw
;
104 static PANEL
*historyp
;
105 static WINDOW
*loadingw
;
106 static PANEL
*loadingp
;
107 static WINDOW
*enginew
;
108 static PANEL
*enginep
;
110 static char gameexp
[255];
111 static char moveexp
[255];
112 static struct itimerval clock_timer
;
113 static int delete_count
= 0;
114 static int markstart
= -1, markend
= -1;
116 static char loadfile
[FILENAME_MAX
];
120 // Loaded filename from the command line or from the file input dialog.
123 FILE_NONE
, FILE_PGN
, FILE_FEN
, FILE_EPD
127 static int nag_total
;
128 static int macro_match
;
132 wchar_t *notify
; // The status window notification line buffer.
135 static int curses_initialized
;
137 // When in history mode a full step is to the next move of the same playing
138 // side. Half stepping is alternating sides.
141 static wchar_t *w_pawn_wchar
;
142 static wchar_t *w_rook_wchar
;
143 static wchar_t *w_bishop_wchar
;
144 static wchar_t *w_knight_wchar
;
145 static wchar_t *w_queen_wchar
;
146 static wchar_t *w_king_wchar
;
147 static wchar_t *b_pawn_wchar
;
148 static wchar_t *b_rook_wchar
;
149 static wchar_t *b_bishop_wchar
;
150 static wchar_t *b_knight_wchar
;
151 static wchar_t *b_queen_wchar
;
152 static wchar_t *b_king_wchar
;
153 static wchar_t *empty_wchar
;
155 static void free_userdata_once(GAME g
);
157 void update_cursor(GAME g
, int idx
)
161 int t
= pgn_history_total(g
->hp
);
162 struct userdata_s
*d
= g
->data
;
165 * If not deincremented then r and c would be the next move.
169 if (idx
> t
|| idx
< 0 || !t
|| !g
->hp
[idx
]->move
) {
170 d
->c_row
= 2, d
->c_col
= 5;
174 p
= g
->hp
[idx
]->move
;
183 d
->c_row
= (g
->turn
== WHITE
) ? 8 : 1;
192 d
->c_row
= RANKTOINT(*p
--);
193 d
->c_col
= FILETOINT(*p
);
196 static int init_nag()
202 if ((fp
= fopen(config
.nagfile
, "r")) == NULL
) {
203 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config
.nagfile
, strerror(errno
));
207 nags
= Realloc(nags
, (i
+2) * sizeof(char *));
208 nags
[i
++] = strdup(_("none"));
212 if (fscanf(fp
, " %[^\n] ", line
) == 1) {
213 nags
= Realloc(nags
, (i
+ 2) * sizeof(char *));
214 nags
[i
++] = strdup(line
);
223 void edit_nag_toggle_item(struct menu_input_s
*m
)
225 struct input_s
*in
= m
->data
;
226 struct input_data_s
*id
= in
->data
;
227 HISTORY
*h
= id
->data
;
230 if (m
->selected
== 0) {
231 for (i
= 0; i
< MAX_PGN_NAG
; i
++)
234 for (i
= 0; m
->items
[i
]; i
++)
235 m
->items
[i
]->selected
= 0;
240 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
241 if (h
->nag
[i
] == m
->selected
)
242 h
->nag
[i
] = m
->selected
= 0;
245 h
->nag
[i
] = m
->selected
;
252 void edit_nag_save(struct menu_input_s
*m
)
257 void edit_nag_help(struct menu_input_s
*m
)
259 message(_("NAG Menu Keys"), _("[ press any key to continue ]"), "%s",
261 " UP/DOWN - previous/next menu item\n"
262 " HOME/END - first/last menu item\n"
263 " PGDN/PGUP - next/previous page\n"
264 " a-zA-Z0-9 - jump to item\n"
265 " SPACE - toggle selected item\n"
266 " CTRL-X - quit with changes"
270 struct menu_item_s
**get_nag_items(WIN
*win
)
273 struct menu_input_s
*m
= win
->data
;
274 struct input_s
*in
= m
->data
;
275 struct input_data_s
*id
= in
->data
;
276 struct menu_item_s
**items
= m
->items
;
277 HISTORY
*h
= id
->data
;
280 for (i
= 0; items
[i
]; i
++)
284 for (i
= 0; nags
[i
]; i
++) {
285 items
= Realloc(items
, (i
+2) * sizeof(struct menu_item_s
*));
286 items
[i
] = Malloc(sizeof(struct menu_item_s
));
287 items
[i
]->name
= nags
[i
];
288 items
[i
]->value
= NULL
;
290 for (n
= 0; n
< MAX_PGN_NAG
; n
++) {
291 if (h
->nag
[n
] == i
) {
292 items
[i
]->selected
= 1;
299 items
[i
]->selected
= 0;
308 void nag_print(WIN
*win
)
310 struct menu_input_s
*m
= win
->data
;
312 mvwprintw(win
->w
, m
->print_line
, 1, "%-*s", win
->cols
- 2, m
->item
->name
);
315 void edit_nag(void *arg
)
317 struct menu_key_s
**keys
= NULL
;
324 add_menu_key(&keys
, ' ', edit_nag_toggle_item
);
325 add_menu_key(&keys
, CTRL('x'), edit_nag_save
);
326 add_menu_key(&keys
, KEY_F(1), edit_nag_help
);
327 construct_menu(0, 0, -1, -1, _("Numeric Annotation Glyphs"), 1, get_nag_items
, keys
, arg
,
332 static void *view_nag(void *arg
)
334 HISTORY
*h
= (HISTORY
*)arg
;
336 char line
[LINE_MAX
] = {0};
339 snprintf(buf
, sizeof(buf
), "%s \"%s\"", _("Viewing NAG for"), h
->move
);
346 for (i
= 0; i
< MAX_PGN_NAG
; i
++) {
350 if (h
->nag
[i
] >= nag_total
)
351 strncat(line
, itoa(h
->nag
[i
]), sizeof(line
));
353 strncat(line
, nags
[h
->nag
[i
]], sizeof(line
));
355 strncat(line
, "\n", sizeof(line
));
358 line
[strlen(line
) - 1] = 0;
359 message(buf
, _("[ press any key to continue ]"), "%s", line
);
363 void view_annotation(HISTORY
*h
)
365 char buf
[MAX_SAN_MOVE_LEN
+ strlen(_("Viewing Annotation for")) + 4];
366 int nag
= 0, comment
= 0;
371 if (h
->comment
&& h
->comment
[0])
377 if (!nag
&& !comment
)
380 snprintf(buf
, sizeof(buf
), "%s \"%s\"", _("Viewing Annotation for"), h
->move
);
383 construct_message(buf
, (nag
) ? _("Any other key to continue") : _("[ press any key to continue ]"), 0, 1,
384 (nag
) ? _("Press 'n' to view NAG") : NULL
,
385 (nag
) ? view_nag
: NULL
, (nag
) ? h
: NULL
, NULL
,
386 (nag
) ? 'n' : 0, 0, "%s", h
->comment
);
388 construct_message(buf
, _("Any other key to continue"), 0, 1, _("Press 'n' to view NAG"), view_nag
, h
, NULL
,
389 'n', 0, "%s", _("No comment text for this move"));
392 int do_game_write(char *filename
, char *mode
, int start
, int end
)
395 struct userdata_s
*d
;
398 i
= pgn_open(filename
, mode
, &pgn
);
400 if (i
== E_PGN_ERR
) {
401 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", filename
, strerror(errno
));
404 else if (i
== E_PGN_INVALID
) {
405 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", filename
, _("Not a regular file"));
409 for (i
= (start
== -1) ? 0 : start
; i
< end
; i
++) {
411 pgn_write(pgn
, game
[i
]);
412 CLEAR_FLAG(d
->flags
, CF_MODIFIED
);
415 if (pgn_close(pgn
) != E_PGN_OK
)
416 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", strerror(errno
));
419 strncpy(loadfile
, filename
, sizeof(loadfile
));
431 void do_save_game_overwrite_confirm(WIN
*win
)
434 struct save_game_s
*s
= win
->data
;
447 if (do_game_write(s
->filename
, mode
, s
->start
, s
->end
))
448 update_status_notify(gp
, "%s", _("Save game failed."));
450 update_status_notify(gp
, "%s", _("Game saved."));
457 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
460 void save_pgn(char *filename
, int saveindex
)
462 char buf
[FILENAME_MAX
];
464 int end
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
465 struct save_game_s
*s
;
467 if (filename
[0] != '/' && config
.savedirectory
) {
468 if (stat(config
.savedirectory
, &st
) == -1) {
469 if (errno
== ENOENT
) {
470 if (mkdir(config
.savedirectory
, 0755) == -1) {
471 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config
.savedirectory
,
477 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config
.savedirectory
,
483 stat(config
.savedirectory
, &st
);
485 if (!S_ISDIR(st
.st_mode
)) {
486 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", config
.savedirectory
, _("Not a directory."));
490 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
494 if (access(filename
, W_OK
) == 0) {
495 s
= Malloc(sizeof(struct save_game_s
));
496 s
->filename
= strdup(filename
);
497 s
->start
= saveindex
;
499 construct_message(NULL
, _("'a' to append, 'o' to overwrite"), 1, 1, NULL
, NULL
,
500 s
, do_save_game_overwrite_confirm
, 0, 0, "%s \"%s\"",
501 _("File exists:"), filename
);
505 if (do_game_write(filename
, "a", saveindex
, end
))
506 update_status_notify(gp
, "%s", _("Save game failed."));
508 update_status_notify(gp
, "%s", _("Game saved."));
511 static int castling_state(GAME g
, BOARD b
, int row
, int col
, int piece
, int mod
)
513 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
515 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
516 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
518 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
521 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
523 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
524 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
526 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
529 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
531 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
532 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
534 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
537 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
539 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
540 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
542 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
545 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
547 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
548 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
550 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
551 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
553 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
554 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
555 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
557 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
561 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
563 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
564 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
566 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
567 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
569 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
570 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
571 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
573 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
581 #define IS_ENPASSANT(c) (c == 'x') ? CP_BOARD_ENPASSANT : isupper(c) ? CP_BOARD_WHITE : CP_BOARD_BLACK
582 #define ATTRS(cp) (cp & (A_BOLD|A_STANDOUT|A_BLINK|A_DIM|A_UNDERLINE|A_INVIS|A_REVERSE))
587 w_pawn_wchar
= str_to_wchar ("♙");
588 w_rook_wchar
= str_to_wchar ("♖");
589 w_bishop_wchar
= str_to_wchar ("♗");
590 w_knight_wchar
= str_to_wchar ("♘");
591 w_queen_wchar
= str_to_wchar ("♕");
592 w_king_wchar
= str_to_wchar ("♔");
593 b_pawn_wchar
= str_to_wchar ("♟");
594 b_rook_wchar
= str_to_wchar ("♜");
595 b_bishop_wchar
= str_to_wchar ("♝");
596 b_knight_wchar
= str_to_wchar ("♞");
597 b_queen_wchar
= str_to_wchar ("♛");
598 b_king_wchar
= str_to_wchar ("♚");
599 empty_wchar
= str_to_wchar (" ");
603 piece_to_wchar (unsigned char p
)
616 return w_bishop_wchar
;
618 return b_bishop_wchar
;
620 return w_knight_wchar
;
622 return b_knight_wchar
;
624 return w_queen_wchar
;
626 return b_queen_wchar
;
636 void update_board_window(GAME g
)
639 int bcol
= 0, brow
= 0;
640 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
641 int ncols
= 0, offset
= 1;
642 unsigned coords_y
= 8;
643 struct userdata_s
*d
= g
->data
;
645 if (d
->mode
!= MODE_PLAY
&& d
->mode
!= MODE_EDIT
)
646 update_cursor(g
, g
->hindex
);
648 for (row
= 0; row
< maxy
; row
++) {
651 for (col
= 0; col
< maxx
; col
++) {
653 chtype attrs
= 0, old_attrs
= 0;
656 if (row
== 0 || row
== maxy
- 2) {
658 mvwaddch(boardw
, row
, col
,
660 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
661 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
662 else if (col
== maxx
- 2)
663 mvwaddch(boardw
, row
, col
,
665 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
666 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
668 mvwaddch(boardw
, row
, col
,
670 ACS_BTEE
| CP_BOARD_GRAPHICS
:
671 ACS_TTEE
| CP_BOARD_GRAPHICS
));
674 mvwaddch(boardw
, row
, col
,
675 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
681 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
682 wattron(boardw
, CP_BOARD_COORDS
);
683 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
684 wattroff(boardw
, CP_BOARD_COORDS
);
688 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
690 mvwaddch(boardw
, row
, col
,
692 ACS_RTEE
| CP_BOARD_GRAPHICS
:
693 ACS_LTEE
| CP_BOARD_GRAPHICS
));
695 mvwaddch(boardw
, row
, col
,
696 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
701 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
702 mvwaddch(boardw
, row
, col
,
703 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
707 if (!(col
% 4) && row
!= maxy
- 1) {
708 mvwaddch(boardw
, row
, col
,
709 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
720 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
726 p
= d
->b
[row
/ 2][bcol
].icon
;
727 int pi
= pgn_piece_to_int(p
);
729 if (config
.details
&& d
->b
[row
/ 2][bcol
].enpassant
) {
731 attrs
= mix_cp(CP_BOARD_ENPASSANT
, (attrwhich
== WHITE
) ? CP_BOARD_WHITE
: CP_BOARD_BLACK
, ATTRS(CP_BOARD_ENPASSANT
), A_FG_B_BG
);
734 if (config
.validmoves
&& d
->b
[brow
][bcol
].valid
) {
737 if (attrwhich
== WHITE
)
738 attrs
= mix_cp(CP_BOARD_MOVES_WHITE
, IS_ENPASSANT(p
),
739 ATTRS(CP_BOARD_MOVES_WHITE
), B_FG_A_BG
);
741 attrs
= mix_cp(CP_BOARD_MOVES_BLACK
, IS_ENPASSANT(p
),
742 ATTRS(CP_BOARD_MOVES_BLACK
), B_FG_A_BG
);
745 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
: CP_BOARD_BLACK
;
747 if (row
== ROWTOMATRIX(d
->c_row
) && col
==
748 COLTOMATRIX(d
->c_col
)) {
749 attrs
= mix_cp(CP_BOARD_CURSOR
, IS_ENPASSANT(p
),
750 ATTRS(CP_BOARD_CURSOR
), B_FG_A_BG
);
753 else if (row
== ROWTOMATRIX(d
->sp
.srow
) &&
754 col
== COLTOMATRIX(d
->sp
.scol
)) {
755 attrs
= mix_cp(CP_BOARD_SELECTED
, IS_ENPASSANT(p
),
756 ATTRS(CP_BOARD_SELECTED
), B_FG_A_BG
);
763 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
766 waddch(boardw
, _("abcdefgh")[bcol
] | CP_BOARD_COORDS
);
768 if (old_attrs
== -1) {
775 if (pi
!= OPEN_SQUARE
&& p
!= 'x') {
776 if (attrwhich
== WHITE
) {
778 attrs
= CP_BOARD_W_W
;
780 attrs
= CP_BOARD_W_B
;
784 attrs
= CP_BOARD_B_W
;
786 attrs
= CP_BOARD_B_B
;
791 if (config
.details
&& castling_state(g
, d
->b
, brow
,
793 attrs
= mix_cp(CP_BOARD_CASTLING
, attrs
,
794 ATTRS(CP_BOARD_CASTLING
), A_FG_B_BG
);
797 wattron (boardw
, attrs
);
798 waddwstr (boardw
, piece_to_wchar (pi
!= OPEN_SQUARE
? p
: 0));
799 wattroff (boardw
, attrs
);
803 waddch(boardw
, ' ' | attrs
);
810 mvwaddch(boardw
, row
, col
,
811 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
819 void invalid_move(int n
, int e
, const char *m
)
821 if (curses_initialized
)
822 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s \"%s\" (round #%i)", (e
== E_PGN_AMBIGUOUS
)
823 ? _("Ambiguous move") : _("Invalid move"), m
, n
);
825 warnx("%s: %s \"%s\" (round #%i)", loadfile
, (e
== E_PGN_AMBIGUOUS
)
826 ? _("Ambiguous move") : _("Invalid move"), m
, n
);
829 void gameover(GAME g
)
831 struct userdata_s
*d
= g
->data
;
833 SET_FLAG(g
->flags
, GF_GAMEOVER
);
834 d
->mode
= MODE_HISTORY
;
838 static void update_clock(GAME g
, struct itimerval it
)
840 struct userdata_s
*d
= g
->data
;
842 if (TEST_FLAG(d
->flags
, CF_CLOCK
) && g
->turn
== WHITE
) {
843 d
->wclock
.elapsed
.tv_sec
+= it
.it_value
.tv_sec
;
844 d
->wclock
.elapsed
.tv_usec
+= it
.it_value
.tv_usec
;
846 if (d
->wclock
.elapsed
.tv_usec
> 1000000 - 1) {
847 d
->wclock
.elapsed
.tv_sec
+= d
->wclock
.elapsed
.tv_usec
/ 1000000;
848 d
->wclock
.elapsed
.tv_usec
= d
->wclock
.elapsed
.tv_usec
% 1000000;
851 if (d
->wclock
.tc
[d
->wclock
.tcn
][1] &&
852 d
->wclock
.elapsed
.tv_sec
>= d
->wclock
.tc
[d
->wclock
.tcn
][1]) {
853 pgn_tag_add(&g
->tag
, "Result", "0-1");
857 else if (TEST_FLAG(d
->flags
, CF_CLOCK
) && g
->turn
== BLACK
) {
858 d
->bclock
.elapsed
.tv_sec
+= it
.it_value
.tv_sec
;
859 d
->bclock
.elapsed
.tv_usec
+= it
.it_value
.tv_usec
;
861 if (d
->bclock
.elapsed
.tv_usec
> 1000000 - 1) {
862 d
->bclock
.elapsed
.tv_sec
+= d
->bclock
.elapsed
.tv_usec
/ 1000000;
863 d
->bclock
.elapsed
.tv_usec
= d
->bclock
.elapsed
.tv_usec
% 1000000;
866 if (d
->bclock
.tc
[d
->bclock
.tcn
][1] &&
867 d
->bclock
.elapsed
.tv_sec
>= d
->bclock
.tc
[d
->bclock
.tcn
][1]) {
868 pgn_tag_add(&g
->tag
, "Result", "1-0");
873 d
->elapsed
.tv_sec
+= it
.it_value
.tv_sec
;
874 d
->elapsed
.tv_usec
+= it
.it_value
.tv_usec
;
876 if (d
->elapsed
.tv_usec
> 1000000 - 1) {
877 d
->elapsed
.tv_sec
+= d
->elapsed
.tv_usec
/ 1000000;
878 d
->elapsed
.tv_usec
= d
->elapsed
.tv_usec
% 1000000;
882 static void update_time_control(GAME g
)
884 struct userdata_s
*d
= g
->data
;
885 struct clock_s
*clk
= (g
->turn
== WHITE
) ? &d
->wclock
: &d
->bclock
;
888 clk
->tc
[clk
->tcn
][1] += clk
->incr
;
890 if (!clk
->tc
[clk
->tcn
][1])
895 if (!clk
->tc
[clk
->tcn
][0] || clk
->move
>= clk
->tc
[clk
->tcn
][0]) {
897 clk
->tc
[clk
->tcn
+ 1][1] += abs(clk
->elapsed
.tv_sec
- clk
->tc
[clk
->tcn
][1]);
898 memset(&clk
->elapsed
, 0, sizeof(clk
->elapsed
));
903 void update_history_window(GAME g
)
905 char buf
[HISTORY_WIDTH
- 1];
908 int t
= pgn_history_total(g
->hp
);
910 n
= (g
->hindex
+ 1) / 2;
918 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, _("of"), total
,
919 (movestep
== 1) ? _(" (ply)") : "");
921 strncpy(buf
, _("not available"), sizeof(buf
));
923 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, _("Move:"),
924 HISTORY_WIDTH
- 13, buf
);
926 h
= pgn_history_by_n(g
->hp
, g
->hindex
);
927 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: _("not available"));
930 if (h
&& ((h
->comment
) || h
->nag
[0])) {
931 strncat(buf
, " (Annotated", sizeof(buf
));
936 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
941 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
946 strncat(buf
, ")", sizeof(buf
));
948 mvwprintw(historyw
, 3, 1, "%s %-*s", _("Next move:"),
949 HISTORY_WIDTH
- 13, buf
);
951 h
= pgn_history_by_n(g
->hp
, g
->hindex
- 1);
952 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: _("not available"));
955 if (h
&& ((h
->comment
) || h
->nag
[0])) {
956 strncat(buf
, " (Annotated", sizeof(buf
));
961 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
966 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
971 strncat(buf
, ")", sizeof(buf
));
973 mvwprintw(historyw
, 4, 1, "%s %-*s", _("Prev move:"),
974 HISTORY_WIDTH
- 13, buf
);
977 void do_validate_move(char *m
)
979 struct userdata_s
*d
= gp
->data
;
983 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
984 if ((n
= pgn_parse_move(gp
, d
->b
, &m
, &frfr
)) != E_PGN_OK
) {
985 invalid_move(d
->n
+ 1, n
, m
);
990 update_time_control(gp
);
991 pgn_history_add(gp
, d
->b
, m
);
996 if ((n
= pgn_validate_move(gp
, d
->b
, &m
, &frfr
)) != E_PGN_OK
) {
997 invalid_move(d
->n
+ 1, n
, m
);
1002 add_engine_command(gp
, ENGINE_THINKING
, "%s\n",
1003 (config
.engine_protocol
== 1) ? frfr
: m
);
1006 d
->sp
.srow
= d
->sp
.scol
= d
->sp
.icon
= 0;
1008 if (config
.validmoves
)
1009 pgn_reset_valid_moves(d
->b
);
1011 if (TEST_FLAG(gp
->flags
, GF_GAMEOVER
))
1012 d
->mode
= MODE_HISTORY
;
1014 SET_FLAG(d
->flags
, CF_MODIFIED
);
1018 update_history_window(gp
);
1019 update_board_window(gp
);
1023 void do_promotion_piece_finalize(WIN
*win
)
1025 char *p
, *str
= win
->data
;
1027 if (pgn_piece_to_int(win
->c
) == -1)
1030 p
= str
+ strlen(str
);
1031 *p
++ = toupper(win
->c
);
1033 do_validate_move(str
);
1036 static void move_to_engine(GAME g
)
1038 struct userdata_s
*d
= g
->data
;
1042 if (config
.validmoves
&&
1043 !d
->b
[RANKTOBOARD(d
->sp
.row
)][FILETOBOARD(d
->sp
.col
)].valid
)
1046 str
= Malloc(MAX_SAN_MOVE_LEN
+ 1);
1047 snprintf(str
, MAX_SAN_MOVE_LEN
+ 1, "%c%i%c%i",
1048 _("abcdefgh")[d
->sp
.scol
- 1],
1049 d
->sp
.srow
, _("abcdefgh")[d
->sp
.col
- 1], d
->sp
.row
);
1051 piece
= pgn_piece_to_int(d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
);
1053 if (piece
== PAWN
&& (d
->sp
.row
== 8 || d
->sp
.row
== 1)) {
1054 construct_message(_("Select Pawn Promotion Piece"), _ ("R/N/B/Q"), 1, 1, NULL
, NULL
,
1055 str
, do_promotion_piece_finalize
, 0, 0, "%s", _("R = Rook, N = Knight, B = Bishop, Q = Queen"));
1059 do_validate_move(str
);
1062 static char *clock_to_char(long n
)
1064 static char buf
[16];
1065 int h
= 0, m
= 0, s
= 0;
1068 m
= (n
% 3600) / 60;
1069 s
= (n
% 3600) % 60;
1070 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i", h
, m
, s
);
1074 static char *timeval_to_char(struct timeval t
, long limit
)
1077 int h
= 0, m
= 0, s
= 0;
1078 int n
= limit
? abs(limit
- t
.tv_sec
) : 0;
1081 m
= (n
% 3600) / 60;
1082 s
= (n
% 3600) % 60;
1083 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i", h
, m
, s
);
1087 static char *time_control_status(struct clock_s
*clk
)
1089 static char buf
[80];
1093 if (clk
->tc
[clk
->tcn
][0] && clk
->tc
[clk
->tcn
+ 1][1])
1094 snprintf(buf
, sizeof(buf
), " M%.2i/%s", abs(clk
->tc
[clk
->tcn
][0] - clk
->move
),
1095 clock_to_char(clk
->tc
[clk
->tcn
+ 1][1]));
1096 else if (!clk
->incr
)
1100 strncat(buf
, " I", sizeof(buf
));
1101 strncat(buf
, itoa(clk
->incr
), sizeof(buf
));
1107 void update_status_window(GAME g
)
1111 char tmp
[15], *engine
, *mode
;
1117 struct userdata_s
*d
= g
->data
;
1121 if (!curses_initialized
)
1124 getmaxyx(statusw
, maxy
, maxx
);
1131 wchar_t *loadfilew
= loadfile
[0] ? str_etc (loadfile
, w
, 1) : str_to_wchar (_ ("not available"));
1132 mvwprintw(statusw
, y
++, 1, "%*s %-*ls", 7, _("File:"), w
, loadfilew
);
1134 snprintf(buf
, len
, "%i %s %i", gindex
+ 1, _("of"), gtotal
);
1135 mvwprintw(statusw
, y
++, 1, "%*s %-*s", 7, _("Game:"), w
, buf
);
1140 if (config
.details
) {
1145 if (TEST_FLAG(d
->flags
, CF_DELETE
)) {
1153 if (TEST_FLAG(g
->flags
, GF_PERROR
)) {
1161 if (TEST_FLAG(d
->flags
, CF_MODIFIED
)) {
1169 pgn_config_get(PGN_STRICT_CASTLING
, &n
);
1179 if (TEST_FLAG(d
->flags
, CF_PERL
)) {
1189 mvwprintw(statusw
, y
++, 1, "%*s %-*s", 7, _("Flags:"), w
, (tmp
[0]) ? tmp
: "-");
1193 mode
= _("move history");
1202 mode
= _("(empty value)");
1206 snprintf(buf
, len
- 1, "%*s %s", 7, _("Mode:"), mode
);
1208 if (d
->mode
== MODE_PLAY
) {
1209 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1210 strncat(buf
, " (human/human)", len
- 1);
1211 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1212 strncat(buf
, " (engine/engine)", len
- 1);
1214 strncat(buf
, " (human/engine)", len
- 1);
1217 mvwprintw(statusw
, y
++, 1, "%-*s", len
, buf
);
1221 switch (d
->engine
->status
) {
1222 case ENGINE_THINKING
:
1223 engine
= _("pondering...");
1226 engine
= _("ready");
1228 case ENGINE_INITIALIZING
:
1229 engine
= _("initializing...");
1231 case ENGINE_OFFLINE
:
1232 engine
= _("offline");
1235 engine
= _("(empty value)");
1240 engine
= _("offline");
1242 mvwprintw(statusw
, y
, 1, "%*s %-*s", 7, _("Engine:"), w
, " ");
1243 wattron(statusw
, CP_STATUS_ENGINE
);
1244 mvwaddstr(statusw
, y
++, 9, engine
);
1245 wattroff(statusw
, CP_STATUS_ENGINE
);
1247 mvwprintw(statusw
, y
++, 1, "%*s %-*s", 7, _("Turn:"), w
,
1248 (g
->turn
== WHITE
) ? _("white") : _("black"));
1250 strncpy(tmp
, _("white"), sizeof(tmp
));
1251 tmp
[0] = toupper(tmp
[0]);
1252 snprintf(t
, sizeof(t
), "%s%s",
1253 timeval_to_char(d
->wclock
.elapsed
, d
->wclock
.tc
[d
->wclock
.tcn
][1]),
1254 time_control_status(&d
->wclock
));
1255 mvwprintw(statusw
, y
++, 1, "%*s: %-*s", 6, tmp
, w
, t
);
1257 strncpy(tmp
, _("black"), sizeof(tmp
));
1258 tmp
[0] = toupper(tmp
[0]);
1259 snprintf(t
, sizeof(t
), "%s%s",
1260 timeval_to_char(d
->bclock
.elapsed
, d
->bclock
.tc
[d
->bclock
.tcn
][1]),
1261 time_control_status(&d
->bclock
));
1262 mvwprintw(statusw
, y
++, 1, "%*s: %-*s", 6, tmp
, w
, t
);
1264 mvwprintw(statusw
, y
++, 1, "%*s %-*s", 7, _("Total:"), w
,
1265 clock_to_char(d
->elapsed
.tv_sec
));
1267 for (i
= 0; i
< STATUS_WIDTH
; i
++)
1268 mvwprintw(stdscr
, STATUS_HEIGHT
, i
, " ");
1271 status
.notify
= str_to_wchar(_("Type F1 for help"));
1273 wattron(stdscr
, CP_STATUS_NOTIFY
);
1274 mvwprintw(stdscr
, STATUS_HEIGHT
, CENTERX(STATUS_WIDTH
, status
.notify
),
1275 "%ls", status
.notify
);
1276 wattroff(stdscr
, CP_STATUS_NOTIFY
);
1279 void update_tag_window(TAG
**t
)
1282 int namel
= 0, valuel
= 0;
1284 for (i
= 0; t
[i
]; i
++) {
1285 l
= strlen(t
[i
]->name
);
1290 l
= strlen(t
[i
]->value
);
1296 w
= TAG_WIDTH
- namel
- 4;
1298 /* FIXME unicode tag pairs */
1299 for (i
= 0; t
[i
] && i
< TAG_HEIGHT
- 3; i
++)
1301 wchar_t *s
= str_etc(t
[i
]->value
, w
, 0);
1302 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*ls", namel
, t
[i
]->name
, w
, s
);
1306 for (; i
< TAG_HEIGHT
- 3; i
++)
1307 mvwprintw(tagw
, (i
+ 2), 1, "%*s", namel
+ w
+ 2, " ");
1310 void append_enginebuf(GAME g
, char *line
)
1313 struct userdata_s
*d
= g
->data
;
1315 if (d
->engine
->enginebuf
)
1316 for (i
= 0; d
->engine
->enginebuf
[i
]; i
++);
1318 if (i
>= LINES
- 3) {
1319 free(d
->engine
->enginebuf
[0]);
1321 for (i
= 0; d
->engine
->enginebuf
[i
+1]; i
++)
1322 d
->engine
->enginebuf
[i
] = d
->engine
->enginebuf
[i
+1];
1324 d
->engine
->enginebuf
[i
] = strdup(line
);
1327 d
->engine
->enginebuf
= Realloc(d
->engine
->enginebuf
, (i
+ 2) * sizeof(char *));
1328 d
->engine
->enginebuf
[i
++] = strdup(line
);
1329 d
->engine
->enginebuf
[i
] = NULL
;
1333 void update_engine_window(GAME g
)
1336 struct userdata_s
*d
= g
->data
;
1338 if (!d
->engine
|| !d
->engine
->enginebuf
)
1341 wmove(enginew
, 0, 0);
1344 if (d
->engine
->enginebuf
) {
1345 for (i
= 0; d
->engine
->enginebuf
[i
]; i
++)
1346 mvwprintw(enginew
, i
+ 2, 1, "%s", d
->engine
->enginebuf
[i
]);
1349 window_draw_title(enginew
, _("Engine IO Window"), COLS
, CP_MESSAGE_TITLE
,
1353 void update_all(GAME g
)
1355 struct userdata_s
*d
= g
->data
;
1358 * In the middle of a macro. Don't update the screen.
1360 if (macro_match
!= -1)
1364 * No need to update when the engine window is being shown.
1366 if (enginep
&& panel_hidden(enginep
) == ERR
) {
1372 wmove(boardw
, ROWTOMATRIX(d
->c_row
), COLTOMATRIX(d
->c_col
));
1373 update_board_window(g
);
1374 update_status_window(g
);
1375 update_history_window(g
);
1376 update_tag_window(g
->tag
);
1377 update_engine_window(g
);
1382 static void game_next_prev(GAME g
, int n
, int count
)
1388 if (gindex
+ count
> gtotal
- 1) {
1390 gindex
= gtotal
- 1;
1398 if (gindex
- count
< 0) {
1402 gindex
= gtotal
- 1;
1411 static void delete_game(int which
)
1416 struct userdata_s
*d
;
1418 for (i
= 0; i
< gtotal
; i
++) {
1421 if (i
== which
|| TEST_FLAG(d
->flags
, CF_DELETE
)) {
1422 free_userdata_once(game
[i
]);
1427 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
*));
1428 g
[gi
] = Calloc(1, sizeof(struct game_s
));
1429 memcpy(g
[gi
], game
[i
], sizeof(struct game_s
));
1430 g
[gi
]->tag
= game
[i
]->tag
;
1431 g
[gi
]->history
= game
[i
]->history
;
1432 g
[gi
]->hp
= game
[i
]->hp
;
1440 if (which
+ 1 >= gtotal
)
1441 gindex
= gtotal
- 1;
1446 gindex
= gtotal
- 1;
1449 gp
->hp
= gp
->history
;
1453 * FIXME find across multiple games.
1455 static int find_move_exp(GAME g
, regex_t r
, int which
, int count
)
1463 incr
= (which
== 0) ? -1 : 1;
1465 for (i
= g
->hindex
+ incr
- 1, found
= 0; ; i
+= incr
) {
1466 if (i
== g
->hindex
- 1)
1469 if (i
>= pgn_history_total(g
->hp
))
1472 i
= pgn_history_total(g
->hp
) - 1;
1475 ret
= regexec(&r
, g
->hp
[i
]->move
, 0, 0, 0);
1478 if (count
== ++found
) {
1483 if (ret
!= REG_NOMATCH
) {
1484 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
1485 cmessage(_("Error Matching Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf
);
1494 static int toggle_delete_flag(int n
)
1497 struct userdata_s
*d
= game
[n
]->data
;
1499 TOGGLE_FLAG(d
->flags
, CF_DELETE
);
1502 for (i
= x
= 0; i
< gtotal
; i
++) {
1505 if (TEST_FLAG(d
->flags
, CF_DELETE
))
1510 cmessage(NULL
, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
1512 CLEAR_FLAG(d
->flags
, CF_DELETE
);
1519 static int find_game_exp(char *str
, int which
, int count
)
1521 char *nstr
= NULL
, *exp
= NULL
;
1525 char buf
[255], *tmp
;
1528 int incr
= (which
== 0) ? -(1) : 1;
1530 strncpy(buf
, str
, sizeof(buf
));
1533 if (strstr(tmp
, ":") != NULL
) {
1534 nstr
= strsep(&tmp
, ":");
1536 if ((ret
= regcomp(&nexp
, nstr
,
1537 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
1538 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
1539 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf
);
1547 while (*exp
&& isspace(*exp
))
1553 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
1554 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
1555 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf
);
1562 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
1573 for (t
= 0; game
[g
]->tag
[t
]; t
++) {
1575 if (regexec(&nexp
, game
[g
]->tag
[t
]->name
, 0, 0, 0) == 0) {
1576 if (regexec(&vexp
, game
[g
]->tag
[t
]->value
, 0, 0, 0) == 0) {
1577 if (count
== ++found
) {
1585 if (regexec(&vexp
, game
[g
]->tag
[t
]->value
, 0, 0, 0) == 0) {
1586 if (count
== ++found
) {
1608 * Updates the notification line in the status window then refreshes the
1611 void update_status_notify(GAME g
, char *fmt
, ...)
1614 #ifdef HAVE_VASPRINTF
1621 if (status
.notify
) {
1622 free(status
.notify
);
1623 status
.notify
= NULL
;
1630 #ifdef HAVE_VASPRINTF
1631 vasprintf(&line
, fmt
, ap
);
1633 vsnprintf(line
, sizeof(line
), fmt
, ap
);
1638 free(status
.notify
);
1640 status
.notify
= str_to_wchar(line
);
1642 #ifdef HAVE_VASPRINTF
1647 int rav_next_prev(GAME g
, BOARD b
, int n
)
1651 if ((!g
->ravlevel
&& g
->hindex
&& g
->hp
[g
->hindex
- 1]->rav
== NULL
) ||
1652 (!g
->ravlevel
&& !g
->hindex
&& g
->hp
[g
->hindex
]->rav
== NULL
) ||
1653 (g
->ravlevel
&& g
->hp
[g
->hindex
]->rav
== NULL
))
1656 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
1657 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1658 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
1659 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(g
, b
));
1660 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1661 g
->hp
= (!g
->ravlevel
) ? (g
->hindex
) ? g
->hp
[g
->hindex
- 1]->rav
: g
->hp
[g
->hindex
]->rav
: g
->hp
[g
->hindex
]->rav
;
1664 pgn_board_update(g
, b
, g
->hindex
+ 1);
1668 if (g
->ravlevel
- 1 < 0)
1673 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
1674 free(g
->rav
[g
->ravlevel
].fen
);
1675 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
1676 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
1677 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
1681 static void draw_window_decor()
1683 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
1684 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
1685 move_panel(statusp
, 0, 0);
1686 wbkgd(boardw
, CP_BOARD_WINDOW
);
1687 wbkgd(statusw
, CP_STATUS_WINDOW
);
1688 window_draw_title(statusw
, _("Game Status"), STATUS_WIDTH
,
1689 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
1690 wbkgd(tagw
, CP_TAG_WINDOW
);
1691 window_draw_title(tagw
, _("Roster Tags"), TAG_WIDTH
, CP_TAG_TITLE
,
1693 wbkgd(historyw
, CP_HISTORY_WINDOW
);
1694 window_draw_title(historyw
, _("Move History"), HISTORY_WIDTH
,
1695 CP_HISTORY_TITLE
, CP_HISTORY_BORDER
);
1699 static void do_window_resize()
1701 if (LINES
< 24 || COLS
< 80)
1704 resizeterm(LINES
, COLS
);
1705 wresize(historyw
, HISTORY_HEIGHT
, HISTORY_WIDTH
);
1706 wresize(statusw
, STATUS_HEIGHT
, STATUS_WIDTH
);
1707 wresize(tagw
, TAG_HEIGHT
, TAG_WIDTH
);
1708 wmove(historyw
, 0, 0);
1709 wclrtobot(historyw
);
1712 wmove(statusw
, 0, 0);
1714 draw_window_decor();
1721 memset(&clock_timer
, 0, sizeof(struct itimerval
));
1722 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1725 void start_clock(GAME g
)
1727 struct userdata_s
*d
= g
->data
;
1729 if (clock_timer
.it_interval
.tv_usec
)
1732 memset(&d
->elapsed
, 0, sizeof(struct timeval
));
1733 clock_timer
.it_value
.tv_sec
= 0;
1734 clock_timer
.it_value
.tv_usec
= 100000;
1735 clock_timer
.it_interval
.tv_sec
= 0;
1736 clock_timer
.it_interval
.tv_usec
= 100000;
1737 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1740 static void update_clocks()
1743 struct userdata_s
*d
;
1744 struct itimerval it
;
1747 getitimer(ITIMER_REAL
, &it
);
1749 for (i
= 0; i
< gtotal
; i
++) {
1752 if (d
&& d
->mode
== MODE_PLAY
) {
1753 if (d
->paused
== 1 || TEST_FLAG(d
->flags
, CF_NEW
))
1755 else if (d
->paused
== -1) {
1756 if (game
[i
]->side
== game
[i
]->turn
) {
1762 update_clock(game
[i
], it
);
1770 update_status_window(gp
);
1776 #define SKIP_SPACE(str) { while (isspace(*str)) str++; }
1778 static int parse_clock_time(char **str
)
1833 static int parse_clock_input(struct clock_s
*clk
, char *str
, int *incr
)
1855 memset(clk
, 0, sizeof(struct clock_s
));
1859 if (strncasecmp(p
, "SD", 2) == 0) {
1865 n
= parse_clock_time(&p
);
1879 /* Sudden death without a previous time control. */
1885 n
= parse_clock_time(&p
);
1891 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s (%i)", _("Maximum number of time controls reached"), MAX_TC
);
1896 clk
->tc
[tc
++][1] = n
;
1911 clk
->tc
[clk
->tcn
][1] = (n
<= clk
->elapsed
.tv_sec
) ? clk
->elapsed
.tv_sec
+ n
: n
;
1919 n
= parse_clock_time(&p
);
1939 static int parse_which_clock(struct clock_s
*clk
, char *str
)
1944 memcpy(&tmp
, clk
, sizeof(struct clock_s
));
1946 if (parse_clock_input(&tmp
, str
, &incr
)) {
1947 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), _("Invalid clock specification"));
1951 memcpy(clk
, &tmp
, sizeof(struct clock_s
));
1952 clk
->tc
[clk
->tcn
][1] += incr
;
1956 void do_clock_input_finalize(WIN
*win
)
1958 struct userdata_s
*d
= gp
->data
;
1959 struct input_data_s
*in
= win
->data
;
1969 if (tolower(*p
) == 'w') {
1972 if (parse_which_clock(&d
->wclock
, p
))
1975 else if (tolower(*p
) == 'b') {
1978 if (parse_which_clock(&d
->bclock
, p
))
1982 if (parse_which_clock(&d
->wclock
, p
))
1985 if (parse_which_clock(&d
->bclock
, p
))
1989 if (!d
->wclock
.tc
[0][1] && !d
->bclock
.tc
[0][1])
1990 CLEAR_FLAG(d
->flags
, CF_CLOCK
);
1992 SET_FLAG(d
->flags
, CF_CLOCK
);
1999 void do_engine_command_finalize(WIN
*win
)
2001 struct userdata_s
*d
= gp
->data
;
2002 struct input_data_s
*in
= win
->data
;
2013 x
= d
->engine
->status
;
2014 send_to_engine(gp
, -1, "%s\n", in
->str
);
2015 d
->engine
->status
= x
;
2022 void do_board_details()
2024 config
.details
= (config
.details
) ? 0 : 1;
2027 void do_toggle_strict_castling()
2031 pgn_config_get(PGN_STRICT_CASTLING
, &n
);
2034 pgn_config_set(PGN_STRICT_CASTLING
, 1);
2036 pgn_config_set(PGN_STRICT_CASTLING
, 0);
2039 void do_play_set_clock()
2041 struct input_data_s
*in
;
2043 in
= Calloc(1, sizeof(struct input_data_s
));
2044 in
->efunc
= do_clock_input_finalize
;
2045 construct_input(_("Set Clock"), NULL
, 1, 1,
2046 _ ("Format: [W | B] [+]T[+I] | ++I | M/T [M/T [...] [SD/T]] [+I]\n" \
2047 "T = time (hms), I = increment, M = moves per, SD = sudden death\ne.g., 30m or 4m+12s or 35/90m SD/30m"),
2048 NULL
, NULL
, 0, in
, INPUT_HIST_CLOCK
, -1);
2051 void do_play_toggle_human()
2053 struct userdata_s
*d
= gp
->data
;
2055 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
2057 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && pgn_history_total(gp
->hp
)) {
2058 if (init_chess_engine(gp
))
2062 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2065 d
->engine
->status
= ENGINE_READY
;
2068 void do_play_toggle_engine()
2070 struct userdata_s
*d
= gp
->data
;
2072 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
2073 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
2075 if (d
->engine
&& TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
2076 pgn_board_update(gp
, d
->b
,
2077 pgn_history_total(gp
->hp
));
2078 add_engine_command(gp
, ENGINE_READY
,
2079 "setboard %s\n", pgn_game_to_fen(gp
, d
->b
));
2084 * This will send a command to the engine skipping the command queue.
2086 void do_play_send_command()
2088 struct userdata_s
*d
= gp
->data
;
2089 struct input_data_s
*in
;
2091 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
) {
2092 if (init_chess_engine(gp
))
2096 in
= Calloc(1, sizeof(struct input_data_s
));
2097 in
->efunc
= do_engine_command_finalize
;
2098 construct_input(_("Engine Command"), NULL
, 1, 1, NULL
, NULL
, NULL
, 0, in
, INPUT_HIST_ENGINE
, -1);
2101 void do_play_switch_turn()
2103 struct userdata_s
*d
= gp
->data
;
2105 pgn_switch_side(gp
);
2106 pgn_switch_turn(gp
);
2108 if (!TEST_FLAG(d
->flags
, CF_HUMAN
))
2109 add_engine_command(gp
, -1,
2110 (gp
->side
== WHITE
) ? "white\n" : "black\n");
2112 update_status_window(gp
);
2117 struct userdata_s
*d
= gp
->data
;
2119 if (!pgn_history_total(gp
->hp
))
2123 if (gp
->hindex
- keycount
< 0)
2126 gp
->hindex
-= keycount
* 2;
2129 if (gp
->hindex
- 2 < 0)
2135 pgn_history_free(gp
->hp
, gp
->hindex
);
2136 gp
->hindex
= pgn_history_total(gp
->hp
);
2137 pgn_board_update(gp
, d
->b
, gp
->hindex
);
2139 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2140 add_engine_command(gp
, ENGINE_READY
, "setboard %s\n",
2141 pgn_game_to_fen(gp
, d
->b
));
2142 d
->engine
->status
= ENGINE_READY
;
2145 update_history_window(gp
);
2148 void do_play_toggle_pause()
2150 struct userdata_s
*d
= gp
->data
;
2152 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && gp
->turn
!=
2158 d
->paused
= (d
->paused
) ? 0 : 1;
2163 struct userdata_s
*d
= gp
->data
;
2165 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
2168 add_engine_command(gp
, ENGINE_THINKING
, "go\n");
2171 void do_play_config_command()
2176 for (x
= 0; config
.keys
[x
]; x
++) {
2177 if (config
.keys
[x
]->c
== input_c
) {
2178 switch (config
.keys
[x
]->type
) {
2180 add_engine_command(gp
, -1, "%s\n",
2181 config
.keys
[x
]->str
);
2187 add_engine_command(gp
, -1,
2188 "%s %i\n", config
.keys
[x
]->str
, keycount
);
2195 for (w
= 0; w
< keycount
; w
++)
2196 add_engine_command(gp
, -1,
2197 "%s\n", config
.keys
[x
]->str
);
2205 update_status_notify(gp
, NULL
);
2208 void do_play_cancel_selected()
2210 struct userdata_s
*d
= gp
->data
;
2212 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2214 update_status_notify(gp
, NULL
);
2217 void do_play_commit()
2219 struct userdata_s
*d
= gp
->data
;
2221 pushkey
= keycount
= 0;
2222 update_status_notify(gp
, NULL
);
2224 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
2225 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
))
2231 d
->sp
.row
= d
->c_row
;
2232 d
->sp
.col
= d
->c_col
;
2236 void do_play_select()
2238 struct userdata_s
*d
= gp
->data
;
2240 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
2241 d
->engine
->status
== ENGINE_OFFLINE
)) {
2242 if (init_chess_engine(gp
))
2246 if (d
->sp
.icon
|| (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2249 d
->sp
.icon
= d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
;
2251 if (pgn_piece_to_int(d
->sp
.icon
) == OPEN_SQUARE
) {
2256 if (((islower(d
->sp
.icon
) && gp
->turn
!= BLACK
)
2257 || (isupper(d
->sp
.icon
) && gp
->turn
!= WHITE
))) {
2258 message(NULL
, _("[ press any key to continue ]"), "%s", _("It is not your turn to move. You can switch sides "));
2262 if (pgn_history_total(gp
->hp
)) {
2263 message(NULL
, _("[ press any key to continue ]"), "%s", _("It is not your turn to move. You can switch sides "));
2268 if (pgn_tag_find(gp
->tag
, "FEN") != E_PGN_ERR
)
2271 add_engine_command(gp
, ENGINE_READY
, "black\n");
2272 pgn_switch_turn(gp
);
2274 if (gp
->side
!= BLACK
)
2275 pgn_switch_side(gp
);
2280 d
->sp
.srow
= d
->c_row
;
2281 d
->sp
.scol
= d
->c_col
;
2283 if (config
.validmoves
)
2284 pgn_find_valid_moves(gp
, d
->b
, d
->sp
.scol
, d
->sp
.srow
);
2286 CLEAR_FLAG(d
->flags
, CF_NEW
);
2290 /* FIXME: keys with the same function should comma deliminated. */
2291 static char *build_help(struct key_s
**keys
)
2293 int i
, nlen
= 1, len
, t
, n
;
2300 for (i
= len
= t
= 0; keys
[i
]; i
++) {
2305 if (strlen(keys
[i
]->key
) > nlen
) {
2306 nlen
= strlen(keys
[i
]->key
);
2314 if (strlen(keys
[i
]->d
) > len
)
2315 len
= strlen(keys
[i
]->d
);
2326 for (i
= 0; keys
[i
]; i
++) {
2331 n
= strlen(keys
[i
]->key
);
2341 strcat(buf
, keys
[i
]->key
);
2342 p
= buf
+ strlen(buf
);
2353 strcat(buf
, keys
[i
]->d
);
2359 p
= buf
+ strlen(buf
);
2365 void do_more_help(WIN
*);
2366 void do_main_help(WIN
*win
)
2372 buf
= build_help(play_keys
);
2373 construct_message(_("Play Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2374 NULL
, NULL
, buf
, do_more_help
, 0, 1, "%s", buf
);
2377 buf
= build_help(history_keys
);
2378 construct_message(_("History Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2379 NULL
, NULL
, buf
, do_more_help
, 0, 1, "%s", buf
);
2382 buf
= build_help(edit_keys
);
2383 construct_message(_("Edit Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2384 NULL
, NULL
, buf
, do_more_help
, 0, 1, "%s", buf
);
2387 buf
= build_help(global_keys
);
2388 construct_message(_("Global Game Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0,
2389 NULL
, NULL
, buf
, do_more_help
, 0, 1, "%s", buf
);
2396 void do_more_help(WIN
*win
)
2398 if (win
->c
== KEY_F(1) || win
->c
== CTRL('g'))
2399 construct_message(_("Command Key Index"),
2400 _ ("p/h/e/g or any other key to quit"), 0, 0,
2401 NULL
, NULL
, NULL
, do_main_help
, 0, 0, "%s",
2403 "p - play mode keys\n"
2404 "h - history mode keys\n"
2405 "e - board edit mode keys\n"
2406 "g - global game keys"
2412 char *buf
= build_help(play_keys
);
2414 construct_message(_("Play Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL
, NULL
, buf
,
2415 do_more_help
, 0, 1, "%s", buf
);
2418 void do_play_history_mode()
2420 struct userdata_s
*d
= gp
->data
;
2422 if (!pgn_history_total(gp
->hp
) ||
2423 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2426 d
->mode
= MODE_HISTORY
;
2427 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
2430 void do_play_edit_mode()
2432 struct userdata_s
*d
= gp
->data
;
2434 if (pgn_history_total(gp
->hp
))
2437 pgn_board_init_fen(gp
, d
->b
, NULL
);
2439 d
->mode
= MODE_EDIT
;
2442 void do_edit_insert_finalize(WIN
*win
)
2444 struct userdata_s
*d
= win
->data
;
2446 if (pgn_piece_to_int(win
->c
) == -1)
2449 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
= win
->c
;
2452 void do_edit_select()
2454 struct userdata_s
*d
= gp
->data
;
2459 d
->sp
.icon
= d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
;
2461 if (pgn_piece_to_int(d
->sp
.icon
) == OPEN_SQUARE
) {
2466 d
->sp
.srow
= d
->c_row
;
2467 d
->sp
.scol
= d
->c_col
;
2470 void do_edit_commit()
2473 struct userdata_s
*d
= gp
->data
;
2475 pushkey
= keycount
= 0;
2476 update_status_notify(gp
, NULL
);
2481 d
->sp
.row
= d
->c_row
;
2482 d
->sp
.col
= d
->c_col
;
2483 p
= d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
;
2484 d
->b
[RANKTOBOARD(d
->sp
.row
)][FILETOBOARD(d
->sp
.col
)].icon
= p
;
2485 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
2486 pgn_int_to_piece(gp
->turn
, OPEN_SQUARE
);
2487 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2490 void do_edit_delete()
2492 struct userdata_s
*d
= gp
->data
;
2495 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
2496 pgn_int_to_piece(gp
->turn
, OPEN_SQUARE
);
2498 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
=
2499 pgn_int_to_piece(gp
->turn
, OPEN_SQUARE
);
2501 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2504 void do_edit_cancel_selected()
2506 struct userdata_s
*d
= gp
->data
;
2508 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2510 update_status_notify(gp
, NULL
);
2513 void do_edit_switch_turn()
2515 pgn_switch_turn(gp
);
2518 void do_edit_toggle_castle()
2520 struct userdata_s
*d
= gp
->data
;
2522 castling_state(gp
, d
->b
, RANKTOBOARD(d
->c_row
),
2523 FILETOBOARD(d
->c_col
),
2524 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
, 1);
2527 void do_edit_insert()
2529 struct userdata_s
*d
= gp
->data
;
2531 construct_message(_("Insert Piece"), _("P=pawn, R=rook, N=knight, B=bishop, "), 0, 0, NULL
, NULL
,
2532 d
->b
, do_edit_insert_finalize
, 0, 0, "%s", _("Type the piece letter to insert. Lowercase "));
2535 void do_edit_enpassant()
2537 struct userdata_s
*d
= gp
->data
;
2539 if (d
->c_row
== 6 || d
->c_row
== 3) {
2540 pgn_reset_enpassant(d
->b
);
2541 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].enpassant
= 1;
2547 char *buf
= build_help(edit_keys
);
2549 construct_message(_("Edit Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL
, NULL
, buf
,
2550 do_more_help
, 0, 1, "%s", buf
);
2555 struct userdata_s
*d
= gp
->data
;
2558 pgn_tag_add(&gp
->tag
, "FEN", pgn_game_to_fen(gp
, d
->b
));
2559 pgn_tag_add(&gp
->tag
, "SetUp", "1");
2560 pgn_tag_sort(gp
->tag
);
2561 pgn_board_update(gp
, d
->b
, gp
->hindex
);
2562 d
->mode
= MODE_PLAY
;
2565 void really_do_annotate_finalize(struct input_data_s
*in
,
2566 struct userdata_s
*d
)
2568 HISTORY
*h
= in
->data
;
2578 len
= strlen(in
->str
) + 1;
2579 h
->comment
= Realloc(h
->comment
, len
);
2580 strncpy(h
->comment
, in
->str
, len
);
2585 SET_FLAG(d
->flags
, CF_MODIFIED
);
2588 void do_annotate_finalize(WIN
*win
)
2590 struct userdata_s
*d
= gp
->data
;
2591 struct input_data_s
*in
= win
->data
;
2593 really_do_annotate_finalize(in
, d
);
2596 void do_find_move_exp_finalize(int init
, int which
)
2599 struct userdata_s
*d
= gp
->data
;
2600 static int firstrun
;
2605 if (init
|| !firstrun
) {
2609 if ((ret
= regcomp(&r
, moveexp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2610 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2611 cmessage(_("Error Compiling Regular Expression"), _("[ press any key to continue ]"), "%s", errbuf
);
2618 if ((n
= find_move_exp(gp
, r
,
2619 (which
== -1) ? 0 : 1, (keycount
) ? keycount
: 1)) == -1)
2623 pgn_board_update(gp
, d
->b
, gp
->hindex
);
2626 void do_find_move_exp(WIN
*win
)
2628 struct input_data_s
*in
= win
->data
;
2633 strncpy(moveexp
, in
->str
, sizeof(moveexp
));
2634 do_find_move_exp_finalize(1, which
);
2642 void do_move_jump_finalize(int n
)
2644 struct userdata_s
*d
= gp
->data
;
2646 if (n
< 0 || n
> (pgn_history_total(gp
->hp
) / 2))
2650 update_status_notify(gp
, NULL
);
2651 gp
->hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2652 pgn_board_update(gp
, d
->b
, gp
->hindex
);
2655 void do_move_jump(WIN
*win
)
2657 struct input_data_s
*in
= win
->data
;
2659 if (!in
->str
|| !isinteger(in
->str
)) {
2667 do_move_jump_finalize(atoi(in
->str
));
2672 struct history_menu_s
{
2680 void free_history_menu_data(struct history_menu_s
**h
)
2687 for (i
= 0; h
[i
]; i
++) {
2695 void get_history_data(HISTORY
**hp
, struct history_menu_s
***menu
, int m
,
2699 int t
= pgn_history_total(hp
);
2700 char buf
[MAX_SAN_MOVE_LEN
+ 4];
2702 struct history_menu_s
**hmenu
= *menu
;
2705 for (n
= 0; hmenu
[n
]; n
++);
2709 for (i
= 0; i
< t
; i
++) {
2710 hmenu
= Realloc(hmenu
, (n
+ 2) * sizeof(struct history_menu_s
*));
2711 hmenu
[n
] = Malloc(sizeof(struct history_menu_s
));
2712 snprintf(buf
, sizeof(buf
), "%c%s%s", (turn
== WHITE
) ? 'W' : 'B',
2713 hp
[i
]->move
, (hp
[i
]->comment
|| hp
[i
]->nag
[0]) ? " !" : "");
2714 hmenu
[n
]->line
= strdup(buf
);
2715 hmenu
[n
]->hindex
= i
;
2716 hmenu
[n
]->indent
= 0;
2717 hmenu
[n
]->ravlevel
= depth
;
2718 hmenu
[n
]->move
= (n
&& depth
> hmenu
[n
-1]->ravlevel
) ? m
++ : m
;
2725 get_history_data(hp
[i
]->rav
, &hmenu
, m
, turn
);
2726 for (n
= 0; hmenu
[n
]; n
++);
2734 turn
= (turn
== WHITE
) ? BLACK
: WHITE
;
2740 void history_draw_update(struct menu_input_s
*m
)
2743 struct userdata_s
*d
= g
->data
;
2745 g
->hindex
= m
->selected
+ 1;
2746 update_cursor(g
, m
->selected
);
2747 pgn_board_update(g
, d
->b
, m
->selected
+ 1);
2750 struct menu_item_s
**get_history_items(WIN
*win
)
2752 struct menu_input_s
*m
= win
->data
;
2754 struct userdata_s
*d
= g
->data
;
2755 struct history_menu_s
**hm
= d
->data
;
2756 struct menu_item_s
**items
= m
->items
;
2760 get_history_data(g
->history
, &hm
, 0,
2761 TEST_FLAG(g
->flags
, GF_BLACK_OPENING
));
2762 m
->selected
= g
->hindex
- 1;
2764 if (m
->selected
< 0)
2767 m
->draw_exit_func
= history_draw_update
;
2773 for (i
= 0; items
[i
]; i
++)
2780 for (i
= 0; hm
[i
]; i
++) {
2781 items
= Realloc(items
, (i
+2) * sizeof(struct menu_item_s
*));
2782 items
[i
] = Malloc(sizeof(struct menu_item_s
));
2783 items
[i
]->name
= hm
[i
]->line
;
2784 items
[i
]->value
= NULL
;
2785 items
[i
]->selected
= 0;
2796 void history_menu_quit(struct menu_input_s
*m
)
2801 void history_menu_exit(WIN
*win
)
2804 struct userdata_s
*d
= g
->data
;
2805 struct history_menu_s
**hm
= d
->data
;
2811 for (i
= 0; hm
[i
]; i
++) {
2821 void history_menu_next(struct menu_input_s
*m
)
2824 struct userdata_s
*d
= g
->data
;
2825 struct history_menu_s
**hm
= d
->data
;
2828 for (t
= 0; hm
[t
]; t
++);
2830 if (m
->selected
+ 1 == t
)
2833 n
= hm
[m
->selected
+ 1]->hindex
;
2840 void history_menu_prev(struct menu_input_s
*m
)
2843 struct userdata_s
*d
= g
->data
;
2844 struct history_menu_s
**hm
= d
->data
;
2847 for (t
= 0; hm
[t
]; t
++);
2849 if (m
->selected
- 1 < 0)
2852 n
= hm
[m
->selected
- 1]->hindex
;
2858 void history_menu_help(struct menu_input_s
*m
)
2860 message("History Menu Help", _("[ press any key to continue ]"), "%s",
2862 " UP/DOWN - previous/next menu item\n"
2863 " HOME/END - first/last menu item\n"
2864 " PGDN/PGUP - next/previous page\n"
2865 " a-zA-Z0-9 - jump to item\n"
2866 " CTRL-a - annotate the selected move\n"
2867 " ENTER - view annotation\n"
2868 " CTRL-d - toggle board details\n"
2873 void do_annotate_move(HISTORY
*hp
)
2876 struct input_data_s
*in
;
2878 snprintf(buf
, sizeof(buf
), "%s \"%s\"", _("Editing Annotation for"), hp
->move
);
2879 in
= Calloc(1, sizeof(struct input_data_s
));
2881 in
->efunc
= do_annotate_finalize
;
2882 construct_input(buf
, hp
->comment
, MAX_PGN_LINE_LEN
/ INPUT_WIDTH
, 0,
2883 _("Type CTRL-t to edit NAG"), edit_nag
, NULL
, CTRL('T'), in
, -1, -1);
2886 void history_menu_view_annotation(struct menu_input_s
*m
)
2891 view_annotation(g
->history
[m
->selected
]);
2894 void history_menu_annotate_finalize(WIN
*win
)
2896 struct input_data_s
*in
= win
->data
;
2897 GAME g
= in
->moredata
;
2898 struct userdata_s
*d
= g
->data
;
2899 struct history_menu_s
**hm
= d
->data
;
2901 really_do_annotate_finalize(in
, d
);
2902 free_history_menu_data(hm
);
2904 get_history_data(g
->history
, &hm
, 0, TEST_FLAG(g
->flags
, GF_BLACK_OPENING
));
2906 pushkey
= REFRESH_MENU
;
2909 void history_menu_annotate(struct menu_input_s
*m
)
2913 struct input_data_s
*in
;
2914 HISTORY
*hp
= g
->history
[m
->selected
]; // FIXME RAV
2916 snprintf(buf
, sizeof(buf
), "%s \"%s\"", _("Editing Annotation for"), hp
->move
);
2917 in
= Calloc(1, sizeof(struct input_data_s
));
2919 in
->moredata
= m
->data
;
2920 in
->efunc
= history_menu_annotate_finalize
;
2921 construct_input(buf
, hp
->comment
, MAX_PGN_LINE_LEN
/ INPUT_WIDTH
, 0,
2922 _("Type CTRL-t to edit NAG"), edit_nag
, NULL
, CTRL('T'), in
, -1, -1);
2925 void history_menu_details(struct menu_input_s
*m
)
2931 void history_menu_print(WIN
*win
)
2933 struct menu_input_s
*m
= win
->data
;
2935 struct userdata_s
*d
= g
->data
;
2936 struct history_menu_s
**hm
= d
->data
;
2937 struct history_menu_s
*h
= hm
[m
->top
];
2939 char *p
= m
->item
->name
;
2940 int line
= m
->print_line
- 2;
2942 * Solaris 5.9 doesn't have wattr_get() or any function that requires an
2951 for (total
= 0; hm
[total
]; total
++);
2953 wattr_get(win
->w
, &attrs
, &pair
, NULL
);
2954 wattroff(win
->w
, COLOR_PAIR(pair
));
2956 mvwaddch(win
->w
, m
->print_line
, 1,
2957 *p
== 'W' ? *p
| mix_cp(CP_BOARD_WHITE
, CP_HISTORY_WINDOW
, ATTRS(CP_BOARD_WHITE
), A_FG_B_BG
) : *p
| mix_cp(CP_BOARD_BLACK
, CP_HISTORY_WINDOW
, ATTRS(CP_BOARD_BLACK
), A_FG_B_BG
));
2960 if (h
->hindex
== 0 && line
== 0)
2961 waddch(win
->w
, ACS_ULCORNER
| CP_HISTORY_MENU_LG
);
2962 else if ((!hm
[h
->hindex
+ (win
->rows
- 5) + 1] && line
== win
->rows
- 5) ||
2963 (m
->top
+ line
== total
- 1))
2964 waddch(win
->w
, ACS_LLCORNER
| CP_HISTORY_MENU_LG
);
2965 else if (hm
[m
->top
+ 1]->ravlevel
!= h
->ravlevel
|| !h
->ravlevel
)
2966 waddch(win
->w
, ACS_LTEE
| CP_HISTORY_MENU_LG
);
2968 waddch(win
->w
, ACS_VLINE
| CP_HISTORY_MENU_LG
);
2971 wattron(win
->w
, COLOR_PAIR(pair
) | attrs
);
2974 for (i
= 2; *p
; p
++, i
++)
2975 waddch(win
->w
, (*p
== '!') ? *p
| A_BOLD
: *p
);
2977 while (i
++ < win
->cols
- 2)
2978 waddch(win
->w
, ' ');
2981 void history_menu(GAME g
)
2983 struct menu_key_s
**keys
= NULL
;
2985 add_menu_key(&keys
, KEY_ESCAPE
, history_menu_quit
);
2986 add_menu_key(&keys
, KEY_UP
, history_menu_prev
);
2987 add_menu_key(&keys
, KEY_DOWN
, history_menu_next
);
2988 add_menu_key(&keys
, KEY_F(1), history_menu_help
);
2989 add_menu_key(&keys
, CTRL('a'), history_menu_annotate
);
2990 add_menu_key(&keys
, CTRL('d'), history_menu_details
);
2991 add_menu_key(&keys
, '\n', history_menu_view_annotation
);
2992 construct_menu(LINES
, TAG_WIDTH
, 0, 0, _("Move History Tree"), 1,
2993 get_history_items
, keys
, g
, history_menu_print
, history_menu_exit
);
2996 void do_history_menu()
3001 void do_history_half_move_toggle()
3003 movestep
= (movestep
== 1) ? 2 : 1;
3004 update_history_window(gp
);
3007 void do_history_jump_next()
3009 struct userdata_s
*d
= gp
->data
;
3011 pgn_history_next(gp
, d
->b
, (keycount
> 0) ?
3012 config
.jumpcount
* keycount
* movestep
:
3013 config
.jumpcount
* movestep
);
3016 void do_history_jump_prev()
3018 struct userdata_s
*d
= gp
->data
;
3020 pgn_history_prev(gp
, d
->b
, (keycount
) ?
3021 config
.jumpcount
* keycount
* movestep
:
3022 config
.jumpcount
* movestep
);
3025 void do_history_prev()
3027 struct userdata_s
*d
= gp
->data
;
3029 pgn_history_prev(gp
, d
->b
,
3030 (keycount
) ? keycount
* movestep
: movestep
);
3033 void do_history_next()
3035 struct userdata_s
*d
= gp
->data
;
3037 pgn_history_next(gp
, d
->b
, (keycount
) ?
3038 keycount
* movestep
: movestep
);
3041 void do_history_mode_finalize(struct userdata_s
*d
)
3044 d
->mode
= MODE_PLAY
;
3047 void do_history_mode_confirm(WIN
*win
)
3049 struct userdata_s
*d
= gp
->data
;
3054 pgn_history_free(gp
->hp
,
3056 pgn_board_update(gp
, d
->b
,
3057 pgn_history_total(gp
->hp
));
3062 if (pgn_history_rav_new(gp
, d
->b
,
3063 gp
->hindex
) != E_PGN_OK
)
3072 if (!TEST_FLAG(d
->flags
, CF_HUMAN
))
3073 add_engine_command(gp
, ENGINE_READY
,
3074 "setboard %s\n", pgn_game_to_fen(gp
, d
->b
));
3076 do_history_mode_finalize(d
);
3079 void do_history_toggle()
3081 struct userdata_s
*d
= gp
->data
;
3083 // FIXME Resuming from previous history could append to a RAV.
3084 if (gp
->hindex
!= pgn_history_total(gp
->hp
)) {
3086 construct_message(NULL
, "(r)esume or abort", 0, 1, NULL
, NULL
, NULL
,
3087 do_history_mode_confirm
, 0, 0, "%s",
3088 _("Resuming a game from previous "));
3093 if (TEST_FLAG(gp
->flags
, GF_GAMEOVER
))
3097 do_history_mode_finalize(d
);
3100 void do_history_annotate()
3104 if (n
&& gp
->hp
[n
- 1]->move
)
3109 do_annotate_move(gp
->hp
[n
]);
3112 void do_history_help()
3114 char *buf
= build_help(history_keys
);
3116 construct_message(_("History Mode Keys (* = can take a repeat count)"), _("[ press any key to continue ]"), 0, 0, NULL
, NULL
, buf
,
3117 do_more_help
, 0, 1, "%s", buf
);
3120 void do_history_find(int which
)
3122 struct input_data_s
*in
;
3125 if (pgn_history_total(gp
->hp
) < 2)
3128 in
= Calloc(1, sizeof(struct input_data_s
));
3129 p
= Malloc(sizeof(int));
3132 in
->efunc
= do_find_move_exp
;
3134 if (!*moveexp
|| which
== 0) {
3135 construct_input(_("Find Move Text Expression"), NULL
, 1, 0, NULL
, NULL
, NULL
,
3136 0, in
, INPUT_HIST_MOVE_EXP
, -1);
3140 do_find_move_exp_finalize(0, which
);
3143 void do_history_find_new()
3148 void do_history_find_prev()
3150 do_history_find(-1);
3153 void do_history_find_next()
3158 void do_history_rav(int which
)
3160 struct userdata_s
*d
= gp
->data
;
3162 rav_next_prev(gp
, d
->b
, which
);
3165 void do_history_rav_next()
3170 void do_history_rav_prev()
3175 void do_history_jump()
3177 struct input_data_s
*in
;
3179 if (pgn_history_total(gp
->hp
) < 2)
3183 in
= Calloc(1, sizeof(struct input_data_s
));
3184 in
->efunc
= do_move_jump
;
3186 construct_input(_("Jump to Move Number"), NULL
, 1, 1, NULL
,
3187 NULL
, NULL
, 0, in
, -1, 0);
3191 do_move_jump_finalize(keycount
);
3194 static void free_userdata_once(GAME g
)
3196 struct userdata_s
*d
= g
->data
;
3204 if (d
->engine
->enginebuf
) {
3207 for (n
= 0; d
->engine
->enginebuf
[n
]; n
++)
3208 free(d
->engine
->enginebuf
[n
]);
3210 free(d
->engine
->enginebuf
);
3213 if (d
->engine
->queue
) {
3216 for (q
= d
->engine
->queue
; *q
; q
++)
3219 free(d
->engine
->queue
);
3237 static void free_userdata()
3241 for (i
= 0; i
< gtotal
; i
++) {
3242 free_userdata_once(game
[i
]);
3243 game
[i
]->data
= NULL
;
3247 void update_loading_window(int n
)
3250 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
3251 loadingp
= new_panel(loadingw
);
3252 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
3255 wmove(loadingw
, 0, 0);
3256 wclrtobot(loadingw
);
3257 wattron(loadingw
, CP_MESSAGE_BORDER
);
3258 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
3259 wattroff(loadingw
, CP_MESSAGE_BORDER
);
3260 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
3261 11 + strlen(itoa(gtotal
))), "Loading... %i%% (%i games)", n
,
3267 static void init_userdata_once(GAME g
, int n
)
3269 struct userdata_s
*d
= NULL
;
3271 d
= Calloc(1, sizeof(struct userdata_s
));
3273 d
->c_row
= 2, d
->c_col
= 5;
3274 SET_FLAG(d
->flags
, CF_NEW
);
3277 if (pgn_board_init_fen(g
, d
->b
, NULL
) != E_PGN_OK
)
3278 pgn_board_init(d
->b
);
3281 void init_userdata()
3285 for (i
= 0; i
< gtotal
; i
++)
3286 init_userdata_once(game
[i
], i
);
3289 void fix_marks(int *start
, int *end
)
3293 *start
= (*start
< 0) ? 0 : *start
;
3294 *end
= (*end
< 0) ? 0 : *end
;
3296 if (*start
> *end
) {
3302 *end
= (*end
> gtotal
) ? gtotal
: *end
;
3305 void do_new_game_finalize(GAME g
)
3307 struct userdata_s
*d
= g
->data
;
3309 d
->mode
= MODE_PLAY
;
3310 update_status_notify(g
, NULL
);
3313 void do_new_game_from_scratch(WIN
*win
)
3315 if (tolower(win
->c
) != 'y')
3322 add_custom_tags(&gp
->tag
);
3325 do_new_game_finalize(gp
);
3332 add_custom_tags(&gp
->tag
);
3333 init_userdata_once(gp
, gindex
);
3334 do_new_game_finalize(gp
);
3337 void do_game_delete_finalize(int n
)
3339 struct userdata_s
*d
;
3341 delete_game((!n
) ? gindex
: -1);
3343 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
3346 void do_game_delete_confirm(WIN
*win
)
3350 if (tolower(win
->c
) != 'y') {
3356 n
= (int *)win
->data
;
3357 do_game_delete_finalize(*n
);
3361 void do_game_delete()
3365 struct userdata_s
*d
;
3369 cmessage(NULL
, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
3375 for (i
= n
= 0; i
< gtotal
; i
++) {
3378 if (TEST_FLAG(d
->flags
, CF_DELETE
))
3383 tmp
= _("Delete the current game?");
3386 cmessage(NULL
, _("[ press any key to continue ]"), "%s", _("Cannot delete last game."));
3390 tmp
= _("Delete all games marked for deletion?");
3393 if (config
.deleteprompt
) {
3394 p
= Malloc(sizeof(int));
3396 construct_message(NULL
, _("[ Yes or No ]"), 1, 1, NULL
, NULL
, p
,
3397 do_game_delete_confirm
, 0, 0, tmp
);
3401 do_game_delete_finalize(n
);
3404 void do_find_game_exp_finalize(int which
)
3406 struct userdata_s
*d
= gp
->data
;
3409 if ((n
= find_game_exp(gameexp
, (which
== -1) ? 0 : 1,
3410 (keycount
) ? keycount
: 1)) == -1) {
3411 update_status_notify(gp
, "%s", _("No matches found"));
3418 if (pgn_history_total(gp
->hp
))
3419 d
->mode
= MODE_HISTORY
;
3421 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
3424 void do_find_game_exp(WIN
*win
)
3426 struct input_data_s
*in
= win
->data
;
3431 strncpy(gameexp
, in
->str
, sizeof(gameexp
));
3436 do_find_game_exp_finalize(c
);
3444 void do_game_jump_finalize(int n
)
3446 struct userdata_s
*d
;
3448 if (--n
> gtotal
- 1 || n
< 0)
3454 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
3455 update_status_notify(gp
, NULL
);
3458 void do_game_jump(WIN
*win
)
3460 struct input_data_s
*in
= win
->data
;
3462 if (!in
->str
|| !isinteger(in
->str
)) {
3470 do_game_jump_finalize(atoi(in
->str
));
3475 void do_load_file(WIN
*win
)
3477 struct input_data_s
*in
= win
->data
;
3478 char *tmp
= in
->str
;
3479 struct userdata_s
*d
;
3480 PGN_FILE
*pgn
= NULL
;
3488 if ((tmp
= pathfix(tmp
)) == NULL
)
3491 n
= pgn_open(tmp
, "r", &pgn
);
3493 if (n
== E_PGN_ERR
) {
3494 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", tmp
, strerror(errno
));
3497 else if (n
== E_PGN_INVALID
) {
3498 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s\n%s", tmp
, _("Not a regular file"));
3505 * FIXME what is the game state after a parse error?
3507 if (pgn_parse(pgn
) == E_PGN_ERR
) {
3508 del_panel(loadingp
);
3516 del_panel(loadingp
);
3521 strncpy(loadfile
, tmp
, sizeof(loadfile
));
3525 if (pgn_history_total(gp
->hp
))
3526 d
->mode
= MODE_HISTORY
;
3528 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
3539 void do_game_save(WIN
*win
)
3541 struct input_data_s
*in
= win
->data
;
3544 char *tmp
= in
->str
;
3545 char tfile
[FILENAME_MAX
];
3548 struct userdata_s
*d
;
3550 if (!tmp
|| (tmp
= pathfix(tmp
)) == NULL
)
3553 if (pgn_is_compressed(tmp
) == E_PGN_ERR
) {
3554 p
= tmp
+ strlen(tmp
) - 1;
3556 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
3558 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
3564 * When in edit mode, update the FEN tag.
3567 for (i
= 0; i
< gtotal
; i
++) {
3570 if (d
->mode
== MODE_EDIT
)
3571 pgn_tag_add(&game
[i
]->tag
, "FEN", pgn_game_to_fen(game
[i
], d
->b
));
3577 if (d
->mode
== MODE_EDIT
)
3578 pgn_tag_add(&game
[n
]->tag
, "FEN", pgn_game_to_fen(game
[n
], d
->b
));
3591 void do_get_game_save_input(int n
)
3593 struct input_data_s
*in
= Calloc(1, sizeof(struct input_data_s
));
3594 int *p
= Malloc(sizeof(int));
3596 in
->efunc
= do_game_save
;
3600 construct_input(_("Save Game Filename"), loadfile
, 1, 1, _("Type TAB for file browser"),
3601 file_browser
, NULL
, '\t', in
, INPUT_HIST_FILE
, -1);
3604 void do_game_save_multi_confirm(WIN
*win
)
3610 else if (win
->c
== 'a')
3613 update_status_notify(gp
, "%s", _("Save game aborted."));
3617 do_get_game_save_input(i
);
3620 void do_global_about()
3622 cmessage("ABOUT", _("[ press any key to continue ]"), "%s\nUsing %s with %i colors "
3623 "and %i color pairs\n%s",
3624 PACKAGE_STRING
, curses_version(), COLORS
, COLOR_PAIRS
,
3628 void global_game_next_prev(int which
)
3630 struct userdata_s
*d
;
3632 game_next_prev(gp
, (which
== 1) ? 1 : 0,
3633 (keycount
) ? keycount
: 1);
3638 markend
= markstart
+ delete_count
;
3642 markend
= markstart
- delete_count
+ 1;
3643 delete_count
= -1; // to fix gindex in the other direction
3646 fix_marks(&markstart
, &markend
);
3647 do_global_toggle_delete();
3650 if (d
->mode
== MODE_HISTORY
)
3651 pgn_board_update(gp
, d
->b
, gp
->hindex
);
3652 else if (d
->mode
== MODE_PLAY
)
3653 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
3656 void do_global_next_game()
3658 global_game_next_prev(1);
3661 void do_global_prev_game()
3663 global_game_next_prev(0);
3666 void global_find(int which
)
3668 struct input_data_s
*in
;
3674 in
= Calloc(1, sizeof(struct input_data_s
));
3675 p
= Malloc(sizeof(int));
3678 in
->efunc
= do_find_game_exp
;
3680 if (!*gameexp
|| which
== 0) {
3681 construct_input(_("Find Game by Tag Expression"), NULL
, 1, 0,
3682 _("[name expression:]value expression"), NULL
, NULL
, 0, in
,
3683 INPUT_HIST_GAME_EXP
, -1);
3687 do_find_game_exp_finalize(which
);
3690 void do_global_find_new()
3695 void do_global_find_next()
3700 void do_global_find_prev()
3705 void do_global_game_jump()
3707 struct input_data_s
*in
;
3712 in
= Calloc(1, sizeof(struct input_data_s
));
3713 in
->efunc
= do_game_jump
;
3716 construct_input(_("Jump to Game Number"), NULL
, 1, 1, NULL
, NULL
, NULL
, 0, in
,
3721 do_game_jump_finalize(keycount
);
3724 void do_global_toggle_delete()
3733 if (keycount
&& delete_count
== 0) {
3735 delete_count
= keycount
;
3736 update_status_notify(gp
, "%s (delete)", status
.notify
);
3740 if (markstart
>= 0 && markend
>= 0) {
3741 for (i
= markstart
; i
< markend
; i
++) {
3742 if (toggle_delete_flag(i
)) {
3747 gindex
= (delete_count
< 0) ? markstart
: i
- 1;
3750 if (toggle_delete_flag(gindex
))
3754 markstart
= markend
= -1;
3756 update_status_window(gp
);
3759 void do_global_delete_game()
3764 void do_global_tag_edit()
3766 struct userdata_s
*d
= gp
->data
;
3768 edit_tags(gp
, d
->b
, 1);
3771 void do_global_tag_view()
3773 struct userdata_s
*d
= gp
->data
;
3775 edit_tags(gp
, d
->b
, 0);
3778 void do_global_resume_game()
3780 struct input_data_s
*in
;
3782 in
= Calloc(1, sizeof(struct input_data_s
));
3783 in
->efunc
= do_load_file
;
3784 construct_input(_("Load Filename"), NULL
, 1, 1, _("Type TAB for file browser"), file_browser
,
3785 NULL
, '\t', in
, INPUT_HIST_FILE
, -1);
3788 void do_global_save_game()
3791 construct_message(NULL
, _("Type 'c' or 'a' or any other key "), 1, 1, NULL
, NULL
, NULL
,
3792 do_game_save_multi_confirm
, 0, 0, "%s", _("There is more than one game "));
3796 do_get_game_save_input(-1);
3799 void do_global_new_game()
3804 void do_global_copy_game()
3808 struct userdata_s
*d
;
3810 do_global_new_game();
3811 n
= pgn_history_total(game
[g
]->history
);
3815 for (i
= 0; i
< n
; i
++) {
3817 char *move
= strdup(game
[g
]->history
[i
]->move
);
3819 if (pgn_parse_move(gp
, d
->b
, &move
, &frfr
) != E_PGN_OK
) {
3820 SET_FLAG(gp
->flags
, GF_PERROR
);
3824 pgn_history_add(gp
, d
->b
, move
);
3827 pgn_switch_turn(gp
);
3830 n
= pgn_tag_total(game
[g
]->tag
);
3832 for (i
= 0; i
< n
; i
++)
3833 pgn_tag_add(&gp
->tag
, game
[g
]->tag
[i
]->name
,
3834 game
[g
]->tag
[i
]->value
);
3837 pgn_board_update(gp
, d
->b
,
3838 pgn_history_total(gp
->hp
));
3841 void do_global_new_all()
3843 construct_message(NULL
, _("[ Yes or No ]"), 1, 1, NULL
, NULL
, NULL
,
3844 do_new_game_from_scratch
, 0, 0, "%s", _("Really start a new game from scratch?"));
3847 void do_global_quit()
3852 void do_global_toggle_engine_window()
3855 enginew
= newwin(LINES
, COLS
, 0, 0);
3856 enginep
= new_panel(enginew
);
3857 window_draw_title(enginew
, _("Engine IO Window"), COLS
, CP_MESSAGE_TITLE
,
3859 hide_panel(enginep
);
3862 if (panel_hidden(enginep
)) {
3863 update_engine_window(gp
);
3867 hide_panel(enginep
);
3871 void do_global_toggle_board_details()
3876 void do_global_toggle_strict_castling()
3878 do_toggle_strict_castling();
3881 // Global and other keys.
3882 static int globalkeys()
3884 struct userdata_s
*d
= gp
->data
;
3888 * These cannot be modified and other game mode keys cannot conflict with
3894 keypad(boardw
, TRUE
);
3895 wmove(stdscr
, 0, 0);
3899 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
3900 markend
= markstart
= 0;
3904 update_status_notify(gp
, NULL
);
3907 if (config
.validmoves
)
3908 pgn_reset_valid_moves(d
->b
);
3915 keycount
= keycount
* 10 + i
;
3919 update_status_notify(gp
, "Repeat %i", keycount
);
3922 if (d
->mode
== MODE_HISTORY
)
3926 d
->c_row
+= keycount
;
3935 if (d
->mode
== MODE_HISTORY
)
3939 d
->c_row
-= keycount
;
3940 update_status_notify(gp
, NULL
);
3950 if (d
->mode
== MODE_HISTORY
)
3954 d
->c_col
-= keycount
;
3963 if (d
->mode
== MODE_HISTORY
)
3967 d
->c_col
+= keycount
;
3982 for (i
= 0; global_keys
[i
]; i
++) {
3983 if (input_c
== global_keys
[i
]->c
) {
3984 (*global_keys
[i
]->f
)();
3995 static void perl_error(const char *fmt
, ...)
4001 vasprintf(&buf
, fmt
, ap
);
4004 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", buf
);
4008 static void do_perl_finalize(WIN
*win
)
4010 struct input_data_s
*in
= win
->data
;
4012 struct userdata_s
*d
= g
->data
;
4014 char *result
= NULL
;
4018 asprintf(&filename
, "%s/perl.pl", config
.datadir
);
4023 if (perl_init_file(filename
, perl_error
))
4026 arg
= strdup(pgn_game_to_fen(g
, d
->b
));
4028 if (perl_call_sub(trim(in
->str
), arg
, &result
))
4031 d
->perlfen
= strdup(pgn_game_to_fen(g
, d
->b
));
4032 d
->perlflags
= g
->flags
;
4034 if (pgn_board_init_fen(g
, d
->b
, result
) != E_PGN_OK
) {
4035 message(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s", _("FEN parse error."));
4036 pgn_board_init_fen(g
, d
->b
, d
->perlfen
);
4037 g
->flags
= d
->perlflags
;
4043 SET_FLAG(d
->flags
, CF_PERL
);
4044 n
= pgn_tag_find(g
->tag
, "FEN");
4047 d
->oldfen
= strdup(g
->tag
[n
]->value
);
4049 pgn_tag_add(&g
->tag
, "FEN", result
);
4050 update_status_notify(g
, "%s", _("[ press any key to continue ]"));
4061 void do_global_perl()
4063 struct input_data_s
*in
;
4065 in
= Calloc(1, sizeof(struct input_data_s
));
4067 in
->efunc
= do_perl_finalize
;
4068 construct_input(_("PERL Subroutine Filter"), NULL
, 1, 0, NULL
, NULL
, NULL
, 0, in
, INPUT_HIST_PERL
, -1);
4073 * A macro may contain a key that belongs to another macro so macro_match will
4074 * need to be updated to the new index of the matching macro.
4076 static void find_macro(struct userdata_s
*d
)
4081 * Macros can't contain macros when in a window.
4087 for (i
= 0; macros
[i
]; i
++) {
4088 if ((macros
[i
]->mode
== -1 || macros
[i
]->mode
== d
->mode
) &&
4089 input_c
== macros
[i
]->c
) {
4090 input_c
= macros
[i
]->keys
[macros
[i
]->n
++];
4092 if (!macro_depth_n
&& macro_match
> -1) {
4093 macro_depth
= realloc(macro_depth
, (macro_depth_n
+ 1) * sizeof(int));
4094 macro_depth
[macro_depth_n
++] = macro_match
;
4097 macro_depth
= realloc(macro_depth
, (macro_depth_n
+ 1) * sizeof(int));
4098 macro_depth
[macro_depth_n
++] = i
;
4106 * Resets the position in each macro to the first key.
4108 static void reset_macros()
4111 struct userdata_s
*d
= gp
->data
;
4114 if (macro_depth_n
> 0) {
4116 macro_match
= macro_depth
[macro_depth_n
];
4118 if (macros
[macro_match
]->n
>= macros
[macro_match
]->total
)
4121 input_c
= macros
[macro_match
]->keys
[macros
[macro_match
]->n
++];
4126 for (i
= 0; macros
[i
]; i
++)
4137 struct userdata_s
*d
;
4140 gindex
= gtotal
- 1;
4144 if (pgn_history_total(gp
->hp
))
4145 d
->mode
= MODE_HISTORY
;
4147 d
->mode
= MODE_PLAY
;
4149 if (d
->mode
== MODE_HISTORY
)
4150 pgn_board_update(gp
, d
->b
, pgn_history_total(gp
->hp
));
4152 update_status_notify(gp
, "%s", _("Type F1 for help"));
4156 wtimeout(boardw
, WINDOW_TIMEOUT
);
4160 char fdbuf
[8192] = {0};
4162 struct timeval tv
= {0, 0};
4170 for (i
= 0; i
< gtotal
; i
++) {
4173 if (d
->engine
&& d
->engine
->pid
!= -1) {
4174 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
4175 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
4176 n
= d
->engine
->fd
[ENGINE_IN_FD
];
4178 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
4181 if (d
->engine
->fd
[ENGINE_OUT_FD
] > 2) {
4182 if (d
->engine
->fd
[ENGINE_OUT_FD
] > n
)
4183 n
= d
->engine
->fd
[ENGINE_OUT_FD
];
4185 FD_SET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
);
4191 if ((n
= select(n
+ 1, &rfds
, &wfds
, NULL
, &tv
)) > 0) {
4192 for (i
= 0; i
< gtotal
; i
++) {
4195 if (d
->engine
&& d
->engine
->pid
!= -1) {
4196 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
4197 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
4201 if (d
->engine
->iobuf
)
4202 d
->engine
->iobuf
= Realloc(d
->engine
->iobuf
, d
->engine
->len
+ len
+ 1);
4204 d
->engine
->iobuf
= Calloc(1, len
+ 1);
4206 memcpy(&(d
->engine
->iobuf
[d
->engine
->len
]), &fdbuf
, len
);
4207 d
->engine
->len
+= len
;
4208 d
->engine
->iobuf
[d
->engine
->len
] = 0;
4211 * The fdbuf is full or no newline
4212 * was found. So we'll append the next
4213 * read() to this games buffer.
4215 if (d
->engine
->iobuf
[d
->engine
->len
- 1] != '\n')
4218 parse_engine_output(game
[i
], d
->engine
->iobuf
);
4219 free(d
->engine
->iobuf
);
4220 d
->engine
->iobuf
= NULL
;
4223 else if (len
== -1) {
4224 if (errno
!= EAGAIN
) {
4225 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "Engine read(): %s",
4227 waitpid(d
->engine
->pid
, &n
, 0);
4235 if (FD_ISSET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
)) {
4236 if (d
->engine
->queue
)
4237 send_engine_command(game
[i
]);
4244 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "select(): %s", strerror(errno
));
4253 * This is needed to detect terminal resizing.
4258 * Finds the top level window in the window stack so we know what
4259 * window the wgetch()ed key belongs to.
4262 for (i
= 0; wins
[i
]; i
++);
4265 wtimeout(wp
, WINDOW_TIMEOUT
);
4274 if (macros
&& macro_match
>= 0) {
4275 if (macros
[macro_match
]->n
>= macros
[macro_match
]->total
)
4278 input_c
= macros
[macro_match
]->keys
[macros
[macro_match
]->n
++];
4283 if ((input_c
= wgetch(wp
)) == ERR
)
4294 keypad(boardw
, TRUE
);
4295 wmove(stdscr
, 0, 0);
4303 * Run the function associated with the window. When the
4304 * function returns 0 win->efunc is ran (if not NULL) with
4305 * win as the one and only parameter. Then the window is
4308 * The exit function may create another window which will
4309 * mess up the window stack when window_destroy() is called.
4310 * So don't destory the window until the top window is
4311 * destroyable. See window_destroy().
4313 if ((*win
->func
)(win
) == 0) {
4318 window_destroy(win
);
4326 if (!keycount
&& status
.notify
)
4327 update_status_notify(gp
, NULL
);
4330 if (TEST_FLAG(d
->flags
, CF_PERL
)) {
4331 CLEAR_FLAG(d
->flags
, CF_PERL
);
4332 pgn_board_init_fen(gp
, d
->b
, d
->perlfen
);
4333 gp
->flags
= d
->perlflags
;
4335 pgn_tag_add(&gp
->tag
, "FEN", d
->oldfen
);
4337 d
->perlfen
= d
->oldfen
= NULL
;
4343 if (macros
&& macro_match
< 0)
4346 if ((n
= globalkeys()) == 1) {
4347 if (macro_match
== -1)
4357 for (i
= 0; edit_keys
[i
]; i
++) {
4358 if (input_c
== edit_keys
[i
]->c
) {
4359 (*edit_keys
[i
]->f
)();
4365 for (i
= 0; play_keys
[i
]; i
++) {
4366 if (input_c
== play_keys
[i
]->c
) {
4367 (*play_keys
[i
]->f
)();
4372 do_play_config_command();
4375 for (i
= 0; history_keys
[i
]; i
++) {
4376 if (input_c
== history_keys
[i
]->c
) {
4377 (*history_keys
[i
]->f
)();
4388 update_status_notify(gp
, NULL
);
4397 void usage(const char *pn
, int ret
)
4399 fprintf((ret
) ? stderr
: stdout
, "%s",
4401 "Usage: cboard [-hvCD] [-p [-VtRSE] <file>]\n"
4402 " -D Dump libchess debugging info to \"libchess.debug\" (stderr)\n"
4404 "Usage: cboard [-hvC] [-p [-VtRSE] <file>]\n"
4406 " -p Load PGN file.\n"
4407 " -V Validate a game file.\n"
4408 " -S Validate and output a PGN formatted game.\n"
4409 " -R Like -S but write a reduced PGN formatted game.\n"
4410 " -t Also write custom PGN tags from config file.\n"
4411 " -E Stop processing on file parsing error (overrides config).\n"
4412 " -C Enable strict castling (overrides config).\n"
4413 " -v Version information.\n"
4414 " -h This help text.\n");
4426 free(config
.engine_cmd
);
4427 free(config
.pattern
);
4428 free(config
.ccfile
);
4429 free(config
.nagfile
);
4430 free(config
.configfile
);
4433 for (i
= 0; config
.keys
[i
]; i
++) {
4434 free(config
.keys
[i
]->str
);
4435 free(config
.keys
[i
]);
4442 for (i
= 0; config
.einit
[i
]; i
++)
4443 free(config
.einit
[i
]);
4449 pgn_tag_free(config
.tag
);
4451 free(config
.datadir
);
4453 if (curses_initialized
) {
4455 del_panel(historyp
);
4476 static void signal_save_pgn(int sig
)
4480 char *p
= config
.savedirectory
? config
.savedirectory
: config
.datadir
;
4483 asprintf(&buf
, "%s/signal-%i-%li.pgn", p
, sig
, now
);
4485 if (do_game_write(buf
, "w", 0, gtotal
)) {
4486 cmessage(_("[ ERROR ]"), _("[ press any key to continue ]"), "%s: %s", p
, strerror(errno
));
4487 update_status_notify(gp
, "%s", _("Save game failed."));
4494 void catch_signal(int which
)
4501 if (which
== SIGPIPE
&& quit
)
4504 if (which
== SIGPIPE
)
4505 cmessage(NULL
, _("[ press any key to continue ]"), "%s", _("Broken pipe. Quitting."));
4515 keypad(boardw
, TRUE
);
4524 signal_save_pgn(which
);
4531 void loading_progress(long total
, long offset
)
4533 int n
= (100 * (offset
/ 100) / (total
/ 100));
4535 if (curses_initialized
)
4536 update_loading_window(n
);
4538 fprintf(stderr
, "Loading... %i%% (%i games)\r", n
, gtotal
);
4543 static void set_defaults()
4545 set_config_defaults();
4547 filetype
= FILE_NONE
;
4548 pgn_config_set(PGN_PROGRESS
, 1024);
4549 pgn_config_set(PGN_PROGRESS_FUNC
, loading_progress
);
4552 int main(int argc
, char *argv
[])
4556 char buf
[FILENAME_MAX
];
4557 char datadir
[FILENAME_MAX
];
4558 int ret
= EXIT_SUCCESS
;
4559 int validate_only
= 0, validate_and_write
= 0;
4560 int write_custom_tags
= 0;
4564 setlocale (LC_ALL
, "");
4565 init_wchar_pieces ();
4568 #ifndef HAVE_PROGNAME
4569 __progname
= argv
[0];
4572 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
4573 err(EXIT_FAILURE
, "getpwuid()");
4575 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
4576 config
.datadir
= strdup(datadir
);
4577 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
4578 config
.ccfile
= strdup(buf
);
4579 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
4580 config
.nagfile
= strdup(buf
);
4581 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
4582 config
.configfile
= strdup(buf
);
4584 if (stat(datadir
, &st
) == -1) {
4585 if (errno
== ENOENT
) {
4586 if (mkdir(datadir
, 0755) == -1)
4587 err(EXIT_FAILURE
, "%s", datadir
);
4590 err(EXIT_FAILURE
, "%s", datadir
);
4595 if (!S_ISDIR(st
.st_mode
))
4596 errx(EXIT_FAILURE
, "%s: %s", datadir
, _("Not a directory."));
4601 while ((opt
= getopt(argc
, argv
, "DCEVtSRhp:v")) != -1) {
4603 while ((opt
= getopt(argc
, argv
, "ECVtSRhp:v")) != -1) {
4608 unlink("libchess.debug");
4609 pgn_config_set(PGN_DEBUG
, 1);
4613 pgn_config_set(PGN_STRICT_CASTLING
, 1);
4616 write_custom_tags
= 1;
4622 pgn_config_set(PGN_REDUCED
, 1);
4624 validate_and_write
= 1;
4629 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
4633 filetype
= FILE_PGN
;
4634 strncpy(loadfile
, optarg
, sizeof(loadfile
));
4638 usage(argv
[0], EXIT_SUCCESS
);
4642 if ((validate_only
|| validate_and_write
) && !*loadfile
)
4643 usage(argv
[0], EXIT_FAILURE
);
4645 if (access(config
.configfile
, R_OK
) == 0)
4646 parse_rcfile(config
.configfile
);
4649 pgn_config_set(PGN_STOP_ON_ERROR
, 1);
4651 signal(SIGPIPE
, catch_signal
);
4652 signal(SIGCONT
, catch_signal
);
4653 signal(SIGSTOP
, catch_signal
);
4654 signal(SIGINT
, catch_signal
);
4655 signal(SIGALRM
, catch_signal
);
4656 signal(SIGTERM
, catch_signal
);
4662 if (pgn_open(loadfile
, "r", &pgn
) != E_PGN_OK
)
4663 err(EXIT_FAILURE
, "%s", loadfile
);
4665 ret
= pgn_parse(pgn
);
4669 //ret = parse_fen_file(loadfile);
4671 case FILE_EPD
: // Not implemented.
4674 // No file specified. Empty game.
4675 ret
= pgn_parse(NULL
);
4677 add_custom_tags(&gp
->tag
);
4681 if (validate_only
|| validate_and_write
) {
4682 if (validate_and_write
) {
4683 if (pgn_open("-", "r", &pgn
) != E_PGN_OK
)
4684 err(EXIT_FAILURE
, "pgn_open()");
4686 for (i
= 0; i
< gtotal
; i
++) {
4687 if (write_custom_tags
)
4688 add_custom_tags(&game
[i
]->tag
);
4690 pgn_write(pgn
, game
[i
]);
4699 else if (ret
== E_PGN_ERR
)
4705 * This fixes window resizing in an xterm.
4707 if (getenv("DISPLAY") != NULL
) {
4712 if (initscr() == NULL
)
4713 errx(EXIT_FAILURE
, "%s", _("Could not initialize curses."));
4715 curses_initialized
= 1;
4717 if (LINES
< 24 || COLS
< 80) {
4719 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
4722 if (has_colors() == TRUE
&& start_color() == OK
)
4725 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
4726 boardp
= new_panel(boardw
);
4727 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
4728 COLS
- HISTORY_WIDTH
);
4729 historyp
= new_panel(historyw
);
4730 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, 0, 0);
4731 statusp
= new_panel(statusw
);
4732 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, STATUS_HEIGHT
+ 1, 0);
4733 tagp
= new_panel(tagw
);
4734 keypad(boardw
, TRUE
);
4735 // leaveok(boardw, TRUE);
4736 leaveok(tagw
, TRUE
);
4737 leaveok(statusw
, TRUE
);
4738 leaveok(historyw
, TRUE
);
4742 draw_window_decor();