| 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 */ |
|
| 29 |
|
| 30 #include "game.h" |
|
| 31 #include "network.h" |
|
| 32 #include "input.h" |
|
| 33 #include "colors.h" |
|
| 34 #include "chess/rules.h" |
|
| 35 #include "chess/pgn.h" |
|
| 36 #include <ncurses.h> |
|
| 37 #include <string.h> |
|
| 38 #include <stdio.h> |
|
| 39 #include <errno.h> |
|
| 40 |
|
| 41 static const uint8_t boardx = 4, boardy = 10; |
|
| 42 static int inputy = 21; /* should be overridden on game startup */ |
|
| 43 |
|
| 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 char clkstr[16]; |
|
| 49 bool always_hours = gameinfo->time >= 3600; |
|
| 50 print_clk(white, clkstr, always_hours); |
|
| 51 mvprintw(boardy+4, boardx-1, "White time: %s", clkstr); |
|
| 52 print_clk(black, clkstr, always_hours); |
|
| 53 mvprintw(boardy+5, boardx-1, "Black time: %s", clkstr); |
|
| 54 |
|
| 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 } |
|
| 70 |
|
| 71 return 0; |
|
| 72 } |
|
| 73 |
|
| 74 static void draw_board(GameState *gamestate, |
|
| 75 uint8_t perspective, |
|
| 76 bool unicode) { |
|
| 77 char fen[90]; |
|
| 78 compute_fen(fen, gamestate); |
|
| 79 mvaddstr(0, 0, fen); |
|
| 80 |
|
| 81 for (uint8_t y = 0 ; y < 8 ; y++) { |
|
| 82 for (uint8_t x = 0 ; x < 8 ; x++) { |
|
| 83 uint8_t col = gamestate->board[y][x] & COLOR_MASK; |
|
| 84 uint8_t piece = gamestate->board[y][x]; |
|
| 85 char piecestr[5]; |
|
| 86 if (piece) { |
|
| 87 if (unicode) { |
|
| 88 char* uc = getpieceunicode(piece); |
|
| 89 strncpy(piecestr, uc, 5); |
|
| 90 } else { |
|
| 91 piecestr[0] = (piece & PIECE_MASK) == PAWN |
|
| 92 ? 'P' : getpiecechr(piece); |
|
| 93 piecestr[1] = '\0'; |
|
| 94 } |
|
| 95 } else { |
|
| 96 piecestr[0] = ' '; |
|
| 97 piecestr[1] = '\0'; |
|
| 98 } |
|
| 99 |
|
| 100 bool boardblack = (y&1)==(x&1); |
|
| 101 attrset((col==WHITE ? A_BOLD : A_DIM)| |
|
| 102 COLOR_PAIR(col == WHITE ? |
|
| 103 (boardblack ? COL_WB : COL_WW) : |
|
| 104 (boardblack ? COL_BB : COL_BW) |
|
| 105 ) |
|
| 106 ); |
|
| 107 |
|
| 108 int cy = perspective == WHITE ? boardy-y : boardy-7+y; |
|
| 109 int cx = perspective == WHITE ? boardx+x*3 : boardx+21-x*3; |
|
| 110 mvprintw(cy, cx, " %s ", piecestr); |
|
| 111 } |
|
| 112 } |
|
| 113 |
|
| 114 attrset(A_NORMAL); |
|
| 115 for (uint8_t i = 0 ; i < 8 ; i++) { |
|
| 116 int x = perspective == WHITE ? boardx+i*3+1 : boardx+22-i*3; |
|
| 117 int y = perspective == WHITE ? boardy-i : boardy-7+i; |
|
| 118 mvaddch(boardy+1, x, 'a'+i); |
|
| 119 mvaddch(y, boardx-2, '1'+i); |
|
| 120 } |
|
| 121 |
|
| 122 /* move log */ |
|
| 123 uint8_t logy = 2; |
|
| 124 const uint8_t logx = boardx + 28; |
|
| 125 move(logy, logx); |
|
| 126 |
|
| 127 /* count full moves */ |
|
| 128 unsigned int logi = 0; |
|
| 129 |
|
| 130 /* wrap log after 45 moves */ |
|
| 131 while (gamestate->movecount/6-logi/3 >= 15) { |
|
| 132 logi++; |
|
| 133 } |
|
| 134 |
|
| 135 for (unsigned mi = logi*2 ; mi < gamestate->movecount ; mi++) { |
|
| 136 bool iswhite = mi % 2 == 0; |
|
| 137 if (iswhite) { |
|
| 138 logi++; |
|
| 139 printw("%d. ", logi); |
|
| 140 } |
|
| 141 |
|
| 142 addstr(gamestate->moves[mi].string); |
|
| 143 if (!iswhite && logi%3 == 0) { |
|
| 144 move(++logy, logx); |
|
| 145 } else { |
|
| 146 addch(' '); |
|
| 147 } |
|
| 148 } |
|
| 149 } |
|
| 150 |
|
| 151 static void eval_move_failed_msg(int code) { |
|
| 152 switch (code) { |
|
| 153 case AMBIGUOUS_MOVE: |
|
| 154 printw("Ambiguous move - please specify the piece to move."); |
|
| 155 break; |
|
| 156 case INVALID_POSITION: |
|
| 157 printw("No piece can be moved this way."); |
|
| 158 break; |
|
| 159 case NEED_PROMOTION: |
|
| 160 printw("You need to promote the pawn (append \"=Q\" e.g.)!"); |
|
| 161 break; |
|
| 162 case KING_IN_CHECK: |
|
| 163 printw("Your king is in check!"); |
|
| 164 break; |
|
| 165 case PIECE_PINNED: |
|
| 166 printw("This piece is pinned!"); |
|
| 167 break; |
|
| 168 case INVALID_MOVE_SYNTAX: |
|
| 169 printw("Can't interpret move - please use algebraic notation."); |
|
| 170 break; |
|
| 171 case RULES_VIOLATED: |
|
| 172 printw("Move does not comply chess rules."); |
|
| 173 break; |
|
| 174 case KING_MOVES_INTO_CHECK: |
|
| 175 printw("Can't move the king into a check position."); |
|
| 176 break; |
|
| 177 default: |
|
| 178 printw("Unknown move parser error."); |
|
| 179 } |
|
| 180 } |
|
| 181 |
|
| 182 static void save_pgn(GameState *gamestate, GameInfo *gameinfo) { |
|
| 183 int y = getcury(stdscr); |
|
| 184 |
|
| 185 /* ask for player names */ |
|
| 186 { |
|
| 187 char pname[PLAYER_NAME_BUFLEN]; |
|
| 188 printw("\rWhite's name (%s): ", pgn_player_name(gamestate, WHITE)); |
|
| 189 clrtoeol(); |
|
| 190 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') { |
|
| 191 strncpy(gamestate->wname, pname, PLAYER_NAME_BUFLEN); |
|
| 192 } |
|
| 193 move(y, 0); |
|
| 194 printw("\rBlack's name (%s): ", pgn_player_name(gamestate, BLACK)); |
|
| 195 clrtoeol(); |
|
| 196 if (getnstr(pname, PLAYER_NAME_BUFLEN) == OK && pname[0] != '\0') { |
|
| 197 strncpy(gamestate->bname, pname, PLAYER_NAME_BUFLEN); |
|
| 198 } |
|
| 199 move(y, 0); |
|
| 200 } |
|
| 201 |
|
| 202 bool export_comments = prompt_yesno("Export with comments"); |
|
| 203 |
|
| 204 printw("\rFilename: "); |
|
| 205 clrtoeol(); |
|
| 206 |
|
| 207 char filename[64]; |
|
| 208 if (getnstr(filename, 64) == OK && filename[0] != '\0') { |
|
| 209 move(y, 0); |
|
| 210 FILE *file = fopen(filename, "w"); |
|
| 211 if (file) { |
|
| 212 write_pgn(file, gamestate, gameinfo, export_comments); |
|
| 213 fclose(file); |
|
| 214 printw("File saved."); |
|
| 215 } else { |
|
| 216 printw("Can't write to file (%s).", strerror(errno)); |
|
| 217 } |
|
| 218 clrtoeol(); |
|
| 219 } |
|
| 220 } |
|
| 221 |
|
| 222 #define MOVESTR_BUFLEN 10 |
|
| 223 static int domove_singlemachine(GameState *gamestate, |
|
| 224 GameInfo *gameinfo, uint8_t curcolor) { |
|
| 225 |
|
| 226 |
|
| 227 size_t bufpos = 0; |
|
| 228 char movestr[MOVESTR_BUFLEN]; |
|
| 229 |
|
| 230 flushinp(); |
|
| 231 while (1) { |
|
| 232 const char *curcolorstr = curcolor == WHITE ? "White" : "Black"; |
|
| 233 if (timecontrol(gamestate, gameinfo)) { |
|
| 234 return 1; |
|
| 235 } |
|
| 236 move(inputy, 0); |
|
| 237 printw( |
|
| 238 "Use chess notation to enter your move.\n" |
|
| 239 "Or use a command: remis, resign, savepgn\n\n" |
|
| 240 "%s to move: ", curcolorstr); |
|
| 241 clrtoeol(); |
|
| 242 |
|
| 243 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { |
|
| 244 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { |
|
| 245 if (curcolor == WHITE) { |
|
| 246 gamestate->wresign = true; |
|
| 247 } else { |
|
| 248 gamestate->bresign = true; |
|
| 249 } |
|
| 250 return 1; |
|
| 251 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { |
|
| 252 gamestate->remis = true; |
|
| 253 return 1; |
|
| 254 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { |
|
| 255 save_pgn(gamestate, gameinfo); |
|
| 256 } else if (movestr[0] == 0) { |
|
| 257 /* ignore empty move strings and ask again */ |
|
| 258 } else { |
|
| 259 Move move; |
|
| 260 int result = eval_move(gamestate, movestr, &move, curcolor); |
|
| 261 if (result == VALID_MOVE_SYNTAX) { |
|
| 262 result = validate_move(gamestate, &move); |
|
| 263 if (result == VALID_MOVE_SEMANTICS) { |
|
| 264 apply_move(gamestate, &move); |
|
| 265 if (gamestate->checkmate) { |
|
| 266 return 1; |
|
| 267 } else if (gamestate->stalemate) { |
|
| 268 return 1; |
|
| 269 } else { |
|
| 270 return 0; |
|
| 271 } |
|
| 272 } else { |
|
| 273 eval_move_failed_msg(result); |
|
| 274 } |
|
| 275 } else { |
|
| 276 eval_move_failed_msg(result); |
|
| 277 } |
|
| 278 clrtoeol(); |
|
| 279 } |
|
| 280 } |
|
| 281 } |
|
| 282 } |
|
| 283 |
|
| 284 static int sendmove(GameState *gamestate, GameInfo *gameinfo, |
|
| 285 int opponent, uint8_t mycolor) { |
|
| 286 |
|
| 287 size_t bufpos = 0; |
|
| 288 char movestr[MOVESTR_BUFLEN]; |
|
| 289 bool remis_rejected = false; |
|
| 290 bool remis_suggested = false; |
|
| 291 bool resign_suggested = false; |
|
| 292 bool use_premove = false; |
|
| 293 uint8_t code; |
|
| 294 |
|
| 295 if (*gamestate->premove) { |
|
| 296 use_premove = true; |
|
| 297 const unsigned mlen = sizeof(gamestate->premove); |
|
| 298 strncpy(movestr, gamestate->premove, mlen); |
|
| 299 movestr[mlen] = '\0'; |
|
| 300 memset(gamestate->premove, 0, mlen); |
|
| 301 } |
|
| 302 |
|
| 303 flushinp(); |
|
| 304 while (1) { |
|
| 305 if (timecontrol(gamestate, gameinfo)) { |
|
| 306 net_send_code(opponent, NETCODE_TIMEOVER); |
|
| 307 return 1; |
|
| 308 } |
|
| 309 |
|
| 310 move(inputy, 0); |
|
| 311 printw("Use chess notation to enter your move.\n"); |
|
| 312 if (resign_suggested) { |
|
| 313 if (remis_suggested) { |
|
| 314 printw("The opponent asks you to resign or accept remis. \n\n"); |
|
| 315 } else { |
|
| 316 printw("The opponent asks you to resign. \n\n"); |
|
| 317 } |
|
| 318 } else if (remis_suggested) { |
|
| 319 printw("The opponent offers remis. Type remis to accept. \n\n"); |
|
| 320 } else if (remis_rejected) { |
|
| 321 printw("Remis offer rejected. \n\n"); |
|
| 322 } else { |
|
| 323 printw("Or use a command: remis, resign, savepgn \n\n"); |
|
| 324 } |
|
| 325 printw("Type your move: "); |
|
| 326 clrtoeol(); |
|
| 327 |
|
| 328 /* check if the opponent sent us something */ |
|
| 329 code = net_recieve_code_async(opponent); |
|
| 330 switch (code) { |
|
| 331 case NETCODE_REMIS: |
|
| 332 remis_suggested = true; |
|
| 333 break; |
|
| 334 case NETCODE_TAUNT: |
|
| 335 resign_suggested = true; |
|
| 336 break; |
|
| 337 case NETCODE_RESIGN: |
|
| 338 if (mycolor == WHITE) { |
|
| 339 gamestate->bresign = true; |
|
| 340 } else { |
|
| 341 gamestate->wresign = true; |
|
| 342 } |
|
| 343 return 1; |
|
| 344 case NETCODE_CONNLOST: |
|
| 345 gamestate->ragequit = true; |
|
| 346 return 1; |
|
| 347 case NETCODE_ERROR: |
|
| 348 printw("\rCannot perform asynchronous network IO"); |
|
| 349 cbreak(); getch(); |
|
| 350 exit(EXIT_FAILURE); |
|
| 351 case NETCODE_AGAIN: |
|
| 352 /* try again */ |
|
| 353 break; |
|
| 354 default: |
|
| 355 printw("\nThe opponent sent an invalid network pacakge."); |
|
| 356 } |
|
| 357 |
|
| 358 /* read move */ |
|
| 359 if (use_premove || asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { |
|
| 360 bool was_premove = use_premove; |
|
| 361 use_premove = false; |
|
| 362 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { |
|
| 363 if (mycolor == WHITE) { |
|
| 364 gamestate->wresign = true; |
|
| 365 } else { |
|
| 366 gamestate->bresign = true; |
|
| 367 } |
|
| 368 net_send_code(opponent, NETCODE_RESIGN); |
|
| 369 return 1; |
|
| 370 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { |
|
| 371 save_pgn(gamestate, gameinfo); |
|
| 372 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { |
|
| 373 if (remis_suggested) { |
|
| 374 net_send_code(opponent, NETCODE_REMIS); |
|
| 375 gamestate->remis = true; |
|
| 376 return 1; |
|
| 377 } if (!remis_rejected) { |
|
| 378 net_send_code(opponent, NETCODE_REMIS); |
|
| 379 printw("Remis offer sent - waiting for acceptance..."); |
|
| 380 refresh(); |
|
| 381 code = net_recieve_code(opponent); |
|
| 382 if (code == NETCODE_ACCEPT) { |
|
| 383 gamestate->remis = true; |
|
| 384 return 1; |
|
| 385 } else if (code == NETCODE_CONNLOST) { |
|
| 386 gamestate->ragequit = true; |
|
| 387 return 1; |
|
| 388 } else { |
|
| 389 remis_rejected = true; |
|
| 390 } |
|
| 391 } |
|
| 392 } else if (movestr[0] == 0) { |
|
| 393 /* ignore empty move strings and ask again */ |
|
| 394 } else { |
|
| 395 Move move; |
|
| 396 int eval_result = eval_move(gamestate, movestr, &move, mycolor); |
|
| 397 switch (eval_result) { |
|
| 398 case VALID_MOVE_SYNTAX: |
|
| 399 net_send_data(opponent, NETCODE_MOVE, &move, sizeof(Move)); |
|
| 400 code = net_recieve_code(opponent); |
|
| 401 move.check = code == NETCODE_CHECK || |
|
| 402 code == NETCODE_CHECKMATE; |
|
| 403 gamestate->checkmate = code == NETCODE_CHECKMATE; |
|
| 404 gamestate->stalemate = code == NETCODE_STALEMATE; |
|
| 405 if (code == NETCODE_DECLINE) { |
|
| 406 uint32_t reason; |
|
| 407 net_recieve_data(opponent, &reason, sizeof(uint32_t)); |
|
| 408 reason = ntohl(reason); |
|
| 409 eval_move_failed_msg(reason); |
|
| 410 } else if (code == NETCODE_ACCEPT |
|
| 411 || code == NETCODE_CHECK |
|
| 412 || code == NETCODE_CHECKMATE |
|
| 413 || code == NETCODE_STALEMATE) { |
|
| 414 apply_move(gamestate, &move); |
|
| 415 if (gamestate->checkmate || gamestate->stalemate) { |
|
| 416 return 1; |
|
| 417 } else { |
|
| 418 return 0; |
|
| 419 } |
|
| 420 } else if (code == NETCODE_CONNLOST) { |
|
| 421 printw("Your opponent left the game."); |
|
| 422 return 1; |
|
| 423 } else { |
|
| 424 printw("Invalid network response."); |
|
| 425 } |
|
| 426 break; |
|
| 427 default: |
|
| 428 if (was_premove) { |
|
| 429 printw("\nThe prepared move could not be executed."); |
|
| 430 } else { |
|
| 431 eval_move_failed_msg(eval_result); |
|
| 432 } |
|
| 433 } |
|
| 434 clrtoeol(); |
|
| 435 } |
|
| 436 } |
|
| 437 } |
|
| 438 } |
|
| 439 |
|
| 440 static int recvmove(GameState *gamestate, GameInfo *gameinfo, |
|
| 441 int opponent, uint8_t mycolor) { |
|
| 442 memset(gamestate->premove, 0, sizeof(gamestate->premove)); |
|
| 443 |
|
| 444 size_t bufpos = 0; |
|
| 445 char movestr[MOVESTR_BUFLEN]; |
|
| 446 bool remis_suggested = false, resign_suggested = false; |
|
| 447 while (1) { |
|
| 448 timecontrol(gamestate, gameinfo); |
|
| 449 |
|
| 450 move(inputy, 0); |
|
| 451 printw("Waiting for opponent. Use chess notation to prepare a move.\n"); |
|
| 452 if (*gamestate->premove) { |
|
| 453 printw("Current pre-move: %s \n\n", |
|
| 454 gamestate->premove); |
|
| 455 } else if (remis_suggested && !resign_suggested) { |
|
| 456 printw("Suggested remis. \n\n"); |
|
| 457 } else if (resign_suggested) { |
|
| 458 if (remis_suggested) { |
|
| 459 printw("Suggested to resign or at least to accept remis. \n\n"); |
|
| 460 } else { |
|
| 461 printw("Suggested to resign. \n\n"); |
|
| 462 } |
|
| 463 } else { |
|
| 464 printw("Or use a command: remis, resign, taunt, savepgn \n\n"); |
|
| 465 } |
|
| 466 printw("Prepare your next move: "); |
|
| 467 clrtoeol(); |
|
| 468 refresh(); |
|
| 469 |
|
| 470 /* allow the player to prepare a move */ |
|
| 471 if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) { |
|
| 472 if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) { |
|
| 473 if (mycolor == WHITE) { |
|
| 474 gamestate->wresign = true; |
|
| 475 } else { |
|
| 476 gamestate->bresign = true; |
|
| 477 } |
|
| 478 net_send_code(opponent, NETCODE_RESIGN); |
|
| 479 return 1; |
|
| 480 } else if (strncmp(movestr, "taunt", MOVESTR_BUFLEN) == 0) { |
|
| 481 resign_suggested = true; |
|
| 482 net_send_code(opponent, NETCODE_TAUNT); |
|
| 483 } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) { |
|
| 484 remis_suggested = true; |
|
| 485 net_send_code(opponent, NETCODE_REMIS); |
|
| 486 } else if (strncmp(movestr, "savepgn", MOVESTR_BUFLEN) == 0) { |
|
| 487 save_pgn(gamestate, gameinfo); |
|
| 488 } else if (movestr[0] == 0) { |
|
| 489 memset(gamestate->premove, 0, sizeof(gamestate->premove)); |
|
| 490 } else { |
|
| 491 int res = check_move(movestr, mycolor); |
|
| 492 if (res == VALID_MOVE_SYNTAX) { |
|
| 493 strncpy(gamestate->premove, movestr, 8); |
|
| 494 memset(movestr, 0, MOVESTR_BUFLEN); |
|
| 495 bufpos = 0; |
|
| 496 clrtobot(); |
|
| 497 } else { |
|
| 498 eval_move_failed_msg(res); |
|
| 499 } |
|
| 500 } |
|
| 501 } |
|
| 502 |
|
| 503 /* read opponent's move */ |
|
| 504 uint8_t code = net_recieve_code_async(opponent); |
|
| 505 switch (code) { |
|
| 506 case NETCODE_TIMEOVER: |
|
| 507 /* redraw the time control */ |
|
| 508 timecontrol(gamestate, gameinfo); |
|
| 509 return 1; |
|
| 510 case NETCODE_RESIGN: |
|
| 511 if (mycolor == WHITE) { |
|
| 512 gamestate->bresign = true; |
|
| 513 } else { |
|
| 514 gamestate->wresign = true; |
|
| 515 } |
|
| 516 return 1; |
|
| 517 case NETCODE_CONNLOST: |
|
| 518 gamestate->ragequit = true; |
|
| 519 return 1; |
|
| 520 case NETCODE_REMIS: |
|
| 521 if (remis_suggested) { |
|
| 522 gamestate->remis = true; |
|
| 523 return 1; |
|
| 524 } else { |
|
| 525 if (prompt_yesno( |
|
| 526 "\rYour opponent offers remis - do you accept")) { |
|
| 527 gamestate->remis = true; |
|
| 528 net_send_code(opponent, NETCODE_ACCEPT); |
|
| 529 return 1; |
|
| 530 } else { |
|
| 531 net_send_code(opponent, NETCODE_DECLINE); |
|
| 532 } |
|
| 533 } |
|
| 534 break; |
|
| 535 case NETCODE_MOVE: { |
|
| 536 Move move; |
|
| 537 net_recieve_data(opponent, &move, sizeof(Move)); |
|
| 538 code = validate_move(gamestate, &move); |
|
| 539 if (code == VALID_MOVE_SEMANTICS) { |
|
| 540 apply_move(gamestate, &move); |
|
| 541 if (gamestate->checkmate) { |
|
| 542 net_send_code(opponent, NETCODE_CHECKMATE); |
|
| 543 return 1; |
|
| 544 } else if (gamestate->stalemate) { |
|
| 545 net_send_code(opponent, NETCODE_STALEMATE); |
|
| 546 return 1; |
|
| 547 } else if (move.check) { |
|
| 548 net_send_code(opponent, NETCODE_CHECK); |
|
| 549 } else { |
|
| 550 net_send_code(opponent, NETCODE_ACCEPT); |
|
| 551 } |
|
| 552 return 0; |
|
| 553 } else { |
|
| 554 uint32_t reason = htonl(code); |
|
| 555 net_send_data(opponent, NETCODE_DECLINE, |
|
| 556 &reason, sizeof(uint32_t)); |
|
| 557 } |
|
| 558 break; |
|
| 559 } |
|
| 560 case NETCODE_ERROR: |
|
| 561 printw("\rCannot perform asynchronous network IO"); |
|
| 562 cbreak(); getch(); |
|
| 563 exit(EXIT_FAILURE); |
|
| 564 case NETCODE_AGAIN: |
|
| 565 /* try again */ |
|
| 566 break; |
|
| 567 default: |
|
| 568 printw("\nInvalid network request."); |
|
| 569 } |
|
| 570 } |
|
| 571 } |
|
| 572 |
|
| 573 void game_review(Settings* settings, GameState *gamestate) { |
|
| 574 const unsigned page_moves = 10; |
|
| 575 GameInfo *gameinfo = &(settings->gameinfo); |
|
| 576 GameState viewedstate = {0}; |
|
| 577 unsigned viewedmove = gamestate->movecount; |
|
| 578 bool redraw = true; |
|
| 579 |
|
| 580 noecho(); |
|
| 581 int c; |
|
| 582 do { |
|
| 583 if (redraw) { |
|
| 584 gamestate_cleanup(&viewedstate); |
|
| 585 gamestate_at_move(gamestate, viewedmove, &viewedstate); |
|
| 586 |
|
| 587 erase(); /* don't use clear() to avoid flickering */ |
|
| 588 draw_board(&viewedstate, WHITE, settings->unicode); |
|
| 589 timecontrol(&viewedstate, gameinfo); |
|
| 590 |
|
| 591 move(getmaxy(stdscr)-5, 0); |
|
| 592 if (gamestate->wresign) { |
|
| 593 addstr("White resigned.\n"); |
|
| 594 } else if (gamestate->bresign) { |
|
| 595 addstr("Black resigned.\n"); |
|
| 596 } else if (gamestate->remis) { |
|
| 597 addstr("The game ended remis.\n"); |
|
| 598 } else if (gamestate->stalemate) { |
|
| 599 addstr("The game ended in a stalemate.\n"); |
|
| 600 } else if (gamestate->checkmate) { |
|
| 601 printw("%s was checkmated.\n", |
|
| 602 gamestate->movecount % 2 == 0 ? "White" : "Black"); |
|
| 603 } else if (gamestate->ragequit) { |
|
| 604 printw("Your opponent disconnected.\n"); |
|
| 605 } |
|
| 606 addstr("\nPress 'q' to quit, 's' to save the position as PGN, or\n" |
|
| 607 "arrow keys, home/end, page up/down to review the game.\n"); |
|
| 608 flushinp(); |
|
| 609 redraw = false; |
|
| 610 } |
|
| 611 c = getch(); |
|
| 612 if (c == 's') { |
|
| 613 addch('\r'); |
|
| 614 echo(); |
|
| 615 save_pgn(&viewedstate, gameinfo); |
|
| 616 noecho(); |
|
| 617 redraw = true; |
|
| 618 } else if (c == KEY_UP || c == KEY_LEFT) { |
|
| 619 if (viewedmove > 0) { |
|
| 620 viewedmove--; |
|
| 621 redraw = true; |
|
| 622 } |
|
| 623 } else if (c == KEY_DOWN || c == KEY_RIGHT) { |
|
| 624 if (viewedmove < gamestate->movecount) { |
|
| 625 viewedmove++; |
|
| 626 redraw = true; |
|
| 627 } |
|
| 628 } else if (c == KEY_HOME) { |
|
| 629 viewedmove = 0; |
|
| 630 redraw = true; |
|
| 631 } else if (c == KEY_END) { |
|
| 632 viewedmove = gamestate->movecount; |
|
| 633 redraw = true; |
|
| 634 } else if (c == KEY_PPAGE) { |
|
| 635 if (viewedmove > page_moves) { |
|
| 636 viewedmove -= page_moves; |
|
| 637 } else { |
|
| 638 viewedmove = 0; |
|
| 639 } |
|
| 640 redraw = true; |
|
| 641 } else if (c == KEY_NPAGE) { |
|
| 642 viewedmove += page_moves; |
|
| 643 if (viewedmove > gamestate->movecount) { |
|
| 644 viewedmove = gamestate->movecount; |
|
| 645 } |
|
| 646 redraw = true; |
|
| 647 } |
|
| 648 } while (c != 'q'); |
|
| 649 echo(); |
|
| 650 gamestate_cleanup(&viewedstate); |
|
| 651 } |
|
| 652 |
|
| 653 void game_play_singlemachine(Settings *settings) { |
|
| 654 inputy = getmaxy(stdscr) - 6; |
|
| 655 |
|
| 656 GameState gamestate; |
|
| 657 gamestate_init(&gamestate); |
|
| 658 uint8_t curcol = WHITE; |
|
| 659 |
|
| 660 if (settings->continuepgn) { |
|
| 661 FILE *pgnfile = fopen(settings->continuepgn, "r"); |
|
| 662 if (pgnfile) { |
|
| 663 int result = read_pgn(pgnfile, &gamestate, &(settings->gameinfo)); |
|
| 664 long position = ftell(pgnfile); |
|
| 665 fclose(pgnfile); |
|
| 666 if (result) { |
|
| 667 printw("Invalid PGN file content at position %ld:\n%s\n", |
|
| 668 position, pgn_error_str(result)); |
|
| 669 return; |
|
| 670 } |
|
| 671 if (!is_game_running(&gamestate)) { |
|
| 672 addstr("Game has ended. Use -S to analyze it.\n"); |
|
| 673 return; |
|
| 674 } |
|
| 675 curcol = opponent_color(last_move(&gamestate).piece&COLOR_MASK); |
|
| 676 } else { |
|
| 677 printw("Can't read PGN file (%s)\n", strerror(errno)); |
|
| 678 return; |
|
| 679 } |
|
| 680 } |
|
| 681 |
|
| 682 bool running; |
|
| 683 do { |
|
| 684 clear(); |
|
| 685 uint8_t perspective = settings->disableflip ? WHITE : curcol; |
|
| 686 draw_board(&gamestate, perspective, settings->unicode); |
|
| 687 running = !domove_singlemachine(&gamestate, |
|
| 688 &(settings->gameinfo), curcol); |
|
| 689 curcol = opponent_color(curcol); |
|
| 690 } while (running); |
|
| 691 |
|
| 692 game_review(settings, &gamestate); |
|
| 693 gamestate_cleanup(&gamestate); |
|
| 694 } |
|
| 695 |
|
| 696 void game_play(Settings *settings, GameState *gamestate, int opponent) { |
|
| 697 inputy = getmaxy(stdscr) - 6; |
|
| 698 |
|
| 699 uint8_t mycolor = settings->gameinfo.servercolor; |
|
| 700 if (!settings->ishost) { |
|
| 701 mycolor = opponent_color(mycolor); |
|
| 702 } |
|
| 703 |
|
| 704 bool myturn = (gamestate->movecount > 0 ? |
|
| 705 (last_move(gamestate).piece & COLOR_MASK) : BLACK) != mycolor; |
|
| 706 |
|
| 707 bool running; |
|
| 708 do { |
|
| 709 clear(); |
|
| 710 draw_board(gamestate, mycolor, settings->unicode); |
|
| 711 if (myturn) { |
|
| 712 running = !sendmove(gamestate, &(settings->gameinfo), |
|
| 713 opponent, mycolor); |
|
| 714 } else { |
|
| 715 running = !recvmove(gamestate, &(settings->gameinfo), |
|
| 716 opponent, mycolor); |
|
| 717 } |
|
| 718 myturn ^= true; |
|
| 719 } while (running); |
|
| 720 } |
|
| 721 |
|
| 722 void dump_gameinfo(GameInfo *gameinfo) { |
|
| 723 int serverwhite = gameinfo->servercolor == WHITE; |
|
| 724 attron(A_UNDERLINE); |
|
| 725 printw("Game details\n"); |
|
| 726 attroff(A_UNDERLINE); |
|
| 727 printw(" Server: %s\n Client: %s\n", |
|
| 728 serverwhite?"White":"Black", serverwhite?"Black":"White" |
|
| 729 ); |
|
| 730 if (gameinfo->timecontrol) { |
|
| 731 if (gameinfo->time % 60) { |
|
| 732 printw(" Time limit: %ds + %ds\n", |
|
| 733 gameinfo->time, gameinfo->addtime); |
|
| 734 } else { |
|
| 735 printw(" Time limit: %dm + %ds\n", |
|
| 736 gameinfo->time/60, gameinfo->addtime); |
|
| 737 } |
|
| 738 } else { |
|
| 739 printw(" No time limit\n"); |
|
| 740 } |
|
| 741 refresh(); |
|
| 742 } |
|
| 743 |
|
| 744 void dump_moveinfo(GameState *gamestate) { |
|
| 745 for (unsigned i = 0 ; i < gamestate->movecount ; i++) { |
|
| 746 if (i % 2 == 0) { |
|
| 747 printw("%d. %s", 1+i/2, gamestate->moves[i].string); |
|
| 748 } else { |
|
| 749 printw("%s", gamestate->moves[i].string); |
|
| 750 } |
|
| 751 // only five moves reliably fit into one screen row |
|
| 752 if ((i+1) % 10) { |
|
| 753 addch(' '); |
|
| 754 } else { |
|
| 755 addch('\n'); |
|
| 756 } |
|
| 757 } |
|
| 758 refresh(); |
|
| 759 } |
|