Tue, 28 Aug 2018 14:16:30 +0200
fixes inappropriate use of EXIT_ macros + adds a sample PGN file
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 = 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 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 // TODO: introduce window to avoid bugs with a long move log
116 uint8_t logy = 1;
117 const uint8_t logx = boardx + 30;
118 int logi = 1;
119 MoveList *logelem = gamestate->movelist;
121 while (logelem) {
122 logi++;
123 if (logi % 2 == 0) {
124 if ((logi - 2) % 4 == 0) {
125 logy++;
126 move(logy, logx);
127 }
128 printw("%d. ", logi / 2);
129 }
131 addstr(logelem->move.string);
132 if (!logelem->next) {
133 if (gamestate->stalemate) {
134 addstr(" stalemate");
135 }
136 }
137 addch(' ');
139 logelem = logelem->next;
140 }
141 }
143 static void eval_move_failed_msg(int code) {
144 switch (code) {
145 case AMBIGUOUS_MOVE:
146 printw("Ambiguous move - please specify the piece to move.");
147 break;
148 case INVALID_POSITION:
149 printw("No piece can be moved this way.");
150 break;
151 case NEED_PROMOTION:
152 printw("You need to promote the pawn (append \"=Q\" e.g.)!");
153 break;
154 case KING_IN_CHECK:
155 printw("Your king is in check!");
156 break;
157 case PIECE_PINNED:
158 printw("This piece is pinned!");
159 break;
160 case INVALID_MOVE_SYNTAX:
161 printw("Can't interpret move - please use algebraic notation.");
162 break;
163 case RULES_VIOLATED:
164 printw("Move does not comply chess rules.");
165 break;
166 case KING_MOVES_INTO_CHECK:
167 printw("Can't move the king into a check position.");
168 break;
169 default:
170 printw("Unknown move parser error.");
171 }
172 }
174 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) {
175 printw("Filename: ");
176 clrtoeol();
177 refresh();
179 char filename[64];
180 int y = getcury(stdscr);
181 if (getnstr(filename, 64) == OK && filename[0] != '\0') {
182 move(y, 0);
183 FILE *file = fopen(filename, "w");
184 if (file) {
185 write_pgn(file, gamestate, gameinfo);
186 fclose(file);
187 printw("File saved.");
188 } else {
189 printw("Can't write to file (%s).", strerror(errno));
190 }
191 clrtoeol();
192 }
193 }
195 #define MOVESTR_BUFLEN 10
196 static int domove_singlemachine(GameState *gamestate,
197 GameInfo *gameinfo, uint8_t curcolor) {
200 size_t bufpos = 0;
201 char movestr[MOVESTR_BUFLEN];
203 flushinp();
204 while (1) {
205 if (timecontrol(gamestate, gameinfo)) {
206 return 1;
207 }
209 move(inputy, 0);
210 printw(
211 "Use chess notation to enter your move.\n"
212 "Or use a command: remis, resign, savepgn\n\n"
213 "Type your move: ");
214 clrtoeol();
216 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
217 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
218 gamestate->resign = 1;
219 printw("%s resigned!",
220 curcolor==WHITE?"White":"Black");
221 clrtobot();
222 refresh();
223 return 1;
224 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
225 gamestate->remis = 1;
226 printw("Game ends remis.");
227 clrtobot();
228 refresh();
229 return 1;
230 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
231 save_pgn(gamestate, gameinfo);
232 } else {
233 Move move;
234 int result = eval_move(gamestate, movestr, &move, curcolor);
235 switch (result) {
236 case VALID_MOVE_SYNTAX:
237 result = validate_move(gamestate, &move);
238 if (result == VALID_MOVE_SEMANTICS) {
239 apply_move(gamestate, &move);
240 if (gamestate->checkmate) {
241 printw("Checkmate!");
242 clrtoeol();
243 return 1;
244 } else if (gamestate->stalemate) {
245 printw("Stalemate!");
246 clrtoeol();
247 return 1;
248 } else {
249 return 0;
250 }
251 } else {
252 eval_move_failed_msg(result);
253 }
254 break;
255 default:
256 eval_move_failed_msg(result);
257 }
258 clrtoeol();
259 }
260 }
261 }
262 }
264 static int sendmove(GameState *gamestate, GameInfo *gameinfo,
265 int opponent, uint8_t mycolor) {
267 size_t bufpos = 0;
268 char movestr[MOVESTR_BUFLEN];
269 _Bool remisrejected = FALSE;
270 uint8_t code;
272 flushinp();
273 while (1) {
274 if (timecontrol(gamestate, gameinfo)) {
275 net_send_code(opponent, NETCODE_TIMEOVER);
276 return 1;
277 }
279 move(inputy, 0);
280 if (remisrejected) {
281 printw(
282 "Use chess notation to enter your move.\n"
283 "Remis offer rejected \n\n"
284 "Type your move: ");
285 } else {
286 printw(
287 "Use chess notation to enter your move.\n"
288 "Or use a command: remis, resign, savepgn\n\n"
289 "Type your move: ");
290 }
291 clrtoeol();
293 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
294 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
295 gamestate->resign = 1;
296 printw("You resigned!");
297 clrtoeol();
298 refresh();
299 net_send_code(opponent, NETCODE_RESIGN);
300 return 1;
301 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) {
302 save_pgn(gamestate, gameinfo);
303 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
304 if (!remisrejected) {
305 net_send_code(opponent, NETCODE_REMIS);
306 printw("Remis offer sent - waiting for acceptance...");
307 refresh();
308 code = net_recieve_code(opponent);
309 if (code == NETCODE_ACCEPT) {
310 gamestate->remis = 1;
311 printw("\rRemis accepted!");
312 clrtoeol();
313 refresh();
314 return 1;
315 } else if (code == NETCODE_CONNLOST) {
316 printw("\rYour opponent left the game.");
317 clrtoeol();
318 refresh();
319 return 1;
320 } else {
321 remisrejected = TRUE;
322 }
323 }
324 } else {
325 Move move;
326 int eval_result = eval_move(gamestate, movestr, &move, mycolor);
327 switch (eval_result) {
328 case VALID_MOVE_SYNTAX:
329 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move));
330 code = net_recieve_code(opponent);
331 move.check = code == NETCODE_CHECK ||
332 code == NETCODE_CHECKMATE;
333 gamestate->checkmate = code == NETCODE_CHECKMATE;
334 gamestate->stalemate = code == NETCODE_STALEMATE;
335 if (code == NETCODE_DECLINE) {
336 uint32_t reason;
337 net_recieve_data(opponent, &reason, sizeof(uint32_t));
338 reason = ntohl(reason);
339 eval_move_failed_msg(reason);
340 } else if (code == NETCODE_ACCEPT
341 || code == NETCODE_CHECK
342 || code == NETCODE_CHECKMATE
343 || code == NETCODE_STALEMATE) {
344 apply_move(gamestate, &move);
345 if (gamestate->checkmate) {
346 printw("Checkmate!");
347 clrtoeol();
348 return 1;
349 } else if (gamestate->stalemate) {
350 printw("Stalemate!");
351 clrtoeol();
352 return 1;
353 } else {
354 return 0;
355 }
356 } else if (code == NETCODE_CONNLOST) {
357 printw("Your opponent left the game.");
358 return 1;
359 } else {
360 printw("Invalid network response.");
361 }
362 break;
363 default:
364 eval_move_failed_msg(eval_result);
365 }
366 clrtoeol();
367 }
368 }
369 }
370 }
372 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
374 struct timeval timeout;
375 while (1) {
376 timecontrol(gamestate, gameinfo);
378 move(inputy, 0);
379 printw("Awaiting opponent move...");
380 clrtoeol();
381 refresh();
383 fd_set readfds;
385 FD_ZERO(&readfds);
386 FD_SET(opponent, &readfds);
387 timeout.tv_sec = 0;
388 timeout.tv_usec = 1e5;
390 // TODO: allow commands
392 int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
393 if (result == -1) {
394 printw("\rCannot perform asynchronous network IO");
395 cbreak(); getch();
396 exit(EXIT_FAILURE);
397 }
398 if (result > 0) {
399 uint8_t code = net_recieve_code(opponent);
401 Move move;
402 switch (code) {
403 case NETCODE_TIMEOVER:
404 printw("\rYour opponent's time ran out - you win!");
405 clrtoeol();
406 return 1;
407 case NETCODE_RESIGN:
408 gamestate->resign = 1;
409 printw("\rYour opponent resigned!");
410 clrtoeol();
411 return 1;
412 case NETCODE_CONNLOST:
413 printw("\rYour opponent has left the game.");
414 clrtoeol();
415 return 1;
416 case NETCODE_REMIS:
417 if (prompt_yesno(
418 "\rYour opponent offers remis - do you accept")) {
419 gamestate->remis = 1;
420 printw("\rRemis accepted!");
421 clrtoeol();
422 net_send_code(opponent, NETCODE_ACCEPT);
423 return 1;
424 } else {
425 net_send_code(opponent, NETCODE_DECLINE);
426 }
427 break;
428 case NETCODE_MOVE:
429 net_recieve_data(opponent, &move, sizeof(Move));
430 code = validate_move(gamestate, &move);
431 if (code == VALID_MOVE_SEMANTICS) {
432 apply_move(gamestate, &move);
433 if (gamestate->checkmate) {
434 net_send_code(opponent, NETCODE_CHECKMATE);
435 printw("\rCheckmate!");
436 clrtoeol();
437 return 1;
438 } else if (gamestate->stalemate) {
439 net_send_code(opponent, NETCODE_STALEMATE);
440 printw("\rStalemate!");
441 clrtoeol();
442 return 1;
443 } else if (move.check) {
444 net_send_code(opponent, NETCODE_CHECK);
445 } else {
446 net_send_code(opponent, NETCODE_ACCEPT);
447 }
448 return 0;
449 } else {
450 uint32_t reason = htonl(code);
451 net_send_data(opponent, NETCODE_DECLINE,
452 &reason, sizeof(uint32_t));
453 }
454 break;
455 default:
456 printw("\nInvalid network request.");
457 }
458 }
459 }
460 }
462 static void post_game(GameState *gamestate, GameInfo *gameinfo) {
463 move(0,0);
464 draw_board(gamestate, WHITE);
466 // TODO: network connection is still open here - think about it!
468 mvaddstr(getmaxy(stdscr)-1, 0,
469 "Press 'q' to quit or 's' to save a PGN file...");
470 refresh();
471 flushinp();
473 noecho();
474 int c;
475 do {
476 c = getch();
477 if (c == 's') {
478 addch('\r');
479 echo();
480 save_pgn(gamestate, gameinfo);
481 addstr(" Press 'q' to quit...");
482 noecho();
483 }
484 } while (c != 'q');
485 echo();
487 gamestate_cleanup(gamestate);
488 }
490 void game_start_singlemachine(Settings *settings) {
491 inputy = getmaxy(stdscr) - 6;
493 GameState gamestate;
494 gamestate_init(&gamestate);
495 uint8_t curcol = WHITE;
497 if (settings->continuepgn) {
498 FILE *pgnfile = fopen(settings->continuepgn, "r");
499 if (pgnfile) {
500 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo));
501 fclose(pgnfile);
502 if (result) {
503 addstr("Invalid PGN file content.\n");
504 return;
505 }
506 if (!is_game_running(&gamestate)) {
507 addstr("Game has ended. Use -S to analyze it.\n");
508 return;
509 }
510 curcol = opponent_color(gamestate.lastmove->move.piece&COLOR_MASK);
511 } else {
512 printw("Can't read PGN file (%s)\n", strerror(errno));
513 return;
514 }
515 }
517 _Bool running;
518 do {
519 clear();
520 draw_board(&gamestate, curcol);
521 running = !domove_singlemachine(&gamestate,
522 &(settings->gameinfo), curcol);
523 curcol = opponent_color(curcol);
524 } while (running);
526 post_game(&gamestate, &(settings->gameinfo));
527 }
529 void game_continue(Settings *settings, int opponent, GameState *gamestate) {
530 inputy = getmaxy(stdscr) - 6;
532 uint8_t mycolor = is_server(settings) ? settings->gameinfo.servercolor :
533 opponent_color(settings->gameinfo.servercolor);
535 _Bool myturn = (gamestate->lastmove ?
536 (gamestate->lastmove->move.piece & COLOR_MASK) : BLACK) != mycolor;
538 _Bool running;
539 do {
540 clear();
541 draw_board(gamestate, mycolor);
542 if (myturn) {
543 running = !sendmove(gamestate, &(settings->gameinfo),
544 opponent, mycolor);
545 } else {
546 running = !recvmove(gamestate, &(settings->gameinfo), opponent);
547 }
548 myturn ^= TRUE;
549 } while (running);
551 post_game(gamestate, &(settings->gameinfo));
552 }
554 void game_start(Settings *settings, int opponent) {
555 GameState gamestate;
556 gamestate_init(&gamestate);
558 game_continue(settings, opponent, &gamestate);
559 }