fix for 'N' new games
[cboard.git] / src / cboard.c
blob4b485d525bfd25a190ee12c5b293dcfb4d07f37c
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].tag,
1178 game[gindex].tindex, "FEN");
1180 else {
1181 game[gindex].mode = MODE_EDIT;
1182 editmode = 1;
1185 update_all(game[gindex]);
1186 break;
1187 case '}':
1188 case '{':
1189 case '?':
1190 if (gtotal < 2)
1191 break;
1193 if (!*gameexp || c == '?') {
1194 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
1195 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
1196 NULL, 0, -1)) == NULL)
1197 break;
1199 strncpy(gameexp, tmp, sizeof(gameexp));
1202 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
1203 (count) ? count : 1)) == -1)
1204 break;
1206 gindex = n;
1207 init_history(&game[gindex]);
1208 update_all(game[gindex]);
1209 update_tag_window(game[gindex].tag);
1210 break;
1211 case '!':
1212 crow = 1;
1213 break;
1214 case '@':
1215 crow = 2;
1216 break;
1217 case '#':
1218 crow = 3;
1219 break;
1220 case '$':
1221 crow = 4;
1222 break;
1223 case '%':
1224 crow = 5;
1225 break;
1226 case '^':
1227 crow = 6;
1228 break;
1229 case '&':
1230 crow = 7;
1231 break;
1232 case '*':
1233 crow = 8;
1234 break;
1235 case 'A':
1236 ccol = 1;
1237 break;
1238 case 'B':
1239 ccol = 2;
1240 break;
1241 case 'C':
1242 ccol = 3;
1243 break;
1244 case 'D':
1245 ccol = 4;
1246 break;
1247 case 'E':
1248 ccol = 5;
1249 break;
1250 case 'F':
1251 ccol = 6;
1252 break;
1253 case 'G':
1254 ccol = 7;
1255 break;
1256 case 'H':
1257 ccol = 8;
1258 break;
1259 case '_':
1260 case '+':
1261 if (status.engine != ENGINE_READY)
1262 break;
1264 n = (count) ? count : 1;
1266 if (c == '_') {
1267 if (config.engine_depth - n < 0)
1268 n = 0;
1269 else
1270 n -= config.engine_depth;
1272 else
1273 n += config.engine_depth;
1275 SEND_TO_ENGINE("depth %i\n", abs(n));
1276 break;
1277 case ']':
1278 case '[':
1279 case '/':
1280 if (game[gindex].htotal < 2)
1281 break;
1283 n = 0;
1285 if (!*moveexp || c == '/') {
1286 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
1287 NULL, NULL, 0, -1)) == NULL)
1288 break;
1290 strncpy(moveexp, tmp, sizeof(moveexp));
1291 n = 1;
1294 if ((n = find_move_exp(game[gindex], moveexp, n,
1295 (c == '[') ? 0 : 1, (count) ? count : 1)) == -1)
1296 break;
1298 game[gindex].hindex = n;
1299 parse_history_move(game[gindex], game[gindex].hindex);
1300 update_all(game[gindex]);
1301 break;
1302 case 'v':
1303 view_annotation(game[gindex].hp[game[gindex].hindex]);
1304 break;
1305 case 'V':
1306 if (game[gindex].hindex - 1 >= 0)
1307 view_annotation(game[gindex].hp[game[gindex].hindex - 1]);
1308 break;
1309 case '>':
1310 game_next_prev(game[gindex], 1, (count) ? count : 1);
1312 if (delete_count) {
1313 markend = gindex;
1314 pushkey = 'x';
1315 delete_count = 0;
1318 game[gindex].mode = MODE_HISTORY;
1319 editmode = 0;
1320 break;
1321 case '<':
1322 game_next_prev(game[gindex], 0, (count) ? count : 1);
1324 if (delete_count) {
1325 markend = gindex;
1326 pushkey = 'x';
1327 delete_count = 0;
1330 game[gindex].mode = MODE_HISTORY;
1331 editmode = 0;
1332 break;
1333 case 'j':
1334 if (game[gindex].mode != MODE_HISTORY ||
1335 game[gindex].htotal < 2)
1336 break;
1339 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1340 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
1341 game[gindex].htotal)) == NULL)
1342 break;
1345 if (!count) {
1346 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1347 NULL, NULL, NULL, 0, -1)) == NULL)
1348 break;
1350 if (!isinteger(tmp))
1351 break;
1353 i = atoi(tmp);
1355 else
1356 i = count;
1358 if (i > (game[gindex].htotal / 2) || i < 0)
1359 break;
1361 game[gindex].hindex = i * 2;
1362 init_history(&game[gindex]);
1363 update_all(game[gindex]);
1364 break;
1365 case 'J':
1366 if (gtotal < 2)
1367 break;
1370 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
1371 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
1372 == NULL)
1373 break;
1376 if (!count) {
1377 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
1378 NULL, NULL, 0, -1)) == NULL)
1379 break;
1381 if (!isinteger(tmp))
1382 break;
1384 i = atoi(tmp);
1386 else
1387 i = count;
1389 if (--i > gtotal - 1 || i < 0)
1390 break;
1392 gindex = i;
1393 init_history(&game[gindex]);
1394 update_all(game[gindex]);
1395 update_tag_window(game[gindex].tag);
1396 break;
1397 case 'x':
1398 pushkey = 0;
1400 if (editmode) {
1401 if (game[gindex].sp.icon)
1402 game[gindex].b[ROWTOBOARD(game[gindex].sp.row)][COLTOBOARD(game[gindex].sp.col)].icon = int_to_piece(game[gindex], OPEN_SQUARE);
1403 else
1404 game[gindex].b[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = int_to_piece(game[gindex], OPEN_SQUARE);
1406 game[gindex].sp.icon = game[gindex].sp.row = game[gindex].sp.col = 0;
1407 break;
1410 if (gtotal < 2)
1411 break;
1413 if (count && !delete_count) {
1414 markstart = gindex;
1415 delete_count = 1;
1416 update_status_notify(game[gindex], "%s (delete)",
1417 status.notify);
1418 continue;
1421 if (markstart >= 0 && markend >= 0) {
1422 if (markstart > markend) {
1423 i = markstart;
1424 markstart = markend;
1425 markend = i;
1428 for (i = markstart; i <= markend; i++) {
1429 if (toggle_delete_flag(i))
1430 break;
1433 else {
1434 if (toggle_delete_flag(gindex))
1435 break;
1438 markstart = markend = -1;
1439 update_status_window(game[gindex]);
1440 break;
1441 case 'X':
1442 if (gtotal < 2) {
1443 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1444 break;
1447 tmp = NULL;
1449 for (i = n = 0; i < gtotal; i++) {
1450 if (TEST_FLAG(game[i].flags, GF_DELETE))
1451 n++;
1454 if (!n)
1455 tmp = GAME_DELETE_GAME_TEXT;
1456 else {
1457 if (n == gtotal) {
1458 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1459 break;
1462 tmp = GAME_DELETE_ALL_TEXT;
1465 if (config.deleteprompt) {
1466 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
1467 break;
1470 delete_game((!n) ? gindex : -1);
1471 init_history(&game[gindex]);
1472 update_all(game[gindex]);
1473 update_tag_window(game[gindex].tag);
1474 break;
1475 case 'a':
1476 annotate = game[gindex].hindex;
1478 if (annotate && game[gindex].hp[annotate - 1].move[0])
1479 annotate--;
1480 else
1481 break;
1483 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
1484 game[gindex].hp[annotate].move);
1486 anno = init_annotation_edit(gindex, annotate,
1487 game[gindex].hp[annotate]);
1488 tmp = get_input(buf, game[gindex].hp[annotate].comment,
1489 0, 0, NAG_PROMPT, history_edit_nag, (void *)anno,
1490 CTRL('T'), -1);
1492 if (!tmp && (!game[gindex].hp[annotate].comment ||
1493 !*game[gindex].hp[annotate].comment))
1494 break;
1495 else if (tmp && game[gindex].hp[annotate].comment) {
1496 if (strcmp(tmp, game[gindex].hp[annotate].comment) == 0)
1497 break;
1500 len = (tmp) ? strlen(tmp) + 1 : 1;
1502 game[gindex].hp[annotate].comment =
1503 Realloc(game[gindex].hp[annotate].comment, len);
1505 strncpy(game[gindex].hp[annotate].comment,
1506 (tmp) ? tmp : "", len);
1508 SET_FLAG(game[gindex].flags, GF_MODIFIED);
1509 update_all(game[gindex]);
1510 break;
1511 case 't':
1512 edit_save_tags(&game[gindex]);
1513 update_all(game[gindex]);
1514 update_tag_window(game[gindex].tag);
1515 break;
1516 case 'I':
1517 if (!editmode)
1518 break;
1520 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
1521 GAME_EDIT_TEXT);
1523 if (piece_to_int(c) == -1 && tolower(c) != 'x')
1524 break;
1526 if (tolower(c) == 'x')
1527 c = tolower(c);
1529 if (c == 'x' && (crow != 6 && crow != 3))
1530 break;
1532 if (c == 'x') {
1533 for (i = 0; i < 8; i++) {
1534 if (game[gindex].b[ROWTOBOARD(3)][COLTOBOARD(i)].icon == 'x')
1535 game[gindex].b[ROWTOBOARD(3)][COLTOBOARD(i)].icon = OPEN_SQUARE;
1536 if (game[gindex].b[ROWTOBOARD(6)][COLTOBOARD(i)].icon == 'x')
1537 game[gindex].b[ROWTOBOARD(6)][COLTOBOARD(i)].icon = OPEN_SQUARE;
1541 game[gindex].b[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
1542 break;
1543 case 'i':
1544 edit_tags(game[gindex], 0);
1545 break;
1546 case 'g':
1547 if (game[gindex].mode == MODE_HISTORY ||
1548 status.engine == ENGINE_THINKING)
1549 break;
1551 status.engine = ENGINE_THINKING;
1552 update_status_window(game[gindex]);
1553 SEND_TO_ENGINE("go\n");
1554 break;
1555 case 'b':
1556 if (config.book_method == -1 || status.engine ==
1557 ENGINE_THINKING || config.engine != GNUCHESS)
1558 break;
1560 if (config.book_method + 1 >= BOOK_MAX)
1561 n = 0;
1562 else
1563 n = config.book_method + 1;
1565 SEND_TO_ENGINE("book %s\n", book_methods[n]);
1566 break;
1567 case 'h':
1568 if (game[gindex].mode == MODE_HISTORY) {
1569 if (game[gindex].openingside == BLACK) {
1570 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
1571 break;
1574 if (game[gindex].hindex != game[gindex].htotal) {
1575 if (!pushkey) {
1576 if ((c = message(NULL, YESNO, "%s",
1577 GAME_RESUME_HISTORY_TEXT)) != 'y')
1578 break;
1581 else {
1582 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1583 break;
1586 if (!noengine)
1587 wtimeout(boardw, 70);
1589 if (!noengine && !engine_initialized) {
1590 if (start_chess_engine() < 0)
1591 break;
1593 pushkey = 'h';
1594 break;
1597 pushkey = 0;
1598 oldhistorytotal = game[gindex].htotal;
1599 game[gindex].htotal = game[gindex].hindex;
1600 game[gindex].mode = MODE_PLAY;
1601 status.engine = ENGINE_READY;
1603 /* FIXME crafty */
1604 if (config.engine != GNUCHESS)
1605 SEND_TO_ENGINE("read %s\n", config.fifo);
1606 else
1607 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1609 update_all(game[gindex]);
1610 break;
1613 if (!game[gindex].htotal || status.engine == ENGINE_THINKING)
1614 break;
1616 wtimeout(boardw, -1);
1617 init_history(&game[gindex]);
1618 break;
1619 case 'u':
1620 /* FIXME dies reading FIFO sometimes. */
1621 if (game[gindex].mode != MODE_PLAY || !game[gindex].htotal)
1622 break;
1624 history_previous(&game[gindex], (count) ? count * 2 : 2, &crow, &ccol);
1625 oldhistorytotal = game[gindex].htotal;
1626 game[gindex].htotal = game[gindex].hindex;
1628 if (status.engine == CRAFTY)
1629 SEND_TO_ENGINE("read %s\n", config.fifo);
1630 else
1631 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1633 update_history_window(game[gindex]);
1634 break;
1635 case 'r':
1636 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
1637 BROWSER_PROMPT, browse_directory, NULL,
1638 '\t', -1)) == NULL)
1639 break;
1641 tmp = tilde_expand(tmp);
1643 if (parse_pgn_file(tmp))
1644 break;
1646 gindex = gtotal - 1;
1647 strncpy(loadfile, tmp, sizeof(loadfile));
1648 init_history(&game[gindex]);
1649 update_all(game[gindex]);
1650 update_tag_window(game[gindex].tag);
1651 break;
1652 case 'S':
1653 case 's':
1654 x = -1;
1656 if (gtotal > 1) {
1657 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
1658 GAME_SAVE_MULTI_TEXT);
1660 if (n == 'c')
1661 x = gindex;
1662 else if (n == 'a')
1663 x = -1;
1664 else {
1665 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
1666 break;
1670 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
1671 BROWSER_PROMPT, browse_directory, NULL,
1672 '\t', -1)) == NULL) {
1673 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_ABORTED);
1674 break;
1677 tmp = tilde_expand(tmp);
1679 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
1680 == NULL) {
1681 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
1682 tmp = tfile;
1685 if (save_pgn(tmp, 0, x)) {
1686 update_status_notify(game[gindex], "%s", NOTIFY_SAVE_FAILED);
1687 break;
1690 update_status_notify(game[gindex], "%s", NOTIFY_SAVED);
1691 update_all(game[gindex]);
1692 break;
1693 case CTRL('G'):
1694 n = 0;
1696 while (n != 'q') {
1697 n = help(GAME_HELP_INDEX_TITLE,
1698 GAME_HELP_INDEX_PROMPT, mainhelp);
1700 switch (n) {
1701 case 'h':
1702 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
1703 break;
1704 case 'p':
1705 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
1706 break;
1707 case 'e':
1708 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
1709 break;
1710 case 'g':
1711 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
1712 break;
1713 default:
1714 n = 'q';
1715 break;
1719 break;
1720 case 'n':
1721 case 'N':
1722 if (c == 'N') {
1723 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
1724 break;
1727 game[gindex].mode = MODE_PLAY;
1728 editmode = 0;
1729 game[gindex].sp.icon = 0;
1731 if (c == 'n') {
1732 newgameinit = 1;
1733 new_game();
1735 else {
1736 reset_history(game[gindex]);
1737 parse_pgn_file(NULL);
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, "EDNVtShp:vu:e:f:i:")) != -1) {
2120 #else
2121 while ((opt = getopt(argc, argv, "ENVtShp:vu:e:f:i:")) != -1) {
2122 #endif
2123 char *tmp;
2124 int i;
2126 switch (opt) {
2127 case 't':
2128 save_custom_tags = 1;
2129 break;
2130 case 'E':
2131 config.stoponerror = 1;
2132 break;
2133 case 'N':
2134 noengine = 1;
2135 break;
2136 case 'S':
2137 validate_and_save = 1;
2138 case 'V':
2139 validate = 1;
2140 break;
2141 #ifdef DEBUG
2142 case 'D':
2143 debug = 1;
2144 break;
2145 #endif
2146 case 'u':
2147 i = 0;
2149 while ((tmp = strsep(&optarg, ":")) != NULL) {
2150 switch (i++) {
2151 case 0:
2152 config.ics_user = optarg;
2153 break;
2154 case 1:
2155 config.ics_passwd = optarg;
2156 break;
2157 default:
2158 usage(argv[0], EXIT_FAILURE);
2161 break;
2162 case 'i':
2163 i = 0;
2165 while ((tmp = strsep(&optarg, ":")) != NULL) {
2166 switch (i++) {
2167 case 0:
2168 strncpy(config.ics_server, tmp,
2169 sizeof(config.ics_server));
2170 break;
2171 case 1:
2172 if (!isinteger(tmp))
2173 usage(argv[0], EXIT_FAILURE);
2175 config.ics_port = atoi(tmp);
2176 break;
2177 default:
2178 usage(argv[0], EXIT_FAILURE);
2181 break;
2182 case 'v':
2183 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
2184 COPYRIGHT);
2185 exit(EXIT_SUCCESS);
2186 case 'p':
2187 filetype = PGN_FILE;
2188 strncpy(loadfile, optarg, sizeof(loadfile));
2189 break;
2190 case 'f':
2191 filetype = FEN_FILE;
2192 strncpy(loadfile, optarg, sizeof(loadfile));
2193 break;
2194 case 'e':
2195 filetype = EPD_FILE;
2196 strncpy(loadfile, optarg, sizeof(loadfile));
2197 break;
2198 case 'h':
2199 default:
2200 usage(argv[0], EXIT_SUCCESS);
2204 if ((validate || validate_and_save) && !*loadfile)
2205 usage(argv[0], EXIT_FAILURE);
2207 if (access(config.configfile, R_OK) == 0)
2208 parse_rcfile(config.configfile);
2210 signal(SIGPIPE, catch_signal);
2211 signal(SIGCONT, catch_signal);
2212 signal(SIGSTOP, catch_signal);
2213 signal(SIGINT, catch_signal);
2215 srandom(getpid());
2217 switch (filetype) {
2218 case PGN_FILE:
2219 ret = parse_pgn_file(loadfile);
2220 break;
2221 case FEN_FILE:
2222 //ret = parse_fen_file(loadfile);
2223 break;
2224 case EPD_FILE: // Not implemented.
2225 case NO_FILE:
2226 default:
2227 // No file specified. Empty game.
2228 ret = parse_pgn_file(NULL);
2229 break;
2232 if (validate || validate_and_save) {
2233 if (validate_and_save) {
2234 int i;
2236 for (i = 0; i < gtotal; i++)
2237 pgn_dumpgame(stdout, &game[i], i, 0);
2240 free_all_games();
2241 exit(ret);
2243 else if (ret)
2244 exit(ret);
2246 if (initscr() == NULL)
2247 errx(EXIT_FAILURE, "%s", E_INITCURSES);
2248 else
2249 curses_initialized = 1;
2251 if (has_colors() == TRUE && start_color() == OK)
2252 init_color_pairs();
2254 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
2255 boardp = new_panel(boardw);
2256 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
2257 COLS - HISTORY_WIDTH);
2258 historyp = new_panel(historyw);
2259 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
2260 statusp = new_panel(statusw);
2261 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
2262 tagp = new_panel(tagw);
2263 keypad(boardw, TRUE);
2264 // leaveok(boardw, TRUE);
2265 leaveok(tagw, TRUE);
2266 leaveok(statusw, TRUE);
2267 leaveok(historyw, TRUE);
2268 curs_set(0);
2269 cbreak();
2270 noecho();
2272 wbkgd(boardw, CP_BOARD_WINDOW);
2273 wbkgd(statusw, CP_STATUS_WINDOW);
2274 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2275 CP_STATUS_TITLE, CP_STATUS_BORDER);
2276 wbkgd(tagw, CP_TAG_WINDOW);
2277 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2278 CP_TAG_BORDER);
2279 wbkgd(historyw, CP_HISTORY_WINDOW);
2280 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2281 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2283 game_loop();
2284 stop_engine();
2286 endwin();
2287 free_all_games();
2288 del_panel(boardp);
2289 del_panel(historyp);
2290 del_panel(statusp);
2291 del_panel(tagp);
2292 delwin(boardw);
2293 delwin(historyw);
2294 delwin(statusw);
2295 delwin(tagw);
2296 exit(EXIT_SUCCESS);