Can now add the en-passant square when editing the board by pressing
[cboard.git] / src / cboard.c
blobb67e1eb43acb1c17579cffa2d5aaf0c1b203bd6d
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
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
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_REGEX_H
39 #include <regex.h>
40 #endif
42 #include "common.h"
43 #include "colors.h"
44 #include "cboard.h"
46 char *random_agony()
48 static int n;
49 FILE *fp;
50 char line[LINE_MAX];
52 if (n == -1 || !config.agony || !curses_initialized ||
53 (status.mode == MODE_HISTORY && !config.historyagony))
54 return NULL;
56 if (!agony) {
57 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
58 n = -1;
59 cmessage(ERROR, ANYKEY, "%s: %s", config.agonyfile, strerror(errno));
60 return NULL;
63 while (!feof(fp)) {
64 if (fscanf(fp, " %[^\n] ", line) == 1) {
65 agony = Realloc(agony, (n + 2) * sizeof(char *));
66 agony[n++] = strdup(trim(line));
70 agony[n] = NULL;
71 fclose(fp);
73 if (agony[0] == NULL || !n) {
74 n = -1;
75 return NULL;
79 return agony[random() % n];
82 void draw_board(BOARD b, int crow, int ccol)
84 int row, col;
85 int bcol = 0, brow = 0;
86 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
87 int ncols = 0, offset = 1;
88 unsigned coords_y = 8;
90 for (row = 0; row < maxy; row++) {
91 bcol = 0;
93 for (col = 0; col < maxx; col++) {
94 int attrwhich = -1;
95 int attrs = 0;
96 chtype piece, movecount = 0;
97 int bold = 0;
99 if (row == 0 || row == maxy - 2) {
100 if (col == 0)
101 mvwaddch(boardw, row, col,
102 LINE_GRAPHIC((row) ?
103 ACS_LLCORNER | CP_BOARD_GRAPHICS :
104 ACS_ULCORNER | CP_BOARD_GRAPHICS));
105 else if (col == maxx - 2)
106 mvwaddch(boardw, row, col,
107 LINE_GRAPHIC((row) ?
108 ACS_LRCORNER | CP_BOARD_GRAPHICS :
109 ACS_URCORNER | CP_BOARD_GRAPHICS));
110 else if (!(col % 4))
111 mvwaddch(boardw, row, col,
112 LINE_GRAPHIC((row) ?
113 ACS_BTEE | CP_BOARD_GRAPHICS :
114 ACS_TTEE | CP_BOARD_GRAPHICS));
115 else {
116 if (col != maxx - 1)
117 mvwaddch(boardw, row, col,
118 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
121 continue;
124 if ((row % 2) && col == maxx - 1 && coords_y) {
125 wattron(boardw, CP_BOARD_COORDS);
126 mvwprintw(boardw, row, col, "%d", coords_y--);
127 wattroff(boardw, CP_BOARD_COORDS);
128 continue;
131 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
132 if (!(row % 2))
133 mvwaddch(boardw, row, col,
134 LINE_GRAPHIC((col) ?
135 ACS_RTEE | CP_BOARD_GRAPHICS :
136 ACS_LTEE | CP_BOARD_GRAPHICS));
137 else
138 mvwaddch(boardw, row, col,
139 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
141 continue;
144 if ((row % 2) && !(col % 4) && row != maxy - 1) {
145 mvwaddch(boardw, row, col,
146 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
147 continue;
150 if (!(col % 4) && row != maxy - 1) {
151 mvwaddch(boardw, row, col,
152 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
153 continue;
156 if ((row % 2)) {
157 if ((col % 4)) {
158 if (ncols++ == 8) {
159 offset++;
160 ncols = 1;
163 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
164 && (offset % 2)))
165 attrwhich = BLACK;
166 else
167 attrwhich = WHITE;
169 if (config.validmoves && b[brow][bcol].valid) {
170 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
171 CP_BOARD_MOVES_BLACK;
173 if (b[brow][bcol].movecount) {
174 if (brow + 1 != crow && bcol + 1 != ccol)
175 movecount = (b[brow][bcol].movecount + '0');
178 else
179 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
180 CP_BOARD_BLACK;
182 if (row == ROWTOMATRIX(crow) && col == COLTOMATRIX(ccol)) {
183 attrs = CP_BOARD_CURSOR;
186 if (row == ROWTOMATRIX(sp.row) &&
187 col == COLTOMATRIX(sp.col)) {
188 attrs = CP_BOARD_SELECTED;
191 if (row == maxy - 1)
192 attrs = 0;
194 mvwaddch(boardw, row, col, ' ' | attrs);
196 if (row == maxy - 1)
197 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
198 else {
199 piece = b[row / 2][bcol].icon;
201 if (attrs & A_BOLD)
202 bold = 1;
204 if (status.side == WHITE && isupper(piece))
205 attrs |= A_BOLD;
206 else if (status.side == BLACK && islower(piece))
207 attrs |= A_BOLD;
209 waddch(boardw,
210 (piece && piece != int_to_piece(OPEN_SQUARE)) ?
211 piece | attrs : ' ' | attrs);
213 if (!bold)
214 attrs &= ~(A_BOLD);
217 if (movecount && row != maxy -1)
218 waddch(boardw, movecount | CP_BOARD_COUNT);
219 else
220 waddch(boardw, ' ' | attrs);
222 col += 2;
223 bcol++;
226 else {
227 if (col != maxx - 1)
228 mvwaddch(boardw, row, col,
229 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
233 brow = row / 2;
236 return;
239 /* Convert the selected piece to SAN format and validate it. */
240 static char *board_to_san(BOARD b)
242 static char str[MAX_PGN_MOVE_LEN + 1], *p;
243 int piece;
244 int promo;
245 BOARD t;
247 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1], sp.row,
248 x_grid_chars[sp.destcol - 1], sp.destrow);
250 p = str;
251 piece = piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
253 if (piece == PAWN && ((sp.destrow == 8 && status.turn == WHITE) ||
254 (sp.destrow == 1 && status.turn == BLACK))) {
255 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
257 if (piece_to_int(promo) == -1)
258 return NULL;
260 p = str + strlen(str);
261 *p++ = toupper(promo);
262 *p = '\0';
265 memcpy(t, b, sizeof(BOARD));
267 if ((p = a2a4tosan(t, str)) == NULL) {
268 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
269 return NULL;
272 if (parse_move_text(t, p)) {
273 invalid_move(p);
274 return NULL;
277 memcpy(b, t, sizeof(BOARD));
278 return p;
281 static int move_to_engine(BOARD b)
283 char *p;
285 if ((p = board_to_san(b)) == NULL)
286 return 0;
288 sp.row = sp.col = sp.icon = 0;
290 if (noengine) {
291 add_to_history(&game[gindex].history, &game[gindex].hindex,
292 &game[gindex].htotal, p);
293 switch_turn();
294 SET_FLAG(game[gindex].flags, GF_MODIFIED);
295 update_all();
296 return 1;
299 SEND_TO_ENGINE("%s\n", p);
300 return 1;
303 char *book_method(int method)
305 char *book;
307 switch (method) {
308 case BOOK_BEST:
309 book = BOOK_BEST_STR;
310 break;
311 case BOOK_WORST:
312 book = BOOK_WORST_STR;
313 break;
314 case BOOK_PREFER:
315 book = BOOK_PREFER_STR;
316 break;
317 case BOOK_RANDOM:
318 book = BOOK_RANDOM_STR;
319 break;
320 case BOOK_OFF:
321 book = BOOK_OFF_STR;
322 break;
323 default:
324 book = UNKNOWN;
325 break;
328 return book;
331 static void update_clock(int n, int *h, int *m, int *s)
333 *h = n / 3600;
334 *m = (n % 3600) / 60;
335 *s = (n % 3600) % 60;
337 return;
340 void update_status_window()
342 int i = 0;
343 char buf[STATUS_WIDTH - 7];
344 char tmp[15], *engine, *mode;
345 int w = STATUS_WIDTH - 10;
346 int h, m, s;
347 char *p;
349 *tmp = '\0';
350 p = tmp;
352 if (TEST_FLAG(game[gindex].flags, GF_DELETE)) {
353 *p++ = '(';
354 *p++ = 'x';
355 i++;
358 if (TEST_FLAG(game[gindex].flags, GF_PERROR)) {
359 if (!i)
360 *p++ = '(';
361 else
362 *p++ = '/';
364 *p++ = '!';
365 i++;
368 if (TEST_FLAG(game[gindex].flags, GF_MODIFIED)) {
369 if (!i)
370 *p++ = '(';
371 else
372 *p++ = '/';
374 *p++ = '*';
375 i++;
378 if (*tmp != '\0')
379 *p++ = ')';
381 *p = '\0';
383 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
384 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
385 snprintf(buf, sizeof(buf), "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
386 (*tmp) ? tmp : "");
387 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
389 switch (status.mode) {
390 case MODE_HISTORY:
391 mode = MODE_HISTORY_STR;
392 break;
393 case MODE_EDIT:
394 mode = MODE_EDIT_STR;
395 break;
396 case MODE_PLAY:
397 mode = MODE_PLAY_STR;
398 break;
399 default:
400 mode = UNKNOWN;
401 break;
404 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
406 switch (status.engine) {
407 case ENGINE_THINKING:
408 engine = ENGINE_THINKING_STR;
409 break;
410 case ENGINE_READY:
411 engine = ENGINE_READY_STR;
412 break;
413 case ENGINE_INITIALIZING:
414 engine = ENGINE_INITIALIZING_STR;
415 break;
416 case ENGINE_OFFLINE:
417 engine = ENGINE_OFFLINE_STR;
418 break;
419 default:
420 engine = UNKNOWN;
421 break;
424 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
425 wattron(statusw, CP_STATUS_ENGINE);
426 mvwaddstr(statusw, 5, 9, engine);
427 wattroff(statusw, CP_STATUS_ENGINE);
429 mvwprintw(statusw, 6, 1, "%*s %-*i", 7, STATUS_DEPTH_STR, w,
430 config.engine_depth);
432 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_BOOK_STR, w,
433 book_method(config.book_method));
435 mvwprintw(statusw, 8, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
436 (status.turn == WHITE) ? WHITE_STR : BLACK_STR);
438 strncpy(tmp, WHITE_STR, sizeof(tmp));
439 tmp[0] = toupper(tmp[0]);
440 update_clock(game[gindex].moveclock, &h, &m, &s);
441 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].wcaptures,
442 h, m, s);
443 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, buf);
445 strncpy(tmp, BLACK_STR, sizeof(tmp));
446 tmp[0] = toupper(tmp[0]);
447 update_clock(game[gindex].moveclock, &h, &m, &s);
448 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].bcaptures,
449 h, m, s);
450 mvwprintw(statusw, 10, 1, "%*s: %-*s", 6, tmp, w, buf);
452 for (i = 1; i < STATUS_WIDTH - 4; i++)
453 mvwprintw(statusw, STATUS_HEIGHT - 2, i, " ");
455 if (!status.notify)
456 status.notify = strdup(GAME_HELP_PROMPT);
458 wattron(statusw, CP_STATUS_NOTIFY);
459 mvwprintw(statusw, STATUS_HEIGHT - 2,
460 CENTERX(STATUS_WIDTH, status.notify), "%s", status.notify);
461 wattroff(statusw, CP_STATUS_NOTIFY);
464 void update_history_window()
466 char buf[HISTORY_WIDTH];
467 HISTORY h = {{0},NULL,{0}};
468 int n, total;
470 n = (game[gindex].hindex + 1) / 2;
472 if ((game[gindex].htotal % 2))
473 total = (game[gindex].htotal + 1) / 2;
474 else
475 total = game[gindex].htotal / 2;
477 if (game[gindex].htotal)
478 snprintf(buf, sizeof(buf), "%u %s %u%s",n, N_OF_N_STR, total,
479 (movestep == 1) ? HISTORY_MOVE_STEP : "");
480 else
481 strncpy(buf, UNAVAILABLE, sizeof(buf));
483 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
484 HISTORY_WIDTH - 13, buf);
486 if (get_history_by_index(game[gindex].hindex, &h))
487 memset(&h, 0, sizeof(HISTORY));
489 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
490 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_NEXT : "");
491 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
492 HISTORY_WIDTH - 13, buf);
494 if (get_history_by_index(game[gindex].hindex - 1, &h))
495 memset(&h, 0, sizeof(HISTORY));
497 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
498 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_PREV : "");
499 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
500 HISTORY_WIDTH - 13, buf);
501 return;
504 void update_tag_window()
506 int i;
507 int w = TAG_WIDTH - 10;
509 for (i = 0; i < 7; i++) {
510 char *value = game[gindex].tag[i].value;
511 int n;
513 if ((*value == '?' || *value == '-') && value[1] == '\0')
514 value = UNAVAILABLE;
515 else if (strcmp(game[gindex].tag[i].name, "Result") == 0) {
516 for (n = 0; n < NARRAY(fancy_results); n++) {
517 if (strcmp(value, fancy_results[n].pgn) == 0) {
518 value = fancy_results[n].fancy;
519 break;
524 value = str_etc(value, w, 0);
526 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, game[gindex].tag[i].name,
527 w, value);
530 return;
533 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
535 int i;
537 wattron(win, attr);
539 for (i = 1; i < width - 1; i++)
540 mvwaddch(win, y, i, ' ');
542 mvwprintw(win, y, CENTERX(width, str), "%s", str);
544 wattroff(win, attr);
545 return;
548 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
549 chtype battr)
551 int i;
553 if (title) {
554 wattron(win, attr);
556 for (i = 1; i < width - 1; i++)
557 mvwaddch(win, 1, i, ' ');
559 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
560 wattroff(win, attr);
563 wattron(win, battr);
564 box(win, ACS_VLINE, ACS_HLINE);
565 wattroff(win, battr);
567 return;
570 void update_all()
572 update_status_window();
573 update_history_window();
574 return;
577 static void game_next_prev(int n, int count)
579 if (gtotal < 2)
580 return;
582 if (n == 1) {
583 if (gindex + count > gtotal - 1) {
584 if (count != 1)
585 gindex = gtotal - 1;
586 else
587 gindex = 0;
589 else
590 gindex += count;
592 else {
593 if (gindex - count < 0) {
594 if (count != 1)
595 gindex = 0;
596 else
597 gindex = gtotal - 1;
599 else
600 gindex -= count;
603 init_history(board);
604 update_all();
605 update_tag_window();
606 return;
609 void free_game_data()
611 int i;
613 if (!gtotal)
614 return;
616 for (i = 0; i < gtotal; i++) {
617 free_historydata(&game[i].history, 0, game[i].htotal);
618 free(game[i].history);
619 free_tag_data(game[i].tag, game[i].tindex);
620 free(game[i].tag);
623 return;
626 static void delete_game(int which)
628 GAME *g = NULL;
629 int gi = 0;
630 int i;
632 for (i = 0; i < gtotal; i++) {
633 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
634 free_historydata(&game[i].history, 0, game[i].htotal);
635 free(game[i].history);
636 free_tag_data(game[i].tag, game[i].tindex);
637 free(game[i].tag);
638 continue;
641 g = Realloc(g, (gi + 2) * sizeof(GAME));
643 memcpy(&g[gi], &game[i], sizeof(GAME));
645 g[gi].tag = game[i].tag;
646 g[gi].history = game[i].history;
647 gi++;
650 game = g;
651 gtotal = gi;
653 if (which != -1) {
654 if (which + 1 >= gtotal)
655 gindex = gtotal - 1;
656 else
657 gindex = which;
659 else
660 gindex = gtotal - 1;
662 return;
665 /* FIXME dont show out of reach counts. Diagonals. */
667 static void number_valid_moves(BOARD b, int srow, int scol)
669 int row, col;
670 int count;
672 for (row = srow + 1, col = scol, count = 1; VALIDFILE(row); row++) {
673 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
674 continue;
676 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
679 for (row = srow - 1, col = scol, count = 1; VALIDFILE(row); row--) {
680 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
681 continue;
683 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
686 for (col = scol + 1, row = srow, count = 1; VALIDFILE(col); col++) {
687 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
688 continue;
690 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
693 for (col = scol - 1, row = srow, count = 1; VALIDFILE(col); col--) {
694 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
695 continue;
697 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
700 return;
705 static void get_valid_cursor(BOARD b, int which, int count, int *crow,
706 int *ccol, int minr, int maxr, int minc, int maxc)
708 int row, col;
709 int incr, cincr;
711 if (which == UP || which == RIGHT)
712 incr = 1;
713 else
714 incr = -(1);
716 switch (which) {
717 case UP:
718 case DOWN:
719 if (count > 1) {
720 row = *crow;
722 if (which == UP)
723 *crow += count;
724 else
725 *crow -= count;
727 if (!VALIDFILE(*crow))
728 *crow = row;
731 for (row = *crow + incr, col = *ccol; VALIDFILE(row); row += incr) {
732 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
733 continue;
735 *crow = row;
736 goto done;
739 break;
740 case RIGHT:
741 case LEFT:
742 if (count > 1) {
743 col = *ccol;
745 if (which == RIGHT)
746 *ccol += count;
747 else
748 *ccol -= count;
750 if (!VALIDFILE(*ccol))
751 *ccol = col;
754 for (col = *ccol + incr, row = *crow; VALIDFILE(col); col += incr) {
755 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
756 continue;
758 *ccol = col;
759 goto done;
762 break;
763 default:
764 break;
767 if (*ccol < sp.col || (which == DOWN || which == LEFT))
768 cincr = -(1);
769 else
770 cincr = 1;
772 if (*ccol < sp.col && (which == DOWN || which == LEFT))
773 cincr = 1;
775 for (row = *crow + incr; VALIDFILE(row); row += incr) {
776 for (col = *ccol + cincr; VALIDFILE(col); col += cincr) {
777 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
778 continue;
780 *crow = row;
781 *ccol = col;
782 goto done;
786 done:
787 number_valid_moves(board, *crow, *ccol);
788 return;
792 static int find_move_exp(const char *str, int init, int which, int count)
794 int i;
795 int ret;
796 static regex_t r;
797 static int firstrun = 1;
798 char errbuf[255];
799 int incr;
800 int found;
802 if (init) {
803 if (!firstrun)
804 regfree(&r);
806 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
807 regerror(ret, &r, errbuf, sizeof(errbuf));
808 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
809 return -1;
812 firstrun = 1;
815 incr = (which == 0) ? -(1) : 1;
817 for (i = game[gindex].hindex + incr - 1, found = 0; ; i += incr) {
818 if (i == game[gindex].hindex - 1)
819 break;
821 if (i > game[gindex].htotal)
822 i = 0;
823 else if (i < 0)
824 i = game[gindex].htotal;
826 ret = regexec(&r, game[gindex].history[i].move, 0, 0, 0);
828 if (ret == 0) {
829 if (count == ++found) {
830 return i + 1;
833 else {
834 if (ret != REG_NOMATCH) {
835 regerror(ret, &r, errbuf, sizeof(errbuf));
836 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
837 return -1;
842 return -1;
845 static int toggle_delete_flag(int n)
847 int i, x;
849 if (TEST_FLAG(game[n].flags, GF_DELETE))
850 CLEAR_FLAG(game[n].flags, GF_DELETE);
851 else
852 SET_FLAG(game[n].flags, GF_DELETE);
855 for (i = x = 0; i < gtotal; i++) {
856 if (TEST_FLAG(game[i].flags, GF_DELETE))
857 x++;
860 if (x == gtotal) {
861 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
862 CLEAR_FLAG(game[n].flags, GF_DELETE);
863 return 1;
866 return 0;
869 static void edit_save_tags(int n)
871 int i;
872 TAG *t;
874 if ((t = edit_tags(board, game[n].tag, game[n].tindex, 1)) == NULL)
875 return;
877 game[n].tindex = 0;
879 for (i = 0; t[i].name; i++) {
880 add_tag(&game[n].tag, &game[n].tindex, t[i].name, t[i].value);
883 free_tag_data(t, i);
884 free(t);
885 SET_FLAG(game[n].flags, GF_MODIFIED);
886 return;
889 static int find_game_exp(char *str, int which, int count)
891 char *nstr = NULL, *exp = NULL;
892 regex_t nexp, vexp;
893 int ret = -1;
894 int g = 0;
895 char buf[255], *tmp;
896 char errbuf[255];
897 int found = 0;
898 int incr = (which == 0) ? -(1) : 1;
900 strncpy(buf, str, sizeof(buf));
901 tmp = buf;
903 if (strstr(tmp, ":") != NULL) {
904 nstr = strsep(&tmp, ":");
906 if ((ret = regcomp(&nexp, nstr,
907 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
908 regerror(ret, &nexp, errbuf, sizeof(errbuf));
909 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
910 ret = g = -1;
911 goto cleanup;
915 exp = tmp;
917 if (exp == NULL)
918 goto cleanup;
920 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
921 regerror(ret, &vexp, errbuf, sizeof(errbuf));
922 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
923 ret = -1;
924 goto cleanup;
927 ret = -1;
929 for (g = gindex + incr, found = 0; ; g += incr) {
930 int t;
932 if (g == gindex)
933 break;
935 if (g == gtotal)
936 g = 0;
937 else if (g < 0)
938 g = gtotal - 1;
940 for (t = 0; t < game[g].tindex; t++) {
941 if (nstr) {
942 if (regexec(&nexp, game[g].tag[t].name, 0, 0, 0) == 0) {
943 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
944 if (count == ++found) {
945 ret = g;
946 goto cleanup;
951 else {
952 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
953 if (count == ++found) {
954 ret = g;
955 goto cleanup;
961 ret = -1;
964 cleanup:
965 if (nstr)
966 regfree(&nexp);
968 if (g != -1)
969 regfree(&vexp);
971 return ret;
974 void edit_board(BOARD b)
976 chtype p;
978 p = b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
979 b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
980 b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
981 int_to_piece(OPEN_SQUARE);
983 return;
986 // Updates the notification line in the status window then refreshes the
987 // status window.
988 void update_status_notify(char *fmt, ...)
990 va_list ap;
991 #ifdef HAVE_VASPRINTF
992 char *line;
993 #else
994 char line[COLS];
995 #endif
997 if (!fmt) {
998 if (status.notify) {
999 free(status.notify);
1000 status.notify = NULL;
1002 if (curses_initialized)
1003 update_status_window();
1006 return;
1009 va_start(ap, fmt);
1010 #ifdef HAVE_VASPRINTF
1011 vasprintf(&line, fmt, ap);
1012 #else
1013 vsnprintf(line, sizeof(line), fmt, ap);
1014 #endif
1015 va_end(ap);
1017 if (status.notify)
1018 free(status.notify);
1020 status.notify = strdup(line);
1022 #ifdef HAVE_VASPRINTF
1023 free(line);
1024 #endif
1025 if (curses_initialized)
1026 update_status_window();
1029 static void switch_side()
1031 if (status.side == WHITE)
1032 status.side = BLACK;
1033 else
1034 status.side = WHITE;
1037 void game_loop()
1039 int error_recover = 0;
1040 int pushkey = 0;
1041 int count = 0;
1042 int crow = 2, ccol = 5;
1043 char moveexp[255] = {0};
1044 char gameexp[255] = {0};
1045 int delete_count = 0;
1046 int markstart = -1, markend = -1;
1047 int editmode = 0;
1049 gindex = gtotal - 1;
1050 markstart = -1, markend = -1;
1052 if (loadfile[0])
1053 init_history(board);
1055 update_status_notify("%s", GAME_HELP_PROMPT);
1056 movestep = 2;
1057 paused = 1; //FIXME clock
1058 flushinp();
1059 update_all();
1060 update_tag_window();
1062 while (!quit) {
1063 int c = 0;
1064 fd_set fds;
1065 int i, x, n = 0, len = 0;
1066 char fdbuf[8192] = {0};
1067 struct timeval tv;
1068 char *tmp = NULL;
1069 char buf[78];
1070 char tfile[FILENAME_MAX];
1071 int minr, maxr, minc, maxc;
1073 if (engine_initialized) {
1074 tv.tv_sec = 0;
1075 tv.tv_usec = 0;
1077 FD_ZERO(&fds);
1078 FD_SET(enginefd[0], &fds);
1080 for (i = 0; i < gtotal; i++) {
1081 if (game[i].sockfd > 0) {
1082 if (game[i].sockfd > n)
1083 n = game[i].sockfd;
1085 FD_SET(game[i].sockfd, &fds);
1089 n = (n > enginefd[0]) ? n : enginefd[0];
1091 if ((n = select(n + 1, &fds, NULL, NULL, &tv)) > 0) {
1092 if (FD_ISSET(enginefd[0], &fds)) {
1093 len = read(enginefd[0], fdbuf, sizeof(fdbuf));
1095 if (len == -1) {
1096 if (errno != EAGAIN) {
1097 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
1098 ++error_recover, strerror(errno));
1099 continue;
1102 else {
1103 if (len) {
1104 parse_engine_output(board, fdbuf);
1105 update_all();
1110 for (i = 0; i < gtotal; i++) {
1111 if (game[i].sockfd <= 0)
1112 continue;
1114 if (FD_ISSET(game[i].sockfd, &fds)) {
1115 len = recv(game[i].sockfd, fdbuf, sizeof(fdbuf), 0);
1117 if (len == -1) {
1118 if (errno != EAGAIN) {
1119 cmessage(ERROR, ANYKEY,
1120 "Attempt #%i. recv(): %s",
1121 ++error_recover, strerror(errno));
1122 continue;
1125 else {
1126 if (len)
1127 parse_ics_output(fdbuf);
1129 update_all();
1134 else {
1135 if (n == -1)
1136 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
1137 else {
1138 /* timeout */
1143 error_recover = 0;
1144 draw_board(board, crow, ccol);
1146 wmove(boardw, ROWTOMATRIX(crow), COLTOMATRIX(ccol));
1148 if (!paused) {
1151 update_panels();
1152 doupdate();
1154 if (pushkey)
1155 c = pushkey;
1156 else {
1157 if ((c = wgetch(boardw)) == ERR)
1158 continue;
1161 if (!count && status.notify)
1162 update_status_notify(NULL);
1164 switch (c) {
1165 int annotate;
1167 case 'p':
1168 if (paused)
1169 paused = 0;
1170 else
1171 paused = 1;
1173 break;
1174 case 'e':
1175 if (game[gindex].htotal)
1176 break;
1178 if (editmode) {
1179 editmode = 0;
1180 add_tag(&game[gindex].tag, &game[gindex].tindex,
1181 "FEN", board_to_fen(board, game[gindex]));
1182 add_tag(&game[gindex].tag, &game[gindex].tindex,
1183 "SetUp", "1");
1184 status.mode = MODE_PLAY;
1185 game[gindex].fentag = game[gindex].tindex - 1;
1187 else {
1188 status.mode = MODE_EDIT;
1189 editmode = 1;
1192 update_all();
1193 break;
1194 case '}':
1195 case '{':
1196 case '?':
1197 if (gtotal < 2)
1198 break;
1200 if (!*gameexp || c == '?') {
1201 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
1202 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
1203 NULL, 0, -1)) == NULL)
1204 break;
1206 strncpy(gameexp, tmp, sizeof(gameexp));
1209 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
1210 (count) ? count : 1)) == -1)
1211 break;
1213 gindex = n;
1214 init_history(board);
1215 update_all();
1216 update_tag_window();
1217 break;
1218 case '!':
1219 crow = 1;
1220 break;
1221 case '@':
1222 crow = 2;
1223 break;
1224 case '#':
1225 crow = 3;
1226 break;
1227 case '$':
1228 crow = 4;
1229 break;
1230 case '%':
1231 crow = 5;
1232 break;
1233 case '^':
1234 crow = 6;
1235 break;
1236 case '&':
1237 crow = 7;
1238 break;
1239 case '*':
1240 crow = 8;
1241 break;
1242 case 'A':
1243 ccol = 1;
1244 break;
1245 case 'B':
1246 ccol = 2;
1247 break;
1248 case 'C':
1249 ccol = 3;
1250 break;
1251 case 'D':
1252 ccol = 4;
1253 break;
1254 case 'E':
1255 ccol = 5;
1256 break;
1257 case 'F':
1258 ccol = 6;
1259 break;
1260 case 'G':
1261 ccol = 7;
1262 break;
1263 case 'H':
1264 ccol = 8;
1265 break;
1266 case '_':
1267 case '+':
1268 if (status.engine != ENGINE_READY)
1269 break;
1271 n = (count) ? count : 1;
1273 if (c == '_') {
1274 if (config.engine_depth - n < 0)
1275 n = 0;
1276 else
1277 n -= config.engine_depth;
1279 else
1280 n += config.engine_depth;
1282 SEND_TO_ENGINE("depth %i\n", abs(n));
1283 break;
1284 case ']':
1285 case '[':
1286 case '/':
1287 if (game[gindex].htotal < 2)
1288 break;
1290 n = 0;
1292 if (!*moveexp || c == '/') {
1293 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
1294 NULL, NULL, 0, -1)) == NULL)
1295 break;
1297 strncpy(moveexp, tmp, sizeof(moveexp));
1298 n = 1;
1301 if ((n = find_move_exp(moveexp, n, (c == '[') ? 0 : 1,
1302 (count) ? count : 1)) == -1)
1303 break;
1305 game[gindex].hindex = n;
1306 parse_history_move(board, game[gindex].hindex);
1307 update_all();
1308 break;
1309 case 'v':
1310 view_annotation(game[gindex].hindex);
1311 break;
1312 case 'V':
1313 view_annotation(game[gindex].hindex - 1);
1314 break;
1315 case '>':
1316 game_next_prev(1, (count) ? count : 1);
1318 if (delete_count) {
1319 markend = gindex;
1320 pushkey = 'x';
1321 delete_count = 0;
1324 status.mode = MODE_HISTORY;
1325 editmode = 0;
1326 break;
1327 case '<':
1328 game_next_prev(0, (count) ? count : 1);
1330 if (delete_count) {
1331 markend = gindex;
1332 pushkey = 'x';
1333 delete_count = 0;
1336 status.mode = MODE_HISTORY;
1337 editmode = 0;
1338 break;
1339 case 'j':
1340 if (status.mode != MODE_HISTORY || game[gindex].htotal < 2)
1341 break;
1344 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1345 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
1346 game[gindex].htotal)) == NULL)
1347 break;
1350 if (!count) {
1351 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1352 NULL, NULL, NULL, 0, -1)) == NULL)
1353 break;
1355 if (!isinteger(tmp))
1356 break;
1358 i = atoi(tmp);
1360 else
1361 i = count;
1363 if (i > (game[gindex].htotal / 2) || i < 0)
1364 break;
1366 game[gindex].hindex = i * 2;
1367 init_history(board);
1368 update_all();
1369 break;
1370 case 'J':
1371 if (gtotal < 2)
1372 break;
1375 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
1376 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
1377 == NULL)
1378 break;
1381 if (!count) {
1382 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
1383 NULL, NULL, 0, -1)) == NULL)
1384 break;
1386 if (!isinteger(tmp))
1387 break;
1389 i = atoi(tmp);
1391 else
1392 i = count;
1394 if (--i > gtotal - 1 || i < 0)
1395 break;
1397 gindex = i;
1398 init_history(board);
1399 update_all();
1400 update_tag_window();
1401 break;
1402 case 'x':
1403 pushkey = 0;
1405 if (editmode) {
1406 if (sp.icon)
1407 board[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
1408 int_to_piece(OPEN_SQUARE);
1409 else
1410 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon =
1411 int_to_piece(OPEN_SQUARE);
1413 sp.icon = sp.row = sp.col = 0;
1414 break;
1417 if (gtotal < 2)
1418 break;
1420 if (count && !delete_count) {
1421 markstart = gindex;
1422 delete_count = 1;
1423 update_status_notify("%s (delete)", status.notify);
1424 continue;
1427 if (markstart >= 0 && markend >= 0) {
1428 if (markstart > markend) {
1429 i = markstart;
1430 markstart = markend;
1431 markend = i;
1434 for (i = markstart; i <= markend; i++) {
1435 if (toggle_delete_flag(i))
1436 break;
1439 else {
1440 if (toggle_delete_flag(gindex))
1441 break;
1444 markstart = markend = -1;
1445 update_status_window();
1446 break;
1447 case 'X':
1448 if (gtotal < 2) {
1449 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1450 break;
1453 tmp = NULL;
1455 for (i = n = 0; i < gtotal; i++) {
1456 if (TEST_FLAG(game[i].flags, GF_DELETE))
1457 n++;
1460 if (!n)
1461 tmp = GAME_DELETE_GAME_TEXT;
1462 else {
1463 if (n == gtotal) {
1464 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1465 break;
1468 tmp = GAME_DELETE_ALL_TEXT;
1471 if (config.deleteprompt) {
1472 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
1473 break;
1476 delete_game((!n) ? gindex : -1);
1477 init_history(board);
1478 update_all();
1479 update_tag_window();
1480 break;
1481 case 'a':
1482 annotate = game[gindex].hindex;
1484 if (annotate && game[gindex].history[annotate - 1].move[0])
1485 annotate--;
1486 else
1487 break;
1489 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
1490 game[gindex].history[annotate].move);
1492 tmp = get_input(buf, game[gindex].history[annotate].comment,
1493 0, 0, NAG_PROMPT, history_edit_nag, (void *)annotate,
1494 CTRL('T'), -1);
1496 if (!tmp && (!game[gindex].history[annotate].comment ||
1497 !*game[gindex].history[annotate].comment))
1498 break;
1499 else if (tmp && game[gindex].history[annotate].comment) {
1500 if (strcmp(tmp, game[gindex].history[annotate].comment)
1501 == 0)
1502 break;
1505 len = (tmp) ? strlen(tmp) + 1 : 1;
1507 game[gindex].history[annotate].comment =
1508 Realloc(game[gindex].history[annotate].comment, len);
1510 strncpy(game[gindex].history[annotate].comment,
1511 (tmp) ? tmp : "", len);
1513 SET_FLAG(game[gindex].flags, GF_MODIFIED);
1515 update_all();
1516 break;
1517 case 't':
1518 edit_save_tags(gindex);
1519 update_all();
1520 update_tag_window();
1521 break;
1522 case 'I':
1523 if (!editmode)
1524 break;
1526 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
1527 GAME_EDIT_TEXT);
1529 if (piece_to_int(c) == -1 && tolower(c) != 'x')
1530 break;
1532 if (tolower(c) == 'x')
1533 c = tolower(c);
1535 if (c == 'x' && (crow != 6 && crow != 3))
1536 break;
1538 if (c == 'x') {
1539 for (i = 0; i < 8; i++) {
1540 if (board[ROWTOBOARD(3)][COLTOBOARD(i)].icon == 'x')
1541 board[ROWTOBOARD(3)][COLTOBOARD(i)].icon =
1542 OPEN_SQUARE;
1543 if (board[ROWTOBOARD(6)][COLTOBOARD(i)].icon == 'x')
1544 board[ROWTOBOARD(6)][COLTOBOARD(i)].icon =
1545 OPEN_SQUARE;
1549 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
1550 break;
1551 case 'i':
1552 edit_tags(board, game[gindex].tag, game[gindex].tindex, 0);
1553 break;
1554 case 'g':
1555 if (status.mode == MODE_HISTORY ||
1556 status.engine == ENGINE_THINKING)
1557 break;
1559 status.engine = ENGINE_THINKING;
1560 update_status_window();
1561 SEND_TO_ENGINE("go\n");
1562 break;
1563 case 'b':
1564 if (config.book_method == -1 || status.engine ==
1565 ENGINE_THINKING || config.engine != GNUCHESS)
1566 break;
1568 if (config.book_method + 1 >= BOOK_MAX)
1569 n = 0;
1570 else
1571 n = config.book_method + 1;
1573 SEND_TO_ENGINE("book %s\n", book_methods[n]);
1574 break;
1575 case 'h':
1576 if (status.mode == MODE_HISTORY) {
1577 if (game[gindex].openingside == BLACK) {
1578 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
1579 break;
1582 if (game[gindex].hindex != game[gindex].htotal) {
1583 if (!pushkey) {
1584 if ((c = message(NULL, YESNO, "%s",
1585 GAME_RESUME_HISTORY_TEXT)) != 'y')
1586 break;
1589 else {
1590 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1591 break;
1594 if (!noengine)
1595 wtimeout(boardw, 70);
1597 if (!noengine && !engine_initialized) {
1598 if (start_chess_engine() < 0)
1599 break;
1601 pushkey = 'h';
1602 break;
1605 pushkey = 0;
1606 oldhistorytotal = game[gindex].htotal;
1607 game[gindex].htotal = game[gindex].hindex;
1608 status.mode = MODE_PLAY;
1609 status.engine = ENGINE_READY;
1611 /* FIXME crafty */
1612 if (config.engine != GNUCHESS)
1613 SEND_TO_ENGINE("read %s\n", config.fifo);
1614 else
1615 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1617 update_all();
1618 break;
1621 if (!game[gindex].htotal || status.engine == ENGINE_THINKING)
1622 break;
1624 wtimeout(boardw, -1);
1625 init_history(board);
1626 break;
1627 case 'u':
1628 /* FIXME dies reading FIFO sometimes. */
1629 if (status.mode != MODE_PLAY || !game[gindex].htotal)
1630 break;
1632 history_previous(board, (count) ? count * 2 : 2, &crow, &ccol);
1633 oldhistorytotal = game[gindex].htotal;
1634 game[gindex].htotal = game[gindex].hindex;
1636 if (status.engine == CRAFTY)
1637 SEND_TO_ENGINE("read %s\n", config.fifo);
1638 else
1639 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1641 update_history_window();
1642 break;
1643 case 'r':
1644 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
1645 BROWSER_PROMPT, browse_directory, NULL,
1646 '\t', -1)) == NULL)
1647 break;
1649 tmp = tilde_expand(tmp);
1651 if (parse_pgn_file(board, tmp))
1652 break;
1654 gindex = gtotal - 1;
1655 strncpy(loadfile, tmp, sizeof(loadfile));
1656 init_history(board);
1657 update_all();
1658 update_tag_window();
1659 break;
1660 case 'S':
1661 case 's':
1662 x = -1;
1664 if (gtotal > 1) {
1665 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
1666 GAME_SAVE_MULTI_TEXT);
1668 if (n == 'c')
1669 x = gindex;
1670 else if (n == 'a')
1671 x = -1;
1672 else {
1673 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1674 break;
1678 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
1679 BROWSER_PROMPT, browse_directory, NULL,
1680 '\t', -1)) == NULL) {
1681 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1682 break;
1685 tmp = tilde_expand(tmp);
1687 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
1688 == NULL) {
1689 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
1690 tmp = tfile;
1693 if (save_pgn(tmp, 0, x)) {
1694 update_status_notify("%s", NOTIFY_SAVE_FAILED);
1695 break;
1698 update_status_notify("%s", NOTIFY_SAVED);
1699 update_all();
1700 break;
1701 case CTRL('G'):
1702 n = 0;
1704 while (n != 'q') {
1705 n = help(GAME_HELP_INDEX_TITLE,
1706 GAME_HELP_INDEX_PROMPT, mainhelp);
1708 switch (n) {
1709 case 'h':
1710 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
1711 break;
1712 case 'p':
1713 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
1714 break;
1715 case 'e':
1716 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
1717 break;
1718 case 'g':
1719 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
1720 break;
1721 default:
1722 n = 'q';
1723 break;
1727 break;
1728 case 'n':
1729 case 'N':
1730 if (c == 'N') {
1731 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
1732 break;
1735 status.mode = MODE_PLAY;
1736 editmode = 0;
1737 sp.icon = 0;
1739 if (c == 'n') {
1740 newgameinit = 1;
1741 new_game(board);
1743 else {
1744 reset_history();
1745 loadfile[0] = '\0';
1746 parse_pgn_file(board, loadfile);
1749 game[gindex].wcaptures = game[gindex].bcaptures = 0;
1750 crow = (status.side == WHITE) ? 2 : 7;
1751 ccol = 4;
1753 if (!noengine && (status.engine == ENGINE_OFFLINE ||
1754 engine_initialized == 0)) {
1755 if (start_chess_engine() < 0)
1756 break;
1759 SEND_TO_ENGINE("\nnew\n");
1760 set_engine_defaults();
1761 status.engine = ENGINE_READY;
1762 update_status_notify(NULL);
1763 update_all();
1764 update_tag_window();
1765 break;
1766 case CTRL('L'):
1767 case 'R':
1768 endwin();
1769 keypad(boardw, TRUE);
1770 update_panels();
1771 doupdate();
1772 break;
1773 case 'c':
1774 if (status.engine == ENGINE_THINKING)
1775 break;
1777 if (status.engine == ENGINE_OFFLINE)
1778 break;
1780 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL))
1781 != NULL) {
1782 SEND_TO_ENGINE("%s\n", tmp);
1784 break;
1785 case KEY_ESCAPE:
1786 sp.icon = sp.row = sp.col = 0;
1787 markend = markstart = 0;
1789 if (count) {
1790 count = 0;
1791 update_status_notify(NULL);
1794 if (config.validmoves)
1795 reset_valid_moves(board);
1797 break;
1798 case '0' ... '9':
1799 n = c - '0';
1801 if (count)
1802 count = count * 10 + n;
1803 else
1804 count = n;
1806 update_status_notify("Repeat %i", count);
1807 continue;
1808 case KEY_UP:
1809 if (status.mode == MODE_HISTORY) {
1810 history_next(board, (count > 0) ?
1811 config.jumpcount * count * movestep :
1812 config.jumpcount * movestep, &crow, &ccol);
1813 update_all();
1814 break;
1818 if (sp.icon && config.validmoves) {
1819 get_valid_cursor(board, UP, (count) ? count : 1,
1820 &crow, &ccol, minr, maxr, minc, maxc);
1821 break;
1825 if (count) {
1826 crow += count;
1827 pushkey = '\n';
1829 else
1830 crow++;
1832 if (crow > 8)
1833 crow = 1;
1835 break;
1836 case KEY_DOWN:
1837 if (status.mode == MODE_HISTORY) {
1838 history_previous(board, (count) ?
1839 config.jumpcount * count * movestep :
1840 config.jumpcount * movestep, &crow, &ccol);
1841 update_all();
1842 break;
1846 if (sp.icon && config.validmoves) {
1847 get_valid_cursor(board, DOWN, (count) ? count : 1,
1848 &crow, &ccol, minr, maxr, minc, maxc);
1849 break;
1853 if (count) {
1854 crow -= count;
1855 pushkey = '\n';
1856 update_status_notify(NULL);
1858 else
1859 crow--;
1861 if (crow < 1)
1862 crow = 8;
1864 break;
1865 case KEY_LEFT:
1866 if (status.mode == MODE_HISTORY) {
1867 history_previous(board, (count) ?
1868 count * movestep : movestep, &crow, &ccol);
1869 update_all();
1870 break;
1874 if (sp.icon && config.validmoves) {
1875 get_valid_cursor(board, LEFT, (count) ? count : 1,
1876 &crow, &ccol, minr, maxr, minc, maxc);
1877 break;
1881 if (count) {
1882 ccol -= count;
1883 pushkey = '\n';
1885 else
1886 ccol--;
1888 if (ccol < 1)
1889 ccol = 8;
1891 break;
1892 case KEY_RIGHT:
1893 if (status.mode == MODE_HISTORY) {
1894 history_next(board, (count) ? count * movestep : movestep,
1895 &crow, &ccol);
1896 update_all();
1897 break;
1901 if (sp.icon && config.validmoves) {
1902 get_valid_cursor(board, RIGHT, (count) ? count : 1,
1903 &crow, &ccol, minr, maxr, minc, maxc);
1904 break;
1908 if (count) {
1909 ccol += count;
1910 pushkey = '\n';
1912 else
1913 ccol++;
1915 if (ccol > 8)
1916 ccol = 1;
1918 break;
1919 case 'w':
1920 if (status.mode == MODE_HISTORY)
1921 break;
1923 if (status.mode == MODE_EDIT)
1924 switch_turn();
1926 /* FIXME crafty. */
1927 SEND_TO_ENGINE("\nswitch\n");
1928 switch_side();
1929 update_status_window();
1930 break;
1931 case ' ':
1932 if (!editmode && status.mode == MODE_HISTORY) {
1933 if (movestep == 1)
1934 movestep = 2;
1935 else
1936 movestep = 1;
1938 update_history_window();
1939 break;
1942 if (!noengine && (status.engine == ENGINE_OFFLINE ||
1943 !engine_initialized) && !editmode) {
1944 if (start_chess_engine() < 0) {
1945 sp.icon = 0;
1946 break;
1951 if (!editmode)
1952 wtimeout(boardw, 70);
1954 if (sp.icon || (!editmode && status.engine == ENGINE_THINKING)) {
1955 beep();
1956 break;
1959 sp.icon = mvwinch(boardw, ROWTOMATRIX(crow),
1960 COLTOMATRIX(ccol)+1) & A_CHARTEXT;
1962 if (sp.icon == ' ') {
1963 sp.icon = 0;
1964 break;
1967 if (!editmode && ((islower(sp.icon) && status.turn != BLACK) ||
1968 (isupper(sp.icon) && status.turn != WHITE))) {
1969 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
1970 sp.icon = 0;
1971 break;
1974 sp.row = crow;
1975 sp.col = ccol;
1977 if (!editmode && config.validmoves) {
1978 get_valid_moves(board, piece_to_int(sp.icon), sp.row,
1979 sp.col, &minr, &maxr, &minc, &maxc);
1981 number_valid_moves(board, sp.row, sp.col);
1985 if (status.mode == MODE_PLAY)
1986 paused = 0;
1988 break;
1989 case '\015':
1990 case '\n':
1991 pushkey = count = 0;
1992 update_status_notify(NULL);
1994 if (!editmode && status.mode == MODE_HISTORY)
1995 break;
1997 if (status.engine == ENGINE_THINKING) {
1998 beep();
1999 break;
2002 if (!sp.icon)
2003 break;
2005 sp.destrow = crow;
2006 sp.destcol = ccol;
2008 if (editmode) {
2009 edit_board(board);
2010 sp.icon = sp.row = sp.col = 0;
2011 break;
2014 if (move_to_engine(board)) {
2015 if (config.validmoves)
2016 reset_valid_moves(board);
2018 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2019 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2020 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2024 break;
2025 case 'q':
2026 quit = 1;
2027 break;
2028 case 0:
2029 break;
2030 default:
2031 beep();
2032 break;
2035 count = 0;
2038 return;
2041 void usage(const char *pn, int ret)
2043 int i;
2045 for (i = 0; cmdlinehelp[i]; i++)
2046 fputs(cmdlinehelp[i], stderr);
2048 exit(ret);
2051 void catch_signal(int which)
2053 switch (which) {
2054 case SIGINT:
2055 stop_engine();
2056 endwin();
2057 exit(EXIT_FAILURE);
2058 break;
2059 case SIGPIPE:
2060 if (quit)
2061 break;
2063 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
2064 endwin();
2065 exit(EXIT_FAILURE);
2066 break;
2067 case SIGSTOP:
2068 savetty();
2069 break;
2070 case SIGCONT:
2071 resetty();
2072 keypad(boardw, TRUE);
2073 break;
2074 default:
2075 break;
2078 return;
2081 int main(int argc, char *argv[])
2083 int opt;
2084 struct stat st;
2085 char buf[FILENAME_MAX];
2086 char datadir[FILENAME_MAX];
2087 int ret = EXIT_SUCCESS;
2088 int validate = 0, validate_and_save = 0;
2090 if ((config.pwd = getpwuid(getuid())) == NULL)
2091 err(EXIT_FAILURE, "getpwuid()");
2093 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
2094 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
2095 config.ccfile = strdup(buf);
2096 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
2097 config.nagfile = strdup(buf);
2098 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
2099 config.agonyfile = strdup(buf);
2100 snprintf(buf, sizeof(buf), "%s/config", datadir);
2101 config.configfile = strdup(buf);
2102 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
2103 config.fifo = strdup(buf);
2104 snprintf(buf, sizeof(buf), "%s/tmpfile", datadir);
2105 config.tmpfile = strdup(buf);
2107 if (stat(datadir, &st) == -1) {
2108 if (errno == ENOENT) {
2109 if (mkdir(datadir, 0755) == -1)
2110 err(EXIT_FAILURE, "%s", datadir);
2112 else
2113 err(EXIT_FAILURE, "%s", datadir);
2115 stat(datadir, &st);
2118 if (!S_ISDIR(st.st_mode))
2119 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
2121 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
2122 if (mkfifo(config.fifo, 0600) == -1)
2123 err(EXIT_FAILURE, "%s", config.fifo);
2126 set_defaults();
2128 #ifdef DEBUG
2129 while ((opt = getopt(argc, argv, "EDNVShp:vu:e:f:i:")) != -1) {
2130 #else
2131 while ((opt = getopt(argc, argv, "ENVShp:vu:e:f:i:")) != -1) {
2132 #endif
2133 char *tmp;
2134 int i;
2136 switch (opt) {
2137 case 'E':
2138 config.stoponerror = 1;
2139 break;
2140 case 'N':
2141 noengine = 1;
2142 break;
2143 case 'S':
2144 validate_and_save = 1;
2145 case 'V':
2146 validate = 1;
2147 break;
2148 #ifdef DEBUG
2149 case 'D':
2150 debug = 1;
2151 break;
2152 #endif
2153 case 'u':
2154 i = 0;
2156 while ((tmp = strsep(&optarg, ":")) != NULL) {
2157 switch (i++) {
2158 case 0:
2159 config.ics_user = optarg;
2160 break;
2161 case 1:
2162 config.ics_passwd = optarg;
2163 break;
2164 default:
2165 usage(argv[0], EXIT_FAILURE);
2168 break;
2169 case 'i':
2170 i = 0;
2172 while ((tmp = strsep(&optarg, ":")) != NULL) {
2173 switch (i++) {
2174 case 0:
2175 strncpy(config.ics_server, tmp,
2176 sizeof(config.ics_server));
2177 break;
2178 case 1:
2179 if (!isinteger(tmp))
2180 usage(argv[0], EXIT_FAILURE);
2182 config.ics_port = atoi(tmp);
2183 break;
2184 default:
2185 usage(argv[0], EXIT_FAILURE);
2188 break;
2189 case 'v':
2190 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
2191 COPYRIGHT);
2192 exit(EXIT_SUCCESS);
2193 case 'p':
2194 filetype = PGN_FILE;
2195 strncpy(loadfile, optarg, sizeof(loadfile));
2196 break;
2197 case 'f':
2198 filetype = FEN_FILE;
2199 strncpy(loadfile, optarg, sizeof(loadfile));
2200 break;
2201 case 'e':
2202 filetype = EPD_FILE;
2203 strncpy(loadfile, optarg, sizeof(loadfile));
2204 break;
2205 case 'h':
2206 default:
2207 usage(argv[0], EXIT_SUCCESS);
2211 if ((validate || validate_and_save) && !*loadfile)
2212 usage(argv[0], EXIT_FAILURE);
2214 if (access(config.configfile, R_OK) == 0)
2215 parse_rcfile(config.configfile);
2217 signal(SIGPIPE, catch_signal);
2218 signal(SIGCONT, catch_signal);
2219 signal(SIGSTOP, catch_signal);
2220 signal(SIGINT, catch_signal);
2222 srandom(getpid());
2224 switch (filetype) {
2225 case PGN_FILE:
2226 ret = parse_pgn_file(board, loadfile);
2227 break;
2228 case FEN_FILE:
2229 ret = parse_fen_file(board, loadfile);
2230 break;
2231 case EPD_FILE:
2232 default:
2233 break;
2236 if (validate || validate_and_save) {
2237 if (validate_and_save) {
2238 int i;
2240 for (i = 0; i < gtotal; i++)
2241 pgn_dumpgame(stdout, &game[i], i, 0);
2244 exit(ret);
2247 if (initscr() == NULL)
2248 errx(EXIT_FAILURE, "%s", E_INITCURSES);
2249 else
2250 curses_initialized = 1;
2252 if (has_colors() == TRUE && start_color() == OK)
2253 init_color_pairs();
2255 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
2256 boardp = new_panel(boardw);
2257 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
2258 COLS - HISTORY_WIDTH);
2259 historyp = new_panel(historyw);
2260 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
2261 statusp = new_panel(statusw);
2262 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
2263 tagp = new_panel(tagw);
2264 keypad(boardw, TRUE);
2265 // leaveok(boardw, TRUE);
2266 leaveok(tagw, TRUE);
2267 leaveok(statusw, TRUE);
2268 leaveok(historyw, TRUE);
2269 curs_set(0);
2270 cbreak();
2271 noecho();
2273 wbkgd(boardw, CP_BOARD_WINDOW);
2274 wbkgd(statusw, CP_STATUS_WINDOW);
2275 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2276 CP_STATUS_TITLE, CP_STATUS_BORDER);
2277 wbkgd(tagw, CP_TAG_WINDOW);
2278 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2279 CP_TAG_BORDER);
2280 wbkgd(historyw, CP_HISTORY_WINDOW);
2281 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2282 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2284 game_loop();
2285 stop_engine();
2287 endwin();
2288 free_game_data();
2289 free(game);
2290 del_panel(boardp);
2291 del_panel(historyp);
2292 del_panel(statusp);
2293 del_panel(tagp);
2294 delwin(boardw);
2295 delwin(historyw);
2296 delwin(statusw);
2297 delwin(tagw);
2298 exit(EXIT_SUCCESS);