src/chess/rules.c

Fri, 04 Apr 2014 17:36:42 +0200

author
Mike Becker <universe@uap-core.de>
date
Fri, 04 Apr 2014 17:36:42 +0200
changeset 28
0c1371488d87
parent 27
efeb98bc69c9
child 29
c6a1ad6cf749
permissions
-rw-r--r--

NEED TESTING: implemented check and checkmate - TODO: avoid checkmate by moving another piece in between

     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 "rules.h"
    31 #include "chess.h"
    32 #include <string.h>
    33 #include <stdlib.h>
    35 void gamestate_cleanup(GameState *gamestate) {
    36     MoveList *elem;
    37     elem = gamestate->movelist;
    38     while (elem) {
    39         MoveList *cur = elem;
    40         elem = elem->next;
    41         free(cur);
    42     };
    43 }
    45 static void addmove(GameState* gamestate, Move *move) {
    46     MoveList *elem = malloc(sizeof(MoveList));
    47     elem->next = NULL;
    48     elem->move = *move;
    50     if (gamestate->lastmove) {
    51         gamestate->lastmove->next = elem;
    52         gamestate->lastmove = elem;
    53     } else {
    54         gamestate->movelist = gamestate->lastmove = elem;
    55     }
    56 }
    58 char getpiecechr(uint8_t piece) {
    59     switch (piece & PIECE_MASK) {
    60     case ROOK: return 'R';
    61     case KNIGHT: return 'N';
    62     case BISHOP: return 'B';
    63     case QUEEN: return 'Q';
    64     case KING: return 'K';
    65     default: return '\0';
    66     }
    67 }
    69 uint8_t getpiece(char c) {
    70     switch (c) {
    71         case 'R': return ROOK;
    72         case 'N': return KNIGHT;
    73         case 'B': return BISHOP;
    74         case 'Q': return QUEEN;
    75         case 'K': return KING;
    76         default: return 0;
    77     }
    78 }
    80 static int getlocation(GameState *gamestate, Move *move) {   
    81     uint8_t piece = move->piece & PIECE_MASK;
    82     switch (piece) {
    83         case PAWN: return pawn_getlocation(gamestate, move);
    84         case ROOK: return rook_getlocation(gamestate, move);
    85         case KNIGHT: return knight_getlocation(gamestate, move);
    86         case BISHOP: return bishop_getlocation(gamestate, move);
    87         case QUEEN: return queen_getlocation(gamestate, move);
    88         case KING: return king_getlocation(gamestate, move);
    89         default: return INVALID_MOVE_SYNTAX;
    90     }
    91 }
    93 _Bool get_any_threat_for(GameState *gamestate, uint8_t row, uint8_t file,
    94         uint8_t color, Move *threat) {
    95     Move threats[16];
    96     int threatcount = 0;
    97     for (uint8_t r = 0 ; r < 8 ; r++) {
    98         for (uint8_t f = 0 ; f < 8 ; f++) {
    99             if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   100                 memset(&(threats[threatcount]), 0, sizeof(Move));
   101                 threats[threatcount].piece = gamestate->board[r][f];
   102                 threats[threatcount].fromrow = r;
   103                 threats[threatcount].fromfile = f;
   104                 threats[threatcount].torow = row;
   105                 threats[threatcount].tofile = file;
   106                 threatcount++;
   107             }
   108         }
   109     }
   111     for (int i = 0 ; i < threatcount ; i++) {
   112         if (validate_move(gamestate, &(threats[i]))) {
   113             if (threat) {
   114                 *threat = threats[i];
   115             }
   116             return 1;
   117         }
   118     }
   120     return 0;
   121 }
   123 void apply_move(GameState *gamestate, Move *move) {
   124     uint8_t piece = move->piece & PIECE_MASK;
   125     uint8_t color = move->piece & COLOR_MASK;
   127     /* en passant capture */
   128     if (move->capture && piece == PAWN &&
   129         mdst(gamestate->board, move) == 0) {
   130         gamestate->board[move->fromrow][move->tofile] = 0;
   131     }
   133     /* remove old en passant threats */
   134     for (uint8_t file = 0 ; file < 8 ; file++) {
   135         gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   136         gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   137     }
   139     /* add new en passant threat */
   140     if (piece == PAWN && (
   141         (move->fromrow == 1 && move->torow == 3) ||
   142         (move->fromrow == 6 && move->torow == 4))) {
   143         move->piece |= ENPASSANT_THREAT;
   144     }
   146     /* move (and maybe capture or promote) */
   147     msrc(gamestate->board, move) = 0;
   148     if (move->promotion) {
   149         mdst(gamestate->board, move) = move->promotion;
   150     } else {
   151         mdst(gamestate->board, move) = move->piece;
   152     }
   154     /* castling */
   155     if (piece == KING &&
   156         move->fromfile == fileidx('e')) {
   158         if (move->tofile == fileidx('g')) {
   159             gamestate->board[move->torow][fileidx('h')] = 0;
   160             gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   161         } else if (move->tofile == fileidx('c')) {
   162             gamestate->board[move->torow][fileidx('a')] = 0;
   163             gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   164         }
   165     }
   167     addmove(gamestate, move);
   168 }
   170 _Bool validate_move(GameState *gamestate, Move *move) {
   171     _Bool result;
   173     /* validate indices (don't trust opponent) */
   174     if (!chkidx(move)) {
   175         return 0;
   176     }
   178     /* must move */
   179     if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   180         return 0;
   181     }
   183     /* does piece exist */
   184     result = msrc(gamestate->board, move) == move->piece;
   186     /* can't capture own pieces */
   187     uint8_t piececolor = (move->piece & COLOR_MASK);
   188     if ((mdst(gamestate->board, move) & COLOR_MASK) == piececolor) {
   189         return 0;
   190     }
   192     /* validate individual rules */
   193     switch (move->piece & PIECE_MASK) {
   194     case PAWN:
   195         result = result && pawn_chkrules(gamestate, move);
   196         result = result && !pawn_isblocked(gamestate, move);
   197         break;
   198     case ROOK:
   199         result = result && rook_chkrules(move);
   200         result = result && !rook_isblocked(gamestate, move);
   201         break;
   202     case KNIGHT:
   203         result = result && knight_chkrules(move);
   204         result = result && !knight_isblocked(gamestate, move);
   205         break;
   206     case BISHOP:
   207         result = result && bishop_chkrules(move);
   208         result = result && !bishop_isblocked(gamestate, move);
   209         break;
   210     case QUEEN:
   211         result = result && queen_chkrules(move);
   212         result = result && !queen_isblocked(gamestate, move);
   213         break;
   214     case KING:
   215         result = result && king_chkrules(gamestate, move);
   216         result = result && !king_isblocked(gamestate, move);
   217         break;
   218     default:
   219         result = 0;
   220     }
   222     /* cancel processing to avoid recursion overflow with is_covered() */
   223     if (!result) {
   224         return 0;
   225     }
   227     /* find kings for check validation */
   228     uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   229     for (uint8_t row = 0 ; row < 8 ; row++) {
   230         for (uint8_t file = 0 ; file < 8 ; file++) {
   231             if (gamestate->board[row][file] ==
   232                     (piececolor == WHITE?WKING:BKING)) {
   233                 mykingfile = file;
   234                 mykingrow = row;
   235             } else if (gamestate->board[row][file] ==
   236                     (piececolor == WHITE?BKING:WKING)) {
   237                 opkingfile = file;
   238                 opkingrow = row;
   239             }
   240         }
   241     }
   243     /* simulation move for check validation */
   244     GameState simulation;
   245     memcpy(&simulation, gamestate, sizeof(GameState));
   246     apply_move(&simulation, move);
   248     /* don't move into or stay in check position */
   249     if (is_covered(&simulation, mykingrow, mykingfile,
   250         opponent_color(piececolor))) {
   251         return 0;
   252     }
   254     /* correct check and checkmate flags (move is still valid) */
   255     Move threat;
   256     move->check = get_any_threat_for(&simulation, opkingrow, opkingfile,
   257         piececolor, &threat);
   259     if (move->check) {
   260         /* determine possible escape fields */
   261         _Bool canescape = 0;
   262         for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   263             for (int df = -1 ; df <= 1 && !canescape ; df++) {
   264                 if (!(dr == 0 && df == 0)  &&
   265                         isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   267                     /* escape field neither blocked nor covered */
   268                     if ((simulation.board[opkingrow + dr][opkingfile + df]
   269                             & COLOR_MASK) != opponent_color(piececolor)) {
   270                         canescape |= !is_covered(&simulation,
   271                             opkingrow + dr, opkingfile + df, piececolor);
   272                     }
   273                 }
   274             }
   275         }
   276         /* can't escape, can we capture? */
   277         if (!canescape) {
   278             canescape = is_covered(&simulation, threat.fromrow,
   279                 threat.fromfile, opponent_color(piececolor));
   281             /* can't capture, can we block? */
   282             // TODO: make it so
   284             if (!canescape) {
   285                 gamestate->checkmate = 1;
   286             }
   287         }
   288     }
   290     return 1;
   291 }
   293 int eval_move(GameState *gamestate, char *mstr, Move *move) {
   294     memset(move, 0, sizeof(Move));
   295     move->fromfile = POS_UNSPECIFIED;
   296     move->fromrow = POS_UNSPECIFIED;
   298     size_t len = strlen(mstr);
   300     /* evaluate check/checkmate flags */
   301     if (mstr[len-1] == '+') {
   302         len--; mstr[len] = '\0';
   303         move->check = 1;
   304     } else if (mstr[len-1] == '#') {
   305         len--; mstr[len] = '\0';
   306         /* ignore - validation should set game state */
   307     }
   309     /* evaluate promotion */
   310     if (len > 3 && mstr[len-2] == '=') {
   311         move->promotion = getpiece(mstr[len-1]);
   312         if (!move->promotion) {
   313             return INVALID_MOVE_SYNTAX;
   314         } else {
   315             move->promotion |= gamestate->mycolor;
   316             len -= 2;
   317             mstr[len] = 0;
   318         }
   319     }
   321     if (len == 2) {
   322         /* pawn move (e.g. "e4") */
   323         move->piece = PAWN;
   324         move->tofile = fileidx(mstr[0]);
   325         move->torow = rowidx(mstr[1]);
   326     } else if (len == 3) {
   327         if (strcmp(mstr, "O-O") == 0) {
   328             /* king side castling */
   329             move->piece = KING;
   330             move->fromfile = fileidx('e');
   331             move->tofile = fileidx('g');
   332             move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
   333         } else {
   334             /* move (e.g. "Nf3") */
   335             move->piece = getpiece(mstr[0]);
   336             move->tofile = fileidx(mstr[1]);
   337             move->torow = rowidx(mstr[2]);
   338         }
   340     } else if (len == 4) {
   341         move->piece = getpiece(mstr[0]);
   342         if (!move->piece) {
   343             move->piece = PAWN;
   344             move->fromfile = fileidx(mstr[0]);
   345         }
   346         if (mstr[1] == 'x') {
   347             /* capture (e.g. "Nxf3", "dxe5") */
   348             move->capture = 1;
   349         } else {
   350             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   351             if (isfile(mstr[1])) {
   352                 move->fromfile = fileidx(mstr[1]);
   353                 if (move->piece == PAWN) {
   354                     move->piece = 0;
   355                 }
   356             } else {
   357                 move->fromrow = rowidx(mstr[1]);
   358             }
   359         }
   360         move->tofile = fileidx(mstr[2]);
   361         move->torow = rowidx(mstr[3]);
   362     } else if (len == 5) {
   363         if (strcmp(mstr, "O-O-O") == 0) {
   364             /* queen side castling "O-O-O" */
   365             move->piece = KING;
   366             move->fromfile = fileidx('e');
   367             move->tofile = fileidx('c');
   368             move->fromrow = move->torow = gamestate->mycolor == WHITE ? 0 : 7;
   369         } else {
   370             move->piece = getpiece(mstr[0]);
   371             if (mstr[2] == 'x') {
   372                 move->capture = 1;
   373                 if (move->piece) {
   374                     /* capture (e.g. "Ndxf3") */
   375                     move->fromfile = fileidx(mstr[1]);
   376                 } else {
   377                     /* long notation capture (e.g. "e5xf6") */
   378                     move->piece = PAWN;
   379                     move->fromfile = fileidx(mstr[0]);
   380                     move->fromrow = rowidx(mstr[1]);
   381                 }
   382             } else {
   383                 /* long notation move (e.g. "Nc5a4") */
   384                 move->fromfile = fileidx(mstr[1]);
   385                 move->fromrow = rowidx(mstr[2]);
   386             }
   387             move->tofile = fileidx(mstr[3]);
   388             move->torow = rowidx(mstr[4]);
   389         }
   390     } else if (len == 6) {
   391         /* long notation capture (e.g. "Nc5xf3") */
   392         if (mstr[3] == 'x') {
   393             move->capture = 1;
   394             move->piece = getpiece(mstr[0]);
   395             move->fromfile = fileidx(mstr[1]);
   396             move->fromrow = rowidx(mstr[2]);
   397             move->tofile = fileidx(mstr[4]);
   398             move->torow = rowidx(mstr[5]);
   399         }
   400     }
   403     if (move->piece) {
   404         if (move->piece == PAWN
   405             && move->torow == (gamestate->mycolor==WHITE?7:0)
   406             && !move->promotion) {
   407             return NEED_PROMOTION;
   408         }
   410         move->piece |= gamestate->mycolor;
   411         if (move->fromfile == POS_UNSPECIFIED
   412             || move->fromrow == POS_UNSPECIFIED) {
   413             return getlocation(gamestate, move);
   414         } else {
   415             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   416         }
   417     } else {
   418         return INVALID_MOVE_SYNTAX;
   419     }
   420 }

mercurial