src/game.c

Wed, 29 Aug 2018 13:45:13 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 29 Aug 2018 13:45:13 +0200
changeset 63
611332453da0
parent 60
0c50aac49e55
child 68
b34de5ce7d0e
permissions
-rw-r--r--

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 }

mercurial