Show the repeat count in the status window notification line.
[cboard.git] / src / cboard.c
blob1f6b349845c2b7123f50a902ac44965b3a94441d
1 /* $Id: cboard.c,v 1.98 2003-09-23 14:30:08 bjk Exp $ */
2 /*
3 Copyright (C) 2002-2003 Ben Kibbey <bjk@arbornet.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <err.h>
23 #include <sys/types.h>
24 #include <sys/time.h>
25 #include <sys/socket.h>
26 #include <sys/stat.h>
27 #include <string.h>
28 #include <panel.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <pwd.h>
32 #include <signal.h>
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
38 #ifdef HAVE_REGEX_H
39 #include <regex.h>
40 #endif
42 #include "common.h"
43 #include "colors.h"
44 #include "cboard.h"
46 char *random_agony()
48 static int index;
49 FILE *fp;
50 char line[LINE_MAX];
52 if (index == -1 || !config.agony || !curses_initialized ||
53 (status.mode == MODE_HISTORY && !config.historyagony))
54 return NULL;
56 if (!agony) {
57 if ((fp = fopen(config.agonyfile, "r")) == NULL) {
58 index = -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, (index + 2) * sizeof(char *));
66 agony[index++] = strdup(trim(line));
70 agony[index] = NULL;
71 fclose(fp);
73 if (agony[0] == NULL || !index) {
74 index = -1;
75 return NULL;
79 return agony[random() % index];
82 void draw_board(BOARD b, int crow, int ccol)
84 int row, col;
85 int bcol = 0, brow = 0;
86 int maxy = BOARD_HEIGHT, maxx = BOARD_WIDTH;
87 int ncols = 0, offset = 1;
88 unsigned coords_y = 8;
90 for (row = 0; row < maxy; row++) {
91 bcol = 0;
93 for (col = 0; col < maxx; col++) {
94 int attrwhich = -1;
95 int attrs = 0;
96 chtype piece, movecount = 0;
97 int bold = 0;
99 if (row == 0 || row == maxy - 2) {
100 if (col == 0)
101 mvwaddch(boardw, row, col,
102 LINE_GRAPHIC((row) ?
103 ACS_LLCORNER | CP_BOARD_GRAPHICS :
104 ACS_ULCORNER | CP_BOARD_GRAPHICS));
105 else if (col == maxx - 2)
106 mvwaddch(boardw, row, col,
107 LINE_GRAPHIC((row) ?
108 ACS_LRCORNER | CP_BOARD_GRAPHICS :
109 ACS_URCORNER | CP_BOARD_GRAPHICS));
110 else if (!(col % 4))
111 mvwaddch(boardw, row, col,
112 LINE_GRAPHIC((row) ?
113 ACS_BTEE | CP_BOARD_GRAPHICS :
114 ACS_TTEE | CP_BOARD_GRAPHICS));
115 else {
116 if (col != maxx - 1)
117 mvwaddch(boardw, row, col,
118 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
121 continue;
124 if ((row % 2) && col == maxx - 1 && coords_y) {
125 wattron(boardw, CP_BOARD_COORDS);
126 mvwprintw(boardw, row, col, "%d", coords_y--);
127 wattroff(boardw, CP_BOARD_COORDS);
128 continue;
131 if ((col == 0 || col == maxx - 2) && row != maxy - 1) {
132 if (!(row % 2))
133 mvwaddch(boardw, row, col,
134 LINE_GRAPHIC((col) ?
135 ACS_RTEE | CP_BOARD_GRAPHICS :
136 ACS_LTEE | CP_BOARD_GRAPHICS));
137 else
138 mvwaddch(boardw, row, col,
139 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
141 continue;
144 if ((row % 2) && !(col % 4) && row != maxy - 1) {
145 mvwaddch(boardw, row, col,
146 LINE_GRAPHIC(ACS_VLINE | CP_BOARD_GRAPHICS));
147 continue;
150 if (!(col % 4) && row != maxy - 1) {
151 mvwaddch(boardw, row, col,
152 LINE_GRAPHIC(ACS_PLUS | CP_BOARD_GRAPHICS));
153 continue;
156 if ((row % 2)) {
157 if ((col % 4)) {
158 if (ncols++ == 8) {
159 offset++;
160 ncols = 1;
163 if (((ncols % 2) && !(offset % 2)) || (!(ncols % 2)
164 && (offset % 2)))
165 attrwhich = BLACK;
166 else
167 attrwhich = WHITE;
169 if (config.validmoves && b[brow][bcol].valid) {
170 attrs = (attrwhich == WHITE) ? CP_BOARD_MOVES_WHITE :
171 CP_BOARD_MOVES_BLACK;
173 if (b[brow][bcol].movecount) {
174 if (brow + 1 != crow && bcol + 1 != ccol)
175 movecount = (b[brow][bcol].movecount + '0');
178 else
179 attrs = (attrwhich == WHITE) ? CP_BOARD_WHITE :
180 CP_BOARD_BLACK;
182 if (row == ROWTOMATRIX(crow) && col == COLTOMATRIX(ccol)) {
183 attrs = CP_BOARD_CURSOR;
186 if (row == ROWTOMATRIX(sp.row) &&
187 col == COLTOMATRIX(sp.col)) {
188 attrs = CP_BOARD_SELECTED;
191 if (row == maxy - 1)
192 attrs = 0;
194 mvwaddch(boardw, row, col, ' ' | attrs);
196 if (row == maxy - 1)
197 waddch(boardw, x_grid_chars[bcol] | CP_BOARD_COORDS);
198 else {
199 piece = b[row / 2][bcol].icon;
201 if (attrs & A_BOLD)
202 bold = 1;
204 if (status.side == WHITE && isupper(piece))
205 attrs |= A_BOLD;
206 else if (status.side == BLACK && islower(piece))
207 attrs |= A_BOLD;
209 waddch(boardw,
210 (piece && piece != int_to_piece(OPEN_SQUARE)) ?
211 piece | attrs : ' ' | attrs);
213 if (!bold)
214 attrs &= ~(A_BOLD);
217 if (movecount && row != maxy -1)
218 waddch(boardw, movecount | CP_BOARD_COUNT);
219 else
220 waddch(boardw, ' ' | attrs);
222 col += 2;
223 bcol++;
226 else {
227 if (col != maxx - 1)
228 mvwaddch(boardw, row, col,
229 LINE_GRAPHIC(ACS_HLINE | CP_BOARD_GRAPHICS));
233 brow = row / 2;
236 return;
239 void copy_board(BOARD s, BOARD d)
241 int row, col;
243 for (row = 0; row < 8; row++) {
244 for (col = 0; col < 8; col++) {
245 d[row][col].icon = s[row][col].icon;
246 d[row][col].valid = s[row][col].valid;
247 d[row][col].movecount = s[row][col].movecount;
251 return;
254 /* Convert the selected piece to SAN format and validate it. */
255 static char *board_to_san(BOARD b)
257 static char str[MAX_PGN_MOVE_LEN + 1], *p;
258 int piece;
259 int promo;
260 BOARD t;
262 snprintf(str, sizeof(str), "%c%i%c%i", x_grid_chars[sp.col - 1], sp.row,
263 x_grid_chars[sp.destcol - 1], sp.destrow);
265 p = str;
266 piece = piece_to_int(b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon);
268 if (piece == PAWN && ((sp.destrow == 8 && status.turn == WHITE) ||
269 (sp.destrow == 1 && status.turn == BLACK))) {
270 promo = cmessage(PROMOTION_TITLE, PROMOTION_PROMPT, PROMOTION_TEXT);
272 if (piece_to_int(promo) == -1)
273 return NULL;
275 p = str + strlen(str);
276 *p++ = toupper(promo);
277 *p = '\0';
280 copy_board(b, t);
282 if ((p = a2a4tosan(t, str)) == NULL) {
283 cmessage(p, ANYKEY, "%s", E_A2A4_PARSE);
284 return NULL;
287 validate_move = 1;
289 if (parse_move_text(t, p)) {
290 cmessage(ERROR, ANYKEY, "%s: %s", E_INVALID_MOVE, p);
291 validate_move = 0;
292 return NULL;
295 validate_move = 0;
296 return p;
299 static int move_to_engine(BOARD b)
301 char *p;
303 if ((p = board_to_san(b)) == NULL)
304 return 0;
306 SEND_TO_ENGINE("%s\n", p);
307 sp.row = sp.col = 0;
308 return 1;
311 char *book_method(int method)
313 char *book;
315 switch (method) {
316 case BOOK_BEST:
317 book = BOOK_BEST_STR;
318 break;
319 case BOOK_WORST:
320 book = BOOK_WORST_STR;
321 break;
322 case BOOK_PREFER:
323 book = BOOK_PREFER_STR;
324 break;
325 case BOOK_RANDOM:
326 book = BOOK_RANDOM_STR;
327 break;
328 case BOOK_OFF:
329 book = BOOK_OFF_STR;
330 break;
331 default:
332 book = UNKNOWN;
333 break;
336 return book;
339 static void update_clock(int n, int *h, int *m, int *s)
341 *h = n / 3600;
342 *m = (n % 3600) / 60;
343 *s = (n % 3600) % 60;
345 return;
348 void update_status_window()
350 int i = 0;
351 char buf[STATUS_WIDTH - 7];
352 char tmp[15], *engine, *mode;
353 int w = STATUS_WIDTH - 10;
354 int h, m, s;
355 char *p;
357 *tmp = '\0';
358 p = tmp;
360 if (TEST_FLAG(game[gindex].flags, GF_DELETE)) {
361 *p++ = '(';
362 *p++ = 'x';
363 i++;
366 if (TEST_FLAG(game[gindex].flags, GF_PERROR)) {
367 if (!i)
368 *p++ = '(';
369 else
370 *p++ = '/';
372 *p++ = '!';
375 if (TEST_FLAG(game[gindex].flags, GF_MODIFIED)) {
376 if (!i)
377 *p++ = '(';
378 else
379 *p++ = '/';
381 *p++ = '*';
384 if (*tmp != '\0')
385 *p++ = ')';
387 *p = '\0';
389 mvwprintw(statusw, 2, 1, "%*s %-*s", 7, STATUS_FILE_STR, w,
390 (loadfile[0]) ? str_etc(loadfile, w, 1) : UNAVAILABLE);
391 snprintf(buf, sizeof(buf), "%i %s %i %s", gindex + 1, N_OF_N_STR, gtotal,
392 (*tmp) ? tmp : "");
393 mvwprintw(statusw, 3, 1, "%*s %-*s", 7, STATUS_GAME_STR, w, buf);
395 switch (status.mode) {
396 case MODE_HISTORY:
397 mode = MODE_HISTORY_STR;
398 break;
399 case MODE_EDIT:
400 mode = MODE_EDIT_STR;
401 break;
402 case MODE_PLAY:
403 mode = MODE_PLAY_STR;
404 break;
405 default:
406 mode = UNKNOWN;
407 break;
410 mvwprintw(statusw, 4, 1, "%*s %-*s", 7, STATUS_MODE_STR, w, mode);
412 switch (status.engine) {
413 case ENGINE_THINKING:
414 engine = ENGINE_THINKING_STR;
415 break;
416 case ENGINE_READY:
417 engine = ENGINE_READY_STR;
418 break;
419 case ENGINE_INITIALIZING:
420 engine = ENGINE_INITIALIZING_STR;
421 break;
422 case ENGINE_OFFLINE:
423 engine = ENGINE_OFFLINE_STR;
424 break;
425 default:
426 engine = UNKNOWN;
427 break;
430 mvwprintw(statusw, 5, 1, "%*s %-*s", 7, STATUS_ENGINE_STR, w, " ");
431 wattron(statusw, CP_STATUS_ENGINE);
432 mvwaddstr(statusw, 5, 9, engine);
433 wattroff(statusw, CP_STATUS_ENGINE);
435 mvwprintw(statusw, 6, 1, "%*s %-*i", 7, STATUS_DEPTH_STR, w,
436 config.engine_depth);
438 mvwprintw(statusw, 7, 1, "%*s %-*s", 7, STATUS_BOOK_STR, w,
439 book_method(config.book_method));
441 mvwprintw(statusw, 8, 1, "%*s %-*s", 7, STATUS_TURN_STR, w,
442 (status.turn == WHITE) ? WHITE_STR : BLACK_STR);
444 strncpy(tmp, WHITE_STR, sizeof(tmp));
445 tmp[0] = toupper(tmp[0]);
446 update_clock(game[gindex].moveclock, &h, &m, &s);
447 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].wcaptures,
448 h, m, s);
449 mvwprintw(statusw, 9, 1, "%*s: %-*s", 6, tmp, w, buf);
451 strncpy(tmp, BLACK_STR, sizeof(tmp));
452 tmp[0] = toupper(tmp[0]);
453 update_clock(game[gindex].moveclock, &h, &m, &s);
454 snprintf(buf, sizeof(buf), "c/%-2i %.2i:%.2i:%.2i", game[gindex].bcaptures,
455 h, m, s);
456 mvwprintw(statusw, 10, 1, "%*s: %-*s", 6, tmp, w, buf);
458 for (i = 1; i < STATUS_WIDTH - 4; i++)
459 mvwprintw(statusw, STATUS_HEIGHT - 2, i, " ");
461 status.notify = (status.notify) ? status.notify : GAME_HELP_PROMPT;
463 if (status.notify) {
464 wattron(statusw, CP_STATUS_NOTIFY);
465 mvwprintw(statusw, STATUS_HEIGHT - 2,
466 CENTERX(STATUS_WIDTH, status.notify), "%s", status.notify);
467 wattroff(statusw, CP_STATUS_NOTIFY);
470 return;
473 void update_history_window()
475 char buf[HISTORY_WIDTH];
476 HISTORY h = {{0},NULL,{0}};
477 int index, total;
479 index = (game[gindex].hindex + 1) / 2;
481 if ((game[gindex].htotal % 2))
482 total = (game[gindex].htotal + 1) / 2;
483 else
484 total = game[gindex].htotal / 2;
486 if (game[gindex].htotal)
487 snprintf(buf, sizeof(buf), "%u %s %u%s", index, N_OF_N_STR, total,
488 (movestep == 1) ? HISTORY_MOVE_STEP : "");
489 else
490 strncpy(buf, UNAVAILABLE, sizeof(buf));
492 mvwprintw(historyw, 2, 1, "%*s %-*s", 10, HISTORY_MOVE_STR,
493 HISTORY_WIDTH - 13, buf);
495 if (get_history_by_index(game[gindex].hindex, &h))
496 memset(&h, 0, sizeof(HISTORY));
498 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
499 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_NEXT : "");
500 mvwprintw(historyw, 3, 1, "%s %-*s", HISTORY_MOVE_NEXT_STR,
501 HISTORY_WIDTH - 13, buf);
503 if (get_history_by_index(game[gindex].hindex - 1, &h))
504 memset(&h, 0, sizeof(HISTORY));
506 snprintf(buf, sizeof(buf), "%s %s", (h.move[0]) ? h.move : UNAVAILABLE,
507 ((h.comment && h.comment[0]) || h.nag[0]) ? HISTORY_ANNO_PREV : "");
508 mvwprintw(historyw, 4, 1, "%s %-*s", HISTORY_MOVE_PREV_STR,
509 HISTORY_WIDTH - 13, buf);
510 return;
513 void update_tag_window()
515 int i;
516 int w = TAG_WIDTH - 10;
518 for (i = 0; i < 7; i++) {
519 char *value = game[gindex].tag[i].value;
520 int n;
522 if ((*value == '?' || *value == '-') && value[1] == '\0')
523 value = UNAVAILABLE;
524 else if (strcmp(game[gindex].tag[i].name, "Result") == 0) {
525 for (n = 0; n < NARRAY(fancy_results); n++) {
526 if (strcmp(value, fancy_results[n].pgn) == 0) {
527 value = fancy_results[n].fancy;
528 break;
533 value = str_etc(value, w, 0);
535 mvwprintw(tagw, (i + 2), 1, "%*s: %-*s", 6, game[gindex].tag[i].name,
536 w, value);
539 return;
542 void draw_prompt(WINDOW *win, int y, int width, const char *str, chtype attr)
544 int i;
546 wattron(win, attr);
548 for (i = 1; i < width - 1; i++)
549 mvwaddch(win, y, i, ' ');
551 mvwprintw(win, y, CENTERX(width, str), "%s", str);
553 wattroff(win, attr);
554 return;
557 void draw_window_title(WINDOW *win, const char *title, int width, chtype attr,
558 chtype battr)
560 int i;
562 if (title) {
563 wattron(win, attr);
565 for (i = 1; i < width - 1; i++)
566 mvwaddch(win, 1, i, ' ');
568 mvwprintw(win, 1, CENTERX(width, title), "%s", title);
569 wattroff(win, attr);
572 wattron(win, battr);
573 box(win, ACS_VLINE, ACS_HLINE);
574 wattroff(win, battr);
576 return;
579 void update_all()
581 update_status_window();
582 update_history_window();
583 return;
586 static void game_next_prev(int n, int count)
588 if (gtotal < 2)
589 return;
591 if (n == 1) {
592 if (gindex + count > gtotal - 1) {
593 if (count != 1)
594 gindex = gtotal - 1;
595 else
596 gindex = 0;
598 else
599 gindex += count;
601 else {
602 if (gindex - count < 0) {
603 if (count != 1)
604 gindex = 0;
605 else
606 gindex = gtotal - 1;
608 else
609 gindex -= count;
612 init_history(board);
613 update_all();
614 update_tag_window();
615 return;
618 void free_game_data()
620 int i;
622 if (!gtotal)
623 return;
625 for (i = 0; i < gtotal; i++) {
626 free_historydata(&game[i].history, 0, game[i].htotal);
627 free(game[i].history);
628 free_tag_data(game[i].tag, game[i].tindex);
629 free(game[i].tag);
632 return;
635 static void delete_game(int which)
637 GAME *g = NULL;
638 int gi = 0;
639 int i;
641 for (i = 0; i < gtotal; i++) {
642 if (i == which || TEST_FLAG(game[i].flags, GF_DELETE)) {
643 free_historydata(&game[i].history, 0, game[i].htotal);
644 free(game[i].history);
645 free_tag_data(game[i].tag, game[i].tindex);
646 free(game[i].tag);
647 continue;
650 g = Realloc(g, (gi + 2) * sizeof(GAME));
652 memcpy(&g[gi], &game[i], sizeof(GAME));
654 g[gi].tag = game[i].tag;
655 g[gi].history = game[i].history;
656 gi++;
659 game = g;
660 gtotal = gi;
662 if (which != -1) {
663 if (which + 1 >= gtotal)
664 gindex = gtotal - 1;
665 else
666 gindex = which;
668 else
669 gindex = gtotal - 1;
671 return;
674 /* FIXME dont show out of reach counts. Diagonals. */
676 static void number_valid_moves(BOARD b, int srow, int scol)
678 int row, col;
679 int count;
681 for (row = srow + 1, col = scol, count = 1; VALIDFILE(row); row++) {
682 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
683 continue;
685 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
688 for (row = srow - 1, col = scol, count = 1; VALIDFILE(row); row--) {
689 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
690 continue;
692 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
695 for (col = scol + 1, row = srow, count = 1; VALIDFILE(col); col++) {
696 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
697 continue;
699 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
702 for (col = scol - 1, row = srow, count = 1; VALIDFILE(col); col--) {
703 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
704 continue;
706 b[ROWTOBOARD(row)][COLTOBOARD(col)].movecount = count++;
709 return;
714 static void get_valid_cursor(BOARD b, int which, int count, int *crow,
715 int *ccol, int minr, int maxr, int minc, int maxc)
717 int row, col;
718 int incr, cincr;
720 if (which == UP || which == RIGHT)
721 incr = 1;
722 else
723 incr = -(1);
725 switch (which) {
726 case UP:
727 case DOWN:
728 if (count > 1) {
729 row = *crow;
731 if (which == UP)
732 *crow += count;
733 else
734 *crow -= count;
736 if (!VALIDFILE(*crow))
737 *crow = row;
740 for (row = *crow + incr, col = *ccol; VALIDFILE(row); row += incr) {
741 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
742 continue;
744 *crow = row;
745 goto done;
748 break;
749 case RIGHT:
750 case LEFT:
751 if (count > 1) {
752 col = *ccol;
754 if (which == RIGHT)
755 *ccol += count;
756 else
757 *ccol -= count;
759 if (!VALIDFILE(*ccol))
760 *ccol = col;
763 for (col = *ccol + incr, row = *crow; VALIDFILE(col); col += incr) {
764 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
765 continue;
767 *ccol = col;
768 goto done;
771 break;
772 default:
773 break;
776 if (*ccol < sp.col || (which == DOWN || which == LEFT))
777 cincr = -(1);
778 else
779 cincr = 1;
781 if (*ccol < sp.col && (which == DOWN || which == LEFT))
782 cincr = 1;
784 for (row = *crow + incr; VALIDFILE(row); row += incr) {
785 for (col = *ccol + cincr; VALIDFILE(col); col += cincr) {
786 if (!b[ROWTOBOARD(row)][COLTOBOARD(col)].valid)
787 continue;
789 *crow = row;
790 *ccol = col;
791 goto done;
795 done:
796 number_valid_moves(board, *crow, *ccol);
797 return;
801 static int find_move_exp(const char *str, int init, int which, int count)
803 int i;
804 int ret;
805 static regex_t r;
806 static int firstrun = 1;
807 char errbuf[255];
808 int incr;
809 int found;
811 if (init) {
812 if (!firstrun)
813 regfree(&r);
815 if ((ret = regcomp(&r, str, REG_EXTENDED|REG_NOSUB)) != 0) {
816 regerror(ret, &r, errbuf, sizeof(errbuf));
817 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
818 return -1;
821 firstrun = 1;
824 incr = (which == 0) ? -(1) : 1;
826 for (i = game[gindex].hindex + incr - 1, found = 0; ; i += incr) {
827 if (i == game[gindex].hindex - 1)
828 break;
830 if (i > game[gindex].htotal)
831 i = 0;
832 else if (i < 0)
833 i = game[gindex].htotal;
835 ret = regexec(&r, game[gindex].history[i].move, 0, 0, 0);
837 if (ret == 0) {
838 if (count == ++found) {
839 return i + 1;
842 else {
843 if (ret != REG_NOMATCH) {
844 regerror(ret, &r, errbuf, sizeof(errbuf));
845 cmessage(E_REGEXEC_TITLE, ANYKEY, "%s", errbuf);
846 return -1;
851 return -1;
854 static int toggle_delete_flag(int index)
856 int i, n;
858 if (TEST_FLAG(game[index].flags, GF_DELETE))
859 CLEAR_FLAG(game[index].flags, GF_DELETE);
860 else
861 SET_FLAG(game[index].flags, GF_DELETE);
864 for (i = n = 0; i < gtotal; i++) {
865 if (TEST_FLAG(game[i].flags, GF_DELETE))
866 n++;
869 if (n == gtotal) {
870 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
871 CLEAR_FLAG(game[index].flags, GF_DELETE);
872 return 1;
875 return 0;
878 static void edit_save_tags(int index)
880 int i;
881 TAG *t;
883 if ((t = edit_tags(board, game[index].tag, game[index].tindex, 1)) == NULL)
884 return;
886 game[index].tindex = 0;
888 for (i = 0; t[i].name; i++) {
889 add_tag(&game[index].tag, &game[index].tindex, t[i].name, t[i].value);
892 free_tag_data(t, i);
893 free(t);
894 SET_FLAG(game[index].flags, GF_MODIFIED);
895 return;
898 static int find_game_exp(char *str, int which, int count)
900 char *nstr = NULL, *exp = NULL;
901 regex_t nexp, vexp;
902 int ret = -1;
903 int g = 0;
904 char buf[255], *tmp;
905 char errbuf[255];
906 int found = 0;
907 int incr = (which == 0) ? -(1) : 1;
909 strncpy(buf, str, sizeof(buf));
910 tmp = buf;
912 if (strstr(tmp, ":") != NULL) {
913 nstr = strsep(&tmp, ":");
915 if ((ret = regcomp(&nexp, nstr,
916 REG_ICASE|REG_EXTENDED|REG_NOSUB)) != 0) {
917 regerror(ret, &nexp, errbuf, sizeof(errbuf));
918 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
919 ret = g = -1;
920 goto cleanup;
924 exp = tmp;
926 if (exp == NULL)
927 goto cleanup;
929 if ((ret = regcomp(&vexp, exp, REG_EXTENDED|REG_NOSUB)) != 0) {
930 regerror(ret, &vexp, errbuf, sizeof(errbuf));
931 cmessage(E_REGCOMP_TITLE, ANYKEY, "%s", errbuf);
932 ret = -1;
933 goto cleanup;
936 ret = -1;
938 for (g = gindex + incr, found = 0; ; g += incr) {
939 int t;
941 if (g == gindex)
942 break;
944 if (g == gtotal)
945 g = 0;
946 else if (g < 0)
947 g = gtotal - 1;
949 for (t = 0; t < game[g].tindex; t++) {
950 if (nstr) {
951 if (regexec(&nexp, game[g].tag[t].name, 0, 0, 0) == 0) {
952 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
953 if (count == ++found) {
954 ret = g;
955 goto cleanup;
960 else {
961 if (regexec(&vexp, game[g].tag[t].value, 0, 0, 0) == 0) {
962 if (count == ++found) {
963 ret = g;
964 goto cleanup;
970 ret = -1;
973 cleanup:
974 if (nstr)
975 regfree(&nexp);
977 if (g != -1)
978 regfree(&vexp);
980 return ret;
983 void edit_board(BOARD b)
985 chtype p;
987 p = b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon;
988 b[ROWTOBOARD(sp.destrow)][COLTOBOARD(sp.destcol)].icon = p;
989 b[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
990 int_to_piece(OPEN_SQUARE);
992 return;
995 // Updates the notification line in the status window then refreshes the
996 // status window.
997 void update_status_notify(char *fmt, ...)
999 va_list ap;
1000 #ifdef HAVE_VASPRINTF
1001 char *line;
1002 #else
1003 char line[COLS];
1004 #endif
1006 if (!fmt) {
1007 if (status.notify) {
1008 free(status.notify);
1009 status.notify = NULL;
1010 update_status_window();
1013 return;
1016 va_start(ap, fmt);
1017 #ifdef HAVE_VASPRINTF
1018 vasprintf(&line, fmt, ap);
1019 #else
1020 vsnprintf(line, sizeof(line), fmt, ap);
1021 #endif
1022 va_end(ap);
1024 if (status.notify)
1025 free(status.notify);
1027 status.notify = strdup(line);
1029 #ifdef HAVE_VASPRINTF
1030 free(line);
1031 #endif
1032 update_status_window();
1035 void game_loop()
1037 int error_recover = 0;
1038 int pushkey = 0;
1039 int count = 0;
1040 int crow = 2, ccol = 5;
1041 char moveexp[255] = {0};
1042 char gameexp[255] = {0};
1043 int delete_count = 0;
1044 int markstart = -1, markend = -1;
1045 int editmode = 0;
1047 /* This is to just initialize the default tags etc. */
1048 parse_pgn_file(board, moveexp);
1049 draw_board(board, crow, ccol);
1050 update_tag_window();
1051 update_all();
1053 switch (filetype) {
1054 case PGN_FILE:
1055 if (parse_pgn_file(board, loadfile))
1056 loadfile[0] = '\0';
1057 break;
1058 case FEN_FILE:
1059 if (parse_fen_file(board, loadfile))
1060 loadfile[0] = '\0';
1061 break;
1062 case EPD_FILE:
1063 default:
1064 break;
1067 gindex = gtotal - 1;
1068 markstart = -1, markend = -1;
1070 if (loadfile[0])
1071 init_history(board);
1073 update_status_notify("%s", GAME_HELP_PROMPT);
1074 movestep = 2;
1075 paused = 1;
1077 flushinp();
1078 update_all();
1079 update_tag_window();
1081 while (!quit) {
1082 int c = 0;
1083 fd_set fds;
1084 int i, x, n = 0, len = 0;
1085 char fdbuf[8192] = {0};
1086 struct timeval tv;
1087 char *tmp = NULL;
1088 char buf[78];
1089 char tfile[FILENAME_MAX];
1090 int minr, maxr, minc, maxc;
1092 if (engine_initialized) {
1093 tv.tv_sec = 0;
1094 tv.tv_usec = 0;
1096 FD_ZERO(&fds);
1097 FD_SET(enginefd[0], &fds);
1099 for (i = 0; i < gtotal; i++) {
1100 if (game[i].sockfd > 0) {
1101 if (game[i].sockfd > n)
1102 n = game[i].sockfd;
1104 FD_SET(game[i].sockfd, &fds);
1108 n = (n > enginefd[0]) ? n : enginefd[0];
1110 if ((n = select(n + 1, &fds, NULL, NULL, &tv)) > 0) {
1111 if (FD_ISSET(enginefd[0], &fds)) {
1112 len = read(enginefd[0], fdbuf, sizeof(fdbuf));
1114 if (len == -1) {
1115 if (errno != EAGAIN) {
1116 cmessage(ERROR, ANYKEY, "Attempt #%i. read(): %s",
1117 ++error_recover, strerror(errno));
1118 continue;
1121 else {
1122 if (len) {
1123 parse_engine_output(board, fdbuf);
1124 update_all();
1129 for (i = 0; i < gtotal; i++) {
1130 if (game[i].sockfd <= 0)
1131 continue;
1133 if (FD_ISSET(game[i].sockfd, &fds)) {
1134 len = recv(game[i].sockfd, fdbuf, sizeof(fdbuf), 0);
1136 if (len == -1) {
1137 if (errno != EAGAIN) {
1138 cmessage(ERROR, ANYKEY,
1139 "Attempt #%i. recv(): %s",
1140 ++error_recover, strerror(errno));
1141 continue;
1144 else {
1145 if (len)
1146 parse_ics_output(fdbuf);
1148 update_all();
1153 else {
1154 if (n == -1)
1155 cmessage(ERROR, ANYKEY, "select(): %s", strerror(errno));
1156 else {
1157 /* timeout */
1162 error_recover = 0;
1163 draw_board(board, crow, ccol);
1165 wmove(boardw, ROWTOMATRIX(crow), COLTOMATRIX(ccol));
1167 if (!paused) {
1170 update_panels();
1171 doupdate();
1173 if (pushkey)
1174 c = pushkey;
1175 else {
1176 if ((c = wgetch(boardw)) == ERR)
1177 continue;
1180 switch (c) {
1181 int annotate;
1183 case 'p':
1184 if (paused)
1185 paused = 0;
1186 else
1187 paused = 1;
1189 break;
1190 case 'e':
1191 if (game[gindex].htotal)
1192 break;
1194 if (editmode) {
1195 editmode = 0;
1196 add_tag(&game[gindex].tag, &game[gindex].tindex,
1197 "FEN", board_to_fen(board, game[gindex]));
1198 status.mode = MODE_PLAY;
1199 game[gindex].fentag = game[gindex].tindex - 1;
1201 else {
1202 status.mode = MODE_EDIT;
1203 editmode = 1;
1206 update_all();
1207 break;
1208 case '}':
1209 case '{':
1210 case '?':
1211 if (gtotal < 2)
1212 break;
1214 if (!*gameexp || c == '?') {
1215 if ((tmp = get_input(GAME_FIND_EXPRESSION_TITLE, gameexp,
1216 1, 1, GAME_FIND_EXPRESSION_PROMPT, NULL,
1217 NULL, 0, -1)) == NULL)
1218 break;
1220 strncpy(gameexp, tmp, sizeof(gameexp));
1223 if ((n = find_game_exp(gameexp, (c == '{') ? 0 : 1,
1224 (count) ? count : 1)) == -1)
1225 break;
1227 gindex = n;
1228 init_history(board);
1229 update_all();
1230 update_tag_window();
1231 break;
1232 case '!':
1233 crow = 1;
1234 break;
1235 case '@':
1236 crow = 2;
1237 break;
1238 case '#':
1239 crow = 3;
1240 break;
1241 case '$':
1242 crow = 4;
1243 break;
1244 case '%':
1245 crow = 5;
1246 break;
1247 case '^':
1248 crow = 6;
1249 break;
1250 case '&':
1251 crow = 7;
1252 break;
1253 case '*':
1254 crow = 8;
1255 break;
1256 case 'A':
1257 ccol = 1;
1258 break;
1259 case 'B':
1260 ccol = 2;
1261 break;
1262 case 'C':
1263 ccol = 3;
1264 break;
1265 case 'D':
1266 ccol = 4;
1267 break;
1268 case 'E':
1269 ccol = 5;
1270 break;
1271 case 'F':
1272 ccol = 6;
1273 break;
1274 case 'G':
1275 ccol = 7;
1276 break;
1277 case 'H':
1278 ccol = 8;
1279 break;
1280 case '_':
1281 case '+':
1282 if (status.engine != ENGINE_READY)
1283 break;
1285 n = (count) ? count : 1;
1287 if (c == '_') {
1288 if (config.engine_depth - n < 0)
1289 n = 0;
1290 else
1291 n -= config.engine_depth;
1293 else
1294 n += config.engine_depth;
1296 SEND_TO_ENGINE("depth %i\n", abs(n));
1297 break;
1298 case ']':
1299 case '[':
1300 case '/':
1301 if (game[gindex].htotal < 2)
1302 break;
1304 n = 0;
1306 if (!*moveexp || c == '/') {
1307 if ((tmp = get_input(FIND_REGEXP, moveexp, 1, 1, NULL,
1308 NULL, NULL, 0, -1)) == NULL)
1309 break;
1311 strncpy(moveexp, tmp, sizeof(moveexp));
1312 n = 1;
1315 if ((n = find_move_exp(moveexp, n, (c == '[') ? 0 : 1,
1316 (count) ? count : 1)) == -1)
1317 break;
1319 game[gindex].hindex = n;
1320 parse_history_move(board, game[gindex].hindex);
1321 update_all();
1322 break;
1323 case 'v':
1324 view_annotation(game[gindex].hindex);
1325 break;
1326 case 'V':
1327 view_annotation(game[gindex].hindex - 1);
1328 break;
1329 case '>':
1330 game_next_prev(1, (count) ? count : 1);
1332 if (delete_count) {
1333 markend = gindex;
1334 pushkey = 'x';
1335 delete_count = 0;
1338 status.mode = MODE_HISTORY;
1339 editmode = 0;
1340 break;
1341 case '<':
1342 game_next_prev(0, (count) ? count : 1);
1344 if (delete_count) {
1345 markend = gindex;
1346 pushkey = 'x';
1347 delete_count = 0;
1350 status.mode = MODE_HISTORY;
1351 editmode = 0;
1352 break;
1353 case 'j':
1354 if (status.mode != MODE_HISTORY || game[gindex].htotal < 2)
1355 break;
1358 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1359 NULL, NULL, NULL, 0, FIELD_TYPE_INTEGER, 1, 0,
1360 game[gindex].htotal)) == NULL)
1361 break;
1364 if (!count) {
1365 if ((tmp = get_input(GAME_HISTORY_JUMP_TITLE, NULL, 1, 1,
1366 NULL, NULL, NULL, 0, -1)) == NULL)
1367 break;
1369 if (!isinteger(tmp))
1370 break;
1372 i = atoi(tmp);
1374 else
1375 i = count;
1377 if (i > game[gindex].htotal || i < 0)
1378 break;
1380 game[gindex].hindex = i * 2;
1381 init_history(board);
1382 update_all();
1383 break;
1384 case 'J':
1385 if (gtotal < 2)
1386 break;
1389 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL, NULL,
1390 NULL, 0, FIELD_TYPE_INTEGER, 1, 1, gtotal))
1391 == NULL)
1392 break;
1395 if (!count) {
1396 if ((tmp = get_input(GAME_JUMP_TITLE, NULL, 1, 1, NULL,
1397 NULL, NULL, 0, -1)) == NULL)
1398 break;
1400 if (!isinteger(tmp))
1401 break;
1403 i = atoi(tmp);
1405 else
1406 i = count;
1408 if (--i > gtotal - 1 || i < 0)
1409 break;
1411 gindex = i;
1412 init_history(board);
1413 update_all();
1414 update_tag_window();
1415 break;
1416 case 'x':
1417 pushkey = 0;
1419 if (editmode) {
1420 if (sp.icon)
1421 board[ROWTOBOARD(sp.row)][COLTOBOARD(sp.col)].icon =
1422 int_to_piece(OPEN_SQUARE);
1423 else
1424 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon =
1425 int_to_piece(OPEN_SQUARE);
1427 sp.icon = sp.row = sp.col = 0;
1428 break;
1431 if (gtotal < 2)
1432 break;
1434 if (count && !delete_count) {
1435 markstart = gindex;
1436 delete_count = 1;
1437 continue;
1440 if (markstart >= 0 && markend >= 0) {
1441 if (markstart > markend) {
1442 i = markstart;
1443 markstart = markend;
1444 markend = i;
1447 for (i = markstart; i <= markend; i++) {
1448 if (toggle_delete_flag(i))
1449 break;
1452 else {
1453 if (toggle_delete_flag(gindex))
1454 break;
1457 markstart = markend = -1;
1458 update_status_window();
1459 break;
1460 case 'X':
1461 if (gtotal < 2) {
1462 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1463 break;
1466 tmp = NULL;
1468 for (i = n = 0; i < gtotal; i++) {
1469 if (TEST_FLAG(game[i].flags, GF_DELETE))
1470 n++;
1473 if (!n)
1474 tmp = GAME_DELETE_GAME_TEXT;
1475 else {
1476 if (n == gtotal) {
1477 cmessage(NULL, ANYKEY, "%s", E_DELETE_GAME);
1478 break;
1481 tmp = GAME_DELETE_ALL_TEXT;
1484 if (config.deleteprompt) {
1485 if ((c = cmessage(NULL, YESNO, "%s", tmp)) != 'y')
1486 break;
1489 delete_game((!n) ? gindex : -1);
1490 init_history(board);
1491 update_all();
1492 update_tag_window();
1493 break;
1494 case 'a':
1495 annotate = game[gindex].hindex;
1497 if (annotate && game[gindex].history[annotate - 1].move[0])
1498 annotate--;
1499 else
1500 break;
1502 snprintf(buf, sizeof(buf), "%s \"%s\"", ANNOTATION_EDIT_TITLE,
1503 game[gindex].history[annotate].move);
1505 tmp = get_input(buf, game[gindex].history[annotate].comment,
1506 0, 0, NAG_PROMPT, history_edit_nag, (void *)annotate,
1507 CTRL('T'), -1);
1509 if (!tmp && (!game[gindex].history[annotate].comment ||
1510 !*game[gindex].history[annotate].comment))
1511 break;
1512 else if (tmp && game[gindex].history[annotate].comment) {
1513 if (strcmp(tmp, game[gindex].history[annotate].comment)
1514 == 0)
1515 break;
1518 len = (tmp) ? strlen(tmp) + 1 : 1;
1520 game[gindex].history[annotate].comment =
1521 Realloc(game[gindex].history[annotate].comment, len);
1523 strncpy(game[gindex].history[annotate].comment,
1524 (tmp) ? tmp : "", len);
1526 SET_FLAG(game[gindex].flags, GF_MODIFIED);
1528 update_all();
1529 break;
1530 case 't':
1531 edit_save_tags(gindex);
1532 update_all();
1533 update_tag_window();
1534 break;
1535 case 'I':
1536 if (!editmode)
1537 break;
1539 c = message(GAME_EDIT_TITLE, GAME_EDIT_PROMPT, "%s",
1540 GAME_EDIT_TEXT);
1542 if (piece_to_int(c) == -1)
1543 break;
1545 board[ROWTOBOARD(crow)][COLTOBOARD(ccol)].icon = c;
1546 break;
1547 case 'i':
1548 edit_tags(board, game[gindex].tag, game[gindex].tindex, 0);
1549 break;
1550 case 'g':
1551 if (status.mode == MODE_HISTORY ||
1552 status.engine == ENGINE_THINKING)
1553 break;
1555 status.engine = ENGINE_THINKING;
1556 update_status_window();
1557 SEND_TO_ENGINE("go\n");
1558 break;
1559 case 'b':
1560 if (config.book_method == -1 || status.engine ==
1561 ENGINE_THINKING || config.engine != GNUCHESS)
1562 break;
1564 if (config.book_method + 1 >= BOOK_MAX)
1565 n = 0;
1566 else
1567 n = config.book_method + 1;
1569 SEND_TO_ENGINE("book %s\n", book_methods[n]);
1570 break;
1571 case 'h':
1572 if (status.mode == MODE_HISTORY) {
1573 if (game[gindex].openingside == BLACK) {
1574 cmessage(NULL, ANYKEY, "%s", E_RESUME_BLACK);
1575 break;
1578 if (game[gindex].hindex != game[gindex].htotal) {
1579 if (!pushkey) {
1580 if ((c = message(NULL, YESNO, "%s",
1581 GAME_RESUME_HISTORY_TEXT)) != 'y')
1582 break;
1585 else {
1586 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER))
1587 break;
1590 wtimeout(boardw, 70);
1592 if (!engine_initialized) {
1593 if (start_chess_engine() < 0)
1594 break;
1596 pushkey = 'h';
1597 break;
1600 pushkey = 0;
1601 oldhistorytotal = game[gindex].htotal;
1602 game[gindex].htotal = game[gindex].hindex;
1603 status.mode = MODE_PLAY;
1604 status.engine = ENGINE_READY;
1606 /* FIXME crafty */
1607 if (config.engine != GNUCHESS)
1608 SEND_TO_ENGINE("read %s\n", config.fifo);
1609 else
1610 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1612 update_all();
1613 break;
1616 if (!game[gindex].htotal || status.engine == ENGINE_THINKING)
1617 break;
1619 wtimeout(boardw, -1);
1620 init_history(board);
1621 break;
1622 case 'u':
1623 /* FIXME dies reading FIFO sometimes. */
1624 if (status.mode != MODE_PLAY || !game[gindex].htotal)
1625 break;
1627 history_previous(board, (count) ? count * 2 : 2, &crow, &ccol);
1628 oldhistorytotal = game[gindex].htotal;
1629 game[gindex].htotal = game[gindex].hindex;
1631 if (status.engine == CRAFTY)
1632 SEND_TO_ENGINE("read %s\n", config.fifo);
1633 else
1634 SEND_TO_ENGINE("\npgnload %s\n", config.fifo);
1636 update_history_window();
1637 break;
1638 case 'r':
1639 if ((tmp = get_input(GAME_LOAD_TITLE, NULL, 1, 1,
1640 BROWSER_PROMPT, browse_directory, NULL,
1641 '\t', -1)) == NULL)
1642 break;
1644 tmp = tilde_expand(tmp);
1646 if (parse_pgn_file(board, tmp))
1647 break;
1649 gindex = gtotal - 1;
1650 strncpy(loadfile, tmp, sizeof(loadfile));
1651 init_history(board);
1652 update_all();
1653 update_tag_window();
1654 break;
1655 case 'S':
1656 case 's':
1657 x = -1;
1659 if (gtotal > 1) {
1660 n = message(NULL, GAME_SAVE_MULTI_PROMPT, "%s",
1661 GAME_SAVE_MULTI_TEXT);
1663 if (n == 'c')
1664 x = gindex;
1665 else if (n == 'a')
1666 x = -1;
1667 else {
1668 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1669 break;
1673 if ((tmp = get_input(GAME_SAVE_TITLE, loadfile, 1, 1,
1674 BROWSER_PROMPT, browse_directory, NULL,
1675 '\t', -1)) == NULL) {
1676 update_status_notify("%s", NOTIFY_SAVE_ABORTED);
1677 break;
1680 tmp = tilde_expand(tmp);
1682 if (strstr(tmp, ".") == NULL && compression_cmd(tmp, 0)
1683 == NULL) {
1684 snprintf(tfile, sizeof(tfile), "%s.pgn", tmp);
1685 tmp = tfile;
1688 if (save_pgn(tmp, 0, x)) {
1689 update_status_notify("%s", NOTIFY_SAVE_FAILED);
1690 break;
1693 update_status_notify("%s", NOTIFY_SAVED);
1694 update_all();
1695 break;
1696 case CTRL('G'):
1697 n = 0;
1699 while (n != 'q') {
1700 n = help(GAME_HELP_INDEX_TITLE,
1701 GAME_HELP_INDEX_PROMPT, mainhelp);
1703 switch (n) {
1704 case 'h':
1705 help(GAME_HELP_HISTORY_TITLE, ANYKEY, historyhelp);
1706 break;
1707 case 'p':
1708 help(GAME_HELP_PLAY_TITLE, ANYKEY, playhelp);
1709 break;
1710 case 'e':
1711 help(GAME_HELP_EDIT_TITLE, ANYKEY, edithelp);
1712 break;
1713 case 'g':
1714 help(GAME_HELP_GAME_TITLE, ANYKEY, gamehelp);
1715 break;
1716 default:
1717 n = 'q';
1718 break;
1722 break;
1723 case 'n':
1724 case 'N':
1725 if (c == 'N') {
1726 if (cmessage(NULL, YESNO, "%s", GAME_NEW_PROMPT) != 'y')
1727 break;
1730 status.mode = MODE_PLAY;
1731 editmode = 0;
1732 sp.icon = 0;
1734 if (c == 'n') {
1735 newgameinit = 1;
1736 new_game(board);
1738 else {
1739 reset_history();
1740 loadfile[0] = '\0';
1741 parse_pgn_file(board, loadfile);
1744 game[gindex].wcaptures = game[gindex].bcaptures = 0;
1745 crow = (status.side == WHITE) ? 2 : 7;
1746 ccol = 4;
1748 if (status.engine == ENGINE_OFFLINE ||
1749 engine_initialized == 0) {
1750 if (start_chess_engine() < 0)
1751 break;
1754 SEND_TO_ENGINE("\nnew\n");
1755 set_engine_defaults();
1756 status.engine = ENGINE_READY;
1757 update_status_notify(NULL);
1758 update_all();
1759 update_tag_window();
1760 break;
1761 case CTRL('L'):
1762 case 'R':
1763 endwin();
1764 keypad(boardw, TRUE);
1765 update_panels();
1766 doupdate();
1767 break;
1768 case 'c':
1769 if (status.engine == ENGINE_THINKING)
1770 break;
1772 if (status.engine == ENGINE_OFFLINE)
1773 break;
1775 if ((tmp = get_input_str_clear(ENGINE_CMD_TITLE, NULL))
1776 != NULL) {
1777 SEND_TO_ENGINE("%s\n", tmp);
1779 break;
1780 case KEY_ESCAPE:
1781 sp.icon = sp.row = sp.col = 0;
1782 markend = markstart = 0;
1784 if (count) {
1785 count = 0;
1786 update_status_notify(NULL);
1789 if (config.validmoves)
1790 reset_valid_moves(board);
1792 break;
1793 case '0' ... '9':
1794 n = c - '0';
1796 if (count)
1797 count = count * 10 + n;
1798 else
1799 count = n;
1801 update_status_notify("Repeat %i", count);
1802 continue;
1803 case KEY_UP:
1804 if (status.mode == MODE_HISTORY) {
1805 history_next(board, (count > 0) ?
1806 config.jumpcount * count * movestep :
1807 config.jumpcount * movestep, &crow, &ccol);
1808 update_all();
1809 break;
1813 if (sp.icon && config.validmoves) {
1814 get_valid_cursor(board, UP, (count) ? count : 1,
1815 &crow, &ccol, minr, maxr, minc, maxc);
1816 break;
1820 if (count) {
1821 crow += count;
1822 pushkey = '\n';
1824 else
1825 crow++;
1827 if (crow > 8)
1828 crow = 1;
1830 break;
1831 case KEY_DOWN:
1832 if (status.mode == MODE_HISTORY) {
1833 history_previous(board, (count) ?
1834 config.jumpcount * count * movestep :
1835 config.jumpcount * movestep, &crow, &ccol);
1836 update_all();
1837 break;
1841 if (sp.icon && config.validmoves) {
1842 get_valid_cursor(board, DOWN, (count) ? count : 1,
1843 &crow, &ccol, minr, maxr, minc, maxc);
1844 break;
1848 if (count) {
1849 crow -= count;
1850 pushkey = '\n';
1851 update_status_notify(NULL);
1853 else
1854 crow--;
1856 if (crow < 1)
1857 crow = 8;
1859 break;
1860 case KEY_LEFT:
1861 if (status.mode == MODE_HISTORY) {
1862 history_previous(board, (count) ?
1863 count * movestep : movestep, &crow, &ccol);
1864 update_all();
1865 break;
1869 if (sp.icon && config.validmoves) {
1870 get_valid_cursor(board, LEFT, (count) ? count : 1,
1871 &crow, &ccol, minr, maxr, minc, maxc);
1872 break;
1876 if (count) {
1877 ccol -= count;
1878 pushkey = '\n';
1880 else
1881 ccol--;
1883 if (ccol < 1)
1884 ccol = 8;
1886 break;
1887 case KEY_RIGHT:
1888 if (status.mode == MODE_HISTORY) {
1889 history_next(board, (count) ? count * movestep : movestep,
1890 &crow, &ccol);
1891 update_all();
1892 break;
1896 if (sp.icon && config.validmoves) {
1897 get_valid_cursor(board, RIGHT, (count) ? count : 1,
1898 &crow, &ccol, minr, maxr, minc, maxc);
1899 break;
1903 if (count) {
1904 ccol += count;
1905 pushkey = '\n';
1907 else
1908 ccol++;
1910 if (ccol > 8)
1911 ccol = 1;
1913 break;
1914 case 'w':
1915 if (status.mode == MODE_HISTORY)
1916 break;
1918 if (status.turn == BLACK && status.side == WHITE) {
1919 status.side = BLACK;
1920 break;
1922 else if (status.turn == WHITE && status.side == BLACK) {
1923 status.side = WHITE;
1924 break;
1927 /* FIXME crafty. */
1928 SEND_TO_ENGINE("\nswitch\n");
1929 break;
1930 case ' ':
1931 if (!editmode && status.mode == MODE_HISTORY) {
1932 if (movestep == 1)
1933 movestep = 2;
1934 else
1935 movestep = 1;
1937 update_history_window();
1938 break;
1941 if ((status.engine == ENGINE_OFFLINE || !engine_initialized)
1942 && !editmode) {
1943 if (start_chess_engine() < 0) {
1944 sp.icon = 0;
1945 break;
1950 if (!editmode)
1951 wtimeout(boardw, 70);
1953 if (sp.icon || (!editmode && status.engine == ENGINE_THINKING)) {
1954 beep();
1955 break;
1958 sp.icon = mvwinch(boardw, ROWTOMATRIX(crow),
1959 COLTOMATRIX(ccol)+1) & A_CHARTEXT;
1961 if (sp.icon == ' ') {
1962 sp.icon = 0;
1963 break;
1966 if (!editmode && ((islower(sp.icon) && status.turn != BLACK) ||
1967 (isupper(sp.icon) && status.turn != WHITE))) {
1968 message(NULL, ANYKEY, "%s", E_SELECT_TURN);
1969 sp.icon = 0;
1970 break;
1973 sp.row = crow;
1974 sp.col = ccol;
1976 if (!editmode && config.validmoves) {
1977 get_valid_moves(board, piece_to_int(sp.icon), sp.row,
1978 sp.col, &minr, &maxr, &minc, &maxc);
1980 number_valid_moves(board, sp.row, sp.col);
1984 if (status.mode == MODE_PLAY)
1985 paused = 0;
1987 break;
1988 case '\015':
1989 case '\n':
1990 pushkey = count = 0;
1991 update_status_notify(NULL);
1993 if (!editmode && status.mode == MODE_HISTORY)
1994 break;
1996 if (status.engine == ENGINE_THINKING) {
1997 beep();
1998 break;
2001 if (!sp.icon)
2002 break;
2004 sp.destrow = crow;
2005 sp.destcol = ccol;
2007 if (editmode) {
2008 edit_board(board);
2009 sp.icon = sp.row = sp.col = 0;
2010 break;
2013 if (move_to_engine(board)) {
2014 if (config.validmoves)
2015 reset_valid_moves(board);
2017 if (TEST_FLAG(game[gindex].flags, GF_GAMEOVER)) {
2018 CLEAR_FLAG(game[gindex].flags, GF_GAMEOVER);
2019 SET_FLAG(game[gindex].flags, GF_MODIFIED);
2023 break;
2024 case 'q':
2025 quit = 1;
2026 break;
2027 case 0:
2028 break;
2029 default:
2030 beep();
2031 break;
2034 count = 0;
2037 return;
2040 void usage(const char *pn)
2042 int i;
2044 for (i = 0; cmdlinehelp[i]; i++)
2045 fputs(cmdlinehelp[i], stderr);
2047 exit(EXIT_FAILURE);
2050 void catch_signal(int which)
2052 switch (which) {
2053 case SIGINT:
2054 stop_engine();
2055 endwin();
2056 exit(EXIT_FAILURE);
2057 break;
2058 case SIGPIPE:
2059 if (quit)
2060 break;
2062 cmessage(NULL, ANYKEY, "%s", E_BROKEN_PIPE);
2063 endwin();
2064 exit(EXIT_FAILURE);
2065 break;
2066 case SIGSTOP:
2067 savetty();
2068 break;
2069 case SIGCONT:
2070 resetty();
2071 keypad(boardw, TRUE);
2072 break;
2073 default:
2074 break;
2077 return;
2080 int main(int argc, char *argv[])
2082 int opt;
2083 struct stat st;
2084 char buf[FILENAME_MAX];
2085 char datadir[FILENAME_MAX];
2087 if ((config.pwd = getpwuid(getuid())) == NULL)
2088 err(EXIT_FAILURE, "getpwuid()");
2090 snprintf(datadir, sizeof(datadir), "%s/.cboard", config.pwd->pw_dir);
2091 snprintf(buf, sizeof(buf), "%s/cc.data", datadir);
2092 config.ccfile = strdup(buf);
2093 snprintf(buf, sizeof(buf), "%s/nag.data", datadir);
2094 config.nagfile = strdup(buf);
2095 snprintf(buf, sizeof(buf), "%s/agony.data", datadir);
2096 config.agonyfile = strdup(buf);
2097 snprintf(buf, sizeof(buf), "%s/config", datadir);
2098 config.configfile = strdup(buf);
2099 snprintf(buf, sizeof(buf), "%s/fifo", datadir);
2100 config.fifo = strdup(buf);
2101 snprintf(buf, sizeof(buf), "%s/tmpfile", datadir);
2102 config.tmpfile = strdup(buf);
2104 if (stat(datadir, &st) == -1) {
2105 if (errno == ENOENT) {
2106 if (mkdir(datadir, 0755) == -1)
2107 err(EXIT_FAILURE, "%s", datadir);
2109 else
2110 err(EXIT_FAILURE, "%s", datadir);
2112 stat(datadir, &st);
2115 if (!S_ISDIR(st.st_mode))
2116 errx(EXIT_FAILURE, "%s: %s", datadir, E_NOTADIR);
2118 if (access(config.fifo, R_OK) == -1 && errno == ENOENT) {
2119 if (mkfifo(config.fifo, 0600) == -1)
2120 err(EXIT_FAILURE, "%s", config.fifo);
2123 set_defaults();
2125 while ((opt = getopt(argc, argv, "hp:vu:e:f:i:")) != -1) {
2126 char *tmp;
2127 int i;
2129 switch (opt) {
2130 case 'u':
2131 i = 0;
2133 while ((tmp = strsep(&optarg, ":")) != NULL) {
2134 switch (i++) {
2135 case 0:
2136 config.ics_user = optarg;
2137 break;
2138 case 1:
2139 config.ics_passwd = optarg;
2140 break;
2141 default:
2142 usage(argv[0]);
2145 break;
2146 case 'i':
2147 i = 0;
2149 while ((tmp = strsep(&optarg, ":")) != NULL) {
2150 switch (i++) {
2151 case 0:
2152 strncpy(config.ics_server, tmp,
2153 sizeof(config.ics_server));
2154 break;
2155 case 1:
2156 if (!isinteger(tmp))
2157 usage(argv[0]);
2159 config.ics_port = atoi(tmp);
2160 break;
2161 default:
2162 usage(argv[0]);
2165 break;
2166 case 'v':
2167 printf("%s (%s)\n%s\n", PACKAGE_STRING, curses_version(),
2168 COPYRIGHT);
2169 exit(EXIT_SUCCESS);
2170 case 'p':
2171 filetype = PGN_FILE;
2172 strncpy(loadfile, optarg, sizeof(loadfile));
2173 break;
2174 case 'f':
2175 filetype = FEN_FILE;
2176 strncpy(loadfile, optarg, sizeof(loadfile));
2177 break;
2178 case 'e':
2179 filetype = EPD_FILE;
2180 strncpy(loadfile, optarg, sizeof(loadfile));
2181 break;
2182 case 'h':
2183 default:
2184 usage(argv[0]);
2188 if (access(config.configfile, R_OK) == 0)
2189 parse_rcfile(config.configfile);
2191 signal(SIGPIPE, catch_signal);
2192 signal(SIGCONT, catch_signal);
2193 signal(SIGSTOP, catch_signal);
2194 signal(SIGINT, catch_signal);
2196 srandom(getpid());
2199 #ifdef DEBUG
2200 switch (filetype) {
2201 case PGN_FILE:
2202 if (parse_pgn_file(board, loadfile))
2203 loadfile[0] = '\0';
2204 break;
2205 case FEN_FILE:
2206 if (parse_fen_file(board, loadfile))
2207 loadfile[0] = '\0';
2208 break;
2209 case EPD_FILE:
2210 default:
2211 break;
2213 #endif
2216 if (initscr() == NULL)
2217 errx(EXIT_FAILURE, "%s", E_INITCURSES);
2218 else
2219 curses_initialized = 1;
2221 if (has_colors() == TRUE && start_color() == OK)
2222 init_color_pairs();
2224 boardw = newwin(BOARD_HEIGHT, BOARD_WIDTH, 0, COLS - BOARD_WIDTH);
2225 boardp = new_panel(boardw);
2226 historyw = newwin(HISTORY_HEIGHT, HISTORY_WIDTH, LINES - HISTORY_HEIGHT,
2227 COLS - HISTORY_WIDTH);
2228 historyp = new_panel(historyw);
2229 statusw = newwin(STATUS_HEIGHT, STATUS_WIDTH, LINES - STATUS_HEIGHT, 0);
2230 statusp = new_panel(statusw);
2231 tagw = newwin(TAG_HEIGHT, TAG_WIDTH, 0, 0);
2232 tagp = new_panel(tagw);
2233 keypad(boardw, TRUE);
2234 // leaveok(boardw, TRUE);
2235 leaveok(tagw, TRUE);
2236 leaveok(statusw, TRUE);
2237 leaveok(historyw, TRUE);
2238 curs_set(0);
2239 cbreak();
2240 noecho();
2242 wbkgd(boardw, CP_BOARD_WINDOW);
2243 wbkgd(statusw, CP_STATUS_WINDOW);
2244 draw_window_title(statusw, STATUS_WINDOW_TITLE, STATUS_WIDTH,
2245 CP_STATUS_TITLE, CP_STATUS_BORDER);
2246 wbkgd(tagw, CP_TAG_WINDOW);
2247 draw_window_title(tagw, TAG_WINDOW_TITLE, TAG_WIDTH, CP_TAG_TITLE,
2248 CP_TAG_BORDER);
2249 wbkgd(historyw, CP_HISTORY_WINDOW);
2250 draw_window_title(historyw, HISTORY_WINDOW_TITLE, HISTORY_WIDTH,
2251 CP_HISTORY_TITLE, CP_HISTORY_BORDER);
2253 game_loop();
2254 stop_engine();
2256 endwin();
2257 free_game_data();
2258 free(game);
2259 del_panel(boardp);
2260 del_panel(historyp);
2261 del_panel(statusp);
2262 del_panel(tagp);
2263 delwin(boardw);
2264 delwin(historyw);
2265 delwin(statusw);
2266 delwin(tagw);
2267 exit(EXIT_SUCCESS);