src/game.c

Tue, 18 Sep 2018 15:02:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 18 Sep 2018 15:02:08 +0200
changeset 69
c8f2c280cff7
parent 68
b34de5ce7d0e
permissions
-rw-r--r--

adds unicode support

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

mercurial