more functions use one less argument for the board.
[cboard.git] / src / cboard.c
blobe092f60198edc2fa15ce1adecabc26e9da8f45d0
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(GAME g)
48 static int n;
49 FILE *fp;
50 char line[LINE_MAX];
52 if (n == -1 || !config.agony || !curses_initialized ||
53 (g.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(GAME g, 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 && g.b[brow][bcol].valid) {
170 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
171 CP_BOARD_MOVES_BLACK;
173 if (g.b[brow][bcol].movecount) {
174 if (brow + 1 != crow && bcol + 1 != ccol)
175 movecount = (g.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(g.sp.row) &&
187 col == COLTOMATRIX(g.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 = g.b[row / 2][bcol].icon;
201 if (attrs & A_BOLD)
202 bold = 1;
204 if (g.side == WHITE && isupper(piece))
205 attrs |= A_BOLD;
206 else if (g.side == BLACK && islower(piece))
207 attrs |= A_BOLD;
209 waddch(boardw, (piece && piece != int_to_piece(g, OPEN_SQUARE)) ? piece | attrs : ' ' | attrs);
211 if (!bold)
212 attrs &= ~(A_BOLD);
215 if (movecount && row != maxy -1)
216 waddch(boardw, movecount | CP_BOARD_COUNT);
217 else
218 waddch(boardw, ' ' | attrs);
220 col += 2;
221 bcol++;
224 else {
225 if (col != maxx - 1)
226 mvwaddch(boardw, row, col,
227 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
231 brow = row / 2;
235 /* Convert the selected piece to SAN format and validate it. */
236 static char *board_to_san(GAME *g)
238 static char str[MAX_PGN_MOVE_LEN + 1], *p;
239 int piece;
240 int promo;
241 BOARD oldboard;
243 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[(*g).sp.col - 1],
244 (*g).sp.row, x_grid_chars[(*g).sp.destcol - 1], (*g).sp.destrow);
246 p = str;
247 piece = piece_to_int((*g).b[ROWTOBOARD((*g).sp.row)][COLTOBOARD((*g).sp.col)].icon);
249 if (piece == PAWN && (((*g).sp.destrow == 8 && (*g).turn == WHITE) ||
250 ((*g).sp.destrow == 1 && (*g).turn == BLACK))) {
251 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
253 if (piece_to_int(promo) == -1)
254 return NULL;
256 p = str + strlen(str);
257 *p++ = toupper(promo);
258 *p = '\0';
261 memcpy(oldboard, (*g).b, sizeof(BOARD));
263 if ((p = a2a4tosan(g, str)) == NULL) {
264 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
265 memcpy((*g).b, oldboard, sizeof(BOARD));
266 return NULL;
269 if (parse_move_text(g, p)) {
270 invalid_move((*g).n, p);
271 memcpy((*g).b, oldboard, sizeof(BOARD));
272 return NULL;
275 return p;
278 static int move_to_engine(GAME *g)
280 char *p;
282 if ((p = board_to_san(g)) == NULL)
283 return 0;
285 (*g).sp.row = (*g).sp.col = (*g).sp.icon = 0;
287 if (noengine) {
288 add_to_history(&(*g).hp, &(*g).hindex, &(*g).htotal, p);
289 switch_turn(&(*g));
290 SET_FLAG((*g).flags, GF_MODIFIED);
291 update_all(*g);
292 return 1;
295 SEND_TO_ENGINE("%s\n", p);
296 return 1;
299 char *book_method(int method)
301 char *book;
303 switch (method) {
304 case BOOK_BEST:
305 book = BOOK_BEST_STR;
306 break;
307 case BOOK_WORST:
308 book = BOOK_WORST_STR;
309 break;
310 case BOOK_PREFER:
311 book = BOOK_PREFER_STR;
312 break;
313 case BOOK_RANDOM:
314 book = BOOK_RANDOM_STR;
315 break;
316 case BOOK_OFF:
317 book = BOOK_OFF_STR;
318 break;
319 default:
320 book = UNKNOWN;
321 break;
324 return book;
327 static void update_clock(int n, int *h, int *m, int *s)
329 *h = n / 3600;
330 *m = (n % 3600) / 60;
331 *s = (n % 3600) % 60;
333 return;
336 void update_status_window(GAME g)
338 int i = 0;
339 char buf[STATUS_WIDTH - 7];
340 char tmp[15], *engine, *mode;
341 int w = STATUS_WIDTH - 10;
342 int h, m, s;
343 char *p;
345 *tmp = '\0';
346 p = tmp;
348 if (TEST_FLAG(g.flags, GF_DELETE)) {
349 *p++ = '(';
350 *p++ = 'x';
351 i++;
354 if (TEST_FLAG(g.flags, GF_PERROR)) {
355 if (!i)
356 *p++ = '(';
357 else
358 *p++ = '/';
360 *p++ = '!';
361 i++;
364 if (TEST_FLAG(g.flags, GF_MODIFIED)) {
365 if (!i)
366 *p++ = '(';
367 else
368 *p++ = '/';
370 *p++ = '*';
371 i++;
374 if (*tmp != '\0')
375 *p++ = ')';
377 *p = '\0';
379 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
380 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
381 snprintf(buf, sizeof(buf), "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
382 (*tmp) ? tmp : "");
383 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
385 switch (g.mode) {
386 case MODE_HISTORY:
387 mode = MODE_HISTORY_STR;
388 break;
389 case MODE_EDIT:
390 mode = MODE_EDIT_STR;
391 break;
392 case MODE_PLAY:
393 mode = MODE_PLAY_STR;
394 break;
395 default:
396 mode = UNKNOWN;
397 break;
400 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
402 switch (status.engine) {
403 case ENGINE_THINKING:
404 engine = ENGINE_THINKING_STR;
405 break;
406 case ENGINE_READY:
407 engine = ENGINE_READY_STR;
408 break;
409 case ENGINE_INITIALIZING:
410 engine = ENGINE_INITIALIZING_STR;
411 break;
412 case ENGINE_OFFLINE:
413 engine = ENGINE_OFFLINE_STR;
414 break;
415 default:
416 engine = UNKNOWN;
417 break;
420 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
421 wattron(statusw, CP_STATUS_ENGINE);
422 mvwaddstr(statusw, 5, 9, engine);
423 wattroff(statusw, CP_STATUS_ENGINE);
425 mvwprintw(statusw, 6, 1, "%*s %-*i", 7, STATUS_DEPTH_STR, w,
426 config.engine_depth);
428 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_BOOK_STR, w,
429 book_method(config.book_method));
431 mvwprintw(statusw, 8, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
432 (g.turn == WHITE) ? WHITE_STR : BLACK_STR);
434 strncpy(tmp, WHITE_STR, sizeof(tmp));
435 tmp[0] = toupper(tmp[0]);
436 update_clock(g.moveclock, &h, &m, &s);
437 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", g.wcaptures, h, m, s);
438 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, buf);
440 strncpy(tmp, BLACK_STR, sizeof(tmp));
441 tmp[0] = toupper(tmp[0]);
442 update_clock(g.moveclock, &h, &m, &s);
443 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", g.bcaptures, h, m, s);
444 mvwprintw(statusw, 10, 1, "%*s: %-*s", 6, tmp, w, buf);
446 for (i = 1; i < STATUS_WIDTH - 4; i++)
447 mvwprintw(statusw, STATUS_HEIGHT - 2, i, " ");
449 if (!status.notify)
450 status.notify = strdup(GAME_HELP_PROMPT);
452 wattron(statusw, CP_STATUS_NOTIFY);
453 mvwprintw(statusw, STATUS_HEIGHT - 2,
454 CENTERX(STATUS_WIDTH, status.notify), "%s", status.notify);
455 wattroff(statusw, CP_STATUS_NOTIFY);
458 void update_history_window(GAME g)
460 char buf[HISTORY_WIDTH];
461 HISTORY h;
462 int n, total;
464 memset(&h, 0, sizeof(HISTORY));
465 n = (g.hindex + 1) / 2;
467 if ((g.htotal % 2))
468 total = (g.htotal + 1) / 2;
469 else
470 total = g.htotal / 2;
472 if (g.htotal)
473 snprintf(buf, sizeof(buf), "%u %s %u%s",n, N_OF_N_STR, total,
474 (movestep == 1) ? HISTORY_MOVE_STEP : "");
475 else
476 strncpy(buf, UNAVAILABLE, sizeof(buf));
478 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
479 HISTORY_WIDTH - 13, buf);
481 if (history_by_index(g, g.hindex, &h))
482 memset(&h, 0, sizeof(HISTORY));
484 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
485 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_NEXT : "");
486 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
487 HISTORY_WIDTH - 13, buf);
489 if (history_by_index(g, game[gindex].hindex - 1, &h))
490 memset(&h, 0, sizeof(HISTORY));
492 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
493 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_PREV : "");
494 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
495 HISTORY_WIDTH - 13, buf);
498 void update_tag_window(TAG *t)
500 int i;
501 int w = TAG_WIDTH - 10;
503 for (i = 0; i < 7; i++) {
504 char *value = t[i].value;
505 int n;
507 if ((*value == '?' || *value == '-') && value[1] == '\0')
508 value = UNAVAILABLE;
509 else if (strcmp(t[i].name, "Result") == 0) {
510 for (n = 0; n < NARRAY(fancy_results); n++) {
511 if (strcmp(value, fancy_results[n].pgn) == 0) {
512 value = fancy_results[n].fancy;
513 break;
518 value = str_etc(value, w, 0);
519 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, t[i].name, w, value);
523 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
525 int i;
527 wattron(win, attr);
529 for (i = 1; i < width - 1; i++)
530 mvwaddch(win, y, i, ' ');
532 mvwprintw(win, y, CENTERX(width, str), "%s", str);
533 wattroff(win, attr);
536 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
537 chtype battr)
539 int i;
541 if (title) {
542 wattron(win, attr);
544 for (i = 1; i < width - 1; i++)
545 mvwaddch(win, 1, i, ' ');
547 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
548 wattroff(win, attr);
551 wattron(win, battr);
552 box(win, ACS_VLINE, ACS_HLINE);
553 wattroff(win, battr);
556 void update_all(GAME g)
558 update_status_window(g);
559 update_history_window(g);
562 static void game_next_prev(GAME g, int n, int count)
564 if (gtotal < 2)
565 return;
567 if (n == 1) {
568 if (gindex + count > gtotal - 1) {
569 if (count != 1)
570 gindex = gtotal - 1;
571 else
572 gindex = 0;
574 else
575 gindex += count;
577 else {
578 if (gindex - count < 0) {
579 if (count != 1)
580 gindex = 0;
581 else
582 gindex = gtotal - 1;
584 else
585 gindex -= count;
588 init_history(&g);
589 update_all(g);
590 update_tag_window(g.tag);
593 void free_game_data(GAME g)
595 free_history_data(g.history, 0);
596 free_tag_data(g.tag, g.tindex);
597 memset(&g, 0, sizeof(GAME));
600 void free_all_games()
602 int i;
604 for (i = 0; i < gtotal; i++)
605 free_game_data(game[i]);
607 if (game)
608 free(game);
609 game = NULL;
612 static void delete_game(int which)
614 GAME *g = NULL;
615 int gi = 0;
616 int i;
618 for (i = 0; i < gtotal; i++) {
619 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
620 free_game_data(game[i]);
621 continue;
624 g = Realloc(g, (gi + 1) * sizeof(GAME));
625 memcpy(&g[gi], &game[i], sizeof(GAME));
626 g[gi].tag = game[i].tag;
627 g[gi].history = game[i].history;
628 g[gi].hp = game[i].hp;
629 gi++;
632 game = g;
633 gtotal = gi;
635 if (which != -1) {
636 if (which + 1 >= gtotal)
637 gindex = gtotal - 1;
638 else
639 gindex = which;
641 else
642 gindex = gtotal - 1;
644 game[gindex].hp = game[gindex].history;
647 /* FIXME dont show out of reach counts. Diagonals. */
649 static void number_valid_moves(BOARD b, int srow, int scol)
651 int row, col;
652 int count;
654 for (row = srow + 1, col = scol, count = 1; VALIDFILE(row); row++) {
655 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
656 continue;
658 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
661 for (row = srow - 1, col = scol, count = 1; VALIDFILE(row); row--) {
662 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
663 continue;
665 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
668 for (col = scol + 1, row = srow, count = 1; VALIDFILE(col); col++) {
669 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
670 continue;
672 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
675 for (col = scol - 1, row = srow, count = 1; VALIDFILE(col); col--) {
676 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
677 continue;
679 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
682 return;
687 static void get_valid_cursor(BOARD b, int which, int count, int *crow,
688 int *ccol, int minr, int maxr, int minc, int maxc)
690 int row, col;
691 int incr, cincr;
693 if (which == UP || which == RIGHT)
694 incr = 1;
695 else
696 incr = -(1);
698 switch (which) {
699 case UP:
700 case DOWN:
701 if (count > 1) {
702 row = *crow;
704 if (which == UP)
705 *crow += count;
706 else
707 *crow -= count;
709 if (!VALIDFILE(*crow))
710 *crow = row;
713 for (row = *crow + incr, col = *ccol; VALIDFILE(row); row += incr) {
714 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
715 continue;
717 *crow = row;
718 goto done;
721 break;
722 case RIGHT:
723 case LEFT:
724 if (count > 1) {
725 col = *ccol;
727 if (which == RIGHT)
728 *ccol += count;
729 else
730 *ccol -= count;
732 if (!VALIDFILE(*ccol))
733 *ccol = col;
736 for (col = *ccol + incr, row = *crow; VALIDFILE(col); col += incr) {
737 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
738 continue;
740 *ccol = col;
741 goto done;
744 break;
745 default:
746 break;
749 if (*ccol < sp.col || (which == DOWN || which == LEFT))
750 cincr = -(1);
751 else
752 cincr = 1;
754 if (*ccol < sp.col && (which == DOWN || which == LEFT))
755 cincr = 1;
757 for (row = *crow + incr; VALIDFILE(row); row += incr) {
758 for (col = *ccol + cincr; VALIDFILE(col); col += cincr) {
759 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
760 continue;
762 *crow = row;
763 *ccol = col;
764 goto done;
768 done:
769 number_valid_moves(board, *crow, *ccol);
770 return;
774 static int find_move_exp(GAME g, const char *str, int init, int which,
775 int count)
777 int i;
778 int ret;
779 static regex_t r;
780 static int firstrun = 1;
781 char errbuf[255];
782 int incr;
783 int found;
785 if (init) {
786 if (!firstrun)
787 regfree(&r);
789 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
790 regerror(ret, &r, errbuf, sizeof(errbuf));
791 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
792 return -1;
795 firstrun = 1;
798 incr = (which == 0) ? -(1) : 1;
800 for (i = g.hindex + incr - 1, found = 0; ; i += incr) {
801 if (i == g.hindex - 1)
802 break;
804 if (i > g.htotal)
805 i = 0;
806 else if (i < 0)
807 i = g.htotal;
809 // FIXME RAV
810 ret = regexec(&r, g.hp[i].move, 0, 0, 0);
812 if (ret == 0) {
813 if (count == ++found) {
814 return i + 1;
817 else {
818 if (ret != REG_NOMATCH) {
819 regerror(ret, &r, errbuf, sizeof(errbuf));
820 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
821 return -1;
826 return -1;
829 static int toggle_delete_flag(int n)
831 int i, x;
833 TOGGLE_FLAG(game[n].flags, GF_DELETE);
835 for (i = x = 0; i < gtotal; i++) {
836 if (TEST_FLAG(game[i].flags, GF_DELETE))
837 x++;
840 if (x == gtotal) {
841 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
842 CLEAR_FLAG(game[n].flags, GF_DELETE);
843 return 1;
846 return 0;
849 static void edit_save_tags(GAME *g)
851 int i;
852 TAG *t;
854 if ((t = edit_tags(*g, 1)) == NULL)
855 return;
857 (*g).tindex = 0;
859 for (i = 0; t[i].name; i++)
860 add_tag(&(*g).tag, &(*g).tindex, t[i].name, t[i].value);
862 free_tag_data(t, i);
863 SET_FLAG((*g).flags, GF_MODIFIED);
866 static int find_game_exp(char *str, int which, int count)
868 char *nstr = NULL, *exp = NULL;
869 regex_t nexp, vexp;
870 int ret = -1;
871 int g = 0;
872 char buf[255], *tmp;
873 char errbuf[255];
874 int found = 0;
875 int incr = (which == 0) ? -(1) : 1;
877 strncpy(buf, str, sizeof(buf));
878 tmp = buf;
880 if (strstr(tmp, ":") != NULL) {
881 nstr = strsep(&tmp, ":");
883 if ((ret = regcomp(&nexp, nstr,
884 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
885 regerror(ret, &nexp, errbuf, sizeof(errbuf));
886 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
887 ret = g = -1;
888 goto cleanup;
892 exp = tmp;
894 if (exp == NULL)
895 goto cleanup;
897 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
898 regerror(ret, &vexp, errbuf, sizeof(errbuf));
899 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
900 ret = -1;
901 goto cleanup;
904 ret = -1;
906 for (g = gindex + incr, found = 0; ; g += incr) {
907 int t;
909 if (g == gindex)
910 break;
912 if (g == gtotal)
913 g = 0;
914 else if (g < 0)
915 g = gtotal - 1;
917 for (t = 0; t < game[g].tindex; t++) {
918 if (nstr) {
919 if (regexec(&nexp, game[g].tag[t].name, 0, 0, 0) == 0) {
920 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
921 if (count == ++found) {
922 ret = g;
923 goto cleanup;
928 else {
929 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
930 if (count == ++found) {
931 ret = g;
932 goto cleanup;
938 ret = -1;
941 cleanup:
942 if (nstr)
943 regfree(&nexp);
945 if (g != -1)
946 regfree(&vexp);
948 return ret;
951 void edit_board(GAME *g)
953 chtype p;
955 p = (*g).b[ROWTOBOARD((*g).sp.row)][COLTOBOARD((*g).sp.col)].icon;
956 (*g).b[ROWTOBOARD((*g).sp.destrow)][COLTOBOARD((*g).sp.destcol)].icon = p;
957 (*g).b[ROWTOBOARD((*g).sp.row)][COLTOBOARD((*g).sp.col)].icon =
958 int_to_piece(*g, OPEN_SQUARE);
961 // Updates the notification line in the status window then refreshes the
962 // status window.
963 void update_status_notify(GAME g, char *fmt, ...)
965 va_list ap;
966 #ifdef HAVE_VASPRINTF
967 char *line;
968 #else
969 char line[COLS];
970 #endif
972 if (!fmt) {
973 if (status.notify) {
974 free(status.notify);
975 status.notify = NULL;
977 if (curses_initialized)
978 update_status_window(g);
981 return;
984 va_start(ap, fmt);
985 #ifdef HAVE_VASPRINTF
986 vasprintf(&line, fmt, ap);
987 #else
988 vsnprintf(line, sizeof(line), fmt, ap);
989 #endif
990 va_end(ap);
992 if (status.notify)
993 free(status.notify);
995 status.notify = strdup(line);
997 #ifdef HAVE_VASPRINTF
998 free(line);
999 #endif
1000 if (curses_initialized)
1001 update_status_window(g);
1004 static void switch_side(GAME *g)
1006 if ((*g).side == WHITE)
1007 (*g).side = BLACK;
1008 else
1009 (*g).side = WHITE;
1012 static struct annotation_edit_s *init_annotation_edit(int g, int n, HISTORY h)
1014 struct annotation_edit_s *a;
1016 a = Malloc(sizeof(struct annotation_edit_s));
1017 a->game = g;
1018 a->n = n;
1019 memcpy(&a->h, &h, sizeof(HISTORY));
1020 a->h.comment = h.comment;
1021 return a;
1024 void game_loop()
1026 int error_recover = 0;
1027 int pushkey = 0;
1028 int count = 0;
1029 int crow = 2, ccol = 5;
1030 char moveexp[255] = {0};
1031 char gameexp[255] = {0};
1032 int delete_count = 0;
1033 int markstart = -1, markend = -1;
1034 int editmode = 0;
1036 gindex = gtotal - 1;
1037 markstart = -1, markend = -1;
1039 if (loadfile[0])
1040 init_history(&game[gindex]);
1042 update_status_notify(game[gindex], "%s", GAME_HELP_PROMPT);
1043 movestep = 2;
1044 paused = 1; //FIXME clock
1045 flushinp();
1046 update_all(game[gindex]);
1047 update_tag_window(game[gindex].tag);
1049 while (!quit) {
1050 int c = 0;
1051 fd_set fds;
1052 int i, x, n = 0, len = 0;
1053 char fdbuf[8192] = {0};
1054 struct timeval tv;
1055 char *tmp = NULL;
1056 char buf[78];
1057 char tfile[FILENAME_MAX];
1058 int minr, maxr, minc, maxc;
1059 struct annotation_edit_s *anno = NULL;
1061 // FIXME game.fds
1062 #if 0
1063 if (engine_initialized) {
1064 tv.tv_sec = 0;
1065 tv.tv_usec = 0;
1067 FD_ZERO(&fds);
1068 FD_SET(enginefd[0], &fds);
1070 for (i = 0; i < gtotal; i++) {
1071 if (game[i].sockfd > 0) {
1072 if (game[i].sockfd > n)
1073 n = game[i].sockfd;
1075 FD_SET(game[i].sockfd, &fds);
1079 n = (n > enginefd[0]) ? n : enginefd[0];
1081 if ((n = select(n + 1, &fds, NULL, NULL, &tv)) > 0) {
1082 if (FD_ISSET(enginefd[0], &fds)) {
1083 len = read(enginefd[0], fdbuf, sizeof(fdbuf));
1085 if (len == -1) {
1086 if (errno != EAGAIN) {
1087 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
1088 ++error_recover, strerror(errno));
1089 continue;
1092 else {
1093 if (len) {
1094 // FIXME engine may be associated with another
1095 // selected game.
1096 parse_engine_output(&game[gindex], fdbuf);
1097 update_all(game[gindex]);
1102 for (i = 0; i < gtotal; i++) {
1103 if (game[i].sockfd <= 0)
1104 continue;
1106 if (FD_ISSET(game[i].sockfd, &fds)) {
1107 len = recv(game[i].sockfd, fdbuf, sizeof(fdbuf), 0);
1109 if (len == -1) {
1110 if (errno != EAGAIN) {
1111 cmessage(ERROR, ANYKEY,
1112 "Attempt #%i. recv(): %s",
1113 ++error_recover, strerror(errno));
1114 continue;
1117 else {
1118 if (len)
1119 parse_ics_output(fdbuf);
1121 update_all(game[gindex]);
1126 else {
1127 if (n == -1)
1128 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
1129 else {
1130 /* timeout */
1134 #endif
1136 error_recover = 0;
1137 draw_board(game[gindex], crow, ccol);
1138 wmove(boardw, ROWTOMATRIX(crow), COLTOMATRIX(ccol));
1140 if (!paused) {
1143 update_panels();
1144 doupdate();
1146 if (pushkey)
1147 c = pushkey;
1148 else {
1149 if ((c = wgetch(boardw)) == ERR)
1150 continue;
1153 if (!count && status.notify)
1154 update_status_notify(game[gindex], NULL);
1156 switch (c) {
1157 int annotate;
1159 case 'p':
1160 if (paused)
1161 paused = 0;
1162 else
1163 paused = 1;
1165 break;
1166 case 'e':
1167 if (game[gindex].htotal)
1168 break;
1170 if (editmode) {
1171 editmode = 0;
1172 add_tag(&game[gindex].tag, &game[gindex].tindex,
1173 "FEN", board_to_fen(game[gindex]));
1174 add_tag(&game[gindex].tag, &game[gindex].tindex,
1175 "SetUp", "1");
1176 game[gindex].mode = MODE_PLAY;
1177 game[gindex].fentag = find_tag(game[gindex], "FEN");
1179 else {
1180 game[gindex].mode = MODE_EDIT;
1181 editmode = 1;
1184 update_all(game[gindex]);
1185 break;
1186 case '}':
1187 case '{':
1188 case '?':
1189 if (gtotal < 2)
1190 break;
1192 if (!*gameexp || c == '?') {
1193 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
1194 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
1195 NULL, 0, -1)) == NULL)
1196 break;
1198 strncpy(gameexp, tmp, sizeof(gameexp));
1201 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
1202 (count) ? count : 1)) == -1)
1203 break;
1205 gindex = n;
1206 init_history(&game[gindex]);
1207 update_all(game[gindex]);
1208 update_tag_window(game[gindex].tag);
1209 break;
1210 case '!':
1211 crow = 1;
1212 break;
1213 case '@':
1214 crow = 2;
1215 break;
1216 case '#':
1217 crow = 3;
1218 break;
1219 case '$':
1220 crow = 4;
1221 break;
1222 case '%':
1223 crow = 5;
1224 break;
1225 case '^':
1226 crow = 6;
1227 break;
1228 case '&':
1229 crow = 7;
1230 break;
1231 case '*':
1232 crow = 8;
1233 break;
1234 case 'A':
1235 ccol = 1;
1236 break;
1237 case 'B':
1238 ccol = 2;
1239 break;
1240 case 'C':
1241 ccol = 3;
1242 break;
1243 case 'D':
1244 ccol = 4;
1245 break;
1246 case 'E':
1247 ccol = 5;
1248 break;
1249 case 'F':
1250 ccol = 6;
1251 break;
1252 case 'G':
1253 ccol = 7;
1254 break;
1255 case 'H':
1256 ccol = 8;
1257 break;
1258 case '_':
1259 case '+':
1260 if (status.engine != ENGINE_READY)
1261 break;
1263 n = (count) ? count : 1;
1265 if (c == '_') {
1266 if (config.engine_depth - n < 0)
1267 n = 0;
1268 else
1269 n -= config.engine_depth;
1271 else
1272 n += config.engine_depth;
1274 SEND_TO_ENGINE("depth %i\n", abs(n));
1275 break;
1276 case ']':
1277 case '[':
1278 case '/':
1279 if (game[gindex].htotal < 2)
1280 break;
1282 n = 0;
1284 if (!*moveexp || c == '/') {
1285 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
1286 NULL, NULL, 0, -1)) == NULL)
1287 break;
1289 strncpy(moveexp, tmp, sizeof(moveexp));
1290 n = 1;
1293 if ((n = find_move_exp(game[gindex], moveexp, n,
1294 (c == '[') ? 0 : 1, (count) ? count : 1)) == -1)
1295 break;
1297 game[gindex].hindex = n;
1298 parse_history_move(game[gindex], game[gindex].hindex);
1299 update_all(game[gindex]);
1300 break;
1301 case 'v':
1302 view_annotation(game[gindex].hp[game[gindex].hindex]);
1303 break;
1304 case 'V':
1305 if (game[gindex].hindex - 1 >= 0)
1306 view_annotation(game[gindex].hp[game[gindex].hindex - 1]);
1307 break;
1308 case '>':
1309 game_next_prev(game[gindex], 1, (count) ? count : 1);
1311 if (delete_count) {
1312 markend = gindex;
1313 pushkey = 'x';
1314 delete_count = 0;
1317 game[gindex].mode = MODE_HISTORY;
1318 editmode = 0;
1319 break;
1320 case '<':
1321 game_next_prev(game[gindex], 0, (count) ? count : 1);
1323 if (delete_count) {
1324 markend = gindex;
1325 pushkey = 'x';
1326 delete_count = 0;
1329 game[gindex].mode = MODE_HISTORY;
1330 editmode = 0;
1331 break;
1332 case 'j':
1333 if (game[gindex].mode != MODE_HISTORY ||
1334 game[gindex].htotal < 2)
1335 break;
1338 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1339 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
1340 game[gindex].htotal)) == NULL)
1341 break;
1344 if (!count) {
1345 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1346 NULL, NULL, NULL, 0, -1)) == NULL)
1347 break;
1349 if (!isinteger(tmp))
1350 break;
1352 i = atoi(tmp);
1354 else
1355 i = count;
1357 if (i > (game[gindex].htotal / 2) || i < 0)
1358 break;
1360 game[gindex].hindex = i * 2;
1361 init_history(&game[gindex]);
1362 update_all(game[gindex]);
1363 break;
1364 case 'J':
1365 if (gtotal < 2)
1366 break;
1369 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
1370 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
1371 == NULL)
1372 break;
1375 if (!count) {
1376 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
1377 NULL, NULL, 0, -1)) == NULL)
1378 break;
1380 if (!isinteger(tmp))
1381 break;
1383 i = atoi(tmp);
1385 else
1386 i = count;
1388 if (--i > gtotal - 1 || i < 0)
1389 break;
1391 gindex = i;
1392 init_history(&game[gindex]);
1393 update_all(game[gindex]);
1394 update_tag_window(game[gindex].tag);
1395 break;
1396 case 'x':
1397 pushkey = 0;
1399 if (editmode) {
1400 if (game[gindex].sp.icon)
1401 game[gindex].b[ROWTOBOARD(game[gindex].sp.row)][COLTOBOARD(game[gindex].sp.col)].icon = int_to_piece(game[gindex], OPEN_SQUARE);
1402 else
1403 game[gindex].b[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = int_to_piece(game[gindex], OPEN_SQUARE);
1405 game[gindex].sp.icon = game[gindex].sp.row = game[gindex].sp.col = 0;
1406 break;
1409 if (gtotal < 2)
1410 break;
1412 if (count && !delete_count) {
1413 markstart = gindex;
1414 delete_count = 1;
1415 update_status_notify(game[gindex], "%s (delete)",
1416 status.notify);
1417 continue;
1420 if (markstart >= 0 && markend >= 0) {
1421 if (markstart > markend) {
1422 i = markstart;
1423 markstart = markend;
1424 markend = i;
1427 for (i = markstart; i <= markend; i++) {
1428 if (toggle_delete_flag(i))
1429 break;
1432 else {
1433 if (toggle_delete_flag(gindex))
1434 break;
1437 markstart = markend = -1;
1438 update_status_window(game[gindex]);
1439 break;
1440 case 'X':
1441 if (gtotal < 2) {
1442 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1443 break;
1446 tmp = NULL;
1448 for (i = n = 0; i < gtotal; i++) {
1449 if (TEST_FLAG(game[i].flags, GF_DELETE))
1450 n++;
1453 if (!n)
1454 tmp = GAME_DELETE_GAME_TEXT;
1455 else {
1456 if (n == gtotal) {
1457 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1458 break;
1461 tmp = GAME_DELETE_ALL_TEXT;
1464 if (config.deleteprompt) {
1465 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
1466 break;
1469 delete_game((!n) ? gindex : -1);
1470 init_history(&game[gindex]);
1471 update_all(game[gindex]);
1472 update_tag_window(game[gindex].tag);
1473 break;
1474 case 'a':
1475 annotate = game[gindex].hindex;
1477 if (annotate && game[gindex].hp[annotate - 1].move[0])
1478 annotate--;
1479 else
1480 break;
1482 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
1483 game[gindex].hp[annotate].move);
1485 anno = init_annotation_edit(gindex, annotate,
1486 game[gindex].hp[annotate]);
1487 tmp = get_input(buf, game[gindex].hp[annotate].comment,
1488 0, 0, NAG_PROMPT, history_edit_nag, (void *)anno,
1489 CTRL('T'), -1);
1491 if (!tmp && (!game[gindex].hp[annotate].comment ||
1492 !*game[gindex].hp[annotate].comment))
1493 break;
1494 else if (tmp && game[gindex].hp[annotate].comment) {
1495 if (strcmp(tmp, game[gindex].hp[annotate].comment) == 0)
1496 break;
1499 len = (tmp) ? strlen(tmp) + 1 : 1;
1501 game[gindex].hp[annotate].comment =
1502 Realloc(game[gindex].hp[annotate].comment, len);
1504 strncpy(game[gindex].hp[annotate].comment,
1505 (tmp) ? tmp : "", len);
1507 SET_FLAG(game[gindex].flags, GF_MODIFIED);
1508 update_all(game[gindex]);
1509 break;
1510 case 't':
1511 edit_save_tags(&game[gindex]);
1512 update_all(game[gindex]);
1513 update_tag_window(game[gindex].tag);
1514 break;
1515 case 'I':
1516 if (!editmode)
1517 break;
1519 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
1520 GAME_EDIT_TEXT);
1522 if (piece_to_int(c) == -1 && tolower(c) != 'x')
1523 break;
1525 if (tolower(c) == 'x')
1526 c = tolower(c);
1528 if (c == 'x' && (crow != 6 && crow != 3))
1529 break;
1531 if (c == 'x') {
1532 for (i = 0; i < 8; i++) {
1533 if (game[gindex].b[ROWTOBOARD(3)][COLTOBOARD(i)].icon == 'x')
1534 game[gindex].b[ROWTOBOARD(3)][COLTOBOARD(i)].icon = OPEN_SQUARE;
1535 if (game[gindex].b[ROWTOBOARD(6)][COLTOBOARD(i)].icon == 'x')
1536 game[gindex].b[ROWTOBOARD(6)][COLTOBOARD(i)].icon = OPEN_SQUARE;
1540 game[gindex].b[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
1541 break;
1542 case 'i':
1543 edit_tags(game[gindex], 0);
1544 break;
1545 case 'g':
1546 if (game[gindex].mode == MODE_HISTORY ||
1547 status.engine == ENGINE_THINKING)
1548 break;
1550 status.engine = ENGINE_THINKING;
1551 update_status_window(game[gindex]);
1552 SEND_TO_ENGINE("go\n");
1553 break;
1554 case 'b':
1555 if (config.book_method == -1 || status.engine ==
1556 ENGINE_THINKING || config.engine != GNUCHESS)
1557 break;
1559 if (config.book_method + 1 >= BOOK_MAX)
1560 n = 0;
1561 else
1562 n = config.book_method + 1;
1564 SEND_TO_ENGINE("book %s\n", book_methods[n]);
1565 break;
1566 case 'h':
1567 if (game[gindex].mode == MODE_HISTORY) {
1568 if (game[gindex].openingside == BLACK) {
1569 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
1570 break;
1573 if (game[gindex].hindex != game[gindex].htotal) {
1574 if (!pushkey) {
1575 if ((c = message(NULL, YESNO, "%s",
1576 GAME_RESUME_HISTORY_TEXT)) != 'y')
1577 break;
1580 else {
1581 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1582 break;
1585 if (!noengine)
1586 wtimeout(boardw, 70);
1588 if (!noengine && !engine_initialized) {
1589 if (start_chess_engine() < 0)
1590 break;
1592 pushkey = 'h';
1593 break;
1596 pushkey = 0;
1597 oldhistorytotal = game[gindex].htotal;
1598 game[gindex].htotal = game[gindex].hindex;
1599 game[gindex].mode = MODE_PLAY;
1600 status.engine = ENGINE_READY;
1602 /* FIXME crafty */
1603 if (config.engine != GNUCHESS)
1604 SEND_TO_ENGINE("read %s\n", config.fifo);
1605 else
1606 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1608 update_all(game[gindex]);
1609 break;
1612 if (!game[gindex].htotal || status.engine == ENGINE_THINKING)
1613 break;
1615 wtimeout(boardw, -1);
1616 init_history(&game[gindex]);
1617 break;
1618 case 'u':
1619 /* FIXME dies reading FIFO sometimes. */
1620 if (game[gindex].mode != MODE_PLAY || !game[gindex].htotal)
1621 break;
1623 history_previous(&game[gindex], (count) ? count * 2 : 2, &crow, &ccol);
1624 oldhistorytotal = game[gindex].htotal;
1625 game[gindex].htotal = game[gindex].hindex;
1627 if (status.engine == CRAFTY)
1628 SEND_TO_ENGINE("read %s\n", config.fifo);
1629 else
1630 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1632 update_history_window(game[gindex]);
1633 break;
1634 case 'r':
1635 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
1636 BROWSER_PROMPT, browse_directory, NULL,
1637 '\t', -1)) == NULL)
1638 break;
1640 tmp = tilde_expand(tmp);
1642 if (parse_pgn_file(tmp))
1643 break;
1645 gindex = gtotal - 1;
1646 strncpy(loadfile, tmp, sizeof(loadfile));
1647 init_history(&game[gindex]);
1648 update_all(game[gindex]);
1649 update_tag_window(game[gindex].tag);
1650 break;
1651 case 'S':
1652 case 's':
1653 x = -1;
1655 if (gtotal > 1) {
1656 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
1657 GAME_SAVE_MULTI_TEXT);
1659 if (n == 'c')
1660 x = gindex;
1661 else if (n == 'a')
1662 x = -1;
1663 else {
1664 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
1665 break;
1669 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
1670 BROWSER_PROMPT, browse_directory, NULL,
1671 '\t', -1)) == NULL) {
1672 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
1673 break;
1676 tmp = tilde_expand(tmp);
1678 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
1679 == NULL) {
1680 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
1681 tmp = tfile;
1684 if (save_pgn(tmp, 0, x)) {
1685 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
1686 break;
1689 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
1690 update_all(game[gindex]);
1691 break;
1692 case CTRL('G'):
1693 n = 0;
1695 while (n != 'q') {
1696 n = help(GAME_HELP_INDEX_TITLE,
1697 GAME_HELP_INDEX_PROMPT, mainhelp);
1699 switch (n) {
1700 case 'h':
1701 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
1702 break;
1703 case 'p':
1704 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
1705 break;
1706 case 'e':
1707 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
1708 break;
1709 case 'g':
1710 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
1711 break;
1712 default:
1713 n = 'q';
1714 break;
1718 break;
1719 case 'n':
1720 case 'N':
1721 if (c == 'N') {
1722 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
1723 break;
1726 game[gindex].mode = MODE_PLAY;
1727 editmode = 0;
1728 game[gindex].sp.icon = 0;
1730 if (c == 'n') {
1731 newgameinit = 1;
1732 new_game();
1734 else {
1735 reset_history(game[gindex]);
1736 loadfile[0] = '\0';
1737 parse_pgn_file(loadfile);
1740 game[gindex].wcaptures = game[gindex].bcaptures = 0;
1741 crow = (game[gindex].side == WHITE) ? 2 : 7;
1742 ccol = 4;
1744 if (!noengine && (status.engine == ENGINE_OFFLINE ||
1745 engine_initialized == 0)) {
1746 if (start_chess_engine() < 0)
1747 break;
1750 SEND_TO_ENGINE("\nnew\n");
1751 set_engine_defaults();
1752 status.engine = ENGINE_READY;
1753 update_status_notify(game[gindex], NULL);
1754 update_all(game[gindex]);
1755 update_tag_window(game[gindex].tag);
1756 break;
1757 case CTRL('L'):
1758 case 'R':
1759 endwin();
1760 keypad(boardw, TRUE);
1761 update_panels();
1762 doupdate();
1763 break;
1764 case 'c':
1765 if (status.engine == ENGINE_THINKING)
1766 break;
1768 if (status.engine == ENGINE_OFFLINE)
1769 break;
1771 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL))
1772 != NULL) {
1773 SEND_TO_ENGINE("%s\n", tmp);
1775 break;
1776 case KEY_ESCAPE:
1777 game[gindex].sp.icon = game[gindex].sp.row = game[gindex].sp.col = 0;
1778 markend = markstart = 0;
1780 if (count) {
1781 count = 0;
1782 update_status_notify(game[gindex], NULL);
1785 if (config.validmoves)
1786 reset_valid_moves(game[gindex].b);
1788 break;
1789 case '0' ... '9':
1790 n = c - '0';
1792 if (count)
1793 count = count * 10 + n;
1794 else
1795 count = n;
1797 update_status_notify(game[gindex], "Repeat %i", count);
1798 continue;
1799 case KEY_UP:
1800 if (game[gindex].mode == MODE_HISTORY) {
1801 history_next(&game[gindex], (count > 0) ?
1802 config.jumpcount * count * movestep :
1803 config.jumpcount * movestep, &crow, &ccol);
1804 update_all(game[gindex]);
1805 break;
1809 if (sp.icon && config.validmoves) {
1810 get_valid_cursor(board, UP, (count) ? count : 1,
1811 &crow, &ccol, minr, maxr, minc, maxc);
1812 break;
1816 if (count) {
1817 crow += count;
1818 pushkey = '\n';
1820 else
1821 crow++;
1823 if (crow > 8)
1824 crow = 1;
1826 break;
1827 case KEY_DOWN:
1828 if (game[gindex].mode == MODE_HISTORY) {
1829 history_previous(&game[gindex], (count) ?
1830 config.jumpcount * count * movestep :
1831 config.jumpcount * movestep, &crow, &ccol);
1832 update_all(game[gindex]);
1833 break;
1837 if (sp.icon && config.validmoves) {
1838 get_valid_cursor(board, DOWN, (count) ? count : 1,
1839 &crow, &ccol, minr, maxr, minc, maxc);
1840 break;
1844 if (count) {
1845 crow -= count;
1846 pushkey = '\n';
1847 update_status_notify(game[gindex], NULL);
1849 else
1850 crow--;
1852 if (crow < 1)
1853 crow = 8;
1855 break;
1856 case KEY_LEFT:
1857 if (game[gindex].mode == MODE_HISTORY) {
1858 history_previous(&game[gindex], (count) ?
1859 count * movestep : movestep, &crow, &ccol);
1860 update_all(game[gindex]);
1861 break;
1865 if (sp.icon && config.validmoves) {
1866 get_valid_cursor(board, LEFT, (count) ? count : 1,
1867 &crow, &ccol, minr, maxr, minc, maxc);
1868 break;
1872 if (count) {
1873 ccol -= count;
1874 pushkey = '\n';
1876 else
1877 ccol--;
1879 if (ccol < 1)
1880 ccol = 8;
1882 break;
1883 case KEY_RIGHT:
1884 if (game[gindex].mode == MODE_HISTORY) {
1885 history_next(&game[gindex], (count) ? count * movestep
1886 : movestep, &crow, &ccol);
1887 update_all(game[gindex]);
1888 break;
1892 if (sp.icon && config.validmoves) {
1893 get_valid_cursor(board, RIGHT, (count) ? count : 1,
1894 &crow, &ccol, minr, maxr, minc, maxc);
1895 break;
1899 if (count) {
1900 ccol += count;
1901 pushkey = '\n';
1903 else
1904 ccol++;
1906 if (ccol > 8)
1907 ccol = 1;
1909 break;
1910 case 'w':
1911 if (game[gindex].mode == MODE_HISTORY)
1912 break;
1914 if (game[gindex].mode == MODE_EDIT)
1915 switch_turn(&game[gindex]);
1917 /* FIXME crafty. */
1918 SEND_TO_ENGINE("\nswitch\n");
1919 switch_side(&game[gindex]);
1920 update_status_window(game[gindex]);
1921 break;
1922 case ' ':
1923 if (!editmode && game[gindex].mode == MODE_HISTORY) {
1924 if (movestep == 1)
1925 movestep = 2;
1926 else
1927 movestep = 1;
1929 update_history_window(game[gindex]);
1930 break;
1933 if (!noengine && (status.engine == ENGINE_OFFLINE ||
1934 !engine_initialized) && !editmode) {
1935 if (start_chess_engine() < 0) {
1936 game[gindex].sp.icon = 0;
1937 break;
1942 if (!editmode)
1943 wtimeout(boardw, 70);
1945 if (game[gindex].sp.icon || (!editmode && status.engine == ENGINE_THINKING)) {
1946 beep();
1947 break;
1950 game[gindex].sp.icon = mvwinch(boardw, ROWTOMATRIX(crow),
1951 COLTOMATRIX(ccol)+1) & A_CHARTEXT;
1953 if (game[gindex].sp.icon == ' ') {
1954 game[gindex].sp.icon = 0;
1955 break;
1958 if (!editmode && ((islower(game[gindex].sp.icon) &&
1959 game[gindex].turn != BLACK) ||
1960 (isupper(game[gindex].sp.icon) &&
1961 game[gindex].turn != WHITE))) {
1962 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
1963 game[gindex].sp.icon = 0;
1964 break;
1967 game[gindex].sp.row = crow;
1968 game[gindex].sp.col = ccol;
1970 if (!editmode && config.validmoves) {
1971 get_valid_moves(&game[gindex],
1972 piece_to_int(game[gindex].sp.icon),
1973 game[gindex].sp.row, game[gindex].sp.col, &minr,
1974 &maxr, &minc, &maxc);
1976 number_valid_moves(board, sp.row, sp.col);
1980 if (game[gindex].mode == MODE_PLAY)
1981 paused = 0;
1982 break;
1983 case '\015':
1984 case '\n':
1985 pushkey = count = 0;
1986 update_status_notify(game[gindex], NULL);
1988 if (!editmode && game[gindex].mode == MODE_HISTORY)
1989 break;
1991 if (status.engine == ENGINE_THINKING) {
1992 beep();
1993 break;
1996 if (!game[gindex].sp.icon)
1997 break;
1999 game[gindex].sp.destrow = crow;
2000 game[gindex].sp.destcol = ccol;
2002 if (editmode) {
2003 edit_board(&game[gindex]);
2004 game[gindex].sp.icon = game[gindex].sp.row = game[gindex].sp.col = 0;
2005 break;
2008 if (move_to_engine(&game[gindex])) {
2009 if (config.validmoves)
2010 reset_valid_moves(game[gindex].b);
2012 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2013 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2014 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2018 break;
2019 case 'q':
2020 quit = 1;
2021 break;
2022 case 0:
2023 break;
2024 default:
2025 beep();
2026 break;
2029 count = 0;
2033 void usage(const char *pn, int ret)
2035 int i;
2037 for (i = 0; cmdlinehelp[i]; i++)
2038 fputs(cmdlinehelp[i], stderr);
2040 exit(ret);
2043 void catch_signal(int which)
2045 switch (which) {
2046 case SIGINT:
2047 stop_engine();
2048 endwin();
2049 exit(EXIT_FAILURE);
2050 break;
2051 case SIGPIPE:
2052 if (quit)
2053 break;
2055 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
2056 endwin();
2057 exit(EXIT_FAILURE);
2058 break;
2059 case SIGSTOP:
2060 savetty();
2061 break;
2062 case SIGCONT:
2063 resetty();
2064 keypad(boardw, TRUE);
2065 break;
2066 default:
2067 break;
2071 int main(int argc, char *argv[])
2073 int opt;
2074 struct stat st;
2075 char buf[FILENAME_MAX];
2076 char datadir[FILENAME_MAX];
2077 int ret = EXIT_SUCCESS;
2078 int validate = 0, validate_and_save = 0;
2080 if ((config.pwd = getpwuid(getuid())) == NULL)
2081 err(EXIT_FAILURE, "getpwuid()");
2083 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
2084 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
2085 config.ccfile = strdup(buf);
2086 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
2087 config.nagfile = strdup(buf);
2088 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
2089 config.agonyfile = strdup(buf);
2090 snprintf(buf, sizeof(buf), "%s/config", datadir);
2091 config.configfile = strdup(buf);
2092 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
2093 config.fifo = strdup(buf);
2094 snprintf(buf, sizeof(buf), "%s/tmpfile", datadir);
2095 config.tmpfile = strdup(buf);
2097 if (stat(datadir, &st) == -1) {
2098 if (errno == ENOENT) {
2099 if (mkdir(datadir, 0755) == -1)
2100 err(EXIT_FAILURE, "%s", datadir);
2102 else
2103 err(EXIT_FAILURE, "%s", datadir);
2105 stat(datadir, &st);
2108 if (!S_ISDIR(st.st_mode))
2109 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
2111 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
2112 if (mkfifo(config.fifo, 0600) == -1)
2113 err(EXIT_FAILURE, "%s", config.fifo);
2116 set_defaults();
2118 #ifdef DEBUG
2119 while ((opt = getopt(argc, argv, "EDNVShp:vu:e:f:i:")) != -1) {
2120 #else
2121 while ((opt = getopt(argc, argv, "ENVShp:vu:e:f:i:")) != -1) {
2122 #endif
2123 char *tmp;
2124 int i;
2126 switch (opt) {
2127 case 'E':
2128 config.stoponerror = 1;
2129 break;
2130 case 'N':
2131 noengine = 1;
2132 break;
2133 case 'S':
2134 validate_and_save = 1;
2135 case 'V':
2136 validate = 1;
2137 break;
2138 #ifdef DEBUG
2139 case 'D':
2140 debug = 1;
2141 break;
2142 #endif
2143 case 'u':
2144 i = 0;
2146 while ((tmp = strsep(&optarg, ":")) != NULL) {
2147 switch (i++) {
2148 case 0:
2149 config.ics_user = optarg;
2150 break;
2151 case 1:
2152 config.ics_passwd = optarg;
2153 break;
2154 default:
2155 usage(argv[0], EXIT_FAILURE);
2158 break;
2159 case 'i':
2160 i = 0;
2162 while ((tmp = strsep(&optarg, ":")) != NULL) {
2163 switch (i++) {
2164 case 0:
2165 strncpy(config.ics_server, tmp,
2166 sizeof(config.ics_server));
2167 break;
2168 case 1:
2169 if (!isinteger(tmp))
2170 usage(argv[0], EXIT_FAILURE);
2172 config.ics_port = atoi(tmp);
2173 break;
2174 default:
2175 usage(argv[0], EXIT_FAILURE);
2178 break;
2179 case 'v':
2180 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
2181 COPYRIGHT);
2182 exit(EXIT_SUCCESS);
2183 case 'p':
2184 filetype = PGN_FILE;
2185 strncpy(loadfile, optarg, sizeof(loadfile));
2186 break;
2187 case 'f':
2188 filetype = FEN_FILE;
2189 strncpy(loadfile, optarg, sizeof(loadfile));
2190 break;
2191 case 'e':
2192 filetype = EPD_FILE;
2193 strncpy(loadfile, optarg, sizeof(loadfile));
2194 break;
2195 case 'h':
2196 default:
2197 usage(argv[0], EXIT_SUCCESS);
2201 if ((validate || validate_and_save) && !*loadfile)
2202 usage(argv[0], EXIT_FAILURE);
2204 if (access(config.configfile, R_OK) == 0)
2205 parse_rcfile(config.configfile);
2207 signal(SIGPIPE, catch_signal);
2208 signal(SIGCONT, catch_signal);
2209 signal(SIGSTOP, catch_signal);
2210 signal(SIGINT, catch_signal);
2212 srandom(getpid());
2214 switch (filetype) {
2215 case PGN_FILE:
2216 ret = parse_pgn_file(loadfile);
2217 break;
2218 case FEN_FILE:
2219 //ret = parse_fen_file(loadfile);
2220 break;
2221 case EPD_FILE: // Not implemented.
2222 case NO_FILE:
2223 default:
2224 // No file specified. Empty game.
2225 ret = parse_pgn_file(NULL);
2226 break;
2229 if (validate || validate_and_save) {
2230 if (validate_and_save) {
2231 int i;
2233 for (i = 0; i < gtotal; i++)
2234 pgn_dumpgame(stdout, &game[i], i, 0);
2237 free_all_games();
2238 exit(ret);
2240 else if (ret)
2241 exit(ret);
2243 if (initscr() == NULL)
2244 errx(EXIT_FAILURE, "%s", E_INITCURSES);
2245 else
2246 curses_initialized = 1;
2248 if (has_colors() == TRUE && start_color() == OK)
2249 init_color_pairs();
2251 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
2252 boardp = new_panel(boardw);
2253 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
2254 COLS - HISTORY_WIDTH);
2255 historyp = new_panel(historyw);
2256 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
2257 statusp = new_panel(statusw);
2258 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
2259 tagp = new_panel(tagw);
2260 keypad(boardw, TRUE);
2261 // leaveok(boardw, TRUE);
2262 leaveok(tagw, TRUE);
2263 leaveok(statusw, TRUE);
2264 leaveok(historyw, TRUE);
2265 curs_set(0);
2266 cbreak();
2267 noecho();
2269 wbkgd(boardw, CP_BOARD_WINDOW);
2270 wbkgd(statusw, CP_STATUS_WINDOW);
2271 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2272 CP_STATUS_TITLE, CP_STATUS_BORDER);
2273 wbkgd(tagw, CP_TAG_WINDOW);
2274 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2275 CP_TAG_BORDER);
2276 wbkgd(historyw, CP_HISTORY_WINDOW);
2277 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2278 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2280 game_loop();
2281 stop_engine();
2283 endwin();
2284 free_all_games();
2285 del_panel(boardp);
2286 del_panel(historyp);
2287 del_panel(statusp);
2288 del_panel(tagp);
2289 delwin(boardw);
2290 delwin(historyw);
2291 delwin(statusw);
2292 delwin(tagw);
2293 exit(EXIT_SUCCESS);