src/game.c

Wed, 11 Jun 2014 16:54:20 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 11 Jun 2014 16:54:20 +0200
changeset 49
02c509a44e98
parent 48
0cedda2544da
child 50
41017d0a72c5
permissions
-rw-r--r--

logging string representation of moves in short algebraic notation

     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>
    39 static const uint8_t boardx = 10, boardy = 10;
    40 static int inputy = 21; /* should be overridden on game startup */
    42 static int timecontrol(GameState *gamestate, GameInfo *gameinfo) {
    43     if (gameinfo->timecontrol) {
    44         uint16_t white = remaining_movetime(gameinfo, gamestate, WHITE);
    45         uint16_t black = remaining_movetime(gameinfo, gamestate, BLACK);
    46         mvprintw(boardy+4, boardx-1,
    47             "White time: %4" PRIu16 ":%02" PRIu16,
    48             white / 60, white % 60);
    49         mvprintw(boardy+5, boardx-1,
    50             "Black time: %4" PRIu16 ":%02" PRIu16,
    51             black / 60, black % 60);
    53         if (white == 0) {
    54             move(inputy, 0);
    55             printw("Time is over - Black wins!");
    56             clrtobot();
    57             refresh();
    58             return 1;
    59         }
    60         if (black == 0) {
    61             move(inputy, 0);
    62             printw("Time is over - White wins!");
    63             clrtobot();
    64             refresh();
    65             return 1;
    66         }
    67     }
    69     return 0;
    70 }
    72 static void draw_board(GameState *gamestate) {
    73     for (uint8_t y = 0 ; y < 8 ; y++) {
    74         for (uint8_t x = 0 ; x < 8 ; x++) {
    75             uint8_t col = gamestate->board[y][x] & COLOR_MASK;
    76             uint8_t piece = gamestate->board[y][x] & PIECE_MASK;
    77             char piecec;
    78             if (piece) {
    79                 piecec = piece == PAWN ? 'P' : getpiecechr(piece);
    80             } else {
    81                 piecec = ' ';
    82             }
    84             _Bool boardblack = (y&1)==(x&1);
    85             attrset((col==WHITE ? A_BOLD : A_DIM)|
    86                 COLOR_PAIR(col == WHITE ?
    87                     (boardblack ? COL_WB : COL_WW) :
    88                     (boardblack ? COL_BB : COL_BW)
    89                 )
    90             );
    92             int cy = gamestate->mycolor == WHITE ? boardy-y : boardy-7+y;
    93             int cx = gamestate->mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
    94             mvaddch(cy, cx, ' ');
    95             mvaddch(cy, cx+1, piecec);
    96             mvaddch(cy, cx+2, ' ');
    97         }
    98     }
   100     attrset(A_NORMAL);
   101     for (uint8_t i = 0 ; i < 8 ; i++) {
   102         int x = gamestate->mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
   103         int y = gamestate->mycolor == WHITE ? boardy-i : boardy-7+i;
   104         mvaddch(boardy+1, x, 'a'+i);
   105         mvaddch(y, boardx-2, '1'+i);
   106     }
   108     /* move log */
   109     // TODO: introduce window to avoid bugs with a long move log
   110     uint8_t logy = 0;
   111     const uint8_t logx = boardx + 30;
   112     int logi = 1;
   113     MoveList *logelem = gamestate->movelist;
   115     while (logelem) {
   116         logi++;
   117         if (logi % 2 == 0) {
   118             if ((logi - 2) % 4 == 0) {
   119                 logy++;
   120                 move(logy, logx);
   121             }
   122             printw("%d. ", logi / 2);
   123         }
   125         if (logelem) {
   126             addstr(logelem->move.string);
   127             if (!logelem->next) {
   128                 if (gamestate->stalemate) {
   129                     addstr(" stalemate");
   130                 }
   131             }
   132             addch(' ');
   134             logelem = logelem->next;
   135         }
   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 #define MOVESTR_BUFLEN 8
   171 static int domove_singlemachine(GameState *gamestate, GameInfo *gameinfo) {
   174     size_t bufpos = 0;
   175     char movestr[MOVESTR_BUFLEN];
   177     flushinp();
   178     while (1) {
   179         if (timecontrol(gamestate, gameinfo)) {
   180             return 1;
   181         }
   183         move(inputy, 0);
   184         printw(
   185             "Use chess notation to enter your move.\n"
   186             "Or type 'resign' to resign or 'remis' to end with remis.\n\n"
   187             "Type your move: ");
   188         clrtoeol();
   190         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   191             if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
   192                 printw("%s resigned!",
   193                     gamestate->mycolor==WHITE?"White":"Black");
   194                 clrtoeol();
   195                 refresh();
   196                 return 1;
   197             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   198                 printw("Game ends remis.");
   199                 clrtoeol();
   200                 refresh();
   201                 return 1;
   202             } else {
   203                 Move move;
   204                 int eval_result = eval_move(gamestate, movestr, &move);
   205                 switch (eval_result) {
   206                 case VALID_MOVE_SYNTAX:
   207                     eval_result = validate_move(gamestate, &move);
   208                     if (eval_result == VALID_MOVE_SEMANTICS) {
   209                         apply_move(gamestate, &move);
   210                         if (gamestate->checkmate) {
   211                             printw("Checkmate!");
   212                             clrtoeol();
   213                             return 1;
   214                         } else if (gamestate->stalemate) {
   215                             printw("Stalemate!");
   216                             clrtoeol();
   217                             return 1;
   218                         } else {
   219                             return 0;
   220                         }
   221                     } else {
   222                         eval_move_failed_msg(eval_result);
   223                     }
   224                     break;
   225                 default:
   226                     eval_move_failed_msg(eval_result);
   227                 }
   228                 clrtoeol();
   229             }
   230         }
   231     }
   232 }
   234 static int sendmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   236     size_t bufpos = 0;
   237     char movestr[MOVESTR_BUFLEN];
   238     _Bool remisrejected = FALSE;
   239     uint8_t code;
   241     flushinp();
   242     while (1) {
   243         if (timecontrol(gamestate, gameinfo)) {
   244             net_send_code(opponent, NETCODE_TIMEOVER);
   245             return 1;
   246         }
   248         move(inputy, 0);
   249         if (remisrejected) {
   250             printw(
   251                 "Use chess notation to enter your move.\n"
   252                 "Remis offer rejected - type 'resign' to resign.      \n\n"
   253                 "Type your move: ");
   254         } else {
   255             printw(
   256                 "Use chess notation to enter your move.\n"
   257                 "Or type 'resign' to resign or 'remis' to offer remis.\n\n"
   258                 "Type your move: ");
   259         }
   260         clrtoeol();
   262         if (asyncgetnstr(movestr, &bufpos, MOVESTR_BUFLEN)) {
   263             if (strncmp(movestr, "resign", MOVESTR_BUFLEN) == 0) {
   264                 printw("You resigned!");
   265                 clrtoeol();
   266                 refresh();
   267                 net_send_code(opponent, NETCODE_RESIGN);
   268                 return 1;
   269             } else if (strncmp(movestr, "remis", MOVESTR_BUFLEN) == 0) {
   270                 if (!remisrejected) {
   271                     net_send_code(opponent, NETCODE_REMIS);
   272                     printw("Remis offer sent - waiting for acceptance...");
   273                     refresh();
   274                     code = net_recieve_code(opponent);
   275                     if (code == NETCODE_ACCEPT) {
   276                         printw("\rRemis accepted!");
   277                         clrtoeol();
   278                         refresh();
   279                         return 1;
   280                     } else if (code == NETCODE_CONNLOST) {
   281                         printw("\rYour opponent left the game.");
   282                         clrtoeol();
   283                         refresh();
   284                         return 1;
   285                     } else {
   286                         remisrejected = TRUE;
   287                     }
   288                 }
   289             } else {
   290                 Move move;
   291                 int eval_result = eval_move(gamestate, movestr, &move);
   292                 switch (eval_result) {
   293                 case VALID_MOVE_SYNTAX:
   294                     net_send_data(opponent, NETCODE_MOVE, &move,
   295                         sizeof(Move)-8);
   296                     code = net_recieve_code(opponent);
   297                     move.check = code == NETCODE_CHECK ||
   298                         code == NETCODE_CHECKMATE;
   299                     gamestate->checkmate = code == NETCODE_CHECKMATE;
   300                     gamestate->stalemate = code == NETCODE_STALEMATE;
   301                     if (code == NETCODE_DECLINE) {
   302                         uint32_t reason;
   303                         net_recieve_data(opponent, &reason, sizeof(uint32_t));
   304                         reason = ntohl(reason);
   305                         eval_move_failed_msg(reason);
   306                     } else if (code == NETCODE_ACCEPT
   307                             || code == NETCODE_CHECK
   308                             || code == NETCODE_CHECKMATE
   309                             || code == NETCODE_STALEMATE) {
   310                         apply_move(gamestate, &move);
   311                         if (gamestate->checkmate) {
   312                             printw("Checkmate!");
   313                             clrtoeol();
   314                             return 1;
   315                         } else if (gamestate->stalemate) {
   316                             printw("Stalemate!");
   317                             clrtoeol();
   318                             return 1;
   319                         } else {
   320                             return 0;
   321                         }
   322                     } else if (code == NETCODE_CONNLOST) {
   323                         printw("Your opponent left the game.");
   324                         return 1;
   325                     } else {
   326                         printw("Invalid network response.");
   327                     }
   328                     break;
   329                 default:
   330                     eval_move_failed_msg(eval_result);
   331                 }
   332                 clrtoeol();
   333             }
   334         }
   335     }
   336 }
   338 static int recvmove(GameState *gamestate, GameInfo *gameinfo, int opponent) {
   340     struct timeval timeout;
   341     while (1) {
   342         timecontrol(gamestate, gameinfo);
   344         move(inputy, 0);
   345         printw("Awaiting opponent move...");
   346         clrtoeol();
   347         refresh();
   349         fd_set readfds;
   351         FD_ZERO(&readfds);
   352         FD_SET(opponent, &readfds);
   353         timeout.tv_sec = 0;
   354         timeout.tv_usec = 1e5;
   356         int result = select(opponent+1, &readfds, NULL, NULL, &timeout);
   357         if (result == -1) {
   358             printw("\rCannot perform asynchronous network IO");
   359             cbreak(); getch();
   360             exit(EXIT_FAILURE);
   361         }
   362         if (result > 0) {
   363             uint8_t code = net_recieve_code(opponent);
   365             Move move;
   366             switch (code) {
   367             case NETCODE_TIMEOVER:
   368                 printw("\rYour opponent's time ran out - you win!");
   369                 clrtoeol();
   370                 return 1;
   371             case NETCODE_RESIGN:
   372                 printw("\rYour opponent resigned!");
   373                 clrtoeol();
   374                 return 1;
   375             case NETCODE_CONNLOST:
   376                 printw("\rYour opponent has left the game.");
   377                 clrtoeol();
   378                 return 1;
   379             case NETCODE_REMIS:
   380                 if (prompt_yesno(
   381                     "\rYour opponent offers remis - do you accept")) {
   382                     printw("\rRemis accepted!");
   383                     clrtoeol();
   384                     net_send_code(opponent, NETCODE_ACCEPT);
   385                     return 1;
   386                 } else {
   387                     net_send_code(opponent, NETCODE_DECLINE);
   388                 }
   389                 break;
   390             case NETCODE_MOVE:
   391                 net_recieve_data(opponent, &move, sizeof(Move)-8);
   392                 code = validate_move(gamestate, &move);
   393                 if (code == VALID_MOVE_SEMANTICS) {
   394                     apply_move(gamestate, &move);
   395                     if (move.check) {
   396                         net_send_code(opponent, NETCODE_CHECK);
   397                     } else if (gamestate->checkmate) {
   398                         net_send_code(opponent, NETCODE_CHECKMATE);
   399                         printw("\rCheckmate!");
   400                         clrtoeol();
   401                         return 1;
   402                     } else if (gamestate->stalemate) {
   403                         net_send_code(opponent, NETCODE_STALEMATE);
   404                         printw("\rStalemate!");
   405                         clrtoeol();
   406                         return 1;
   407                     } else {
   408                         net_send_code(opponent, NETCODE_ACCEPT);
   409                     }
   410                     return 0;
   411                 } else {
   412                     uint32_t reason = htonl(code);
   413                     net_send_data(opponent, NETCODE_DECLINE,
   414                         &reason, sizeof(uint32_t));
   415                 }
   416                 break;
   417             default:
   418                 printw("\nInvalid network request.");
   419             }
   420         }
   421     }
   422 }
   424 static void init_board(GameState *gamestate) {
   425     Board initboard = {
   426         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   427         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   428         {0,     0,       0,       0,      0,     0,       0,       0},
   429         {0,     0,       0,       0,      0,     0,       0,       0},
   430         {0,     0,       0,       0,      0,     0,       0,       0},
   431         {0,     0,       0,       0,      0,     0,       0,       0},
   432         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   433         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   434     };
   435     memcpy(gamestate->board, initboard, sizeof(Board));
   436 }
   438 void game_start_singlemachine(Settings *settings) {
   439     inputy = getmaxy(stdscr) - 6;
   441     GameState gamestate;
   442     memset(&gamestate, 0, sizeof(GameState));
   443     init_board(&gamestate);
   444     gamestate.mycolor = WHITE;
   446     _Bool running;
   447     do {
   448         clear();
   449         draw_board(&gamestate);
   450         running = !domove_singlemachine(&gamestate, &(settings->gameinfo));
   451         gamestate.mycolor = opponent_color(gamestate.mycolor);
   452     }  while (running);
   453     move(0,0);
   454     draw_board(&gamestate);
   456     gamestate_cleanup(&gamestate);
   457 }
   459 void game_start(Settings *settings, int opponent) {
   460     inputy = getmaxy(stdscr) - 6;
   462     _Bool myturn = is_server(settings) ==
   463         (settings->gameinfo.servercolor == WHITE);
   465     GameState gamestate;
   466     memset(&gamestate, 0, sizeof(GameState));
   467     init_board(&gamestate);
   468     gamestate.mycolor = myturn ? WHITE:BLACK;
   470     _Bool running;
   471     do {
   472         clear();
   473         draw_board(&gamestate);
   474         if (myturn) {
   475             running = !sendmove(&gamestate, &(settings->gameinfo), opponent);
   476         } else {
   477             running = !recvmove(&gamestate, &(settings->gameinfo), opponent);
   478         }
   479         myturn ^= TRUE;
   480     }  while (running);
   482     move(0,0);
   483     draw_board(&gamestate);
   485     gamestate_cleanup(&gamestate);
   486 }

mercurial