src/game.c

Fri, 28 Mar 2014 11:45:01 +0100

author
Mike Becker <universe@uap-core.de>
date
Fri, 28 Mar 2014 11:45:01 +0100
changeset 15
7ffd66591afe
parent 14
970748b9a73b
child 16
a298c6637c30
permissions
-rw-r--r--

completed pawn rules + bug fixes for 4-char-moves

     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     uint8_t piece = move->piece & PIECE_MASK;
    82     uint8_t color = move->piece & COLOR_MASK;
    84     /* en passant capture */
    85     if (move->capture && piece == PAWN &&
    86         mdst(board, move) == 0) {
    87         board[move->fromrow][move->tofile] = 0;
    88     }
    90     /* remove old en passant threats */
    91     for (uint8_t file = 0 ; file < 8 ; file++) {
    92         board[3][file] &= ~ENPASSANT_THREAT;
    93         board[4][file] &= ~ENPASSANT_THREAT;
    94     }
    96     /* add new en passant threat */
    97     if (piece == PAWN && (
    98         (move->fromrow == 1 && move->torow == 3) ||
    99         (move->fromrow == 6 && move->torow == 4))) {
   100         move->piece |= ENPASSANT_THREAT;
   101     }
   103     /* move (and maybe capture) */
   104     msrc(board, move) = 0;
   105     mdst(board, move) = move->piece;
   107     /* castling */
   108     if (piece == KING &&
   109         move->fromfile == fileidx('e')) {
   111         if (move->tofile == fileidx('g')) {
   112             board[move->torow][fileidx('h')] = 0;
   113             board[move->torow][fileidx('f')] = color|ROOK;
   114         } else if (move->tofile == fileidx('c')) {
   115             board[move->torow][fileidx('a')] = 0;
   116             board[move->torow][fileidx('d')] = color|ROOK;
   117         }
   118     }
   119 }
   121 /**
   122  * Validates move by applying chess rules.
   123  * @param board the current board state
   124  * @param move the move to validate
   125  * @return TRUE, if the move complies to chess rules, FALSE otherwise
   126  */
   127 static _Bool validate_move(Board board, Move *move) {
   128     _Bool result;
   130     /* validate indices (don't trust opponent) */
   131     if (!chkidx(move)) {
   132         return FALSE;
   133     }
   135     /* does piece exist */
   136     result = msrc(board, move) == move->piece;
   138     /* can't capture own pieces */
   139     if ((mdst(board, move) & COLOR_MASK) == (move->piece & COLOR_MASK)) {
   140         return FALSE;
   141     }
   143     /* validate individual rules */
   144     switch (move->piece & PIECE_MASK) {
   145     case PAWN:
   146         result = result && pawn_chkrules(board, move);
   147         result = result && !pawn_isblocked(board, move);
   148         break;
   149     case ROOK:
   150         result = result && rook_chkrules(board, move);
   151         result = result && !rook_isblocked(board, move);
   152         break;
   153     case KNIGHT:
   154         result = result && knight_chkrules(board, move);
   155         result = result && !knight_isblocked(board, move);
   156         break;
   157     case BISHOP:
   158         result = result && bishop_chkrules(board, move);
   159         result = result && !bishop_isblocked(board, move);
   160         break;
   161     case QUEEN:
   162         result = result && queen_chkrules(board, move);
   163         result = result && !queen_isblocked(board, move);
   164         break;
   165     case KING:
   166         result = result && king_chkrules(board, move);
   167         result = result && !king_isblocked(board, move);
   168         break;
   169     default:
   170         result = FALSE;
   171     }
   173     /* is piece pinned */
   174     // TODO: make it so
   176     /* correct check and checkmate flags */
   177     // TODO: make it so
   179     return result;
   180 }
   182 /**
   183  * Maps a character to a piece.
   184  * 
   185  * Does not work for pawns, since they don't have a character.
   186  * 
   187  * @param c one of R,N,B,Q,K
   188  * @return numeric value for the specified piece
   189  */
   190 static uint8_t getpiece(char c) {
   191     switch (c) {
   192         case 'R': return ROOK;
   193         case 'N': return KNIGHT;
   194         case 'B': return BISHOP;
   195         case 'Q': return QUEEN;
   196         case 'K': return KING;
   197         default: return 0;
   198     }
   199 }
   201 /**
   202  * Guesses the location of a piece for short algebraic notation.
   203  * 
   204  * @param board the current state of the board
   205  * @param move the move date to operate on
   206  * @return TRUE if the location could be retrieved, FALSE if the location is
   207  * ambiguous
   208  */
   209 static _Bool getlocation(Board board, Move *move) {   
   210     uint8_t piece = move->piece & PIECE_MASK;
   211     switch (piece) {
   212         case PAWN: return pawn_getlocation(board, move);
   213         case ROOK: return rook_getlocation(board, move);
   214         case KNIGHT: return knight_getlocation(board, move);
   215         case BISHOP: return bishop_getlocation(board, move);
   216         case QUEEN: return queen_getlocation(board, move);
   217         case KING: return king_getlocation(board, move);
   218         default: return FALSE;
   219     }
   220 }
   222 /**
   223  * Evaluates a move syntactically and stores the move data in the specified
   224  * object.
   225  * 
   226  * @param board the current state of the board
   227  * @param mycolor the color of the current player
   228  * @param mstr the input string to parse
   229  * @param move a pointer to object where the move data shall be stored
   230  * @return TRUE, if the move is syntactically valid, FALSE otherwise
   231  */
   232 static _Bool eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
   233     memset(move, 0, sizeof(Move));
   234     move->fromfile = POS_UNSPECIFIED;
   235     move->fromrow = POS_UNSPECIFIED;
   237     size_t len = strlen(mstr);
   239     /* evaluate check/checkmate flags */
   240     if (mstr[len-1] == '+') {
   241         len--; mstr[len] = '\0';
   242         move->check = TRUE;
   243     } else if (mstr[len-1] == '#') {
   244         len--; mstr[len] = '\0';
   245         move->checkmate = TRUE;
   246     }
   248     if (len == 2) {
   249         /* pawn move (e.g. "e4") */
   250         move->piece = PAWN;
   251         move->tofile = fileidx(mstr[0]);
   252         move->torow = rowidx(mstr[1]);
   253     } else if (len == 3) {
   254         if (strcmp(mstr, "O-O") == 0) {
   255             /* king side castling */
   256             move->piece = KING;
   257             move->fromfile = fileidx('e');
   258             move->tofile = fileidx('g');
   259             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   260         } else {
   261             /* move (e.g. "Nf3") */
   262             move->piece = getpiece(mstr[0]);
   263             move->tofile = fileidx(mstr[1]);
   264             move->torow = rowidx(mstr[2]);
   265         }
   267     } else if (len == 4) {
   268         move->piece = getpiece(mstr[0]);
   269         if (!move->piece) {
   270             move->piece = PAWN;
   271             move->fromfile = fileidx(mstr[0]);
   272         }
   273         if (mstr[1] == 'x') {
   274             /* capture (e.g. "Nxf3", "dxe5") */
   275             move->capture = TRUE;
   276         } else {
   277             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   278             if (isfile(mstr[1])) {
   279                 move->fromfile = fileidx(mstr[1]);
   280                 if (move->piece == PAWN) {
   281                     move->piece = 0;
   282                 }
   283             } else {
   284                 move->fromrow = rowidx(mstr[1]);
   285             }
   286         }
   287         move->tofile = fileidx(mstr[2]);
   288         move->torow = rowidx(mstr[3]);
   289     } else if (len == 5) {
   290         if (strcmp(mstr, "O-O-O") == 0) {
   291             /* queen side castling "O-O-O" */
   292             move->piece = KING;
   293             move->fromfile = fileidx('e');
   294             move->tofile = fileidx('c');
   295             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   296         } else {
   297             move->piece = getpiece(mstr[0]);
   298             if (mstr[2] == 'x') {
   299                 move->capture = TRUE;
   300                 if (move->piece) {
   301                     /* capture (e.g. "Ndxf3") */
   302                     move->fromfile = fileidx(mstr[1]);
   303                 } else {
   304                     /* long notation capture (e.g. "e5xf6") */
   305                     move->piece = PAWN;
   306                     move->fromfile = fileidx(mstr[0]);
   307                     move->fromrow = rowidx(mstr[1]);
   308                 }
   309             } else {
   310                 /* long notation move (e.g. "Nc5a4") */
   311                 move->fromfile = fileidx(mstr[1]);
   312                 move->fromrow = rowidx(mstr[2]);
   313             }
   314             move->tofile = fileidx(mstr[3]);
   315             move->torow = rowidx(mstr[4]);
   316         }
   317     } else if (len == 6) {
   318         /* long notation capture (e.g. "Nc5xf3") */
   319         if (mstr[3] == 'x') {
   320             move->capture = TRUE;
   321             move->piece = getpiece(mstr[0]);
   322             move->fromfile = fileidx(mstr[1]);
   323             move->fromrow = rowidx(mstr[2]);
   324             move->tofile = fileidx(mstr[4]);
   325             move->torow = rowidx(mstr[5]);
   326         }
   327     }
   330     if (move->piece) {
   331         move->piece |= mycolor;
   332         if (move->fromfile == POS_UNSPECIFIED
   333             || move->fromrow == POS_UNSPECIFIED) {
   334             return getlocation(board, move) && chkidx(move);
   335         } else {
   336             return chkidx(move);
   337         }
   338     } else {
   339         return FALSE;
   340     }
   341     // TODO: return status code to indicate the error type
   342 }
   344 static int sendmove(Board board, uint8_t mycolor, int opponent) {
   345     const size_t buflen = 8;
   346     char movestr[buflen];
   347     _Bool remisrejected = FALSE;
   348     uint8_t code;
   350     while (1) {
   351         move(boardy+3, 0);
   352         if (remisrejected) {
   353             printw(
   354                 "Use chess notation to enter your move.\n"
   355                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   356                 "Type your move: ");
   357         } else {
   358             printw(
   359                 "Use chess notation to enter your move.\n"
   360                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   361                 "Type your move: ");
   362         }
   363         clrtoeol();
   364         refresh();
   365         getnstr(movestr, buflen);
   367         if (strncmp(movestr, "surr", buflen) == 0) {
   368             printw("You surrendered!");
   369             refresh();
   370             net_send_code(opponent, NETCODE_SURRENDER);
   371             return 1;
   372         } else if (strncmp(movestr, "remis", buflen) == 0) {
   373             if (!remisrejected) {
   374                 net_send_code(opponent, NETCODE_REMIS);
   375                 printw("Remis offer sent - waiting for acceptance...");
   376                 refresh();
   377                 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   378                     printw("\rRemis accepted!");
   379                     clrtoeol();
   380                     refresh();
   381                     return 1;
   382                 } else {
   383                     remisrejected = TRUE;
   384                 }
   385             }
   386         } else {
   387             Move move;
   388             if (eval_move(board, mycolor, movestr, &move)) {
   389                 net_send_code(opponent, NETCODE_MOVE);
   390                 net_send_data(opponent, &move, sizeof(Move));
   391                 code = net_recieve_code(opponent);
   392                 move.check = code == NETCODE_CHECK;
   393                 move.checkmate = code == NETCODE_CHECKMATE;
   394                 // TODO: record move
   395                 if (code == NETCODE_DECLINE) {
   396                     printw("Invalid move.");
   397                     clrtoeol();
   398                 } else {
   399                     apply_move(board, &move);
   400                     if (move.checkmate) {
   401                         printw("Checkmate!");
   402                         return 1;
   403                     } else {
   404                         return 0;
   405                     }
   406                 }
   407             } else {
   408                 printw("Can't interpret move - please use algebraic notation.");
   409             }
   410         }
   411     }
   412 }
   414 static int recvmove(Board board, int opponent) {
   416     while (1) {
   417         move(boardy+3, 0);
   418         printw("Awaiting opponent move...");
   419         clrtoeol();
   420         refresh();
   422         // TODO: nonblocking
   423         uint32_t code = net_recieve_code(opponent);
   425         Move move;
   426         switch (code) {
   427             case NETCODE_SURRENDER:
   428                 printw("\rYour opponent surrendered!");
   429                 clrtoeol();
   430                 return 1;
   431             case NETCODE_REMIS:
   432                 if (prompt_yesno(
   433                     "\rYour opponent offers remis - do you accept")) {
   434                     printw("\rRemis accepted!");
   435                     clrtoeol();
   436                     net_send_code(opponent, NETCODE_ACCEPT);
   437                     return 1;
   438                 } else {
   439                     net_send_code(opponent, NETCODE_DECLINE);
   440                 }
   441                 break;
   442             case NETCODE_MOVE:
   443                 net_recieve_data(opponent, &move, sizeof(Move));
   444                 if (validate_move(board, &move)) {
   445                     apply_move(board, &move);
   446                     // TODO: record move
   447                     if (move.check) {
   448                         net_send_code(opponent, NETCODE_CHECK);
   449                     } else if (move.checkmate) {
   450                         net_send_code(opponent, NETCODE_CHECKMATE);
   451                     } else {
   452                         net_send_code(opponent, NETCODE_ACCEPT);
   453                     }
   454                     return 0;
   455                 } else {
   456                     net_send_code(opponent, NETCODE_DECLINE);
   457                 }
   458         }
   459     }
   460 }
   462 void game_start(Settings *settings, int opponent) {
   463     _Bool myturn = is_server(settings) ==
   464         (settings->gameinfo.servercolor == WHITE);
   465     uint8_t mycolor = myturn ? WHITE:BLACK;
   467     _Bool running;
   469     Board board = {
   470         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   471         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   472         {0,     0,       0,       0,      0,     0,       0,       0},
   473         {0,     0,       0,       0,      0,     0,       0,       0},
   474         {0,     0,       0,       0,      0,     0,       0,       0},
   475         {0,     0,       0,       0,      0,     0,       0,       0},
   476         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   477         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   478     };
   480     do {
   481         clear();
   482         draw_board(board, mycolor);
   483         if (myturn) {
   484             running = !sendmove(board, mycolor, opponent);
   485         } else {
   486             running = !recvmove(board, opponent);
   487             flushinp(); // flush any input the user hacked in while waiting
   488         }
   489         myturn ^= TRUE;
   490     }  while (running);
   492     mvaddstr(getmaxy(tchess_window)-1, 0,
   493         "Game has ended. Press any key to leave...");
   494     getch();
   495 }

mercurial