Fix compile time warnings and a couple other cleanups.
[cboard.git] / src / cboard.c
blob0595dc46724b2995a6138c411e5471bd539b786b
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2002-2006 Ben Kibbey <bjk@arbornet.org>
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 validate_move = 1;
274 if (parse_move_text(t, p)) {
275 cmessage(ERROR, ANYKEY, "%s: %s", E_INVALID_MOVE, p);
276 validate_move = 0;
277 return NULL;
280 validate_move = 0;
281 memcpy(b, t, sizeof(BOARD));
282 return p;
285 static int move_to_engine(BOARD b)
287 char *p;
289 if ((p = board_to_san(b)) == NULL)
290 return 0;
292 sp.row = sp.col = sp.icon = 0;
294 if (noengine) {
295 add_to_history(&game[gindex].history, &game[gindex].hindex,
296 &game[gindex].htotal, p);
297 switch_turn();
298 SET_FLAG(game[gindex].flags, GF_MODIFIED);
299 update_all();
300 return 1;
303 SEND_TO_ENGINE("%s\n", p);
304 return 1;
307 char *book_method(int method)
309 char *book;
311 switch (method) {
312 case BOOK_BEST:
313 book = BOOK_BEST_STR;
314 break;
315 case BOOK_WORST:
316 book = BOOK_WORST_STR;
317 break;
318 case BOOK_PREFER:
319 book = BOOK_PREFER_STR;
320 break;
321 case BOOK_RANDOM:
322 book = BOOK_RANDOM_STR;
323 break;
324 case BOOK_OFF:
325 book = BOOK_OFF_STR;
326 break;
327 default:
328 book = UNKNOWN;
329 break;
332 return book;
335 static void update_clock(int n, int *h, int *m, int *s)
337 *h = n / 3600;
338 *m = (n % 3600) / 60;
339 *s = (n % 3600) % 60;
341 return;
344 void update_status_window()
346 int i = 0;
347 char buf[STATUS_WIDTH - 7];
348 char tmp[15], *engine, *mode;
349 int w = STATUS_WIDTH - 10;
350 int h, m, s;
351 char *p;
353 *tmp = '\0';
354 p = tmp;
356 if (TEST_FLAG(game[gindex].flags, GF_DELETE)) {
357 *p++ = '(';
358 *p++ = 'x';
359 i++;
362 if (TEST_FLAG(game[gindex].flags, GF_PERROR)) {
363 if (!i)
364 *p++ = '(';
365 else
366 *p++ = '/';
368 *p++ = '!';
371 if (TEST_FLAG(game[gindex].flags, GF_MODIFIED)) {
372 if (!i)
373 *p++ = '(';
374 else
375 *p++ = '/';
377 *p++ = '*';
380 if (*tmp != '\0')
381 *p++ = ')';
383 *p = '\0';
385 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
386 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
387 snprintf(buf, sizeof(buf), "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
388 (*tmp) ? tmp : "");
389 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
391 switch (status.mode) {
392 case MODE_HISTORY:
393 mode = MODE_HISTORY_STR;
394 break;
395 case MODE_EDIT:
396 mode = MODE_EDIT_STR;
397 break;
398 case MODE_PLAY:
399 mode = MODE_PLAY_STR;
400 break;
401 default:
402 mode = UNKNOWN;
403 break;
406 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
408 switch (status.engine) {
409 case ENGINE_THINKING:
410 engine = ENGINE_THINKING_STR;
411 break;
412 case ENGINE_READY:
413 engine = ENGINE_READY_STR;
414 break;
415 case ENGINE_INITIALIZING:
416 engine = ENGINE_INITIALIZING_STR;
417 break;
418 case ENGINE_OFFLINE:
419 engine = ENGINE_OFFLINE_STR;
420 break;
421 default:
422 engine = UNKNOWN;
423 break;
426 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
427 wattron(statusw, CP_STATUS_ENGINE);
428 mvwaddstr(statusw, 5, 9, engine);
429 wattroff(statusw, CP_STATUS_ENGINE);
431 mvwprintw(statusw, 6, 1, "%*s %-*i", 7, STATUS_DEPTH_STR, w,
432 config.engine_depth);
434 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_BOOK_STR, w,
435 book_method(config.book_method));
437 mvwprintw(statusw, 8, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
438 (status.turn == WHITE) ? WHITE_STR : BLACK_STR);
440 strncpy(tmp, WHITE_STR, sizeof(tmp));
441 tmp[0] = toupper(tmp[0]);
442 update_clock(game[gindex].moveclock, &h, &m, &s);
443 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].wcaptures,
444 h, m, s);
445 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, buf);
447 strncpy(tmp, BLACK_STR, sizeof(tmp));
448 tmp[0] = toupper(tmp[0]);
449 update_clock(game[gindex].moveclock, &h, &m, &s);
450 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].bcaptures,
451 h, m, s);
452 mvwprintw(statusw, 10, 1, "%*s: %-*s", 6, tmp, w, buf);
454 for (i = 1; i < STATUS_WIDTH - 4; i++)
455 mvwprintw(statusw, STATUS_HEIGHT - 2, i, " ");
457 if (!status.notify)
458 status.notify = strdup(GAME_HELP_PROMPT);
460 wattron(statusw, CP_STATUS_NOTIFY);
461 mvwprintw(statusw, STATUS_HEIGHT - 2,
462 CENTERX(STATUS_WIDTH, status.notify), "%s", status.notify);
463 wattroff(statusw, CP_STATUS_NOTIFY);
466 void update_history_window()
468 char buf[HISTORY_WIDTH];
469 HISTORY h = {{0},NULL,{0}};
470 int n, total;
472 n = (game[gindex].hindex + 1) / 2;
474 if ((game[gindex].htotal % 2))
475 total = (game[gindex].htotal + 1) / 2;
476 else
477 total = game[gindex].htotal / 2;
479 if (game[gindex].htotal)
480 snprintf(buf, sizeof(buf), "%u %s %u%s",n, N_OF_N_STR, total,
481 (movestep == 1) ? HISTORY_MOVE_STEP : "");
482 else
483 strncpy(buf, UNAVAILABLE, sizeof(buf));
485 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
486 HISTORY_WIDTH - 13, buf);
488 if (get_history_by_index(game[gindex].hindex, &h))
489 memset(&h, 0, sizeof(HISTORY));
491 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
492 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_NEXT : "");
493 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
494 HISTORY_WIDTH - 13, buf);
496 if (get_history_by_index(game[gindex].hindex - 1, &h))
497 memset(&h, 0, sizeof(HISTORY));
499 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
500 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_PREV : "");
501 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
502 HISTORY_WIDTH - 13, buf);
503 return;
506 void update_tag_window()
508 int i;
509 int w = TAG_WIDTH - 10;
511 for (i = 0; i < 7; i++) {
512 char *value = game[gindex].tag[i].value;
513 int n;
515 if ((*value == '?' || *value == '-') && value[1] == '\0')
516 value = UNAVAILABLE;
517 else if (strcmp(game[gindex].tag[i].name, "Result") == 0) {
518 for (n = 0; n < NARRAY(fancy_results); n++) {
519 if (strcmp(value, fancy_results[n].pgn) == 0) {
520 value = fancy_results[n].fancy;
521 break;
526 value = str_etc(value, w, 0);
528 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, game[gindex].tag[i].name,
529 w, value);
532 return;
535 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
537 int i;
539 wattron(win, attr);
541 for (i = 1; i < width - 1; i++)
542 mvwaddch(win, y, i, ' ');
544 mvwprintw(win, y, CENTERX(width, str), "%s", str);
546 wattroff(win, attr);
547 return;
550 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
551 chtype battr)
553 int i;
555 if (title) {
556 wattron(win, attr);
558 for (i = 1; i < width - 1; i++)
559 mvwaddch(win, 1, i, ' ');
561 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
562 wattroff(win, attr);
565 wattron(win, battr);
566 box(win, ACS_VLINE, ACS_HLINE);
567 wattroff(win, battr);
569 return;
572 void update_all()
574 update_status_window();
575 update_history_window();
576 return;
579 static void game_next_prev(int n, int count)
581 if (gtotal < 2)
582 return;
584 if (n == 1) {
585 if (gindex + count > gtotal - 1) {
586 if (count != 1)
587 gindex = gtotal - 1;
588 else
589 gindex = 0;
591 else
592 gindex += count;
594 else {
595 if (gindex - count < 0) {
596 if (count != 1)
597 gindex = 0;
598 else
599 gindex = gtotal - 1;
601 else
602 gindex -= count;
605 init_history(board);
606 update_all();
607 update_tag_window();
608 return;
611 void free_game_data()
613 int i;
615 if (!gtotal)
616 return;
618 for (i = 0; i < gtotal; i++) {
619 free_historydata(&game[i].history, 0, game[i].htotal);
620 free(game[i].history);
621 free_tag_data(game[i].tag, game[i].tindex);
622 free(game[i].tag);
625 return;
628 static void delete_game(int which)
630 GAME *g = NULL;
631 int gi = 0;
632 int i;
634 for (i = 0; i < gtotal; i++) {
635 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
636 free_historydata(&game[i].history, 0, game[i].htotal);
637 free(game[i].history);
638 free_tag_data(game[i].tag, game[i].tindex);
639 free(game[i].tag);
640 continue;
643 g = Realloc(g, (gi + 2) * sizeof(GAME));
645 memcpy(&g[gi], &game[i], sizeof(GAME));
647 g[gi].tag = game[i].tag;
648 g[gi].history = game[i].history;
649 gi++;
652 game = g;
653 gtotal = gi;
655 if (which != -1) {
656 if (which + 1 >= gtotal)
657 gindex = gtotal - 1;
658 else
659 gindex = which;
661 else
662 gindex = gtotal - 1;
664 return;
667 /* FIXME dont show out of reach counts. Diagonals. */
669 static void number_valid_moves(BOARD b, int srow, int scol)
671 int row, col;
672 int count;
674 for (row = srow + 1, col = scol, count = 1; VALIDFILE(row); row++) {
675 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
676 continue;
678 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
681 for (row = srow - 1, col = scol, count = 1; VALIDFILE(row); row--) {
682 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
683 continue;
685 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
688 for (col = scol + 1, row = srow, count = 1; VALIDFILE(col); col++) {
689 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
690 continue;
692 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
695 for (col = scol - 1, row = srow, count = 1; VALIDFILE(col); col--) {
696 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
697 continue;
699 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
702 return;
707 static void get_valid_cursor(BOARD b, int which, int count, int *crow,
708 int *ccol, int minr, int maxr, int minc, int maxc)
710 int row, col;
711 int incr, cincr;
713 if (which == UP || which == RIGHT)
714 incr = 1;
715 else
716 incr = -(1);
718 switch (which) {
719 case UP:
720 case DOWN:
721 if (count > 1) {
722 row = *crow;
724 if (which == UP)
725 *crow += count;
726 else
727 *crow -= count;
729 if (!VALIDFILE(*crow))
730 *crow = row;
733 for (row = *crow + incr, col = *ccol; VALIDFILE(row); row += incr) {
734 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
735 continue;
737 *crow = row;
738 goto done;
741 break;
742 case RIGHT:
743 case LEFT:
744 if (count > 1) {
745 col = *ccol;
747 if (which == RIGHT)
748 *ccol += count;
749 else
750 *ccol -= count;
752 if (!VALIDFILE(*ccol))
753 *ccol = col;
756 for (col = *ccol + incr, row = *crow; VALIDFILE(col); col += incr) {
757 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
758 continue;
760 *ccol = col;
761 goto done;
764 break;
765 default:
766 break;
769 if (*ccol < sp.col || (which == DOWN || which == LEFT))
770 cincr = -(1);
771 else
772 cincr = 1;
774 if (*ccol < sp.col && (which == DOWN || which == LEFT))
775 cincr = 1;
777 for (row = *crow + incr; VALIDFILE(row); row += incr) {
778 for (col = *ccol + cincr; VALIDFILE(col); col += cincr) {
779 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
780 continue;
782 *crow = row;
783 *ccol = col;
784 goto done;
788 done:
789 number_valid_moves(board, *crow, *ccol);
790 return;
794 static int find_move_exp(const char *str, int init, int which, int count)
796 int i;
797 int ret;
798 static regex_t r;
799 static int firstrun = 1;
800 char errbuf[255];
801 int incr;
802 int found;
804 if (init) {
805 if (!firstrun)
806 regfree(&r);
808 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
809 regerror(ret, &r, errbuf, sizeof(errbuf));
810 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
811 return -1;
814 firstrun = 1;
817 incr = (which == 0) ? -(1) : 1;
819 for (i = game[gindex].hindex + incr - 1, found = 0; ; i += incr) {
820 if (i == game[gindex].hindex - 1)
821 break;
823 if (i > game[gindex].htotal)
824 i = 0;
825 else if (i < 0)
826 i = game[gindex].htotal;
828 ret = regexec(&r, game[gindex].history[i].move, 0, 0, 0);
830 if (ret == 0) {
831 if (count == ++found) {
832 return i + 1;
835 else {
836 if (ret != REG_NOMATCH) {
837 regerror(ret, &r, errbuf, sizeof(errbuf));
838 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
839 return -1;
844 return -1;
847 static int toggle_delete_flag(int n)
849 int i, x;
851 if (TEST_FLAG(game[n].flags, GF_DELETE))
852 CLEAR_FLAG(game[n].flags, GF_DELETE);
853 else
854 SET_FLAG(game[n].flags, GF_DELETE);
857 for (i = x = 0; i < gtotal; i++) {
858 if (TEST_FLAG(game[i].flags, GF_DELETE))
859 x++;
862 if (x == gtotal) {
863 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
864 CLEAR_FLAG(game[n].flags, GF_DELETE);
865 return 1;
868 return 0;
871 static void edit_save_tags(int n)
873 int i;
874 TAG *t;
876 if ((t = edit_tags(board, game[n].tag, game[n].tindex, 1)) == NULL)
877 return;
879 game[n].tindex = 0;
881 for (i = 0; t[i].name; i++) {
882 add_tag(&game[n].tag, &game[n].tindex, t[i].name, t[i].value);
885 free_tag_data(t, i);
886 free(t);
887 SET_FLAG(game[n].flags, GF_MODIFIED);
888 return;
891 static int find_game_exp(char *str, int which, int count)
893 char *nstr = NULL, *exp = NULL;
894 regex_t nexp, vexp;
895 int ret = -1;
896 int g = 0;
897 char buf[255], *tmp;
898 char errbuf[255];
899 int found = 0;
900 int incr = (which == 0) ? -(1) : 1;
902 strncpy(buf, str, sizeof(buf));
903 tmp = buf;
905 if (strstr(tmp, ":") != NULL) {
906 nstr = strsep(&tmp, ":");
908 if ((ret = regcomp(&nexp, nstr,
909 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
910 regerror(ret, &nexp, errbuf, sizeof(errbuf));
911 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
912 ret = g = -1;
913 goto cleanup;
917 exp = tmp;
919 if (exp == NULL)
920 goto cleanup;
922 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
923 regerror(ret, &vexp, errbuf, sizeof(errbuf));
924 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
925 ret = -1;
926 goto cleanup;
929 ret = -1;
931 for (g = gindex + incr, found = 0; ; g += incr) {
932 int t;
934 if (g == gindex)
935 break;
937 if (g == gtotal)
938 g = 0;
939 else if (g < 0)
940 g = gtotal - 1;
942 for (t = 0; t < game[g].tindex; t++) {
943 if (nstr) {
944 if (regexec(&nexp, game[g].tag[t].name, 0, 0, 0) == 0) {
945 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
946 if (count == ++found) {
947 ret = g;
948 goto cleanup;
953 else {
954 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
955 if (count == ++found) {
956 ret = g;
957 goto cleanup;
963 ret = -1;
966 cleanup:
967 if (nstr)
968 regfree(&nexp);
970 if (g != -1)
971 regfree(&vexp);
973 return ret;
976 void edit_board(BOARD b)
978 chtype p;
980 p = b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
981 b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
982 b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
983 int_to_piece(OPEN_SQUARE);
985 return;
988 // Updates the notification line in the status window then refreshes the
989 // status window.
990 void update_status_notify(char *fmt, ...)
992 va_list ap;
993 #ifdef HAVE_VASPRINTF
994 char *line;
995 #else
996 char line[COLS];
997 #endif
999 if (!fmt) {
1000 if (status.notify) {
1001 free(status.notify);
1002 status.notify = NULL;
1004 if (curses_initialized)
1005 update_status_window();
1008 return;
1011 va_start(ap, fmt);
1012 #ifdef HAVE_VASPRINTF
1013 vasprintf(&line, fmt, ap);
1014 #else
1015 vsnprintf(line, sizeof(line), fmt, ap);
1016 #endif
1017 va_end(ap);
1019 if (status.notify)
1020 free(status.notify);
1022 status.notify = strdup(line);
1024 #ifdef HAVE_VASPRINTF
1025 free(line);
1026 #endif
1027 if (curses_initialized)
1028 update_status_window();
1031 void game_loop()
1033 int error_recover = 0;
1034 int pushkey = 0;
1035 int count = 0;
1036 int crow = 2, ccol = 5;
1037 char moveexp[255] = {0};
1038 char gameexp[255] = {0};
1039 int delete_count = 0;
1040 int markstart = -1, markend = -1;
1041 int editmode = 0;
1043 /* This is to just initialize the default tags etc. */
1044 parse_pgn_file(board, moveexp);
1045 draw_board(board, crow, ccol);
1046 update_tag_window();
1047 update_all();
1049 switch (filetype) {
1050 case PGN_FILE:
1051 if (parse_pgn_file(board, loadfile))
1052 loadfile[0] = '\0';
1053 break;
1054 case FEN_FILE:
1055 if (parse_fen_file(board, loadfile))
1056 loadfile[0] = '\0';
1057 break;
1058 case EPD_FILE:
1059 default:
1060 break;
1063 gindex = gtotal - 1;
1064 markstart = -1, markend = -1;
1066 if (loadfile[0])
1067 init_history(board);
1069 update_status_notify("%s", GAME_HELP_PROMPT);
1070 movestep = 2;
1071 paused = 1;
1073 flushinp();
1074 update_all();
1075 update_tag_window();
1077 while (!quit) {
1078 int c = 0;
1079 fd_set fds;
1080 int i, x, n = 0, len = 0;
1081 char fdbuf[8192] = {0};
1082 struct timeval tv;
1083 char *tmp = NULL;
1084 char buf[78];
1085 char tfile[FILENAME_MAX];
1086 int minr, maxr, minc, maxc;
1088 if (engine_initialized) {
1089 tv.tv_sec = 0;
1090 tv.tv_usec = 0;
1092 FD_ZERO(&fds);
1093 FD_SET(enginefd[0], &fds);
1095 for (i = 0; i < gtotal; i++) {
1096 if (game[i].sockfd > 0) {
1097 if (game[i].sockfd > n)
1098 n = game[i].sockfd;
1100 FD_SET(game[i].sockfd, &fds);
1104 n = (n > enginefd[0]) ? n : enginefd[0];
1106 if ((n = select(n + 1, &fds, NULL, NULL, &tv)) > 0) {
1107 if (FD_ISSET(enginefd[0], &fds)) {
1108 len = read(enginefd[0], fdbuf, sizeof(fdbuf));
1110 if (len == -1) {
1111 if (errno != EAGAIN) {
1112 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
1113 ++error_recover, strerror(errno));
1114 continue;
1117 else {
1118 if (len) {
1119 parse_engine_output(board, fdbuf);
1120 update_all();
1125 for (i = 0; i < gtotal; i++) {
1126 if (game[i].sockfd <= 0)
1127 continue;
1129 if (FD_ISSET(game[i].sockfd, &fds)) {
1130 len = recv(game[i].sockfd, fdbuf, sizeof(fdbuf), 0);
1132 if (len == -1) {
1133 if (errno != EAGAIN) {
1134 cmessage(ERROR, ANYKEY,
1135 "Attempt #%i. recv(): %s",
1136 ++error_recover, strerror(errno));
1137 continue;
1140 else {
1141 if (len)
1142 parse_ics_output(fdbuf);
1144 update_all();
1149 else {
1150 if (n == -1)
1151 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
1152 else {
1153 /* timeout */
1158 error_recover = 0;
1159 draw_board(board, crow, ccol);
1161 wmove(boardw, ROWTOMATRIX(crow), COLTOMATRIX(ccol));
1163 if (!paused) {
1166 update_panels();
1167 doupdate();
1169 if (pushkey)
1170 c = pushkey;
1171 else {
1172 if ((c = wgetch(boardw)) == ERR)
1173 continue;
1176 if (!count && status.notify)
1177 update_status_notify(NULL);
1179 switch (c) {
1180 int annotate;
1182 case 'p':
1183 if (paused)
1184 paused = 0;
1185 else
1186 paused = 1;
1188 break;
1189 case 'e':
1190 if (game[gindex].htotal)
1191 break;
1193 if (editmode) {
1194 editmode = 0;
1195 add_tag(&game[gindex].tag, &game[gindex].tindex,
1196 "FEN", board_to_fen(board, game[gindex]));
1197 status.mode = MODE_PLAY;
1198 game[gindex].fentag = game[gindex].tindex - 1;
1200 else {
1201 status.mode = MODE_EDIT;
1202 editmode = 1;
1205 update_all();
1206 break;
1207 case '}':
1208 case '{':
1209 case '?':
1210 if (gtotal < 2)
1211 break;
1213 if (!*gameexp || c == '?') {
1214 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
1215 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
1216 NULL, 0, -1)) == NULL)
1217 break;
1219 strncpy(gameexp, tmp, sizeof(gameexp));
1222 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
1223 (count) ? count : 1)) == -1)
1224 break;
1226 gindex = n;
1227 init_history(board);
1228 update_all();
1229 update_tag_window();
1230 break;
1231 case '!':
1232 crow = 1;
1233 break;
1234 case '@':
1235 crow = 2;
1236 break;
1237 case '#':
1238 crow = 3;
1239 break;
1240 case '$':
1241 crow = 4;
1242 break;
1243 case '%':
1244 crow = 5;
1245 break;
1246 case '^':
1247 crow = 6;
1248 break;
1249 case '&':
1250 crow = 7;
1251 break;
1252 case '*':
1253 crow = 8;
1254 break;
1255 case 'A':
1256 ccol = 1;
1257 break;
1258 case 'B':
1259 ccol = 2;
1260 break;
1261 case 'C':
1262 ccol = 3;
1263 break;
1264 case 'D':
1265 ccol = 4;
1266 break;
1267 case 'E':
1268 ccol = 5;
1269 break;
1270 case 'F':
1271 ccol = 6;
1272 break;
1273 case 'G':
1274 ccol = 7;
1275 break;
1276 case 'H':
1277 ccol = 8;
1278 break;
1279 case '_':
1280 case '+':
1281 if (status.engine != ENGINE_READY)
1282 break;
1284 n = (count) ? count : 1;
1286 if (c == '_') {
1287 if (config.engine_depth - n < 0)
1288 n = 0;
1289 else
1290 n -= config.engine_depth;
1292 else
1293 n += config.engine_depth;
1295 SEND_TO_ENGINE("depth %i\n", abs(n));
1296 break;
1297 case ']':
1298 case '[':
1299 case '/':
1300 if (game[gindex].htotal < 2)
1301 break;
1303 n = 0;
1305 if (!*moveexp || c == '/') {
1306 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
1307 NULL, NULL, 0, -1)) == NULL)
1308 break;
1310 strncpy(moveexp, tmp, sizeof(moveexp));
1311 n = 1;
1314 if ((n = find_move_exp(moveexp, n, (c == '[') ? 0 : 1,
1315 (count) ? count : 1)) == -1)
1316 break;
1318 game[gindex].hindex = n;
1319 parse_history_move(board, game[gindex].hindex);
1320 update_all();
1321 break;
1322 case 'v':
1323 view_annotation(game[gindex].hindex);
1324 break;
1325 case 'V':
1326 view_annotation(game[gindex].hindex - 1);
1327 break;
1328 case '>':
1329 game_next_prev(1, (count) ? count : 1);
1331 if (delete_count) {
1332 markend = gindex;
1333 pushkey = 'x';
1334 delete_count = 0;
1337 status.mode = MODE_HISTORY;
1338 editmode = 0;
1339 break;
1340 case '<':
1341 game_next_prev(0, (count) ? count : 1);
1343 if (delete_count) {
1344 markend = gindex;
1345 pushkey = 'x';
1346 delete_count = 0;
1349 status.mode = MODE_HISTORY;
1350 editmode = 0;
1351 break;
1352 case 'j':
1353 if (status.mode != MODE_HISTORY || game[gindex].htotal < 2)
1354 break;
1357 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1358 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
1359 game[gindex].htotal)) == NULL)
1360 break;
1363 if (!count) {
1364 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1365 NULL, NULL, NULL, 0, -1)) == NULL)
1366 break;
1368 if (!isinteger(tmp))
1369 break;
1371 i = atoi(tmp);
1373 else
1374 i = count;
1376 if (i > (game[gindex].htotal / 2) || i < 0)
1377 break;
1379 game[gindex].hindex = i * 2;
1380 init_history(board);
1381 update_all();
1382 break;
1383 case 'J':
1384 if (gtotal < 2)
1385 break;
1388 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
1389 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
1390 == NULL)
1391 break;
1394 if (!count) {
1395 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
1396 NULL, NULL, 0, -1)) == NULL)
1397 break;
1399 if (!isinteger(tmp))
1400 break;
1402 i = atoi(tmp);
1404 else
1405 i = count;
1407 if (--i > gtotal - 1 || i < 0)
1408 break;
1410 gindex = i;
1411 init_history(board);
1412 update_all();
1413 update_tag_window();
1414 break;
1415 case 'x':
1416 pushkey = 0;
1418 if (editmode) {
1419 if (sp.icon)
1420 board[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
1421 int_to_piece(OPEN_SQUARE);
1422 else
1423 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon =
1424 int_to_piece(OPEN_SQUARE);
1426 sp.icon = sp.row = sp.col = 0;
1427 break;
1430 if (gtotal < 2)
1431 break;
1433 if (count && !delete_count) {
1434 markstart = gindex;
1435 delete_count = 1;
1436 update_status_notify("%s (delete)", status.notify);
1437 continue;
1440 if (markstart >= 0 && markend >= 0) {
1441 if (markstart > markend) {
1442 i = markstart;
1443 markstart = markend;
1444 markend = i;
1447 for (i = markstart; i <= markend; i++) {
1448 if (toggle_delete_flag(i))
1449 break;
1452 else {
1453 if (toggle_delete_flag(gindex))
1454 break;
1457 markstart = markend = -1;
1458 update_status_window();
1459 break;
1460 case 'X':
1461 if (gtotal < 2) {
1462 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1463 break;
1466 tmp = NULL;
1468 for (i = n = 0; i < gtotal; i++) {
1469 if (TEST_FLAG(game[i].flags, GF_DELETE))
1470 n++;
1473 if (!n)
1474 tmp = GAME_DELETE_GAME_TEXT;
1475 else {
1476 if (n == gtotal) {
1477 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1478 break;
1481 tmp = GAME_DELETE_ALL_TEXT;
1484 if (config.deleteprompt) {
1485 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
1486 break;
1489 delete_game((!n) ? gindex : -1);
1490 init_history(board);
1491 update_all();
1492 update_tag_window();
1493 break;
1494 case 'a':
1495 annotate = game[gindex].hindex;
1497 if (annotate && game[gindex].history[annotate - 1].move[0])
1498 annotate--;
1499 else
1500 break;
1502 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
1503 game[gindex].history[annotate].move);
1505 tmp = get_input(buf, game[gindex].history[annotate].comment,
1506 0, 0, NAG_PROMPT, history_edit_nag, (void *)annotate,
1507 CTRL('T'), -1);
1509 if (!tmp && (!game[gindex].history[annotate].comment ||
1510 !*game[gindex].history[annotate].comment))
1511 break;
1512 else if (tmp && game[gindex].history[annotate].comment) {
1513 if (strcmp(tmp, game[gindex].history[annotate].comment)
1514 == 0)
1515 break;
1518 len = (tmp) ? strlen(tmp) + 1 : 1;
1520 game[gindex].history[annotate].comment =
1521 Realloc(game[gindex].history[annotate].comment, len);
1523 strncpy(game[gindex].history[annotate].comment,
1524 (tmp) ? tmp : "", len);
1526 SET_FLAG(game[gindex].flags, GF_MODIFIED);
1528 update_all();
1529 break;
1530 case 't':
1531 edit_save_tags(gindex);
1532 update_all();
1533 update_tag_window();
1534 break;
1535 case 'I':
1536 if (!editmode)
1537 break;
1539 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
1540 GAME_EDIT_TEXT);
1542 if (piece_to_int(c) == -1)
1543 break;
1545 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
1546 break;
1547 case 'i':
1548 edit_tags(board, game[gindex].tag, game[gindex].tindex, 0);
1549 break;
1550 case 'g':
1551 if (status.mode == MODE_HISTORY ||
1552 status.engine == ENGINE_THINKING)
1553 break;
1555 status.engine = ENGINE_THINKING;
1556 update_status_window();
1557 SEND_TO_ENGINE("go\n");
1558 break;
1559 case 'b':
1560 if (config.book_method == -1 || status.engine ==
1561 ENGINE_THINKING || config.engine != GNUCHESS)
1562 break;
1564 if (config.book_method + 1 >= BOOK_MAX)
1565 n = 0;
1566 else
1567 n = config.book_method + 1;
1569 SEND_TO_ENGINE("book %s\n", book_methods[n]);
1570 break;
1571 case 'h':
1572 if (status.mode == MODE_HISTORY) {
1573 if (game[gindex].openingside == BLACK) {
1574 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
1575 break;
1578 if (game[gindex].hindex != game[gindex].htotal) {
1579 if (!pushkey) {
1580 if ((c = message(NULL, YESNO, "%s",
1581 GAME_RESUME_HISTORY_TEXT)) != 'y')
1582 break;
1585 else {
1586 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1587 break;
1590 if (!noengine)
1591 wtimeout(boardw, 70);
1593 if (!noengine && !engine_initialized) {
1594 if (start_chess_engine() < 0)
1595 break;
1597 pushkey = 'h';
1598 break;
1601 pushkey = 0;
1602 oldhistorytotal = game[gindex].htotal;
1603 game[gindex].htotal = game[gindex].hindex;
1604 status.mode = MODE_PLAY;
1605 status.engine = ENGINE_READY;
1607 /* FIXME crafty */
1608 if (config.engine != GNUCHESS)
1609 SEND_TO_ENGINE("read %s\n", config.fifo);
1610 else
1611 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1613 update_all();
1614 break;
1617 if (!game[gindex].htotal || status.engine == ENGINE_THINKING)
1618 break;
1620 wtimeout(boardw, -1);
1621 init_history(board);
1622 break;
1623 case 'u':
1624 /* FIXME dies reading FIFO sometimes. */
1625 if (status.mode != MODE_PLAY || !game[gindex].htotal)
1626 break;
1628 history_previous(board, (count) ? count * 2 : 2, &crow, &ccol);
1629 oldhistorytotal = game[gindex].htotal;
1630 game[gindex].htotal = game[gindex].hindex;
1632 if (status.engine == CRAFTY)
1633 SEND_TO_ENGINE("read %s\n", config.fifo);
1634 else
1635 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1637 update_history_window();
1638 break;
1639 case 'r':
1640 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
1641 BROWSER_PROMPT, browse_directory, NULL,
1642 '\t', -1)) == NULL)
1643 break;
1645 tmp = tilde_expand(tmp);
1647 if (parse_pgn_file(board, tmp))
1648 break;
1650 gindex = gtotal - 1;
1651 strncpy(loadfile, tmp, sizeof(loadfile));
1652 init_history(board);
1653 update_all();
1654 update_tag_window();
1655 break;
1656 case 'S':
1657 case 's':
1658 x = -1;
1660 if (gtotal > 1) {
1661 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
1662 GAME_SAVE_MULTI_TEXT);
1664 if (n == 'c')
1665 x = gindex;
1666 else if (n == 'a')
1667 x = -1;
1668 else {
1669 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1670 break;
1674 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
1675 BROWSER_PROMPT, browse_directory, NULL,
1676 '\t', -1)) == NULL) {
1677 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1678 break;
1681 tmp = tilde_expand(tmp);
1683 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
1684 == NULL) {
1685 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
1686 tmp = tfile;
1689 if (save_pgn(tmp, 0, x)) {
1690 update_status_notify("%s", NOTIFY_SAVE_FAILED);
1691 break;
1694 update_status_notify("%s", NOTIFY_SAVED);
1695 update_all();
1696 break;
1697 case CTRL('G'):
1698 n = 0;
1700 while (n != 'q') {
1701 n = help(GAME_HELP_INDEX_TITLE,
1702 GAME_HELP_INDEX_PROMPT, mainhelp);
1704 switch (n) {
1705 case 'h':
1706 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
1707 break;
1708 case 'p':
1709 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
1710 break;
1711 case 'e':
1712 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
1713 break;
1714 case 'g':
1715 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
1716 break;
1717 default:
1718 n = 'q';
1719 break;
1723 break;
1724 case 'n':
1725 case 'N':
1726 if (c == 'N') {
1727 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
1728 break;
1731 status.mode = MODE_PLAY;
1732 editmode = 0;
1733 sp.icon = 0;
1735 if (c == 'n') {
1736 newgameinit = 1;
1737 new_game(board);
1739 else {
1740 reset_history();
1741 loadfile[0] = '\0';
1742 parse_pgn_file(board, loadfile);
1745 game[gindex].wcaptures = game[gindex].bcaptures = 0;
1746 crow = (status.side == WHITE) ? 2 : 7;
1747 ccol = 4;
1749 if (!noengine && (status.engine == ENGINE_OFFLINE ||
1750 engine_initialized == 0)) {
1751 if (start_chess_engine() < 0)
1752 break;
1755 SEND_TO_ENGINE("\nnew\n");
1756 set_engine_defaults();
1757 status.engine = ENGINE_READY;
1758 update_status_notify(NULL);
1759 update_all();
1760 update_tag_window();
1761 break;
1762 case CTRL('L'):
1763 case 'R':
1764 endwin();
1765 keypad(boardw, TRUE);
1766 update_panels();
1767 doupdate();
1768 break;
1769 case 'c':
1770 if (status.engine == ENGINE_THINKING)
1771 break;
1773 if (status.engine == ENGINE_OFFLINE)
1774 break;
1776 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL))
1777 != NULL) {
1778 SEND_TO_ENGINE("%s\n", tmp);
1780 break;
1781 case KEY_ESCAPE:
1782 sp.icon = sp.row = sp.col = 0;
1783 markend = markstart = 0;
1785 if (count) {
1786 count = 0;
1787 update_status_notify(NULL);
1790 if (config.validmoves)
1791 reset_valid_moves(board);
1793 break;
1794 case '0' ... '9':
1795 n = c - '0';
1797 if (count)
1798 count = count * 10 + n;
1799 else
1800 count = n;
1802 update_status_notify("Repeat %i", count);
1803 continue;
1804 case KEY_UP:
1805 if (status.mode == MODE_HISTORY) {
1806 history_next(board, (count > 0) ?
1807 config.jumpcount * count * movestep :
1808 config.jumpcount * movestep, &crow, &ccol);
1809 update_all();
1810 break;
1814 if (sp.icon && config.validmoves) {
1815 get_valid_cursor(board, UP, (count) ? count : 1,
1816 &crow, &ccol, minr, maxr, minc, maxc);
1817 break;
1821 if (count) {
1822 crow += count;
1823 pushkey = '\n';
1825 else
1826 crow++;
1828 if (crow > 8)
1829 crow = 1;
1831 break;
1832 case KEY_DOWN:
1833 if (status.mode == MODE_HISTORY) {
1834 history_previous(board, (count) ?
1835 config.jumpcount * count * movestep :
1836 config.jumpcount * movestep, &crow, &ccol);
1837 update_all();
1838 break;
1842 if (sp.icon && config.validmoves) {
1843 get_valid_cursor(board, DOWN, (count) ? count : 1,
1844 &crow, &ccol, minr, maxr, minc, maxc);
1845 break;
1849 if (count) {
1850 crow -= count;
1851 pushkey = '\n';
1852 update_status_notify(NULL);
1854 else
1855 crow--;
1857 if (crow < 1)
1858 crow = 8;
1860 break;
1861 case KEY_LEFT:
1862 if (status.mode == MODE_HISTORY) {
1863 history_previous(board, (count) ?
1864 count * movestep : movestep, &crow, &ccol);
1865 update_all();
1866 break;
1870 if (sp.icon && config.validmoves) {
1871 get_valid_cursor(board, LEFT, (count) ? count : 1,
1872 &crow, &ccol, minr, maxr, minc, maxc);
1873 break;
1877 if (count) {
1878 ccol -= count;
1879 pushkey = '\n';
1881 else
1882 ccol--;
1884 if (ccol < 1)
1885 ccol = 8;
1887 break;
1888 case KEY_RIGHT:
1889 if (status.mode == MODE_HISTORY) {
1890 history_next(board, (count) ? count * movestep : movestep,
1891 &crow, &ccol);
1892 update_all();
1893 break;
1897 if (sp.icon && config.validmoves) {
1898 get_valid_cursor(board, RIGHT, (count) ? count : 1,
1899 &crow, &ccol, minr, maxr, minc, maxc);
1900 break;
1904 if (count) {
1905 ccol += count;
1906 pushkey = '\n';
1908 else
1909 ccol++;
1911 if (ccol > 8)
1912 ccol = 1;
1914 break;
1915 case 'w':
1916 if (status.mode == MODE_HISTORY)
1917 break;
1919 if (status.turn == BLACK && status.side == WHITE) {
1920 status.side = BLACK;
1921 break;
1923 else if (status.turn == WHITE && status.side == BLACK) {
1924 status.side = WHITE;
1925 break;
1928 /* FIXME crafty. */
1929 SEND_TO_ENGINE("\nswitch\n");
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, "DNVShp:vu:e:f:i:")) != -1) {
2130 #else
2131 while ((opt = getopt(argc, argv, "NVShp:vu:e:f:i:")) != -1) {
2132 #endif
2133 char *tmp;
2134 int i;
2136 switch (opt) {
2137 case 'N':
2138 noengine = 1;
2139 break;
2140 case 'S':
2141 validate_and_save = 1;
2142 case 'V':
2143 validate = 1;
2144 break;
2145 #ifdef DEBUG
2146 case 'D':
2147 debug = 1;
2148 break;
2149 #endif
2150 case 'u':
2151 i = 0;
2153 while ((tmp = strsep(&optarg, ":")) != NULL) {
2154 switch (i++) {
2155 case 0:
2156 config.ics_user = optarg;
2157 break;
2158 case 1:
2159 config.ics_passwd = optarg;
2160 break;
2161 default:
2162 usage(argv[0], EXIT_FAILURE);
2165 break;
2166 case 'i':
2167 i = 0;
2169 while ((tmp = strsep(&optarg, ":")) != NULL) {
2170 switch (i++) {
2171 case 0:
2172 strncpy(config.ics_server, tmp,
2173 sizeof(config.ics_server));
2174 break;
2175 case 1:
2176 if (!isinteger(tmp))
2177 usage(argv[0], EXIT_FAILURE);
2179 config.ics_port = atoi(tmp);
2180 break;
2181 default:
2182 usage(argv[0], EXIT_FAILURE);
2185 break;
2186 case 'v':
2187 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
2188 COPYRIGHT);
2189 exit(EXIT_SUCCESS);
2190 case 'p':
2191 filetype = PGN_FILE;
2192 strncpy(loadfile, optarg, sizeof(loadfile));
2193 break;
2194 case 'f':
2195 filetype = FEN_FILE;
2196 strncpy(loadfile, optarg, sizeof(loadfile));
2197 break;
2198 case 'e':
2199 filetype = EPD_FILE;
2200 strncpy(loadfile, optarg, sizeof(loadfile));
2201 break;
2202 case 'h':
2203 default:
2204 usage(argv[0], EXIT_SUCCESS);
2208 if ((validate || validate_and_save) && !*loadfile)
2209 usage(argv[0], EXIT_FAILURE);
2211 if (access(config.configfile, R_OK) == 0)
2212 parse_rcfile(config.configfile);
2214 signal(SIGPIPE, catch_signal);
2215 signal(SIGCONT, catch_signal);
2216 signal(SIGSTOP, catch_signal);
2217 signal(SIGINT, catch_signal);
2219 srandom(getpid());
2221 switch (filetype) {
2222 case PGN_FILE:
2223 ret = parse_pgn_file(board, loadfile);
2224 break;
2225 case FEN_FILE:
2226 ret = parse_fen_file(board, loadfile);
2227 break;
2228 case EPD_FILE:
2229 default:
2230 break;
2233 if (validate || validate_and_save) {
2234 if (validate_and_save) {
2235 int i;
2237 for (i = 0; i < gtotal; i++)
2238 pgn_dumpgame(stdout, &game[i], i, 0);
2241 exit(ret);
2244 if (initscr() == NULL)
2245 errx(EXIT_FAILURE, "%s", E_INITCURSES);
2246 else
2247 curses_initialized = 1;
2249 if (has_colors() == TRUE && start_color() == OK)
2250 init_color_pairs();
2252 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
2253 boardp = new_panel(boardw);
2254 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
2255 COLS - HISTORY_WIDTH);
2256 historyp = new_panel(historyw);
2257 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
2258 statusp = new_panel(statusw);
2259 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
2260 tagp = new_panel(tagw);
2261 keypad(boardw, TRUE);
2262 // leaveok(boardw, TRUE);
2263 leaveok(tagw, TRUE);
2264 leaveok(statusw, TRUE);
2265 leaveok(historyw, TRUE);
2266 curs_set(0);
2267 cbreak();
2268 noecho();
2270 wbkgd(boardw, CP_BOARD_WINDOW);
2271 wbkgd(statusw, CP_STATUS_WINDOW);
2272 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2273 CP_STATUS_TITLE, CP_STATUS_BORDER);
2274 wbkgd(tagw, CP_TAG_WINDOW);
2275 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2276 CP_TAG_BORDER);
2277 wbkgd(historyw, CP_HISTORY_WINDOW);
2278 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2279 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2281 game_loop();
2282 stop_engine();
2284 endwin();
2285 free_game_data();
2286 free(game);
2287 del_panel(boardp);
2288 del_panel(historyp);
2289 del_panel(statusp);
2290 del_panel(tagp);
2291 delwin(boardw);
2292 delwin(historyw);
2293 delwin(statusw);
2294 delwin(tagw);
2295 exit(EXIT_SUCCESS);