src/game.c

Mon, 16 Jun 2014 15:41:06 +0200

author
Mike Becker <universe@uap-core.de>
date
Mon, 16 Jun 2014 15:41:06 +0200
changeset 51
84f2e380a434
parent 50
41017d0a72c5
child 53
78b580bfde33
permissions
-rw-r--r--

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 }

mercurial