src/game.c

Sat, 29 Mar 2014 14:46:33 +0100

author
Mike Becker <universe@uap-core.de>
date
Sat, 29 Mar 2014 14:46:33 +0100
changeset 17
2aed5418e142
parent 16
a298c6637c30
child 18
6008840b859e
permissions
-rw-r--r--

implemented bishop rules

     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(move);
   151         result = result && !rook_isblocked(board, move);
   152         break;
   153     case KNIGHT:
   154         result = result && knight_chkrules(move);
   155         result = result && !knight_isblocked(board, move);
   156         break;
   157     case BISHOP:
   158         result = result && bishop_chkrules(move);
   159         result = result && !bishop_isblocked(board, move);
   160         break;
   161     case QUEEN:
   162         result = result && queen_chkrules(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 status code (see rules/rules.h for the codes)
   207  */
   208 static int getlocation(Board board, Move *move) {   
   209     uint8_t piece = move->piece & PIECE_MASK;
   210     switch (piece) {
   211         case PAWN: return pawn_getlocation(board, move);
   212         case ROOK: return rook_getlocation(board, move);
   213         case KNIGHT: return knight_getlocation(board, move);
   214         case BISHOP: return bishop_getlocation(board, move);
   215         case QUEEN: return queen_getlocation(board, move);
   216         case KING: return king_getlocation(board, move);
   217         default: return INVALID_MOVE_SYNTAX;
   218     }
   219 }
   221 /**
   222  * Evaluates a move syntactically and stores the move data in the specified
   223  * object.
   224  * 
   225  * @param board the current state of the board
   226  * @param mycolor the color of the current player
   227  * @param mstr the input string to parse
   228  * @param move a pointer to object where the move data shall be stored
   229  * @return status code (see rules/rules.h for the list of codes)
   230  */
   231 static int eval_move(Board board, uint8_t mycolor, char *mstr, Move *move) {
   232     memset(move, 0, sizeof(Move));
   233     move->fromfile = POS_UNSPECIFIED;
   234     move->fromrow = POS_UNSPECIFIED;
   235     // TODO: promotion
   236     size_t len = strlen(mstr);
   238     /* evaluate check/checkmate flags */
   239     if (mstr[len-1] == '+') {
   240         len--; mstr[len] = '\0';
   241         move->check = TRUE;
   242     } else if (mstr[len-1] == '#') {
   243         len--; mstr[len] = '\0';
   244         move->checkmate = TRUE;
   245     }
   247     if (len == 2) {
   248         /* pawn move (e.g. "e4") */
   249         move->piece = PAWN;
   250         move->tofile = fileidx(mstr[0]);
   251         move->torow = rowidx(mstr[1]);
   252     } else if (len == 3) {
   253         if (strcmp(mstr, "O-O") == 0) {
   254             /* king side castling */
   255             move->piece = KING;
   256             move->fromfile = fileidx('e');
   257             move->tofile = fileidx('g');
   258             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   259         } else {
   260             /* move (e.g. "Nf3") */
   261             move->piece = getpiece(mstr[0]);
   262             move->tofile = fileidx(mstr[1]);
   263             move->torow = rowidx(mstr[2]);
   264         }
   266     } else if (len == 4) {
   267         move->piece = getpiece(mstr[0]);
   268         if (!move->piece) {
   269             move->piece = PAWN;
   270             move->fromfile = fileidx(mstr[0]);
   271         }
   272         if (mstr[1] == 'x') {
   273             /* capture (e.g. "Nxf3", "dxe5") */
   274             move->capture = TRUE;
   275         } else {
   276             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   277             if (isfile(mstr[1])) {
   278                 move->fromfile = fileidx(mstr[1]);
   279                 if (move->piece == PAWN) {
   280                     move->piece = 0;
   281                 }
   282             } else {
   283                 move->fromrow = rowidx(mstr[1]);
   284             }
   285         }
   286         move->tofile = fileidx(mstr[2]);
   287         move->torow = rowidx(mstr[3]);
   288     } else if (len == 5) {
   289         if (strcmp(mstr, "O-O-O") == 0) {
   290             /* queen side castling "O-O-O" */
   291             move->piece = KING;
   292             move->fromfile = fileidx('e');
   293             move->tofile = fileidx('c');
   294             move->fromrow = move->torow = mycolor == WHITE ? 0 : 7;
   295         } else {
   296             move->piece = getpiece(mstr[0]);
   297             if (mstr[2] == 'x') {
   298                 move->capture = TRUE;
   299                 if (move->piece) {
   300                     /* capture (e.g. "Ndxf3") */
   301                     move->fromfile = fileidx(mstr[1]);
   302                 } else {
   303                     /* long notation capture (e.g. "e5xf6") */
   304                     move->piece = PAWN;
   305                     move->fromfile = fileidx(mstr[0]);
   306                     move->fromrow = rowidx(mstr[1]);
   307                 }
   308             } else {
   309                 /* long notation move (e.g. "Nc5a4") */
   310                 move->fromfile = fileidx(mstr[1]);
   311                 move->fromrow = rowidx(mstr[2]);
   312             }
   313             move->tofile = fileidx(mstr[3]);
   314             move->torow = rowidx(mstr[4]);
   315         }
   316     } else if (len == 6) {
   317         /* long notation capture (e.g. "Nc5xf3") */
   318         if (mstr[3] == 'x') {
   319             move->capture = TRUE;
   320             move->piece = getpiece(mstr[0]);
   321             move->fromfile = fileidx(mstr[1]);
   322             move->fromrow = rowidx(mstr[2]);
   323             move->tofile = fileidx(mstr[4]);
   324             move->torow = rowidx(mstr[5]);
   325         }
   326     }
   329     if (move->piece) {
   330         move->piece |= mycolor;
   331         if (move->fromfile == POS_UNSPECIFIED
   332             || move->fromrow == POS_UNSPECIFIED) {
   333             return getlocation(board, move);
   334         } else {
   335             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   336         }
   337     } else {
   338         return INVALID_MOVE_SYNTAX;
   339     }
   340 }
   342 static int sendmove(Board board, uint8_t mycolor, int opponent) {
   343     const size_t buflen = 8;
   344     char movestr[buflen];
   345     _Bool remisrejected = FALSE;
   346     uint8_t code;
   348     while (1) {
   349         move(boardy+3, 0);
   350         if (remisrejected) {
   351             printw(
   352                 "Use chess notation to enter your move.\n"
   353                 "Remis offer rejected - type 'surr' to surrender.      \n\n"
   354                 "Type your move: ");
   355         } else {
   356             printw(
   357                 "Use chess notation to enter your move.\n"
   358                 "Or type 'surr' to surrender or 'remis' to offer remis.\n\n"
   359                 "Type your move: ");
   360         }
   361         clrtoeol();
   362         refresh();
   363         getnstr(movestr, buflen);
   365         if (strncmp(movestr, "surr", buflen) == 0) {
   366             printw("You surrendered!");
   367             clrtoeol();
   368             refresh();
   369             net_send_code(opponent, NETCODE_SURRENDER);
   370             return 1;
   371         } else if (strncmp(movestr, "remis", buflen) == 0) {
   372             if (!remisrejected) {
   373                 net_send_code(opponent, NETCODE_REMIS);
   374                 printw("Remis offer sent - waiting for acceptance...");
   375                 refresh();
   376                 if (net_recieve_code(opponent) == NETCODE_ACCEPT) {
   377                     printw("\rRemis accepted!");
   378                     clrtoeol();
   379                     refresh();
   380                     return 1;
   381                 } else {
   382                     remisrejected = TRUE;
   383                 }
   384             }
   385         } else {
   386             Move move;
   387             int eval_result = eval_move(board, mycolor, movestr, &move);
   388             switch (eval_result) {
   389                 case VALID_MOVE_SYNTAX:
   390                     net_send_code(opponent, NETCODE_MOVE);
   391                     net_send_data(opponent, &move, sizeof(Move));
   392                     code = net_recieve_code(opponent);
   393                     move.check = code == NETCODE_CHECK;
   394                     move.checkmate = code == NETCODE_CHECKMATE;
   395                     // TODO: record move
   396                     if (code == NETCODE_DECLINE) {
   397                         printw("Invalid move.");
   398                     } else {
   399                         apply_move(board, &move);
   400                         if (move.checkmate) {
   401                             printw("Checkmate!");
   402                             clrtoeol();
   403                             return 1;
   404                         } else {
   405                             return 0;
   406                         }
   407                     }
   408                     break;
   409                 case AMBIGUOUS_MOVE:
   410                     printw("Ambiguous move - "
   411                         "please specify the piece to move.");
   412                     break;
   413                 case INVALID_POSITION:
   414                     printw("Cannot find the piece that shall be moved.");
   415                     break;
   416                 default:
   417                     printw("Can't interpret move - "
   418                         "please use algebraic notation.");
   419             }
   420             clrtoeol();
   421         }
   422     }
   423 }
   425 static int recvmove(Board board, int opponent) {
   427     while (1) {
   428         move(boardy+3, 0);
   429         printw("Awaiting opponent move...");
   430         clrtoeol();
   431         refresh();
   433         // TODO: nonblocking
   434         uint32_t code = net_recieve_code(opponent);
   436         Move move;
   437         switch (code) {
   438             case NETCODE_SURRENDER:
   439                 printw("\rYour opponent surrendered!");
   440                 clrtoeol();
   441                 return 1;
   442             case NETCODE_REMIS:
   443                 if (prompt_yesno(
   444                     "\rYour opponent offers remis - do you accept")) {
   445                     printw("\rRemis accepted!");
   446                     clrtoeol();
   447                     net_send_code(opponent, NETCODE_ACCEPT);
   448                     return 1;
   449                 } else {
   450                     net_send_code(opponent, NETCODE_DECLINE);
   451                 }
   452                 break;
   453             case NETCODE_MOVE:
   454                 net_recieve_data(opponent, &move, sizeof(Move));
   455                 if (validate_move(board, &move)) {
   456                     apply_move(board, &move);
   457                     // TODO: record move
   458                     if (move.check) {
   459                         net_send_code(opponent, NETCODE_CHECK);
   460                     } else if (move.checkmate) {
   461                         net_send_code(opponent, NETCODE_CHECKMATE);
   462                     } else {
   463                         net_send_code(opponent, NETCODE_ACCEPT);
   464                     }
   465                     return 0;
   466                 } else {
   467                     net_send_code(opponent, NETCODE_DECLINE);
   468                 }
   469         }
   470     }
   471 }
   473 void game_start(Settings *settings, int opponent) {
   474     _Bool myturn = is_server(settings) ==
   475         (settings->gameinfo.servercolor == WHITE);
   476     uint8_t mycolor = myturn ? WHITE:BLACK;
   478     _Bool running;
   480     Board board = {
   481         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
   482         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
   483         {0,     0,       0,       0,      0,     0,       0,       0},
   484         {0,     0,       0,       0,      0,     0,       0,       0},
   485         {0,     0,       0,       0,      0,     0,       0,       0},
   486         {0,     0,       0,       0,      0,     0,       0,       0},
   487         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
   488         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
   489     };
   491     do {
   492         clear();
   493         draw_board(board, mycolor);
   494         if (myturn) {
   495             running = !sendmove(board, mycolor, opponent);
   496         } else {
   497             running = !recvmove(board, opponent);
   498             flushinp(); // flush any input the user hacked in while waiting
   499         }
   500         myturn ^= TRUE;
   501     }  while (running);
   503     mvaddstr(getmaxy(tchess_window)-1, 0,
   504         "Game has ended. Press any key to leave...");
   505     getch();
   506 }

mercurial