src/game.c

Wed, 26 Mar 2014 14:53:15 +0100

author
Mike Becker <universe@uap-core.de>
date
Wed, 26 Mar 2014 14:53:15 +0100
changeset 14
970748b9a73b
parent 13
faec61c4901f
child 15
7ffd66591afe
permissions
-rw-r--r--

fixed crucial bug where both players could move at the same time + added pawn rules (TODO: en passant)

     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 "input.h"
    32 #include "rules/rules.h"
    33 #include <ncurses.h>
    34 #include <string.h>
    36 static const uint8_t boardx = 10, boardy = 10;
    38 static void draw_board(Board board, uint8_t mycolor) {
    40     for (uint8_t y = 0 ; y < 8 ; y++) {
    41         for (uint8_t x = 0 ; x < 8 ; x++) {
    42             uint8_t col = board[y][x] & COLOR_MASK;
    43             uint8_t piece = board[y][x] & PIECE_MASK;
    44             char piecec = ' ';
    45             switch (piece) {
    46                 case PAWN: piecec = 'P'; break;
    47                 case ROOK: piecec = 'R'; break;
    48                 case KNIGHT: piecec = 'N'; break;
    49                 case BISHOP: piecec = 'B'; break;
    50                 case QUEEN: piecec = 'Q'; break;
    51                 case KING: piecec = 'K'; break;
    52             }
    54             attrset((col == WHITE ? A_BOLD : A_DIM) |
    55                 COLOR_PAIR((y&1)==(x&1) ? COL_WB : COL_BW));
    57             int cy = mycolor == WHITE ? boardy-y : boardy-7+y;
    58             int cx = mycolor == WHITE ? boardx+x*3 : boardx+21-x*3;
    59             mvaddch(cy, cx, ' ');
    60             mvaddch(cy, cx+1, piecec);
    61             mvaddch(cy, cx+2, ' ');
    62         }
    63     }
    65     attrset(A_NORMAL);
    66     for (uint8_t i = 0 ; i < 8 ; i++) {
    67         int x = mycolor == WHITE ? boardx+i*3+1 : boardx+22-i*3;
    68         int y = mycolor == WHITE ? boardy-i : boardy-7+i;
    69         mvaddch(boardy+1, x, 'a'+i);
    70         mvaddch(y, boardx-2, '1'+i);
    71     }
    72 }
    74 /**
    75  * Applies a move and deletes captured pieces.
    76  * 
    77  * @param board the current board state
    78  * @param move the move to apply
    79  */
    80 static void apply_move(Board board, Move *move) {
    81     msrc(board, move) = 0;
    82     // TODO: care for en passant capture
    83     mdst(board, move) = move->piece;
    85     /* castling */
    86     if ((move->piece & PIECE_MASK) == KING &&
    87         move->fromfile == fileidx('e')) {
    88         uint8_t color = move->piece & COLOR_MASK;
    90         if (move->tofile == fileidx('g')) {
    91             board[move->torow][fileidx('h')] = 0;
    92             board[move->torow][fileidx('f')] = color|ROOK;
    93         } else if (move->tofile == fileidx('c')) {
    94             board[move->torow][fileidx('a')] = 0;
    95             board[move->torow][fileidx('d')] = color|ROOK;
    96         }
    97     }
    98 }
   100 /**
   101  * Validates move by applying chess rules.
   102  * @param board the current board state
   103  * @param move the move to validate
   104  * @return TRUE, if the move complies to chess rules, FALSE otherwise
   105  */
   106 static _Bool validate_move(Board board, Move *move) {
   107     _Bool result;
   109     /* validate indices (don't trust opponent) */
   110     if (!chkidx(move)) {
   111         return FALSE;
   112     }
   114     /* does piece exist */
   115     result = msrc(board, move) == move->piece;
   117     /* can't capture own pieces */
   118     if ((mdst(board, move) & COLOR_MASK) == (move->piece & COLOR_MASK)) {
   119         return FALSE;
   120     }
   122     /* validate individual rules */
   123     switch (move->piece & PIECE_MASK) {
   124     case PAWN:
   125         result = result && pawn_chkrules(board, move);
   126         result = result && !pawn_isblocked(board, move);
   127         break;
   128     case ROOK:
   129         result = result && rook_chkrules(board, move);
   130         result = result && !rook_isblocked(board, move);
   131         break;
   132     case KNIGHT:
   133         result = result && knight_chkrules(board, move);
   134         result = result && !knight_isblocked(board, move);
   135         break;
   136     case BISHOP:
   137         result = result && bishop_chkrules(board, move);
   138         result = result && !bishop_isblocked(board, move);
   139         break;
   140     case QUEEN:
   141         result = result && queen_chkrules(board, move);
   142         result = result && !queen_isblocked(board, move);
   143         break;
   144     case KING:
   145         result = result && king_chkrules(board, move);
   146         result = result && !king_isblocked(board, move);
   147         break;
   148     default:
   149         result = FALSE;
   150     }
   152     /* is piece pinned */
   153     // TODO: make it so
   155     /* correct check and checkmate flags */
   156     // TODO: make it so
   158     return result;
   159 }
   161 /**
   162  * Maps a character to a piece.
   163  * 
   164  * Does not work for pawns, since they don't have a character.
   165  * 
   166  * @param c one of R,N,B,Q,K
   167  * @return numeric value for the specified piece
   168  */
   169 static uint8_t getpiece(char c) {
   170     switch (c) {
   171         case 'R': return ROOK;
   172         case 'N': return KNIGHT;
   173         case 'B': return BISHOP;
   174         case 'Q': return QUEEN;
   175         case 'K': return KING;
   176         default: return 0;
   177     }
   178 }
   180 /**
   181  * Guesses the location of a piece for short algebraic notation.
   182  * 
   183  * @param board the current state of the board
   184  * @param move the move date to operate on
   185  * @return TRUE if the location could be retrieved, FALSE if the location is
   186  * ambiguous
   187  */
   188 static _Bool getlocation(Board board, Move *move) {   
   189     uint8_t piece = move->piece & PIECE_MASK;
   190     switch (piece) {
   191         case PAWN: return pawn_getlocation(board, move);
   192         case ROOK: return rook_getlocation(board, move);
   193         case KNIGHT: return knight_getlocation(board, move);
   194         case BISHOP: return bishop_getlocation(board, move);
   195         case QUEEN: return queen_getlocation(board, move);
   196         case KING: return king_getlocation(board, move);
   197         default: return FALSE;
   198     }
   199 }
   201 /**
   202  * Evaluates a move syntactically and stores the move data in the specified
   203  * object.
   204  * 
   205  * @param board the current state of the board
   206  * @param mycolor the color of the current player
   207  * @param mstr the input string to parse
   208  * @param move a pointer to object where the move data shall be stored
   209  * @return TRUE, if the move is syntactically valid, FALSE otherwise
   210  */
   211 static _Bool eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
   212     memset(move, 0, sizeof(Move));
   213     move->fromfile = POS_UNSPECIFIED;
   214     move->fromrow = POS_UNSPECIFIED;
   216     size_t len = strlen(mstr);
   218     /* evaluate check/checkmate flags */
   219     if (mstr[len-1] == '+') {
   220         len--; mstr[len] = '\0';
   221         move->check = TRUE;
   222     } else if (mstr[len-1] == '#') {
   223         len--; mstr[len] = '\0';
   224         move->checkmate = TRUE;
   225     }
   227     if (len == 2) {
   228         /* pawn move (e.g. "e4") */
   229         move->piece = PAWN;
   230         move->tofile = fileidx(mstr[0]);
   231         move->torow = rowidx(mstr[1]);
   232     } else if (len == 3) {
   233         if (strcmp(mstr, "O-O") == 0) {
   234             /* king side castling */
   235             move->piece = KING;
   236             move->fromfile = fileidx('e');
   237             move->tofile = fileidx('g');
   238             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   239         } else {
   240             /* move (e.g. "Nf3") */
   241             move->piece = getpiece(mstr[0]);
   242             move->tofile = fileidx(mstr[1]);
   243             move->torow = rowidx(mstr[2]);
   244         }
   246     } else if (len == 4) {
   247         move->piece = getpiece(mstr[0]);
   248         if (mstr[1] == 'x') {
   249             /* capture (e.g. "Nxf3", "dxe5") */
   250             move->capture = TRUE;
   251             if (!move->piece) {
   252                 move->piece = PAWN;
   253                 move->fromfile = fileidx(mstr[0]);
   254             }
   255         } else {
   256             /* move (e.g. "Ndf3", "N2c3") */
   257             move->fromfile = isfile(mstr[1]) ?
   258                 fileidx(mstr[1]) : rowidx(mstr[1]);
   259         }
   260         move->tofile = fileidx(mstr[2]);
   261         move->torow = rowidx(mstr[3]);
   262     } else if (len == 5) {
   263         if (strcmp(mstr, "O-O-O") == 0) {
   264             /* queen side castling "O-O-O" */
   265             move->piece = KING;
   266             move->fromfile = fileidx('e');
   267             move->tofile = fileidx('c');
   268             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   269         } else {
   270             move->piece = getpiece(mstr[0]);
   271             if (mstr[2] == 'x') {
   272                 move->capture = TRUE;
   273                 if (move->piece) {
   274                     /* capture (e.g. "Ndxf3") */
   275                     move->fromfile = fileidx(mstr[1]);
   276                 } else {
   277                     /* long notation capture (e.g. "e5xf6") */
   278                     move->piece = PAWN;
   279                     move->fromfile = fileidx(mstr[0]);
   280                     move->fromrow = rowidx(mstr[1]);
   281                 }
   282             } else {
   283                 /* long notation move (e.g. "Nc5a4") */
   284                 move->fromfile = fileidx(mstr[1]);
   285                 move->fromrow = rowidx(mstr[2]);
   286             }
   287             move->tofile = fileidx(mstr[3]);
   288             move->torow = rowidx(mstr[4]);
   289         }
   290     } else if (len == 6) {
   291         /* long notation capture (e.g. "Nc5xf3") */
   292         if (mstr[3] == 'x') {
   293             move->capture = TRUE;
   294             move->piece = getpiece(mstr[0]);
   295             move->fromfile = fileidx(mstr[1]);
   296             move->fromrow = rowidx(mstr[2]);
   297             move->tofile = fileidx(mstr[4]);
   298             move->torow = rowidx(mstr[5]);
   299         }
   300     }
   303     if (move->piece) {
   304         move->piece |= mycolor;
   305         if (move->fromfile == POS_UNSPECIFIED
   306             || move->fromrow == POS_UNSPECIFIED) {
   307             return getlocation(board, move) && chkidx(move);
   308         } else {
   309             return chkidx(move);
   310         }
   311     } else {
   312         return FALSE;
   313     }
   314     // TODO: return status code to indicate the error type
   315 }
   317 static int sendmove(Board board, uint8_t mycolor, int opponent) {
   318     const size_t buflen = 8;
   319     char movestr[buflen];
   320     _Bool remisrejected = FALSE;
   321     uint8_t code;
   323     while (1) {
   324         move(boardy+3, 0);
   325         if (remisrejected) {
   326             printw(
   327                 "Use chess notation to enter your move.\n"
   328                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   329                 "Type your move: ");
   330         } else {
   331             printw(
   332                 "Use chess notation to enter your move.\n"
   333                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   334                 "Type your move: ");
   335         }
   336         clrtoeol();
   337         refresh();
   338         getnstr(movestr, buflen);
   340         if (strncmp(movestr, "surr", buflen) == 0) {
   341             printw("You surrendered!");
   342             refresh();
   343             net_send_code(opponent, NETCODE_SURRENDER);
   344             return 1;
   345         } else if (strncmp(movestr, "remis", buflen) == 0) {
   346             if (!remisrejected) {
   347                 net_send_code(opponent, NETCODE_REMIS);
   348                 printw("Remis offer sent - waiting for acceptance...");
   349                 refresh();
   350                 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   351                     printw("\rRemis accepted!");
   352                     clrtoeol();
   353                     refresh();
   354                     return 1;
   355                 } else {
   356                     remisrejected = TRUE;
   357                 }
   358             }
   359         } else {
   360             Move move;
   361             if (eval_move(board, mycolor, movestr, &move)) {
   362                 net_send_code(opponent, NETCODE_MOVE);
   363                 net_send_data(opponent, &move, sizeof(Move));
   364                 code = net_recieve_code(opponent);
   365                 move.check = code == NETCODE_CHECK;
   366                 move.checkmate = code == NETCODE_CHECKMATE;
   367                 // TODO: record move
   368                 if (code == NETCODE_DECLINE) {
   369                     printw("Invalid move.");
   370                     clrtoeol();
   371                 } else {
   372                     apply_move(board, &move);
   373                     if (move.checkmate) {
   374                         printw("Checkmate!");
   375                         return 1;
   376                     } else {
   377                         return 0;
   378                     }
   379                 }
   380             } else {
   381                 printw("Can't interpret move - please use algebraic notation.");
   382             }
   383         }
   384     }
   385 }
   387 static int recvmove(Board board, int opponent) {
   389     while (1) {
   390         move(boardy+3, 0);
   391         printw("Awaiting opponent move...");
   392         clrtoeol();
   393         refresh();
   395         // TODO: nonblocking
   396         uint32_t code = net_recieve_code(opponent);
   398         Move move;
   399         switch (code) {
   400             case NETCODE_SURRENDER:
   401                 printw("\rYour opponent surrendered!");
   402                 clrtoeol();
   403                 return 1;
   404             case NETCODE_REMIS:
   405                 if (prompt_yesno(
   406                     "\rYour opponent offers remis - do you accept")) {
   407                     printw("\rRemis accepted!");
   408                     clrtoeol();
   409                     net_send_code(opponent, NETCODE_ACCEPT);
   410                     return 1;
   411                 } else {
   412                     net_send_code(opponent, NETCODE_DECLINE);
   413                 }
   414                 break;
   415             case NETCODE_MOVE:
   416                 net_recieve_data(opponent, &move, sizeof(Move));
   417                 if (validate_move(board, &move)) {
   418                     apply_move(board, &move);
   419                     // TODO: record move
   420                     if (move.check) {
   421                         net_send_code(opponent, NETCODE_CHECK);
   422                     } else if (move.checkmate) {
   423                         net_send_code(opponent, NETCODE_CHECKMATE);
   424                     } else {
   425                         net_send_code(opponent, NETCODE_ACCEPT);
   426                     }
   427                     return 0;
   428                 } else {
   429                     net_send_code(opponent, NETCODE_DECLINE);
   430                 }
   431         }
   432     }
   433 }
   435 void game_start(Settings *settings, int opponent) {
   436     _Bool myturn = is_server(settings) ==
   437         (settings->gameinfo.servercolor == WHITE);
   438     uint8_t mycolor = myturn ? WHITE:BLACK;
   440     _Bool running;
   442     Board board = {
   443         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   444         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   445         {0,     0,       0,       0,      0,     0,       0,       0},
   446         {0,     0,       0,       0,      0,     0,       0,       0},
   447         {0,     0,       0,       0,      0,     0,       0,       0},
   448         {0,     0,       0,       0,      0,     0,       0,       0},
   449         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   450         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   451     };
   453     do {
   454         clear();
   455         draw_board(board, mycolor);
   456         if (myturn) {
   457             running = !sendmove(board, mycolor, opponent);
   458         } else {
   459             running = !recvmove(board, opponent);
   460             flushinp(); // flush any input the user hacked in while waiting
   461         }
   462         myturn ^= TRUE;
   463     }  while (running);
   465     mvaddstr(getmaxy(tchess_window)-1, 0,
   466         "Game has ended. Press any key to leave...");
   467     getch();
   468 }

mercurial