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
);
547 void file_browser_abort(struct menu_input_s
*m
)
552 void file_browser(void *arg
)
554 struct menu_key_s
**keys
= NULL
;
555 struct input_s
*in
= arg
;
557 char path
[FILENAME_MAX
];
559 if (config
.savedirectory
) {
560 if ((p
= word_expand(config
.savedirectory
)) == NULL
)
563 strncpy(path
, p
, sizeof(path
));
565 if (access(path
, R_OK
) == -1) {
566 cmessage(ERROR
, ANYKEY
, "%s: %s", path
, strerror(errno
));
567 getcwd(path
, sizeof(path
));
571 getcwd(path
, sizeof(path
));
573 in
->arg
= strdup(path
);
574 add_menu_key(&keys
, '\n', file_browser_select
);
575 add_menu_key(&keys
, KEY_F(1), file_browser_help
);
576 add_menu_key(&keys
, '~', file_browser_home
);
577 add_menu_key(&keys
, KEY_ESCAPE
, file_browser_abort
);
578 construct_menu(LINES
- 4, 0, -1, -1, NULL
, 0, get_file_items
, keys
, in
,
579 file_browser_finalize
);
583 void do_game_write(char *filename
, char *mode
, int start
, int end
)
585 char *command
= NULL
;
588 struct userdata_s
*d
;
591 if ((fp
= popen(command
, "w")) == NULL
) {
592 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
597 if ((fp
= fopen(filename
, mode
)) == NULL
) {
598 cmessage(ERROR
, ANYKEY
, "%s: %s", filename
, strerror(errno
));
603 for (i
= (start
== -1) ? 0 : start
; i
< end
; i
++) {
605 pgn_write(fp
, game
[i
]);
606 CLEAR_FLAG(d
->flags
, CF_MODIFIED
);
615 strncpy(loadfile
, filename
, sizeof(loadfile
));
617 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVED
);
618 update_all(game
[gindex
]);
622 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_FAILED
);
623 update_all(game
[gindex
]);
633 void do_save_game_overwrite_confirm(WIN
*win
)
636 struct save_game_s
*s
= win
->data
;
640 if (pgn_is_compressed(s
->filename
) == E_PGN_OK
) {
641 cmessage(NULL
, ANYKEY
, "%s", E_SAVE_COMPRESS
);
654 do_game_write(s
->filename
, mode
, s
->start
, s
->end
);
661 /* If the saveindex argument is -1, all games will be saved. Otherwise it's a
664 // FIXME command (compression)
665 void save_pgn(char *filename
, int saveindex
)
667 char buf
[FILENAME_MAX
];
669 int end
= (saveindex
== -1) ? gtotal
: saveindex
+ 1;
670 struct save_game_s
*s
;
672 if (filename
[0] != '/' && config
.savedirectory
) {
673 if (stat(config
.savedirectory
, &st
) == -1) {
674 if (errno
== ENOENT
) {
675 if (mkdir(config
.savedirectory
, 0755) == -1) {
676 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
682 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
,
688 stat(config
.savedirectory
, &st
);
690 if (!S_ISDIR(st
.st_mode
)) {
691 cmessage(ERROR
, ANYKEY
, "%s: %s", config
.savedirectory
, E_NOTADIR
);
695 snprintf(buf
, sizeof(buf
), "%s/%s", config
.savedirectory
, filename
);
699 if (access(filename
, W_OK
) == 0) {
700 s
= Malloc(sizeof(struct save_game_s
));
701 s
->filename
= strdup(filename
);
702 s
->start
= saveindex
;
704 construct_message(NULL
, GAME_SAVE_OVERWRITE_PROMPT
, 1, NULL
, NULL
,
705 s
, do_save_game_overwrite_confirm
, 0, "%s \"%s\"",
706 E_FILEEXISTS
, filename
);
710 do_game_write(filename
, "a", saveindex
, end
);
713 static int castling_state(GAME
*g
, BOARD b
, int row
, int col
, int piece
, int mod
)
715 if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
717 (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) || mod
) &&
718 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
720 TOGGLE_FLAG(g
->flags
, GF_WK_CASTLE
);
723 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
725 (TEST_FLAG(g
->flags
, GF_WQ_CASTLE
) || mod
) &&
726 pgn_piece_to_int(b
[7][4].icon
) == KING
&& isupper(piece
)) {
728 TOGGLE_FLAG(g
->flags
, GF_WQ_CASTLE
);
731 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 7
733 (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) || mod
) &&
734 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
736 TOGGLE_FLAG(g
->flags
, GF_BK_CASTLE
);
739 else if (pgn_piece_to_int(piece
) == ROOK
&& col
== 0
741 (TEST_FLAG(g
->flags
, GF_BQ_CASTLE
) || mod
) &&
742 pgn_piece_to_int(b
[0][4].icon
) == KING
&& islower(piece
)) {
744 TOGGLE_FLAG(g
->flags
, GF_BQ_CASTLE
);
747 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
749 (mod
|| (pgn_piece_to_int(b
[7][7].icon
) == ROOK
&&
750 TEST_FLAG(g
->flags
, GF_WK_CASTLE
))
752 (pgn_piece_to_int(b
[7][0].icon
) == ROOK
&&
753 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))) && isupper(piece
)) {
755 if (TEST_FLAG(g
->flags
, GF_WK_CASTLE
) ||
756 TEST_FLAG(g
->flags
, GF_WQ_CASTLE
))
757 CLEAR_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
759 SET_FLAG(g
->flags
, GF_WK_CASTLE
|GF_WQ_CASTLE
);
763 else if (pgn_piece_to_int(piece
) == KING
&& col
== 4
765 (mod
|| (pgn_piece_to_int(b
[0][7].icon
) == ROOK
&&
766 TEST_FLAG(g
->flags
, GF_BK_CASTLE
))
768 (pgn_piece_to_int(b
[0][0].icon
) == ROOK
&&
769 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))) && islower(piece
)) {
771 if (TEST_FLAG(g
->flags
, GF_BK_CASTLE
) ||
772 TEST_FLAG(g
->flags
, GF_BQ_CASTLE
))
773 CLEAR_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
775 SET_FLAG(g
->flags
, GF_BK_CASTLE
|GF_BQ_CASTLE
);
783 static void draw_board(GAME
*g
)
786 int bcol
= 0, brow
= 0;
787 int maxy
= BOARD_HEIGHT
, maxx
= BOARD_WIDTH
;
788 int ncols
= 0, offset
= 1;
789 unsigned coords_y
= 8;
790 struct userdata_s
*d
= g
->data
;
792 if (d
->mode
!= MODE_PLAY
&& d
->mode
!= MODE_EDIT
)
793 update_cursor(*g
, g
->hindex
);
795 for (row
= 0; row
< maxy
; row
++) {
798 for (col
= 0; col
< maxx
; col
++) {
803 if (row
== 0 || row
== maxy
- 2) {
805 mvwaddch(boardw
, row
, col
,
807 ACS_LLCORNER
| CP_BOARD_GRAPHICS
:
808 ACS_ULCORNER
| CP_BOARD_GRAPHICS
));
809 else if (col
== maxx
- 2)
810 mvwaddch(boardw
, row
, col
,
812 ACS_LRCORNER
| CP_BOARD_GRAPHICS
:
813 ACS_URCORNER
| CP_BOARD_GRAPHICS
));
815 mvwaddch(boardw
, row
, col
,
817 ACS_BTEE
| CP_BOARD_GRAPHICS
:
818 ACS_TTEE
| CP_BOARD_GRAPHICS
));
821 mvwaddch(boardw
, row
, col
,
822 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
828 if ((row
% 2) && col
== maxx
- 1 && coords_y
) {
829 wattron(boardw
, CP_BOARD_COORDS
);
830 mvwprintw(boardw
, row
, col
, "%d", coords_y
--);
831 wattroff(boardw
, CP_BOARD_COORDS
);
835 if ((col
== 0 || col
== maxx
- 2) && row
!= maxy
- 1) {
837 mvwaddch(boardw
, row
, col
,
839 ACS_RTEE
| CP_BOARD_GRAPHICS
:
840 ACS_LTEE
| CP_BOARD_GRAPHICS
));
842 mvwaddch(boardw
, row
, col
,
843 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
848 if ((row
% 2) && !(col
% 4) && row
!= maxy
- 1) {
849 mvwaddch(boardw
, row
, col
,
850 LINE_GRAPHIC(ACS_VLINE
| CP_BOARD_GRAPHICS
));
854 if (!(col
% 4) && row
!= maxy
- 1) {
855 mvwaddch(boardw
, row
, col
,
856 LINE_GRAPHIC(ACS_PLUS
| CP_BOARD_GRAPHICS
));
867 if (((ncols
% 2) && !(offset
% 2)) || (!(ncols
% 2)
873 if (config
.validmoves
&& d
->b
[brow
][bcol
].valid
) {
874 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_MOVES_WHITE
:
875 CP_BOARD_MOVES_BLACK
;
878 attrs
= (attrwhich
== WHITE
) ? CP_BOARD_WHITE
:
881 if (row
== ROWTOMATRIX(d
->c_row
) && col
==
882 COLTOMATRIX(d
->c_col
)) {
883 attrs
= CP_BOARD_CURSOR
;
886 if (row
== ROWTOMATRIX(d
->sp
.srow
) &&
887 col
== COLTOMATRIX(d
->sp
.scol
)) {
888 attrs
= CP_BOARD_SELECTED
;
894 mvwaddch(boardw
, row
, col
, ' ' | attrs
);
897 waddch(boardw
, x_grid_chars
[bcol
] | CP_BOARD_COORDS
);
899 if (config
.details
&& d
->b
[row
/ 2][bcol
].enpassant
)
902 piece
= d
->b
[row
/ 2][bcol
].icon
;
904 if (config
.details
&& castling_state(g
, d
->b
, brow
,
908 if (g
->side
== WHITE
&& isupper(piece
))
910 else if (g
->side
== BLACK
&& islower(piece
))
913 waddch(boardw
, (pgn_piece_to_int(piece
) != OPEN_SQUARE
) ? piece
| attrs
: ' ' | attrs
);
915 CLEAR_FLAG(attrs
, A_BOLD
);
916 CLEAR_FLAG(attrs
, A_REVERSE
);
919 waddch(boardw
, ' ' | attrs
);
926 mvwaddch(boardw
, row
, col
,
927 LINE_GRAPHIC(ACS_HLINE
| CP_BOARD_GRAPHICS
));
934 mvwaddch(boardw
, maxy
- 1, maxx
- 2, (config
.details
) ? '!' : ' ');
937 void invalid_move(int n
, int e
, const char *m
)
939 if (curses_initialized
)
940 cmessage(ERROR
, ANYKEY
, "%s \"%s\" (round #%i)", (e
== E_PGN_AMBIGUOUS
)
941 ? E_AMBIGUOUS
: E_INVALID_MOVE
, m
, n
);
943 warnx("%s: %s \"%s\" (round #%i)", loadfile
, (e
== E_PGN_AMBIGUOUS
)
944 ? E_AMBIGUOUS
: E_INVALID_MOVE
, m
, n
);
947 static void update_clock(GAME
*g
, struct itimerval it
)
949 struct userdata_s
*d
= g
->data
;
952 if (g
->turn
== WHITE
) {
953 d
->wc
.tv_sec
+= it
.it_value
.tv_sec
;
954 d
->wc
.tv_usec
+= it
.it_value
.tv_usec
;
956 if (d
->wc
.tv_usec
> 1000000 - 1) {
957 d
->wc
.tv_sec
+= d
->wc
.tv_usec
/ 1000000;
958 d
->wc
.tv_usec
= d
->wc
.tv_usec
% 1000000;
962 d
->bc
.tv_sec
+= it
.it_value
.tv_sec
;
963 d
->bc
.tv_usec
+= it
.it_value
.tv_usec
;
965 if (d
->bc
.tv_usec
> 1000000 - 1) {
966 d
->bc
.tv_sec
+= d
->bc
.tv_usec
/ 1000000;
967 d
->bc
.tv_usec
= d
->bc
.tv_usec
% 1000000;
971 d
->elapsed
= d
->wc
.tv_sec
+ d
->bc
.tv_sec
;
972 n
= d
->wc
.tv_usec
+ d
->bc
.tv_usec
;
973 d
->elapsed
+= (n
> 1000000 - 1) ? n
/ 1000000 : 0;
975 if (TEST_FLAG(d
->flags
, CF_CLOCK
)) {
976 if (d
->elapsed
>= d
->limit
) {
977 SET_FLAG(g
->flags
, GF_GAMEOVER
);
978 pgn_tag_add(&g
->tag
, "Result", "1/2-1/2");
983 void do_validate_move(char *m
)
985 struct userdata_s
*d
= game
[gindex
].data
;
988 if (TEST_FLAG(d
->flags
, CF_HUMAN
)) {
989 if ((n
= pgn_parse_move(&game
[gindex
], d
->b
, &m
)) != E_PGN_OK
) {
990 invalid_move(d
->n
+ 1, n
, m
);
995 pgn_history_add(&game
[gindex
], m
);
996 pgn_switch_turn(&game
[gindex
]);
999 if ((n
= pgn_validate_move(&game
[gindex
], d
->b
, &m
)) != E_PGN_OK
) {
1000 invalid_move(d
->n
+ 1, n
, m
);
1005 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "%s\n", m
);
1008 d
->sp
.srow
= d
->sp
.scol
= d
->sp
.icon
= 0;
1010 if (config
.validmoves
)
1011 pgn_reset_valid_moves(d
->b
);
1013 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
1014 d
->mode
= MODE_HISTORY
;
1016 SET_FLAG(d
->flags
, CF_MODIFIED
);
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 x_grid_chars
[d
->sp
.scol
- 1],
1049 d
->sp
.srow
, x_grid_chars
[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(PROMOTION_TITLE
, PROMOTION_PROMPT
, 1, NULL
, NULL
,
1055 str
, do_promotion_piece_finalize
, 0, "%s", PROMOTION_TEXT
);
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
)
1076 static char buf
[16];
1077 int h
= 0, m
= 0, s
= 0;
1081 m
= (n
% 3600) / 60;
1082 s
= (n
% 3600) % 60;
1083 snprintf(buf
, sizeof(buf
), "%.2i:%.2i:%.2i.%.2i", h
, m
, s
,
1084 (int)t
.tv_usec
/ 10000);
1088 void update_status_window(GAME g
)
1092 char tmp
[15], *engine
, *mode
;
1097 struct userdata_s
*d
= g
.data
;
1099 getmaxyx(statusw
, maxy
, maxx
);
1107 if (TEST_FLAG(d
->flags
, CF_DELETE
)) {
1113 if (TEST_FLAG(g
.flags
, GF_PERROR
)) {
1123 if (TEST_FLAG(d
->flags
, CF_MODIFIED
)) {
1138 mvwprintw(statusw
, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR
, w
,
1139 (loadfile
[0]) ? str_etc(loadfile
, w
, 1) : UNAVAILABLE
);
1140 snprintf(buf
, len
, "%i %s %i %s", gindex
+ 1, N_OF_N_STR
, gtotal
,
1142 mvwprintw(statusw
, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR
, w
, buf
);
1146 mode
= MODE_HISTORY_STR
;
1149 mode
= MODE_EDIT_STR
;
1152 mode
= MODE_PLAY_STR
;
1159 snprintf(buf
, len
- 1, "%*s %s", 7, STATUS_MODE_STR
, mode
);
1161 if (d
->mode
== MODE_PLAY
) {
1162 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
1163 strncat(buf
, " (human/human)", len
- 1);
1164 else if (TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
))
1165 strncat(buf
, " (engine/engine)", len
- 1);
1167 strncat(buf
, " (human/engine)", len
- 1);
1170 mvwprintw(statusw
, 4, 1, "%-*s", len
, buf
);
1173 switch (d
->engine
->status
) {
1174 case ENGINE_THINKING
:
1175 engine
= ENGINE_PONDER_STR
;
1178 engine
= ENGINE_READY_STR
;
1180 case ENGINE_INITIALIZING
:
1181 engine
= ENGINE_INITIALIZING_STR
;
1183 case ENGINE_OFFLINE
:
1184 engine
= ENGINE_OFFLINE_STR
;
1192 engine
= ENGINE_OFFLINE_STR
;
1194 mvwprintw(statusw
, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR
, w
, " ");
1195 wattron(statusw
, CP_STATUS_ENGINE
);
1196 mvwaddstr(statusw
, 5, 9, engine
);
1197 wattroff(statusw
, CP_STATUS_ENGINE
);
1199 mvwprintw(statusw
, 6, 1, "%*s %-*s", 7, STATUS_TURN_STR
, w
,
1200 (g
.turn
== WHITE
) ? WHITE_STR
: BLACK_STR
);
1202 mvwprintw(statusw
, 7, 1, "%*s %-*s", 7, STATUS_CLOCK_STR
, w
,
1203 clock_to_char((TEST_FLAG(d
->flags
, CF_CLOCK
)) ?
1204 d
->limit
- d
->elapsed
: 0));
1206 strncpy(tmp
, WHITE_STR
, sizeof(tmp
));
1207 tmp
[0] = toupper(tmp
[0]);
1208 mvwprintw(statusw
, 8, 1, "%*s: %-*s", 6, tmp
, w
, timeval_to_char(d
->wc
));
1210 strncpy(tmp
, BLACK_STR
, sizeof(tmp
));
1211 tmp
[0] = toupper(tmp
[0]);
1212 mvwprintw(statusw
, 9, 1, "%*s: %-*s", 6, tmp
, w
, timeval_to_char(d
->bc
));
1215 for (i
= 1; i
< maxx
- 4; i
++)
1216 mvwprintw(statusw
, maxy
- 2, i
, " ");
1219 status
.notify
= strdup(GAME_HELP_PROMPT
);
1221 wattron(statusw
, CP_STATUS_NOTIFY
);
1222 mvwprintw(statusw
, maxy
- 2, CENTERX(maxx
, status
.notify
), "%s",
1224 wattroff(statusw
, CP_STATUS_NOTIFY
);
1227 void update_history_window(GAME g
)
1229 char buf
[HISTORY_WIDTH
- 1];
1232 int t
= pgn_history_total(g
.hp
);
1234 n
= (g
.hindex
+ 1) / 2;
1237 total
= (t
+ 1) / 2;
1242 snprintf(buf
, sizeof(buf
), "%u %s %u%s", n
, N_OF_N_STR
, total
,
1243 (movestep
== 1) ? HISTORY_PLY_STEP
: "");
1245 strncpy(buf
, UNAVAILABLE
, sizeof(buf
));
1247 mvwprintw(historyw
, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR
,
1248 HISTORY_WIDTH
- 13, buf
);
1250 h
= pgn_history_by_n(g
.hp
, g
.hindex
);
1251 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1254 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1255 strncat(buf
, " (v", sizeof(buf
));
1260 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1265 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1270 strncat(buf
, ")", sizeof(buf
));
1272 mvwprintw(historyw
, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR
,
1273 HISTORY_WIDTH
- 13, buf
);
1275 h
= pgn_history_by_n(g
.hp
, g
.hindex
- 1);
1276 snprintf(buf
, sizeof(buf
), "%s", (h
&& h
->move
) ? h
->move
: UNAVAILABLE
);
1279 if (h
&& ((h
->comment
) || h
->nag
[0])) {
1280 strncat(buf
, " (V", sizeof(buf
));
1285 strncat(buf
, (n
) ? ",+" : " (+", sizeof(buf
));
1290 strncat(buf
, (n
) ? ",-" : " (-", sizeof(buf
));
1295 strncat(buf
, ")", sizeof(buf
));
1297 mvwprintw(historyw
, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR
,
1298 HISTORY_WIDTH
- 13, buf
);
1301 void update_tag_window(TAG
**t
)
1304 int w
= TAG_WIDTH
- 10;
1306 for (i
= 0; i
< 7; i
++)
1307 mvwprintw(tagw
, (i
+ 2), 1, "%*s: %-*s", 6, t
[i
]->name
, w
,
1308 str_etc(t
[i
]->value
, w
, 0));
1311 void append_enginebuf(char *line
)
1316 for (i
= 0; enginebuf
[i
]; i
++);
1318 if (i
>= LINES
- 3) {
1321 for (i
= 0; enginebuf
[i
+1]; i
++)
1322 enginebuf
[i
] = enginebuf
[i
+1];
1324 enginebuf
[i
] = strdup(line
);
1327 enginebuf
= Realloc(enginebuf
, (i
+ 2) * sizeof(char *));
1328 enginebuf
[i
++] = strdup(line
);
1329 enginebuf
[i
] = NULL
;
1333 void update_engine_window()
1340 wmove(enginew
, 0, 0);
1344 for (i
= 0; enginebuf
[i
]; i
++)
1345 mvwprintw(enginew
, i
+ 2, 1, "%s", enginebuf
[i
]);
1348 window_draw_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1352 void toggle_engine_window()
1355 enginew
= newwin(LINES
, COLS
, 0, 0);
1356 enginep
= new_panel(enginew
);
1357 window_draw_title(enginew
, "Engine IO Window", COLS
, CP_MESSAGE_TITLE
,
1359 hide_panel(enginep
);
1362 if (panel_hidden(enginep
)) {
1363 update_engine_window();
1368 hide_panel(enginep
);
1379 void update_all(GAME g
)
1381 update_status_window(g
);
1382 update_history_window(g
);
1383 update_tag_window(g
.tag
);
1384 update_engine_window();
1387 static void game_next_prev(GAME g
, int n
, int count
)
1393 if (gindex
+ count
> gtotal
- 1) {
1395 gindex
= gtotal
- 1;
1403 if (gindex
- count
< 0) {
1407 gindex
= gtotal
- 1;
1414 static void delete_game(int which
)
1419 struct userdata_s
*d
;
1421 for (i
= 0; i
< gtotal
; i
++) {
1424 if (i
== which
|| TEST_FLAG(d
->flags
, CF_DELETE
)) {
1429 g
= Realloc(g
, (gi
+ 1) * sizeof(GAME
));
1430 memcpy(&g
[gi
], &game
[i
], sizeof(GAME
));
1431 g
[gi
].tag
= game
[i
].tag
;
1432 g
[gi
].history
= game
[i
].history
;
1433 g
[gi
].hp
= game
[i
].hp
;
1441 if (which
+ 1 >= gtotal
)
1442 gindex
= gtotal
- 1;
1447 gindex
= gtotal
- 1;
1449 game
[gindex
].hp
= game
[gindex
].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(E_REGEXEC_TITLE
, ANYKEY
, "%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
);
1501 update_all(game
[gindex
]);
1503 for (i
= x
= 0; i
< gtotal
; i
++) {
1506 if (TEST_FLAG(d
->flags
, CF_DELETE
))
1511 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
1513 CLEAR_FLAG(d
->flags
, CF_DELETE
);
1520 static int find_game_exp(char *str
, int which
, int count
)
1522 char *nstr
= NULL
, *exp
= NULL
;
1526 char buf
[255], *tmp
;
1529 int incr
= (which
== 0) ? -(1) : 1;
1531 strncpy(buf
, str
, sizeof(buf
));
1534 if (strstr(tmp
, ":") != NULL
) {
1535 nstr
= strsep(&tmp
, ":");
1537 if ((ret
= regcomp(&nexp
, nstr
,
1538 REG_ICASE
|REG_EXTENDED
|REG_NOSUB
)) != 0) {
1539 regerror(ret
, &nexp
, errbuf
, sizeof(errbuf
));
1540 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
1551 if ((ret
= regcomp(&vexp
, exp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
1552 regerror(ret
, &vexp
, errbuf
, sizeof(errbuf
));
1553 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
1560 for (g
= gindex
+ incr
, found
= 0; ; g
+= incr
) {
1571 for (t
= 0; game
[g
].tag
[t
]; t
++) {
1573 if (regexec(&nexp
, game
[g
].tag
[t
]->name
, 0, 0, 0) == 0) {
1574 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
1575 if (count
== ++found
) {
1583 if (regexec(&vexp
, game
[g
].tag
[t
]->value
, 0, 0, 0) == 0) {
1584 if (count
== ++found
) {
1606 * Updates the notification line in the status window then refreshes the
1609 void update_status_notify(GAME g
, char *fmt
, ...)
1612 #ifdef HAVE_VASPRINTF
1619 if (status
.notify
) {
1620 free(status
.notify
);
1621 status
.notify
= NULL
;
1623 if (curses_initialized
)
1624 update_status_window(g
);
1631 #ifdef HAVE_VASPRINTF
1632 vasprintf(&line
, fmt
, ap
);
1634 vsnprintf(line
, sizeof(line
), fmt
, ap
);
1639 free(status
.notify
);
1641 status
.notify
= strdup(line
);
1643 #ifdef HAVE_VASPRINTF
1646 if (curses_initialized
)
1647 update_status_window(g
);
1650 int rav_next_prev(GAME
*g
, BOARD b
, int n
)
1654 if ((!g
->ravlevel
&& g
->hp
[g
->hindex
- 1]->rav
== NULL
) ||
1655 (g
->ravlevel
&& g
->hp
[g
->hindex
]->rav
== NULL
))
1658 g
->rav
= Realloc(g
->rav
, (g
->ravlevel
+ 1) * sizeof(RAV
));
1659 g
->rav
[g
->ravlevel
].hp
= g
->hp
;
1660 g
->rav
[g
->ravlevel
].flags
= g
->flags
;
1661 g
->rav
[g
->ravlevel
].fen
= strdup(pgn_game_to_fen(*g
, b
));
1662 g
->rav
[g
->ravlevel
].hindex
= g
->hindex
;
1663 g
->hp
= (!g
->ravlevel
) ? g
->hp
[g
->hindex
- 1]->rav
: g
->hp
[g
->hindex
]->rav
;
1666 pgn_board_update(g
, b
, g
->hindex
+ 1);
1670 if (g
->ravlevel
- 1 < 0)
1675 pgn_board_init_fen(g
, b
, g
->rav
[g
->ravlevel
].fen
);
1676 free(g
->rav
[g
->ravlevel
].fen
);
1677 g
->hp
= g
->rav
[g
->ravlevel
].hp
;
1678 g
->flags
= g
->rav
[g
->ravlevel
].flags
;
1679 g
->hindex
= g
->rav
[g
->ravlevel
].hindex
;
1683 static void draw_window_decor()
1685 move_panel(historyp
, LINES
- HISTORY_HEIGHT
, COLS
- HISTORY_WIDTH
);
1686 move_panel(boardp
, 0, COLS
- BOARD_WIDTH
);
1687 wbkgd(boardw
, CP_BOARD_WINDOW
);
1688 wbkgd(statusw
, CP_STATUS_WINDOW
);
1689 window_draw_title(statusw
, STATUS_WINDOW_TITLE
, STATUS_WIDTH
,
1690 CP_STATUS_TITLE
, CP_STATUS_BORDER
);
1691 wbkgd(tagw
, CP_TAG_WINDOW
);
1692 window_draw_title(tagw
, TAG_WINDOW_TITLE
, TAG_WIDTH
, CP_TAG_TITLE
,
1694 wbkgd(historyw
, CP_HISTORY_WINDOW
);
1695 window_draw_title(historyw
, HISTORY_WINDOW_TITLE
, HISTORY_WIDTH
,
1696 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();
1715 update_all(game
[gindex
]);
1720 memset(&clock_timer
, 0, sizeof(struct itimerval
));
1721 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1726 if (clock_timer
.it_interval
.tv_usec
)
1729 clock_timer
.it_value
.tv_sec
= 0;
1730 clock_timer
.it_value
.tv_usec
= 100000;
1731 clock_timer
.it_interval
.tv_sec
= 0;
1732 clock_timer
.it_interval
.tv_usec
= 100000;
1733 setitimer(ITIMER_REAL
, &clock_timer
, NULL
);
1736 static void update_clocks()
1739 struct userdata_s
*d
;
1740 struct itimerval it
;
1742 getitimer(ITIMER_REAL
, &it
);
1744 for (i
= 0; i
< gtotal
; i
++) {
1747 if (d
->mode
== MODE_PLAY
) {
1748 if (d
->paused
== 1 || TEST_FLAG(d
->flags
, CF_NEW
))
1750 else if (d
->paused
== -1) {
1751 if (game
[i
].side
== game
[i
].turn
) {
1757 update_clock(&game
[i
], it
);
1762 static int init_chess_engine(GAME
*g
)
1764 struct userdata_s
*d
= g
->data
;
1767 if (start_chess_engine(g
) > 0) {
1772 x
= pgn_tag_find(g
->tag
, "FEN");
1773 w
= pgn_tag_find(g
->tag
, "SetUp");
1775 if ((w
>= 0 && x
>= 0 && atoi(g
->tag
[w
]->value
) == 1) ||
1776 (x
>= 0 && w
== -1))
1777 add_engine_command(g
, ENGINE_READY
, "setboard %s\n", g
->tag
[x
]->value
);
1779 add_engine_command(g
, ENGINE_READY
, "setboard %s\n",
1780 pgn_game_to_fen(*g
, d
->b
));
1785 static int parse_clock_input(struct userdata_s
*d
, char *str
)
1807 if (!t
&& *p
!= ' ')
1841 CLEAR_FLAG(d
->flags
, CF_CLOCK
);
1844 SET_FLAG(d
->flags
, CF_CLOCK
);
1849 d
->limit
= (n
<= d
->elapsed
) ? d
->elapsed
+ n
: n
;
1855 void do_clock_input_finalize(WIN
*win
)
1857 struct userdata_s
*d
= game
[gindex
].data
;
1858 struct input_data_s
*in
= win
->data
;
1863 if (parse_clock_input(d
, in
->str
))
1864 cmessage(ERROR
, ANYKEY
, "Invalid time specification");
1870 void do_engine_command_finalize(WIN
*win
)
1872 struct userdata_s
*d
= game
[gindex
].data
;
1873 struct input_data_s
*in
= win
->data
;
1881 x
= d
->engine
->status
;
1882 send_to_engine(&game
[gindex
], -1, "%s\n", in
->str
);
1883 d
->engine
->status
= x
;
1888 static void historymode_keys(chtype
);
1889 static int playmode_keys(chtype c
)
1891 // More keys in MODE_EDIT share keys with MODE_PLAY than don't.
1892 struct userdata_s
*d
= game
[gindex
].data
;
1893 int editmode
= (d
->mode
== MODE_EDIT
) ? 1 : 0;
1896 struct input_data_s
*in
;
1900 in
= Calloc(1, sizeof(struct input_data_s
));
1901 in
->efunc
= do_clock_input_finalize
;
1902 construct_input(CLOCK_TITLE
, NULL
, 1, 1, CLOCK_HELP
, NULL
, NULL
, 0,
1906 TOGGLE_FLAG(d
->flags
, CF_HUMAN
);
1908 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) &&
1909 pgn_history_total(game
[gindex
].hp
)) {
1910 if (init_chess_engine(&game
[gindex
]))
1914 CLEAR_FLAG(d
->flags
, CF_ENGINE_LOOP
);
1917 d
->engine
->status
= ENGINE_READY
;
1919 update_all(game
[gindex
]);
1925 TOGGLE_FLAG(d
->flags
, CF_ENGINE_LOOP
);
1926 CLEAR_FLAG(d
->flags
, CF_HUMAN
);
1928 if (d
->engine
&& TEST_FLAG(d
->flags
, CF_ENGINE_LOOP
)) {
1929 pgn_board_update(&game
[gindex
], d
->b
,
1930 pgn_history_total(game
[gindex
].hp
));
1931 add_engine_command(&game
[gindex
], ENGINE_READY
,
1932 "setboard %s\n", pgn_game_to_fen(game
[gindex
], d
->b
));
1935 update_all(game
[gindex
]);
1941 if (d
->engine
->status
== ENGINE_OFFLINE
)
1944 x
= d
->engine
->status
;
1945 in
= Calloc(1, sizeof(struct input_data_s
));
1946 in
->efunc
= do_engine_command_finalize
;
1947 construct_input(ENGINE_CMD_TITLE
, NULL
, 1, 1, NULL
, NULL
, NULL
, 0,
1952 pushkey
= keycount
= 0;
1953 update_status_notify(game
[gindex
], NULL
);
1955 if (!editmode
&& !TEST_FLAG(d
->flags
, CF_HUMAN
) &&
1956 (!d
->engine
|| d
->engine
->status
== ENGINE_THINKING
)) {
1964 d
->sp
.row
= d
->c_row
;
1965 d
->sp
.col
= d
->c_col
;
1968 p
= d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
;
1969 d
->b
[RANKTOBOARD(d
->sp
.row
)][FILETOBOARD(d
->sp
.col
)].icon
= p
;
1970 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
1971 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
1972 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
1976 move_to_engine(&game
[gindex
]);
1979 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && (!d
->engine
||
1980 d
->engine
->status
== ENGINE_OFFLINE
) && !editmode
) {
1981 if (init_chess_engine(&game
[gindex
]))
1985 if (d
->sp
.icon
|| (!editmode
&& d
->engine
&&
1986 d
->engine
->status
== ENGINE_THINKING
)) {
1991 d
->sp
.icon
= mvwinch(boardw
, ROWTOMATRIX(d
->c_row
),
1992 COLTOMATRIX(d
->c_col
)+1) & A_CHARTEXT
;
1994 if (d
->sp
.icon
== ' ') {
1999 if (!editmode
&& ((islower(d
->sp
.icon
) && game
[gindex
].turn
!= BLACK
)
2000 || (isupper(d
->sp
.icon
) && game
[gindex
].turn
!= WHITE
))) {
2001 if (pgn_history_total(game
[gindex
].hp
)) {
2002 message(NULL
, ANYKEY
, "%s", E_SELECT_TURN
);
2007 if (pgn_tag_find(game
[gindex
].tag
, "FEN") != E_PGN_ERR
)
2010 add_engine_command(&game
[gindex
], ENGINE_READY
, "black\n");
2011 pgn_switch_turn(&game
[gindex
]);
2013 if (game
[gindex
].side
!= BLACK
)
2014 pgn_switch_side(&game
[gindex
]);
2018 d
->sp
.srow
= d
->c_row
;
2019 d
->sp
.scol
= d
->c_col
;
2021 if (!editmode
&& config
.validmoves
)
2022 pgn_find_valid_moves(game
[gindex
], d
->b
, d
->sp
.scol
, d
->sp
.srow
);
2025 CLEAR_FLAG(d
->flags
, CF_NEW
);
2031 pgn_switch_side(&game
[gindex
]);
2032 pgn_switch_turn(&game
[gindex
]);
2033 add_engine_command(&game
[gindex
], -1,
2034 (game
[gindex
].side
== WHITE
) ? "white\n" : "black\n");
2035 update_status_window(game
[gindex
]);
2038 if (!pgn_history_total(game
[gindex
].hp
))
2041 if (d
->engine
&& d
->engine
->status
== ENGINE_READY
) {
2042 add_engine_command(&game
[gindex
], ENGINE_READY
, "remove\n");
2043 d
->engine
->status
= ENGINE_READY
;
2046 game
[gindex
].hindex
-= 2;
2047 pgn_history_free(game
[gindex
].hp
, game
[gindex
].hindex
);
2048 game
[gindex
].hindex
= pgn_history_total(game
[gindex
].hp
);
2049 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2050 update_history_window(game
[gindex
]);
2053 historymode_keys(c
);
2056 config
.details
= (config
.details
) ? 0 : 1;
2059 if (!TEST_FLAG(d
->flags
, CF_HUMAN
) && game
[gindex
].turn
!=
2060 game
[gindex
].side
) {
2065 d
->paused
= (d
->paused
) ? 0 : 1;
2068 if (TEST_FLAG(d
->flags
, CF_HUMAN
))
2071 if (!d
->engine
|| d
->engine
->status
== ENGINE_OFFLINE
) {
2072 if (init_chess_engine(&game
[gindex
]))
2076 add_engine_command(&game
[gindex
], ENGINE_THINKING
, "go\n");
2083 for (x
= 0; config
.keys
[x
]; x
++) {
2084 if (config
.keys
[x
]->c
== c
) {
2085 switch (config
.keys
[x
]->type
) {
2087 add_engine_command(&game
[gindex
], -1, "%s\n",
2088 config
.keys
[x
]->str
);
2094 add_engine_command(&game
[gindex
], -1,
2095 "%s %i\n", config
.keys
[x
]->str
, keycount
);
2102 for (w
= 0; w
< keycount
; w
++)
2103 add_engine_command(&game
[gindex
], -1,
2104 "%s\n", config
.keys
[x
]->str
);
2111 update_status_notify(game
[gindex
], NULL
);
2119 void do_edit_insert_finalize(WIN
*win
)
2121 struct userdata_s
*d
= win
->data
;
2123 if (pgn_piece_to_int(win
->c
) == -1)
2126 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
= win
->c
;
2129 static void editmode_keys(chtype c
)
2131 struct userdata_s
*d
= game
[gindex
].data
;
2141 d
->b
[RANKTOBOARD(d
->sp
.srow
)][FILETOBOARD(d
->sp
.scol
)].icon
=
2142 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2144 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
=
2145 pgn_int_to_piece(game
[gindex
].turn
, OPEN_SQUARE
);
2147 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
2150 pgn_switch_turn(&game
[gindex
]);
2151 update_all(game
[gindex
]);
2154 castling_state(&game
[gindex
], d
->b
, RANKTOBOARD(d
->c_row
),
2155 FILETOBOARD(d
->c_col
),
2156 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].icon
, 1);
2159 construct_message(GAME_EDIT_TITLE
, GAME_EDIT_PROMPT
, 0, NULL
, NULL
,
2160 d
->b
, do_edit_insert_finalize
, 0, "%s", GAME_EDIT_TEXT
);
2163 if (d
->c_row
== 6 || d
->c_row
== 3) {
2164 pgn_reset_enpassant(d
->b
);
2165 d
->b
[RANKTOBOARD(d
->c_row
)][FILETOBOARD(d
->c_col
)].enpassant
= 1;
2173 void do_annotate_finalize(WIN
*win
)
2175 struct userdata_s
*d
= game
[gindex
].data
;
2176 struct input_data_s
*in
= win
->data
;
2177 HISTORY
*h
= in
->data
;
2187 len
= strlen(in
->str
) + 1;
2188 h
->comment
= Realloc(h
->comment
, len
);
2189 strncpy(h
->comment
, in
->str
, len
);
2194 SET_FLAG(d
->flags
, CF_MODIFIED
);
2195 update_all(game
[gindex
]);
2198 void do_find_move_exp_finalize(int init
, int c
)
2201 struct userdata_s
*d
= game
[gindex
].data
;
2202 static int firstrun
;
2207 if (init
|| !firstrun
) {
2211 if ((ret
= regcomp(&r
, moveexp
, REG_EXTENDED
|REG_NOSUB
)) != 0) {
2212 regerror(ret
, &r
, errbuf
, sizeof(errbuf
));
2213 cmessage(E_REGCOMP_TITLE
, ANYKEY
, "%s", errbuf
);
2220 if ((n
= find_move_exp(game
[gindex
], r
,
2221 (c
== '[') ? 0 : 1, (keycount
) ? keycount
: 1)) == -1)
2224 game
[gindex
].hindex
= n
;
2225 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2226 update_all(game
[gindex
]);
2229 void do_find_move_exp(WIN
*win
)
2231 struct input_data_s
*in
= win
->data
;
2236 strncpy(moveexp
, in
->str
, sizeof(moveexp
));
2241 do_find_move_exp_finalize(1, c
);
2249 void do_move_jump_finalize(int n
)
2251 struct userdata_s
*d
= game
[gindex
].data
;
2253 if (n
< 0 || n
> (pgn_history_total(game
[gindex
].hp
) / 2))
2257 update_status_notify(game
[gindex
], NULL
);
2258 game
[gindex
].hindex
= (n
) ? n
* 2 - 1 : n
* 2;
2259 pgn_board_update(&game
[gindex
], d
->b
, game
[gindex
].hindex
);
2260 update_all(game
[gindex
]);
2263 void do_move_jump(WIN
*win
)
2265 struct input_data_s
*in
= win
->data
;
2267 if (!in
->str
|| !isinteger(in
->str
)) {
2275 do_move_jump_finalize(atoi(in
->str
));
2280 static void historymode_keys(chtype c
)
2284 struct userdata_s
*d
= game
[gindex
].data
;
2285 struct input_data_s
*in
;
2290 config
.details
= (config
.details
) ? 0 : 1;
2293 movestep
= (movestep
== 1) ? 2 : 1;
2294 update_history_window(game
[gindex
]);
2297 pgn_history_next(&game
[gindex
], d
->b
, (keycount
> 0) ?
2298 config
.jumpcount
* keycount
* movestep
:
2299 config
.jumpcount
* movestep
);
2300 update_all(game
[gindex
]);
2303 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2304 config
.jumpcount
* keycount
* movestep
:
2305 config
.jumpcount
* movestep
);
2306 update_all(game
[gindex
]);
2309 pgn_history_prev(&game
[gindex
], d
->b
, (keycount
) ?
2310 keycount
* movestep
: movestep
);
2311 update_all(game
[gindex
]);
2314 pgn_history_next(&game
[gindex
], d
->b
, (keycount
) ?
2315 keycount
* movestep
: movestep
);
2316 update_all(game
[gindex
]);
2319 n
= game
[gindex
].hindex
;
2321 if (n
&& game
[gindex
].hp
[n
- 1]->move
)
2326 snprintf(buf
, sizeof(buf
), "%s \"%s\"", ANNOTATION_EDIT_TITLE
,
2327 game
[gindex
].hp
[n
]->move
);
2328 in
= Calloc(1, sizeof(struct input_data_s
));
2329 in
->data
= game
[gindex
].hp
[n
];
2330 in
->efunc
= do_annotate_finalize
;
2331 construct_input(buf
, game
[gindex
].hp
[n
]->comment
,
2332 MAX_PGN_LINE_LEN
/ INPUT_WIDTH
, 0, NAG_PROMPT
, edit_nag
,
2333 NULL
, CTRL('T'), in
, -1);
2338 if (pgn_history_total(game
[gindex
].hp
) < 2)
2341 in
= Calloc(1, sizeof(struct input_data_s
));
2342 p
= Malloc(sizeof(int));
2345 in
->efunc
= do_find_move_exp
;
2347 if (!*moveexp
|| c
== '/') {
2348 construct_input(FIND_REGEXP
, moveexp
, 1, 0, NULL
, NULL
, NULL
,
2353 do_find_move_exp_finalize(0, c
);
2356 view_annotation(game
[gindex
].hp
[game
[gindex
].hindex
]);
2359 if (game
[gindex
].hindex
- 1 >= 0)
2360 view_annotation(game
[gindex
].hp
[game
[gindex
].hindex
- 1]);
2364 rav_next_prev(&game
[gindex
], d
->b
, (c
== '-') ? 0 : 1);
2365 update_all(game
[gindex
]);
2368 if (pgn_history_total(game
[gindex
].hp
) < 2)
2372 in
= Calloc(1, sizeof(struct input_data_s
));
2373 in
->efunc
= do_move_jump
;
2375 construct_input(GAME_HISTORY_JUMP_TITLE
, NULL
, 1, 1, NULL
,
2376 NULL
, NULL
, 0, in
, 0);
2380 do_move_jump_finalize(keycount
);
2387 static void free_userdata()
2391 for (i
= 0; i
< gtotal
; i
++) {
2392 struct userdata_s
*d
;
2398 stop_engine(&game
[i
]);
2403 game
[i
].data
= NULL
;
2408 void update_loading_window(int n
)
2411 loadingw
= newwin(3, COLS
/ 2, CALCPOSY(3), CALCPOSX(COLS
/ 2));
2412 loadingp
= new_panel(loadingw
);
2413 wbkgd(loadingw
, CP_MESSAGE_WINDOW
);
2416 wmove(loadingw
, 0, 0);
2417 wclrtobot(loadingw
);
2418 wattron(loadingw
, CP_MESSAGE_BORDER
);
2419 box(loadingw
, ACS_VLINE
, ACS_HLINE
);
2420 wattroff(loadingw
, CP_MESSAGE_BORDER
);
2421 mvwprintw(loadingw
, 1, CENTER_INT((COLS
/ 2),
2422 11 + strlen(itoa(gtotal
))), "Loading... %i%% (%i games)", n
,
2427 static void init_userdata_once(GAME
*g
, int n
)
2429 struct userdata_s
*d
= NULL
;
2431 d
= Calloc(1, sizeof(struct userdata_s
));
2433 d
->c_row
= 2, d
->c_col
= 5;
2434 SET_FLAG(d
->flags
, CF_NEW
);
2437 if (pgn_board_init_fen(g
, d
->b
, NULL
) != E_PGN_OK
)
2438 pgn_board_init(d
->b
);
2441 void init_userdata()
2445 for (i
= 0; i
< gtotal
; i
++)
2446 init_userdata_once(&game
[i
], i
);
2449 void fix_marks(int *start
, int *end
)
2453 *start
= (*start
< 0) ? 0 : *start
;
2454 *end
= (*end
< 0) ? 0 : *end
;
2456 if (*start
> *end
) {
2462 *end
= (*end
> gtotal
) ? gtotal
: *end
;
2465 void do_new_game_finalize(GAME
*g
)
2467 struct userdata_s
*d
= g
->data
;
2469 d
->mode
= MODE_PLAY
;
2470 update_status_notify(*g
, NULL
);
2474 void do_new_game_from_scratch(WIN
*win
)
2476 if (tolower(win
->c
) != 'y')
2482 add_custom_tags(&game
[gindex
].tag
);
2485 do_new_game_finalize(&game
[gindex
]);
2491 add_custom_tags(&game
[gindex
].tag
);
2492 init_userdata_once(&game
[gindex
], gindex
);
2493 do_new_game_finalize(&game
[gindex
]);
2496 void do_game_delete_finalize(int n
)
2498 struct userdata_s
*d
;
2500 delete_game((!n
) ? gindex
: -1);
2501 d
= game
[gindex
].data
;
2502 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2503 update_all(game
[gindex
]);
2506 void do_game_delete_confirm(WIN
*win
)
2510 if (tolower(win
->c
) != 'y') {
2516 n
= (int *)win
->data
;
2517 do_game_delete_finalize(*n
);
2521 void do_game_delete()
2525 struct userdata_s
*d
;
2529 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2535 for (i
= n
= 0; i
< gtotal
; i
++) {
2538 if (TEST_FLAG(d
->flags
, CF_DELETE
))
2543 tmp
= GAME_DELETE_GAME_TEXT
;
2546 cmessage(NULL
, ANYKEY
, "%s", E_DELETE_GAME
);
2550 tmp
= GAME_DELETE_ALL_TEXT
;
2553 if (config
.deleteprompt
) {
2554 p
= Malloc(sizeof(int));
2556 construct_message(NULL
, YESNO
, 1, NULL
, NULL
, p
,
2557 do_game_delete_confirm
, 0, tmp
);
2561 do_game_delete_finalize(n
);
2564 void do_history_mode_finalize(struct userdata_s
*d
)
2567 d
->mode
= MODE_PLAY
;
2568 update_all(game
[gindex
]);
2571 void do_history_mode_confirm(WIN
*win
)
2573 struct userdata_s
*d
= game
[gindex
].data
;
2578 pgn_history_free(game
[gindex
].hp
,
2579 game
[gindex
].hindex
);
2580 pgn_board_update(&game
[gindex
], d
->b
,
2581 pgn_history_total(game
[gindex
].hp
));
2586 if (pgn_history_rav_new(&game
[gindex
], d
->b
,
2587 game
[gindex
].hindex
) != E_PGN_OK
)
2596 if (!TEST_FLAG(d
->flags
, CF_HUMAN
))
2597 add_engine_command(&game
[gindex
], ENGINE_READY
,
2598 "setboard %s\n", pgn_game_to_fen(game
[gindex
], d
->b
));
2600 do_history_mode_finalize(d
);
2603 void do_find_game_exp_finalize(int c
)
2605 struct userdata_s
*d
= game
[gindex
].data
;
2608 if ((n
= find_game_exp(gameexp
, (c
== '{') ? 0 : 1,
2609 (keycount
) ? keycount
: 1)) == -1)
2613 d
= game
[gindex
].data
;
2615 if (pgn_history_total(game
[gindex
].hp
))
2616 d
->mode
= MODE_HISTORY
;
2618 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2619 update_all(game
[gindex
]);
2622 void do_find_game_exp(WIN
*win
)
2624 struct input_data_s
*in
= win
->data
;
2629 strncpy(gameexp
, in
->str
, sizeof(gameexp
));
2634 do_find_game_exp_finalize(c
);
2642 void do_game_jump_finalize(int n
)
2644 struct userdata_s
*d
;
2646 if (--n
> gtotal
- 1 || n
< 0)
2650 d
= game
[gindex
].data
;
2651 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2652 update_status_notify(game
[gindex
], NULL
);
2653 update_all(game
[gindex
]);
2656 void do_game_jump(WIN
*win
)
2658 struct input_data_s
*in
= win
->data
;
2660 if (!in
->str
|| !isinteger(in
->str
)) {
2668 do_game_jump_finalize(atoi(in
->str
));
2673 void do_load_file(WIN
*win
)
2676 struct input_data_s
*in
= win
->data
;
2677 char *tmp
= in
->str
;
2678 struct userdata_s
*d
;
2685 if ((tmp
= word_expand(tmp
)) == NULL
)
2688 if ((fp
= pgn_open(tmp
)) == NULL
) {
2689 cmessage(ERROR
, ANYKEY
, "%s\n%s", tmp
, strerror(errno
));
2696 * FIXME what is the game state after a parse error?
2698 if (pgn_parse(fp
) == E_PGN_ERR
) {
2699 del_panel(loadingp
);
2704 update_all(game
[gindex
]);
2708 del_panel(loadingp
);
2713 strncpy(loadfile
, tmp
, sizeof(loadfile
));
2714 d
= game
[gindex
].data
;
2716 if (pgn_history_total(game
[gindex
].hp
))
2717 d
->mode
= MODE_HISTORY
;
2719 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2720 update_all(game
[gindex
]);
2729 void do_game_save(WIN
*win
)
2731 struct input_data_s
*in
= win
->data
;
2734 char *tmp
= in
->str
;
2735 char tfile
[FILENAME_MAX
];
2738 if (!tmp
|| (tmp
= word_expand(tmp
)) == NULL
)
2741 if (pgn_is_compressed(tmp
)) {
2742 p
= tmp
+ strlen(tmp
) - 1;
2744 if (*p
!= 'n' || *(p
-1) != 'g' || *(p
-2) != 'p' ||
2746 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2751 if ((p
= strchr(tmp
, '.')) != NULL
) {
2752 if (strcmp(p
, ".pgn") != 0) {
2753 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2758 snprintf(tfile
, sizeof(tfile
), "%s.pgn", tmp
);
2773 void do_get_game_save_input(int n
)
2775 struct input_data_s
*in
= Calloc(1, sizeof(struct input_data_s
));
2776 int *p
= Malloc(sizeof(int));
2778 in
->efunc
= do_game_save
;
2782 construct_input(GAME_SAVE_TITLE
, loadfile
, 1, 1, BROWSER_PROMPT
,
2783 file_browser
, NULL
, '\t', in
, -1);
2786 void do_game_save_multi_confirm(WIN
*win
)
2792 else if (win
->c
== 'a')
2795 update_status_notify(game
[gindex
], "%s", NOTIFY_SAVE_ABORTED
);
2799 do_get_game_save_input(i
);
2802 void do_more_help(WIN
*);
2803 void do_main_help(WIN
*win
)
2808 construct_message(GAME_HELP_PLAY_TITLE
, ANYKEY
, 0,
2809 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
2813 construct_message(GAME_HELP_HISTORY_TITLE
, ANYKEY
, 0,
2814 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
2818 construct_message(GAME_HELP_EDIT_TITLE
, ANYKEY
, 0,
2819 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
2823 construct_message(GAME_HELP_INDEX_TITLE
, ANYKEY
, 0,
2824 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
2832 void do_more_help(WIN
*win
)
2834 if (win
->c
== KEY_F(1))
2835 construct_message(GAME_HELP_INDEX_PROMPT
, ANYKEY
, 0, NULL
, NULL
, NULL
,
2836 do_main_help
, 0, "%s", mainhelp
);
2839 // Global and other keys.
2840 static int globalkeys(chtype c
)
2844 struct userdata_s
*d
= game
[gindex
].data
;
2845 struct input_data_s
*in
;
2849 toggle_engine_window();
2852 cmessage("ABOUT", ANYKEY
, "%s (%s)\nUsing %s with %i colors "
2853 "and %i color pairs\nCopyright 2002-2006 %s",
2854 PACKAGE_STRING
, pgn_version(), curses_version(), COLORS
,
2855 COLOR_PAIRS
, PACKAGE_BUGREPORT
);
2858 if (d
->mode
!= MODE_HISTORY
) {
2859 if (!pgn_history_total(game
[gindex
].hp
) ||
2860 (d
->engine
&& d
->engine
->status
== ENGINE_THINKING
))
2863 d
->mode
= MODE_HISTORY
;
2864 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2865 update_all(game
[gindex
]);
2869 // FIXME Resuming from previous history could append to a RAV.
2870 if (game
[gindex
].hindex
!= pgn_history_total(game
[gindex
].hp
)) {
2872 construct_message(NULL
, GAME_RESUME_HISTORY_TEXT
, 0,
2873 NULL
, NULL
, NULL
, do_history_mode_confirm
, 0,
2874 "%s", GAME_RESUME_HISTORY_TEXT
);
2879 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
2883 do_history_mode_finalize(d
);
2887 game_next_prev(game
[gindex
], (c
== '>') ? 1 : 0, (keycount
) ?
2889 d
= game
[gindex
].data
;
2893 markend
= markstart
+ delete_count
;
2897 markend
= markstart
- delete_count
;
2898 delete_count
= -1; // to fix gindex in the other direction
2902 fix_marks(&markstart
, &markend
);
2905 if (d
->mode
!= MODE_EDIT
)
2906 pgn_board_update(&game
[gindex
], d
->b
, pgn_history_total(game
[gindex
].hp
));
2908 update_status_notify(game
[gindex
], NULL
);
2909 update_all(game
[gindex
]);
2917 in
= Calloc(1, sizeof(struct input_data_s
));
2918 p
= Malloc(sizeof(int));
2921 in
->efunc
= do_find_game_exp
;
2923 if (!*gameexp
|| c
== '?') {
2924 construct_input(GAME_FIND_EXPRESSION_TITLE
, gameexp
, 1, 0,
2925 GAME_FIND_EXPRESSION_PROMPT
, NULL
, NULL
, 0, in
, -1);
2929 do_find_game_exp_finalize(c
);
2935 in
= Calloc(1, sizeof(struct input_data_s
));
2936 in
->efunc
= do_game_jump
;
2939 construct_input(GAME_JUMP_TITLE
, NULL
, 1, 1, NULL
, NULL
, NULL
,
2944 do_game_jump_finalize(keycount
);
2952 if (keycount
&& delete_count
== 0) {
2954 delete_count
= keycount
;
2955 update_status_notify(game
[gindex
], "%s (delete)",
2960 if (markstart
>= 0 && markend
>= 0) {
2961 for (i
= markstart
; i
< markend
; i
++) {
2962 if (toggle_delete_flag(i
)) {
2963 update_all(game
[gindex
]);
2968 gindex
= (delete_count
< 0) ? markstart
: i
- 1;
2969 update_all(game
[gindex
]);
2972 if (toggle_delete_flag(gindex
))
2976 markstart
= markend
= -1;
2978 update_status_window(game
[gindex
]);
2984 edit_tags(game
[gindex
], d
->b
, 1);
2987 edit_tags(game
[gindex
], d
->b
, 0);
2990 in
= Calloc(1, sizeof(struct input_data_s
));
2991 in
->efunc
= do_load_file
;
2992 construct_input(GAME_LOAD_TITLE
, NULL
, 1, 1, BROWSER_PROMPT
,
2993 file_browser
, NULL
, '\t', in
, -1);
2998 construct_message(NULL
, GAME_SAVE_MULTI_PROMPT
, 1, NULL
,
2999 NULL
, NULL
, do_game_save_multi_confirm
, 0, "%s",
3000 GAME_SAVE_MULTI_TEXT
);
3004 do_get_game_save_input(-1);
3009 construct_message(GAME_HELP_PLAY_TITLE
, ANYKEY
, 0,
3010 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3014 construct_message(GAME_HELP_HISTORY_TITLE
, ANYKEY
, 0,
3015 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3019 construct_message(GAME_HELP_EDIT_TITLE
, ANYKEY
, 0,
3020 NULL
, NULL
, NULL
, do_more_help
, 0, "%s",
3032 construct_message(NULL
, YESNO
, 1, NULL
, NULL
, NULL
,
3033 do_new_game_from_scratch
, 0, "%s", GAME_NEW_PROMPT
);
3037 keypad(boardw
, TRUE
);
3041 d
->sp
.icon
= d
->sp
.srow
= d
->sp
.scol
= 0;
3042 markend
= markstart
= 0;
3046 update_status_notify(game
[gindex
], NULL
);
3049 if (config
.validmoves
)
3050 pgn_reset_valid_moves(d
->b
);
3057 keycount
= keycount
* 10 + n
;
3061 update_status_notify(game
[gindex
], "Repeat %i", keycount
);
3064 if (d
->mode
== MODE_HISTORY
)
3068 d
->c_row
+= keycount
;
3079 if (d
->mode
== MODE_HISTORY
)
3083 d
->c_row
-= keycount
;
3085 update_status_notify(game
[gindex
], NULL
);
3095 if (d
->mode
== MODE_HISTORY
)
3099 d
->c_col
-= keycount
;
3110 if (d
->mode
== MODE_HISTORY
)
3114 d
->c_col
+= keycount
;
3125 if (d
->mode
!= MODE_EDIT
&& d
->mode
!=
3129 // Don't edit a running game (for now).
3130 if (pgn_history_total(game
[gindex
].hp
))
3133 if (d
->mode
!= MODE_EDIT
) {
3134 pgn_board_init_fen(&game
[gindex
], d
->b
, NULL
);
3136 d
->mode
= MODE_EDIT
;
3137 update_all(game
[gindex
]);
3142 pgn_tag_add(&game
[gindex
].tag
, "FEN",
3143 pgn_game_to_fen(game
[gindex
], d
->b
));
3144 pgn_tag_add(&game
[gindex
].tag
, "SetUp", "1");
3145 pgn_tag_sort(game
[gindex
].tag
);
3146 d
->mode
= MODE_PLAY
;
3147 update_all(game
[gindex
]);
3157 message("DEBUG BOARD", ANYKEY
, "%s", debug_board(d
->b
));
3170 int error_recover
= 0;
3171 struct userdata_s
*d
= game
[gindex
].data
;
3173 gindex
= gtotal
- 1;
3175 if (pgn_history_total(game
[gindex
].hp
))
3176 d
->mode
= MODE_HISTORY
;
3178 d
->mode
= MODE_PLAY
;
3180 if (d
->mode
== MODE_HISTORY
) {
3181 pgn_board_update(&game
[gindex
], d
->b
,
3182 pgn_history_total(game
[gindex
].hp
));
3185 update_status_notify(game
[gindex
], "%s", GAME_HELP_PROMPT
);
3188 update_all(game
[gindex
]);
3189 update_tag_window(game
[gindex
].tag
);
3190 wtimeout(boardw
, 70);
3195 char fdbuf
[8192] = {0};
3197 struct timeval tv
= {0, 0};
3204 for (i
= 0; i
< gtotal
; i
++) {
3208 if (d
->engine
->fd
[ENGINE_IN_FD
] > 2) {
3209 if (d
->engine
->fd
[ENGINE_IN_FD
] > n
)
3210 n
= d
->engine
->fd
[ENGINE_IN_FD
];
3212 FD_SET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
);
3215 if (d
->engine
->fd
[ENGINE_OUT_FD
] > 2) {
3216 if (d
->engine
->fd
[ENGINE_OUT_FD
] > n
)
3217 n
= d
->engine
->fd
[ENGINE_OUT_FD
];
3219 FD_SET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
);
3225 if ((n
= select(n
+ 1, &rfds
, &wfds
, NULL
, &tv
)) > 0) {
3226 for (i
= 0; i
< gtotal
; i
++) {
3230 if (FD_ISSET(d
->engine
->fd
[ENGINE_IN_FD
], &rfds
)) {
3231 len
= read(d
->engine
->fd
[ENGINE_IN_FD
], fdbuf
,
3235 if (errno
!= EAGAIN
) {
3236 cmessage(ERROR
, ANYKEY
, "Engine read(): %s",
3238 waitpid(d
->engine
->pid
, &n
, 0);
3246 parse_engine_output(&game
[i
], fdbuf
);
3250 if (FD_ISSET(d
->engine
->fd
[ENGINE_OUT_FD
], &wfds
)) {
3251 if (d
->engine
->queue
)
3252 send_engine_command(&game
[i
]);
3259 cmessage(ERROR
, ANYKEY
, "select(): %s", strerror(errno
));
3264 if (TEST_FLAG(game
[gindex
].flags
, GF_GAMEOVER
))
3265 d
->mode
= MODE_HISTORY
;
3267 d
= game
[gindex
].data
;
3269 draw_board(&game
[gindex
]);
3270 update_all(game
[gindex
]);
3271 wmove(boardw
, ROWTOMATRIX(d
->c_row
), COLTOMATRIX(d
->c_col
));
3275 * Finds the top level window in the window stack so we know what
3276 * window the wgetch()ed key belongs to.
3279 for (i
= 0; wins
[i
]; i
++);
3282 wtimeout(wp
, WINDOW_TIMEOUT
);
3291 if ((c
= wgetch(wp
)) == ERR
)
3301 * Run the function associated with the window. When the
3302 * function returns 0 win->efunc is ran (if not NULL) with
3303 * win as the one and only parameter. Then the window is
3306 * The exit function may create another window which will
3307 * mess up the window stack when window_destroy() is called.
3308 * So don't destory the window until the top window is
3309 * destroyable. See window_destroy().
3311 if ((*win
->func
)(win
) == 0) {
3316 window_destroy(win
);
3323 if (!keycount
&& status
.notify
)
3324 update_status_notify(game
[gindex
], NULL
);
3326 if ((n
= globalkeys(c
)) == 1) {
3338 if (playmode_keys(c
))
3342 historymode_keys(c
);
3352 void usage(const char *pn
, int ret
)
3354 fprintf((ret
) ? stderr
: stdout
, "%s",
3355 "Usage: cboard [-hvE] [-VtRS] [-p <file>]\n"
3356 " -p Load PGN file.\n"
3357 " -V Validate a game file.\n"
3358 " -S Validate and output a PGN formatted game.\n"
3359 " -R Like -S but write a reduced PGN formatted game.\n"
3360 " -t Also write custom PGN tags from config file.\n"
3361 " -E Stop processing on file parsing error (overrides config).\n"
3362 " -v Version information.\n"
3363 " -h This help text.\n");
3375 free(config
.engine_cmd
);
3376 free(config
.pattern
);
3377 free(config
.ccfile
);
3378 free(config
.nagfile
);
3379 free(config
.configfile
);
3382 for (i
= 0; config
.keys
[i
]; i
++)
3383 free(config
.keys
[i
]->str
);
3389 for (i
= 0; config
.einit
[i
]; i
++)
3390 free(config
.einit
[i
]);
3396 pgn_tag_free(config
.tag
);
3398 if (curses_initialized
) {
3400 del_panel(historyp
);
3413 for (i
= 0; enginebuf
[i
]; i
++)
3424 void catch_signal(int which
)
3431 if (which
== SIGPIPE
&& quit
)
3434 if (which
== SIGPIPE
)
3435 cmessage(NULL
, ANYKEY
, "%s", E_BROKEN_PIPE
);
3445 keypad(boardw
, TRUE
);
3459 void loading_progress(long total
, long offset
)
3461 int n
= (100 * (offset
/ 100) / (total
/ 100));
3463 if (curses_initialized
)
3464 update_loading_window(n
);
3466 fprintf(stderr
, "Loading... %i%% (%i games)\r", n
, gtotal
);
3471 static void set_defaults()
3473 set_config_defaults();
3475 pgn_config_set(PGN_PROGRESS
, 1024);
3476 pgn_config_set(PGN_PROGRESS_FUNC
, loading_progress
);
3479 int main(int argc
, char *argv
[])
3483 char buf
[FILENAME_MAX
];
3484 char datadir
[FILENAME_MAX
];
3485 int ret
= EXIT_SUCCESS
;
3486 int validate_only
= 0, validate_and_write
= 0;
3487 int write_custom_tags
= 0;
3491 if ((config
.pwd
= getpwuid(getuid())) == NULL
)
3492 err(EXIT_FAILURE
, "getpwuid()");
3494 snprintf(datadir
, sizeof(datadir
), "%s/.cboard", config
.pwd
->pw_dir
);
3495 snprintf(buf
, sizeof(buf
), "%s/cc.data", datadir
);
3496 config
.ccfile
= strdup(buf
);
3497 snprintf(buf
, sizeof(buf
), "%s/nag.data", datadir
);
3498 config
.nagfile
= strdup(buf
);
3499 snprintf(buf
, sizeof(buf
), "%s/config", datadir
);
3500 config
.configfile
= strdup(buf
);
3502 if (stat(datadir
, &st
) == -1) {
3503 if (errno
== ENOENT
) {
3504 if (mkdir(datadir
, 0755) == -1)
3505 err(EXIT_FAILURE
, "%s", datadir
);
3508 err(EXIT_FAILURE
, "%s", datadir
);
3513 if (!S_ISDIR(st
.st_mode
))
3514 errx(EXIT_FAILURE
, "%s: %s", datadir
, E_NOTADIR
);
3518 while ((opt
= getopt(argc
, argv
, "EVtSRhp:v")) != -1) {
3521 write_custom_tags
= 1;
3527 pgn_config_set(PGN_REDUCED
, 1);
3529 validate_and_write
= 1;
3534 printf("%s (%s)\n%s\n", PACKAGE_STRING
, curses_version(),
3538 filetype
= PGN_FILE
;
3539 strncpy(loadfile
, optarg
, sizeof(loadfile
));
3543 usage(argv
[0], EXIT_SUCCESS
);
3547 if ((validate_only
|| validate_and_write
) && !*loadfile
)
3548 usage(argv
[0], EXIT_FAILURE
);
3550 if (access(config
.configfile
, R_OK
) == 0)
3551 parse_rcfile(config
.configfile
);
3554 pgn_config_set(PGN_STOP_ON_ERROR
, 1);
3556 signal(SIGPIPE
, catch_signal
);
3557 signal(SIGCONT
, catch_signal
);
3558 signal(SIGSTOP
, catch_signal
);
3559 signal(SIGINT
, catch_signal
);
3560 signal(SIGALRM
, catch_signal
);
3561 signal(SIGTERM
, catch_signal
);
3567 if ((fp
= pgn_open(loadfile
)) == NULL
)
3568 err(EXIT_FAILURE
, "%s", loadfile
);
3570 ret
= pgn_parse(fp
);
3573 //ret = parse_fen_file(loadfile);
3575 case EPD_FILE
: // Not implemented.
3578 // No file specified. Empty game.
3579 ret
= pgn_parse(NULL
);
3580 add_custom_tags(&game
[gindex
].tag
);
3584 if (validate_only
|| validate_and_write
) {
3585 if (validate_and_write
) {
3586 for (i
= 0; i
< gtotal
; i
++) {
3587 if (write_custom_tags
)
3588 add_custom_tags(&game
[i
].tag
);
3590 pgn_write(stdout
, game
[i
]);
3597 else if (ret
== E_PGN_ERR
)
3602 if (initscr() == NULL
)
3603 errx(EXIT_FAILURE
, "%s", E_INITCURSES
);
3605 curses_initialized
= 1;
3607 if (LINES
< 24 || COLS
< 80) {
3609 errx(EXIT_FAILURE
, "Need at least an 80x24 terminal.");
3612 if (has_colors() == TRUE
&& start_color() == OK
)
3615 boardw
= newwin(BOARD_HEIGHT
, BOARD_WIDTH
, 0, COLS
- BOARD_WIDTH
);
3616 boardp
= new_panel(boardw
);
3617 historyw
= newwin(HISTORY_HEIGHT
, HISTORY_WIDTH
, LINES
- HISTORY_HEIGHT
,
3618 COLS
- HISTORY_WIDTH
);
3619 historyp
= new_panel(historyw
);
3620 statusw
= newwin(STATUS_HEIGHT
, STATUS_WIDTH
, LINES
- STATUS_HEIGHT
, 0);
3621 statusp
= new_panel(statusw
);
3622 tagw
= newwin(TAG_HEIGHT
, TAG_WIDTH
, 0, 0);
3623 tagp
= new_panel(tagw
);
3624 keypad(boardw
, TRUE
);
3625 // leaveok(boardw, TRUE);
3626 leaveok(tagw
, TRUE
);
3627 leaveok(statusw
, TRUE
);
3628 leaveok(historyw
, TRUE
);
3632 draw_window_decor();