Mon, 16 Jun 2014 15:41:06 +0200
added support for game continuation over network + fixed major bug in checkmate anticipation when the king is attacked diagonally
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2014 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 = 10, 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 for (uint8_t y = 0 ; y < 8 ; y++) {
76 for (uint8_t x = 0 ; x < 8 ; x++) {
77 uint8_t col = gamestate->board[y][x] & COLOR_MASK;
78 uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
79 char piecec;
80 if (piece) {
81 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
82 } else {
83 piecec = ' ';
84 }
86 _Bool boardblack = (y&1)==(x&1);
87 attrset((col==WHITE ? A_BOLD : A_DIM)|
88 COLOR_PAIR(col == WHITE ?
89 (boardblack ? COL_WB : COL_WW) :
90 (boardblack ? COL_BB : COL_BW)
91 )
92 );
94 int cy = perspective == WHITE ? boardy-y : boardy-7+y;
95 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3;
96 mvaddch(cy, cx, ' ');
97 mvaddch(cy, cx+1, piecec);
98 mvaddch(cy, cx+2, ' ');
99 }
100 }
102 attrset(A_NORMAL);
103 for (uint8_t i = 0 ; i < 8 ; i++) {
104 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3;
105 int y = perspective == WHITE ? boardy-i : boardy-7+i;
106 mvaddch(boardy+1, x, 'a'+i);
107 mvaddch(y, boardx-2, '1'+i);
108 }
110 /* move log */
111 // TODO: introduce window to avoid bugs with a long move log
112 uint8_t logy = 0;
113 const uint8_t logx = boardx + 30;
114 int logi = 1;
115 MoveList *logelem = gamestate->movelist;
117 while (logelem) {
118 logi++;
119 if (logi % 2 == 0) {
120 if ((logi - 2) % 4 == 0) {
121 logy++;
122 move(logy, logx);
123 }
124 printw("%d. ", logi / 2);
125 }
127 addstr(logelem->move.string);
128 if (!logelem->next) {
129 if (gamestate->stalemate) {
130 addstr(" stalemate");
131 }
132 }
133 addch(' ');
135 logelem = logelem->next;
136 }
137 }
139 static void eval_move_failed_msg(int code) {
140 switch (code) {
141 case AMBIGUOUS_MOVE:
142 printw("Ambiguous move - please specify the piece to move.");
143 break;
144 case INVALID_POSITION:
145 printw("No piece can be moved this way.");
146 break;
147 case NEED_PROMOTION:
148 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
149 break;
150 case KING_IN_CHECK:
151 printw("Your king is in check!");
152 break;
153 case PIECE_PINNED:
154 printw("This piece is pinned!");
155 break;
156 case INVALID_MOVE_SYNTAX:
157 printw("Can't interpret move - please use algebraic notation.");
158 break;
159 case RULES_VIOLATED:
160 printw("Move does not comply chess rules.");
161 break;
162 case KING_MOVES_INTO_CHECK:
163 printw("Can't move the king into a check position.");
164 break;
165 default:
166 printw("Unknown move parser error.");
167 }
168 }
170 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
171 printw("Filename: ");
172 clrtoeol();
173 refresh();
175 char filename[64];
176 int y = getcury(stdscr);
177 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
178 move(y, 0);
179 FILE *file = fopen(filename, "w");
180 if (file) {
181 write_pgn(file, gamestate, gameinfo);
182 fclose(file);
183 printw("File saved.");
184 } else {
185 printw("Can't write to file (%s).", strerror(errno));
186 }
187 clrtoeol();
188 }
189 }
191 #define MOVESTR_BUFLEN 10
192 static int domove_singlemachine(GameState *gamestate,
193 GameInfo *gameinfo, uint8_t curcolor) {
196 size_t bufpos = 0;
197 char movestr[MOVESTR_BUFLEN];
199 flushinp();
200 while (1) {
201 if (timecontrol(gamestate, gameinfo)) {
202 return 1;
203 }
205 move(inputy, 0);
206 printw(
207 "Use chess notation to enter your move.\n"
208 "Or use a command: remis, resign, savepgn\n\n"
209 "Type your move: ");
210 clrtoeol();
212 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
213 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
214 gamestate->resign = 1;
215 printw("%s resigned!",
216 curcolor==WHITE?"White":"Black");
217 clrtobot();
218 refresh();
219 return 1;
220 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
221 gamestate->remis = 1;
222 printw("Game ends remis.");
223 clrtobot();
224 refresh();
225 return 1;
226 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
227 save_pgn(gamestate, gameinfo);
228 } else {
229 Move move;
230 int result = eval_move(gamestate, movestr, &move, curcolor);
231 switch (result) {
232 case VALID_MOVE_SYNTAX:
233 result = validate_move(gamestate, &move);
234 if (result == VALID_MOVE_SEMANTICS) {
235 apply_move(gamestate, &move);
236 if (gamestate->checkmate) {
237 printw("Checkmate!");
238 clrtoeol();
239 return 1;
240 } else if (gamestate->stalemate) {
241 printw("Stalemate!");
242 clrtoeol();
243 return 1;
244 } else {
245 return 0;
246 }
247 } else {
248 eval_move_failed_msg(result);
249 }
250 break;
251 default:
252 eval_move_failed_msg(result);
253 }
254 clrtoeol();
255 }
256 }
257 }
258 }
260 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
261 int opponent, uint8_t mycolor) {
263 size_t bufpos = 0;
264 char movestr[MOVESTR_BUFLEN];
265 _Bool remisrejected = FALSE;
266 uint8_t code;
268 flushinp();
269 while (1) {
270 if (timecontrol(gamestate, gameinfo)) {
271 net_send_code(opponent, NETCODE_TIMEOVER);
272 return 1;
273 }
275 move(inputy, 0);
276 if (remisrejected) {
277 printw(
278 "Use chess notation to enter your move.\n"
279 "Remis offer rejected \n\n"
280 "Type your move: ");
281 } else {
282 printw(
283 "Use chess notation to enter your move.\n"
284 "Or use a command: remis, resign, savepgn\n\n"
285 "Type your move: ");
286 }
287 clrtoeol();
289 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
290 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
291 gamestate->resign = 1;
292 printw("You resigned!");
293 clrtoeol();
294 refresh();
295 net_send_code(opponent, NETCODE_RESIGN);
296 return 1;
297 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
298 save_pgn(gamestate, gameinfo);
299 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
300 if (!remisrejected) {
301 net_send_code(opponent, NETCODE_REMIS);
302 printw("Remis offer sent - waiting for acceptance...");
303 refresh();
304 code = net_recieve_code(opponent);
305 if (code == NETCODE_ACCEPT) {
306 gamestate->remis = 1;
307 printw("\rRemis accepted!");
308 clrtoeol();
309 refresh();
310 return 1;
311 } else if (code == NETCODE_CONNLOST) {
312 printw("\rYour opponent left the game.");
313 clrtoeol();
314 refresh();
315 return 1;
316 } else {
317 remisrejected = TRUE;
318 }
319 }
320 } else {
321 Move move;
322 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
323 switch (eval_result) {
324 case VALID_MOVE_SYNTAX:
325 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
326 code = net_recieve_code(opponent);
327 move.check = code == NETCODE_CHECK ||
328 code == NETCODE_CHECKMATE;
329 gamestate->checkmate = code == NETCODE_CHECKMATE;
330 gamestate->stalemate = code == NETCODE_STALEMATE;
331 if (code == NETCODE_DECLINE) {
332 uint32_t reason;
333 net_recieve_data(opponent, &reason, sizeof(uint32_t));
334 reason = ntohl(reason);
335 eval_move_failed_msg(reason);
336 } else if (code == NETCODE_ACCEPT
337 || code == NETCODE_CHECK
338 || code == NETCODE_CHECKMATE
339 || code == NETCODE_STALEMATE) {
340 apply_move(gamestate, &move);
341 if (gamestate->checkmate) {
342 printw("Checkmate!");
343 clrtoeol();
344 return 1;
345 } else if (gamestate->stalemate) {
346 printw("Stalemate!");
347 clrtoeol();
348 return 1;
349 } else {
350 return 0;
351 }
352 } else if (code == NETCODE_CONNLOST) {
353 printw("Your opponent left the game.");
354 return 1;
355 } else {
356 printw("Invalid network response.");
357 }
358 break;
359 default:
360 eval_move_failed_msg(eval_result);
361 }
362 clrtoeol();
363 }
364 }
365 }
366 }
368 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
370 struct timeval timeout;
371 while (1) {
372 timecontrol(gamestate, gameinfo);
374 move(inputy, 0);
375 printw("Awaiting opponent move...");
376 clrtoeol();
377 refresh();
379 fd_set readfds;
381 FD_ZERO(&readfds);
382 FD_SET(opponent, &readfds);
383 timeout.tv_sec = 0;
384 timeout.tv_usec = 1e5;
386 // TODO: allow commands
388 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
389 if (result == -1) {
390 printw("\rCannot perform asynchronous network IO");
391 cbreak(); getch();
392 exit(EXIT_FAILURE);
393 }
394 if (result > 0) {
395 uint8_t code = net_recieve_code(opponent);
397 Move move;
398 switch (code) {
399 case NETCODE_TIMEOVER:
400 printw("\rYour opponent's time ran out - you win!");
401 clrtoeol();
402 return 1;
403 case NETCODE_RESIGN:
404 gamestate->resign = 1;
405 printw("\rYour opponent resigned!");
406 clrtoeol();
407 return 1;
408 case NETCODE_CONNLOST:
409 printw("\rYour opponent has left the game.");
410 clrtoeol();
411 return 1;
412 case NETCODE_REMIS:
413 if (prompt_yesno(
414 "\rYour opponent offers remis - do you accept")) {
415 gamestate->remis = 1;
416 printw("\rRemis accepted!");
417 clrtoeol();
418 net_send_code(opponent, NETCODE_ACCEPT);
419 return 1;
420 } else {
421 net_send_code(opponent, NETCODE_DECLINE);
422 }
423 break;
424 case NETCODE_MOVE:
425 net_recieve_data(opponent, &move, sizeof(Move));
426 code = validate_move(gamestate, &move);
427 if (code == VALID_MOVE_SEMANTICS) {
428 apply_move(gamestate, &move);
429 if (gamestate->checkmate) {
430 net_send_code(opponent, NETCODE_CHECKMATE);
431 printw("\rCheckmate!");
432 clrtoeol();
433 return 1;
434 } else if (gamestate->stalemate) {
435 net_send_code(opponent, NETCODE_STALEMATE);
436 printw("\rStalemate!");
437 clrtoeol();
438 return 1;
439 } else if (move.check) {
440 net_send_code(opponent, NETCODE_CHECK);
441 } else {
442 net_send_code(opponent, NETCODE_ACCEPT);
443 }
444 return 0;
445 } else {
446 uint32_t reason = htonl(code);
447 net_send_data(opponent, NETCODE_DECLINE,
448 &reason, sizeof(uint32_t));
449 }
450 break;
451 default:
452 printw("\nInvalid network request.");
453 }
454 }
455 }
456 }
458 static void post_game(GameState *gamestate, GameInfo *gameinfo) {
459 move(0,0);
460 draw_board(gamestate, WHITE);
462 // TODO: network connection is still open here - think about it!
464 mvaddstr(getmaxy(stdscr)-1, 0,
465 "Press 'q' to quit or 's' to save a PGN file...");
466 refresh();
467 flushinp();
469 noecho();
470 int c;
471 do {
472 c = getch();
473 if (c == 's') {
474 addch('\r');
475 echo();
476 save_pgn(gamestate, gameinfo);
477 addstr(" Press 'q' to quit...");
478 noecho();
479 }
480 } while (c != 'q');
481 echo();
483 gamestate_cleanup(gamestate);
484 }
486 void game_start_singlemachine(Settings *settings) {
487 inputy = getmaxy(stdscr) - 6;
489 GameState gamestate;
490 gamestate_init(&gamestate);
491 uint8_t curcol = WHITE;
493 if (settings->continuepgn) {
494 FILE *pgnfile = fopen(settings->continuepgn, "r");
495 if (pgnfile) {
496 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
497 fclose(pgnfile);
498 if (result != EXIT_SUCCESS) {
499 addstr("Invalid PGN file content.\n");
500 return;
501 }
502 if (!is_game_running(&gamestate)) {
503 addstr("Game has ended. Use -S to analyze it.\n");
504 return;
505 }
506 curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
507 } else {
508 printw("Can't read PGN file (%s)\n", strerror(errno));
509 return;
510 }
511 }
513 _Bool running;
514 do {
515 clear();
516 draw_board(&gamestate, curcol);
517 running = !domove_singlemachine(&gamestate,
518 &(settings->gameinfo), curcol);
519 curcol = opponent_color(curcol);
520 } while (running);
522 post_game(&gamestate, &(settings->gameinfo));
523 }
525 void game_continue(Settings *settings, int opponent, GameState *gamestate) {
526 inputy = getmaxy(stdscr) - 6;
528 uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor :
529 opponent_color(settings->gameinfo.servercolor);
531 _Bool myturn = (gamestate->lastmove ?
532 (gamestate->lastmove->move.piece & COLOR_MASK) : WHITE) != mycolor;
534 _Bool running;
535 do {
536 clear();
537 draw_board(gamestate, mycolor);
538 if (myturn) {
539 running = !sendmove(gamestate, &(settings->gameinfo),
540 opponent, mycolor);
541 } else {
542 running = !recvmove(gamestate, &(settings->gameinfo), opponent);
543 }
544 myturn ^= TRUE;
545 } while (running);
547 post_game(gamestate, &(settings->gameinfo));
548 }
550 void game_start(Settings *settings, int opponent) {
551 GameState gamestate;
552 gamestate_init(&gamestate);
554 game_continue(settings, opponent, &gamestate);
555 }