Wed, 29 Aug 2018 13:45:13 +0200
move log has now three columns and starts scrolling after 45 moves
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2016 Mike Becker. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 *
28 */
30 #include "game.h"
31 #include "network.h"
32 #include "input.h"
33 #include "colors.h"
34 #include <ncurses.h>
35 #include <string.h>
36 #include <inttypes.h>
37 #include <sys/select.h>
38 #include <stdio.h>
39 #include <errno.h>
41 static const uint8_t boardx = 4, boardy = 10;
42 static int inputy = 21; /* should be overridden on game startup */
44 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
45 if (gameinfo->timecontrol) {
46 uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
47 uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
48 mvprintw(boardy+4, boardx-1,
49 "White time: %4" PRIu16 ":%02" PRIu16,
50 white / 60, white % 60);
51 mvprintw(boardy+5, boardx-1,
52 "Black time: %4" PRIu16 ":%02" PRIu16,
53 black / 60, black % 60);
55 if (white == 0) {
56 move(inputy, 0);
57 printw("Time is over - Black wins!");
58 clrtobot();
59 refresh();
60 return 1;
61 }
62 if (black == 0) {
63 move(inputy, 0);
64 printw("Time is over - White wins!");
65 clrtobot();
66 refresh();
67 return 1;
68 }
69 }
71 return 0;
72 }
74 static void draw_board(GameState *gamestate, uint8_t perspective) {
75 char fen[90];
76 compute_fen(fen, gamestate);
77 mvaddstr(0, 0, fen);
79 for (uint8_t y = 0 ; y < 8 ; y++) {
80 for (uint8_t x = 0 ; x < 8 ; x++) {
81 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
82 uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
83 char piecec;
84 if (piece) {
85 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
86 } else {
87 piecec = ' ';
88 }
90 _Bool boardblack = (y&1)==(x&1);
91 attrset((col==WHITE ? A_BOLD : A_DIM)|
92 COLOR_PAIR(col == WHITE ?
93 (boardblack ? COL_WB : COL_WW) :
94 (boardblack ? COL_BB : COL_BW)
95 )
96 );
98 int cy = perspective == WHITE ? boardy-y : boardy-7+y;
99 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
100 mvaddch(cy, cx, ' ');
101 mvaddch(cy, cx+1, piecec);
102 mvaddch(cy, cx+2, ' ');
103 }
104 }
106 attrset(A_NORMAL);
107 for (uint8_t i = 0 ; i < 8 ; i++) {
108 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
109 int y = perspective == WHITE ? boardy-i : boardy-7+i;
110 mvaddch(boardy+1, x, 'a'+i);
111 mvaddch(y, boardx-2, '1'+i);
112 }
114 /* move log */
115 uint8_t logy = 2;
116 const uint8_t logx = boardx + 28;
117 move(logy, logx);
119 unsigned int logi = 0;
120 MoveList *logelem = gamestate->movelist;
122 /* wrap log after 45 moves */
123 while (gamestate->movecount/6-logi/3 >= 15) {
124 logelem = logelem->next->next;
125 logi++;
126 }
128 while (logelem) {
129 _Bool iswhite = (logelem->move.piece & COLOR_MASK) == WHITE;
130 if (iswhite) {
131 logi++;
132 printw("%d. ", logi);
133 }
135 addstr(logelem->move.string);
136 if (!iswhite && logi%3 == 0) {
137 move(++logy, logx);
138 } else {
139 addch(' ');
140 }
142 logelem = logelem->next;
143 }
144 }
146 static void eval_move_failed_msg(int code) {
147 switch (code) {
148 case AMBIGUOUS_MOVE:
149 printw("Ambiguous move - please specify the piece to move.");
150 break;
151 case INVALID_POSITION:
152 printw("No piece can be moved this way.");
153 break;
154 case NEED_PROMOTION:
155 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
156 break;
157 case KING_IN_CHECK:
158 printw("Your king is in check!");
159 break;
160 case PIECE_PINNED:
161 printw("This piece is pinned!");
162 break;
163 case INVALID_MOVE_SYNTAX:
164 printw("Can't interpret move - please use algebraic notation.");
165 break;
166 case RULES_VIOLATED:
167 printw("Move does not comply chess rules.");
168 break;
169 case KING_MOVES_INTO_CHECK:
170 printw("Can't move the king into a check position.");
171 break;
172 default:
173 printw("Unknown move parser error.");
174 }
175 }
177 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
178 printw("Filename: ");
179 clrtoeol();
180 refresh();
182 char filename[64];
183 int y = getcury(stdscr);
184 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
185 move(y, 0);
186 FILE *file = fopen(filename, "w");
187 if (file) {
188 write_pgn(file, gamestate, gameinfo);
189 fclose(file);
190 printw("File saved.");
191 } else {
192 printw("Can't write to file (%s).", strerror(errno));
193 }
194 clrtoeol();
195 }
196 }
198 #define MOVESTR_BUFLEN 10
199 static int domove_singlemachine(GameState *gamestate,
200 GameInfo *gameinfo, uint8_t curcolor) {
203 size_t bufpos = 0;
204 char movestr[MOVESTR_BUFLEN];
206 flushinp();
207 while (1) {
208 if (timecontrol(gamestate, gameinfo)) {
209 return 1;
210 }
212 move(inputy, 0);
213 printw(
214 "Use chess notation to enter your move.\n"
215 "Or use a command: remis, resign, savepgn\n\n"
216 "Type your move: ");
217 clrtoeol();
219 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
220 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
221 gamestate->resign = 1;
222 printw("%s resigned!",
223 curcolor==WHITE?"White":"Black");
224 clrtobot();
225 refresh();
226 return 1;
227 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
228 gamestate->remis = 1;
229 printw("Game ends remis.");
230 clrtobot();
231 refresh();
232 return 1;
233 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
234 save_pgn(gamestate, gameinfo);
235 } else {
236 Move move;
237 int result = eval_move(gamestate, movestr, &move, curcolor);
238 switch (result) {
239 case VALID_MOVE_SYNTAX:
240 result = validate_move(gamestate, &move);
241 if (result == VALID_MOVE_SEMANTICS) {
242 apply_move(gamestate, &move);
243 if (gamestate->checkmate) {
244 printw("Checkmate!");
245 clrtoeol();
246 return 1;
247 } else if (gamestate->stalemate) {
248 printw("Stalemate!");
249 clrtoeol();
250 return 1;
251 } else {
252 return 0;
253 }
254 } else {
255 eval_move_failed_msg(result);
256 }
257 break;
258 default:
259 eval_move_failed_msg(result);
260 }
261 clrtoeol();
262 }
263 }
264 }
265 }
267 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
268 int opponent, uint8_t mycolor) {
270 size_t bufpos = 0;
271 char movestr[MOVESTR_BUFLEN];
272 _Bool remisrejected = FALSE;
273 uint8_t code;
275 flushinp();
276 while (1) {
277 if (timecontrol(gamestate, gameinfo)) {
278 net_send_code(opponent, NETCODE_TIMEOVER);
279 return 1;
280 }
282 move(inputy, 0);
283 if (remisrejected) {
284 printw(
285 "Use chess notation to enter your move.\n"
286 "Remis offer rejected \n\n"
287 "Type your move: ");
288 } else {
289 printw(
290 "Use chess notation to enter your move.\n"
291 "Or use a command: remis, resign, savepgn\n\n"
292 "Type your move: ");
293 }
294 clrtoeol();
296 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
297 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
298 gamestate->resign = 1;
299 printw("You resigned!");
300 clrtoeol();
301 refresh();
302 net_send_code(opponent, NETCODE_RESIGN);
303 return 1;
304 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
305 save_pgn(gamestate, gameinfo);
306 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
307 if (!remisrejected) {
308 net_send_code(opponent, NETCODE_REMIS);
309 printw("Remis offer sent - waiting for acceptance...");
310 refresh();
311 code = net_recieve_code(opponent);
312 if (code == NETCODE_ACCEPT) {
313 gamestate->remis = 1;
314 printw("\rRemis accepted!");
315 clrtoeol();
316 refresh();
317 return 1;
318 } else if (code == NETCODE_CONNLOST) {
319 printw("\rYour opponent left the game.");
320 clrtoeol();
321 refresh();
322 return 1;
323 } else {
324 remisrejected = TRUE;
325 }
326 }
327 } else {
328 Move move;
329 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
330 switch (eval_result) {
331 case VALID_MOVE_SYNTAX:
332 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
333 code = net_recieve_code(opponent);
334 move.check = code == NETCODE_CHECK ||
335 code == NETCODE_CHECKMATE;
336 gamestate->checkmate = code == NETCODE_CHECKMATE;
337 gamestate->stalemate = code == NETCODE_STALEMATE;
338 if (code == NETCODE_DECLINE) {
339 uint32_t reason;
340 net_recieve_data(opponent, &reason, sizeof(uint32_t));
341 reason = ntohl(reason);
342 eval_move_failed_msg(reason);
343 } else if (code == NETCODE_ACCEPT
344 || code == NETCODE_CHECK
345 || code == NETCODE_CHECKMATE
346 || code == NETCODE_STALEMATE) {
347 apply_move(gamestate, &move);
348 if (gamestate->checkmate) {
349 printw("Checkmate!");
350 clrtoeol();
351 return 1;
352 } else if (gamestate->stalemate) {
353 printw("Stalemate!");
354 clrtoeol();
355 return 1;
356 } else {
357 return 0;
358 }
359 } else if (code == NETCODE_CONNLOST) {
360 printw("Your opponent left the game.");
361 return 1;
362 } else {
363 printw("Invalid network response.");
364 }
365 break;
366 default:
367 eval_move_failed_msg(eval_result);
368 }
369 clrtoeol();
370 }
371 }
372 }
373 }
375 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
377 struct timeval timeout;
378 while (1) {
379 timecontrol(gamestate, gameinfo);
381 move(inputy, 0);
382 printw("Awaiting opponent move...");
383 clrtoeol();
384 refresh();
386 fd_set readfds;
388 FD_ZERO(&readfds);
389 FD_SET(opponent, &readfds);
390 timeout.tv_sec = 0;
391 timeout.tv_usec = 1e5;
393 // TODO: allow commands
395 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
396 if (result == -1) {
397 printw("\rCannot perform asynchronous network IO");
398 cbreak(); getch();
399 exit(EXIT_FAILURE);
400 }
401 if (result > 0) {
402 uint8_t code = net_recieve_code(opponent);
404 Move move;
405 switch (code) {
406 case NETCODE_TIMEOVER:
407 printw("\rYour opponent's time ran out - you win!");
408 clrtoeol();
409 return 1;
410 case NETCODE_RESIGN:
411 gamestate->resign = 1;
412 printw("\rYour opponent resigned!");
413 clrtoeol();
414 return 1;
415 case NETCODE_CONNLOST:
416 printw("\rYour opponent has left the game.");
417 clrtoeol();
418 return 1;
419 case NETCODE_REMIS:
420 if (prompt_yesno(
421 "\rYour opponent offers remis - do you accept")) {
422 gamestate->remis = 1;
423 printw("\rRemis accepted!");
424 clrtoeol();
425 net_send_code(opponent, NETCODE_ACCEPT);
426 return 1;
427 } else {
428 net_send_code(opponent, NETCODE_DECLINE);
429 }
430 break;
431 case NETCODE_MOVE:
432 net_recieve_data(opponent, &move, sizeof(Move));
433 code = validate_move(gamestate, &move);
434 if (code == VALID_MOVE_SEMANTICS) {
435 apply_move(gamestate, &move);
436 if (gamestate->checkmate) {
437 net_send_code(opponent, NETCODE_CHECKMATE);
438 printw("\rCheckmate!");
439 clrtoeol();
440 return 1;
441 } else if (gamestate->stalemate) {
442 net_send_code(opponent, NETCODE_STALEMATE);
443 printw("\rStalemate!");
444 clrtoeol();
445 return 1;
446 } else if (move.check) {
447 net_send_code(opponent, NETCODE_CHECK);
448 } else {
449 net_send_code(opponent, NETCODE_ACCEPT);
450 }
451 return 0;
452 } else {
453 uint32_t reason = htonl(code);
454 net_send_data(opponent, NETCODE_DECLINE,
455 &reason, sizeof(uint32_t));
456 }
457 break;
458 default:
459 printw("\nInvalid network request.");
460 }
461 }
462 }
463 }
465 static void post_game(GameState *gamestate, GameInfo *gameinfo) {
466 move(0,0);
467 draw_board(gamestate, WHITE);
469 // TODO: network connection is still open here - think about it!
471 mvaddstr(getmaxy(stdscr)-1, 0,
472 "Press 'q' to quit or 's' to save a PGN file...");
473 refresh();
474 flushinp();
476 noecho();
477 int c;
478 do {
479 c = getch();
480 if (c == 's') {
481 addch('\r');
482 echo();
483 save_pgn(gamestate, gameinfo);
484 addstr(" Press 'q' to quit...");
485 noecho();
486 }
487 } while (c != 'q');
488 echo();
490 gamestate_cleanup(gamestate);
491 }
493 void game_start_singlemachine(Settings *settings) {
494 inputy = getmaxy(stdscr) - 6;
496 GameState gamestate;
497 gamestate_init(&gamestate);
498 uint8_t curcol = WHITE;
500 if (settings->continuepgn) {
501 FILE *pgnfile = fopen(settings->continuepgn, "r");
502 if (pgnfile) {
503 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
504 long position = ftell(pgnfile);
505 fclose(pgnfile);
506 if (result) {
507 printw("Invalid PGN file content at position %ld:\n%s\n",
508 position, pgn_error_str(result));
509 return;
510 }
511 if (!is_game_running(&gamestate)) {
512 addstr("Game has ended. Use -S to analyze it.\n");
513 return;
514 }
515 curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
516 } else {
517 printw("Can't read PGN file (%s)\n", strerror(errno));
518 return;
519 }
520 }
522 _Bool running;
523 do {
524 clear();
525 draw_board(&gamestate, curcol);
526 running = !domove_singlemachine(&gamestate,
527 &(settings->gameinfo), curcol);
528 curcol = opponent_color(curcol);
529 } while (running);
531 post_game(&gamestate, &(settings->gameinfo));
532 }
534 void game_continue(Settings *settings, int opponent, GameState *gamestate) {
535 inputy = getmaxy(stdscr) - 6;
537 uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor :
538 opponent_color(settings->gameinfo.servercolor);
540 _Bool myturn = (gamestate->lastmove ?
541 (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor;
543 _Bool running;
544 do {
545 clear();
546 draw_board(gamestate, mycolor);
547 if (myturn) {
548 running = !sendmove(gamestate, &(settings->gameinfo),
549 opponent, mycolor);
550 } else {
551 running = !recvmove(gamestate, &(settings->gameinfo), opponent);
552 }
553 myturn ^= TRUE;
554 } while (running);
556 post_game(gamestate, &(settings->gameinfo));
557 }
559 void game_start(Settings *settings, int opponent) {
560 GameState gamestate;
561 gamestate_init(&gamestate);
563 game_continue(settings, opponent, &gamestate);
564 }