src/chess/rules.c

Wed, 29 Aug 2018 14:01:41 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 29 Aug 2018 14:01:41 +0200
changeset 65
dcc5bd2c56c0
parent 64
4eda5df55f86
child 66
f5cc75565f7c
permissions
-rw-r--r--

PGN output now correctly breaks after black has also moved

     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2016 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>
    34 #include <sys/time.h>
    36 static GameState gamestate_copy_sim(GameState *gamestate) {
    37     GameState simulation = *gamestate;
    38     if (simulation.lastmove) {
    39         MoveList *lastmovecopy = malloc(sizeof(MoveList));
    40         *lastmovecopy = *(simulation.lastmove);
    41         simulation.movelist = simulation.lastmove = lastmovecopy;
    42     }
    44     return simulation;
    45 }
    47 void gamestate_init(GameState *gamestate) {
    48     memset(gamestate, 0, sizeof(GameState));
    50     Board initboard = {
    51         {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
    52         {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
    53         {0,     0,       0,       0,      0,     0,       0,       0},
    54         {0,     0,       0,       0,      0,     0,       0,       0},
    55         {0,     0,       0,       0,      0,     0,       0,       0},
    56         {0,     0,       0,       0,      0,     0,       0,       0},
    57         {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
    58         {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
    59     };
    60     memcpy(gamestate->board, initboard, sizeof(Board));
    61 }
    63 void gamestate_cleanup(GameState *gamestate) {
    64     MoveList *elem;
    65     elem = gamestate->movelist;
    66     while (elem) {
    67         MoveList *cur = elem;
    68         elem = elem->next;
    69         free(cur);
    70     };
    71 }
    73 /* MUST be called IMMEDIATLY after applying a move to work correctly */
    74 static void format_move(GameState *gamestate, Move *move) {
    75     char *string = move->string;
    77     /* at least 8 characters should be available, wipe them out */
    78     memset(string, 0, 8);
    80     unsigned int idx;
    81     if ((move->piece&PIECE_MASK) == KING &&
    82             abs(move->tofile-move->fromfile) == 2) {
    83         /* special formats for castling */
    84         if (move->tofile==fileidx('c')) {
    85             memcpy(string, "O-O-O", 5);
    86             idx = 5;
    87         } else {
    88             memcpy(string, "O-O", 3);
    89             idx = 3;
    90         }
    91     } else {
    92         /* start by notating the piece character */
    93         string[0] = getpiecechr(move->piece);
    94         idx = string[0] ? 1 : 0;
    96         /* find out how many source information we do need */
    97         uint8_t piece = move->piece & PIECE_MASK;
    98         if (piece == PAWN) {
    99             if (move->capture) {
   100                 string[idx++] = filechr(move->fromfile);
   101             }
   102         } else if (piece != KING) {
   103             /* resolve ambiguities, if any */
   104             Move threats[16];
   105             uint8_t threatcount;
   106             get_real_threats(gamestate, move->torow, move->tofile,
   107                 move->piece&COLOR_MASK, threats, &threatcount);
   108             if (threatcount > 1) {
   109                 int ambrows = 0, ambfiles = 0;
   110                 for (uint8_t i = 0 ; i < threatcount ; i++) {
   111                     if (threats[i].fromrow == move->fromrow) {
   112                         ambrows++;
   113                     }
   114                     if (threats[i].fromfile == move->fromfile) {
   115                         ambfiles++;
   116                     }
   117                 }
   118                 /* ambiguous row, name file */
   119                 if (ambrows > 1) {
   120                     string[idx++] = filechr(move->fromfile);
   121                 }
   122                 /* ambiguous file, name row */
   123                 if (ambfiles > 1) {
   124                     string[idx++] = filechr(move->fromrow);
   125                 }
   126             }
   127         }
   129         /* capturing? */
   130         if (move->capture) {
   131             string[idx++] = 'x';
   132         }
   134         /* destination */
   135         string[idx++] = filechr(move->tofile);
   136         string[idx++] = rowchr(move->torow);
   138         /* promotion? */
   139         if (move->promotion) {
   140             string[idx++] = '=';
   141             string[idx++] = getpiecechr(move->promotion);
   142         }
   143     }
   145     /* check? */
   146     if (move->check) {
   147         /* works only, if this function is called when applying the move */
   148         string[idx++] = gamestate->checkmate?'#':'+';
   149     }
   150 }
   152 static void addmove(GameState* gamestate, Move *move) {
   153     MoveList *elem = malloc(sizeof(MoveList));
   154     elem->next = NULL;
   155     elem->move = *move;
   157     struct timeval curtimestamp;
   158     gettimeofday(&curtimestamp, NULL);
   159     elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   160     elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   162     if (gamestate->lastmove) {
   163         struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   164         uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   165         suseconds_t micros;
   166         if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   167             micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   168             sec--;
   169         } else {
   170             micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   171         }
   173         elem->move.movetime.tv_sec = sec;
   174         elem->move.movetime.tv_usec = micros;
   176         gamestate->lastmove->next = elem;
   177         gamestate->lastmove = elem;
   178         gamestate->movecount++;
   179     } else {
   180         elem->move.movetime.tv_usec = 0;
   181         elem->move.movetime.tv_sec = 0;
   182         gamestate->movelist = gamestate->lastmove = elem;
   183         gamestate->movecount = 1;
   184     }
   185 }
   187 char getpiecechr(uint8_t piece) {
   188     switch (piece & PIECE_MASK) {
   189     case ROOK: return 'R';
   190     case KNIGHT: return 'N';
   191     case BISHOP: return 'B';
   192     case QUEEN: return 'Q';
   193     case KING: return 'K';
   194     default: return '\0';
   195     }
   196 }
   198 uint8_t getpiece(char c) {
   199     switch (c) {
   200         case 'R': return ROOK;
   201         case 'N': return KNIGHT;
   202         case 'B': return BISHOP;
   203         case 'Q': return QUEEN;
   204         case 'K': return KING;
   205         default: return 0;
   206     }
   207 }
   209 static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   210     uint8_t piece = move->piece & PIECE_MASK;
   211     uint8_t color = move->piece & COLOR_MASK;
   213     /* en passant capture */
   214     if (move->capture && piece == PAWN &&
   215         mdst(gamestate->board, move) == 0) {
   216         gamestate->board[move->fromrow][move->tofile] = 0;
   217     }
   219     /* remove old en passant threats */
   220     for (uint8_t file = 0 ; file < 8 ; file++) {
   221         gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   222         gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   223     }
   225     /* add new en passant threat */
   226     if (piece == PAWN && (
   227         (move->fromrow == 1 && move->torow == 3) ||
   228         (move->fromrow == 6 && move->torow == 4))) {
   229         move->piece |= ENPASSANT_THREAT;
   230     }
   232     /* move (and maybe capture or promote) */
   233     msrc(gamestate->board, move) = 0;
   234     if (move->promotion) {
   235         mdst(gamestate->board, move) = move->promotion;
   236     } else {
   237         mdst(gamestate->board, move) = move->piece;
   238     }
   240     /* castling */
   241     if (piece == KING && move->fromfile == fileidx('e')) {
   243         if (move->tofile == fileidx('g')) {
   244             gamestate->board[move->torow][fileidx('h')] = 0;
   245             gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   246         } else if (move->tofile == fileidx('c')) {
   247             gamestate->board[move->torow][fileidx('a')] = 0;
   248             gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   249         }
   250     }
   252     if (!simulate) {
   253         if (!move->string[0]) {
   254             format_move(gamestate, move);
   255         }
   256     }
   257     /* add move, even in simulation (checkmate test needs it) */
   258     addmove(gamestate, move);
   259 }
   261 void apply_move(GameState *gamestate, Move *move) {
   262     apply_move_impl(gamestate, move, 0);
   263 }
   265 static int validate_move_rules(GameState *gamestate, Move *move) {
   266     /* validate indices (don't trust opponent) */
   267     if (!chkidx(move)) {
   268         return INVALID_POSITION;
   269     }
   271     /* must move */
   272     if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   273         return INVALID_MOVE_SYNTAX;
   274     }
   276     /* does piece exist */
   277     if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   278            != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   279         return INVALID_POSITION;
   280     }
   282     /* can't capture own pieces */
   283     if ((mdst(gamestate->board, move) & COLOR_MASK)
   284             == (move->piece & COLOR_MASK)) {
   285         return RULES_VIOLATED;
   286     }
   288     /* must capture, if and only if destination is occupied */
   289     if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   290             (mdst(gamestate->board, move) != 0 && !move->capture)) {
   291         return INVALID_MOVE_SYNTAX;
   292     }
   294     /* validate individual rules */
   295     _Bool chkrules;
   296     switch (move->piece & PIECE_MASK) {
   297     case PAWN:
   298         chkrules = pawn_chkrules(gamestate, move) &&
   299             !pawn_isblocked(gamestate, move);
   300         break;
   301     case ROOK:
   302         chkrules = rook_chkrules(move) &&
   303             !rook_isblocked(gamestate, move);
   304         break;
   305     case KNIGHT:
   306         chkrules = knight_chkrules(move); /* knight is never blocked */
   307         break;
   308     case BISHOP:
   309         chkrules = bishop_chkrules(move) &&
   310             !bishop_isblocked(gamestate, move);
   311         break;
   312     case QUEEN:
   313         chkrules = queen_chkrules(move) &&
   314             !queen_isblocked(gamestate, move);
   315         break;
   316     case KING:
   317         chkrules = king_chkrules(gamestate, move) &&
   318             !king_isblocked(gamestate, move);
   319         break;
   320     default:
   321         return INVALID_MOVE_SYNTAX;
   322     }
   324     return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   325 }
   327 int validate_move(GameState *gamestate, Move *move) {
   329     int result = validate_move_rules(gamestate, move);
   331     /* cancel processing to save resources */
   332     if (result != VALID_MOVE_SEMANTICS) {
   333         return result;
   334     }
   336     /* simulate move for check validation */
   337     GameState simulation = gamestate_copy_sim(gamestate);
   338     Move simmove = *move;
   339     apply_move_impl(&simulation, &simmove, 1);
   341     /* find kings for check validation */
   342     uint8_t piececolor = (move->piece & COLOR_MASK);
   344     uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   345     for (uint8_t row = 0 ; row < 8 ; row++) {
   346         for (uint8_t file = 0 ; file < 8 ; file++) {
   347             if (simulation.board[row][file] ==
   348                     (piececolor == WHITE?WKING:BKING)) {
   349                 mykingfile = file;
   350                 mykingrow = row;
   351             } else if (simulation.board[row][file] ==
   352                     (piececolor == WHITE?BKING:WKING)) {
   353                 opkingfile = file;
   354                 opkingrow = row;
   355             }
   356         }
   357     }
   359     /* don't move into or stay in check position */
   360     if (is_covered(&simulation, mykingrow, mykingfile,
   361         opponent_color(piececolor))) {
   363         gamestate_cleanup(&simulation);
   364         if ((move->piece & PIECE_MASK) == KING) {
   365             return KING_MOVES_INTO_CHECK;
   366         } else {
   367             /* last move is always not null in this case */
   368             return gamestate->lastmove->move.check ?
   369                 KING_IN_CHECK : PIECE_PINNED;
   370         }
   371     }
   373     /* correct check and checkmate flags (move is still valid) */
   374     Move threats[16];
   375     uint8_t threatcount;
   376     move->check = get_threats(&simulation, opkingrow, opkingfile,
   377         piececolor, threats, &threatcount);
   379     if (move->check) {
   380         /* determine possible escape fields */
   381         _Bool canescape = 0;
   382         for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   383             for (int df = -1 ; df <= 1 && !canescape ; df++) {
   384                 if (!(dr == 0 && df == 0)  &&
   385                         isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   387                     /* escape field neither blocked nor covered */
   388                     if ((simulation.board[opkingrow + dr][opkingfile + df]
   389                             & COLOR_MASK) != opponent_color(piececolor)) {
   390                         canescape |= !is_covered(&simulation,
   391                             opkingrow + dr, opkingfile + df, piececolor);
   392                     }
   393                 }
   394             }
   395         }
   396         /* can't escape, can he capture? */
   397         if (!canescape && threatcount == 1) {
   398             canescape = is_attacked(&simulation, threats[0].fromrow,
   399                 threats[0].fromfile, opponent_color(piececolor));
   400         }
   402         /* can't capture, can he block? */
   403         if (!canescape && threatcount == 1) {
   404             Move *threat = &(threats[0]);
   405             uint8_t threatpiece = threat->piece & PIECE_MASK;
   407             /* knight, pawns and the king cannot be blocked */
   408             if (threatpiece == BISHOP || threatpiece == ROOK
   409                 || threatpiece == QUEEN) {
   410                 if (threat->fromrow == threat->torow) {
   411                     /* rook aspect (on row) */
   412                     int d = threat->tofile > threat->fromfile ? 1 : -1;
   413                     uint8_t file = threat->fromfile;
   414                     while (!canescape && file != threat->tofile - d) {
   415                         file += d;
   416                         canescape |= is_protected(&simulation,
   417                             threat->torow, file, opponent_color(piececolor));
   418                     }
   419                 } else if (threat->fromfile == threat->tofile) {
   420                     /* rook aspect (on file) */
   421                     int d = threat->torow > threat->fromrow ? 1 : -1;
   422                     uint8_t row = threat->fromrow;
   423                     while (!canescape && row != threat->torow - d) {
   424                         row += d;
   425                         canescape |= is_protected(&simulation,
   426                             row, threat->tofile, opponent_color(piececolor));
   427                     }
   428                 } else {
   429                     /* bishop aspect */
   430                     int dr = threat->torow > threat->fromrow ? 1 : -1;
   431                     int df = threat->tofile > threat->fromfile ? 1 : -1;
   433                     uint8_t row = threat->fromrow;
   434                     uint8_t file = threat->fromfile;
   435                     while (!canescape && file != threat->tofile - df
   436                         && row != threat->torow - dr) {
   437                         row += dr;
   438                         file += df;
   439                         canescape |= is_protected(&simulation, row, file,
   440                             opponent_color(piececolor));
   441                     }
   442                 }
   443             }
   444         }
   446         if (!canescape) {
   447             gamestate->checkmate = 1;
   448         }
   449     }
   451     gamestate_cleanup(&simulation);
   453     return VALID_MOVE_SEMANTICS;
   454 }
   456 _Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   457         uint8_t color, Move *threats, uint8_t *threatcount) {
   458     Move candidates[32];
   459     int candidatecount = 0;
   460     for (uint8_t r = 0 ; r < 8 ; r++) {
   461         for (uint8_t f = 0 ; f < 8 ; f++) {
   462             if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   463                 /* non-capturing move */
   464                 memset(&(candidates[candidatecount]), 0, sizeof(Move));
   465                 candidates[candidatecount].piece = gamestate->board[r][f];
   466                 candidates[candidatecount].fromrow = r;
   467                 candidates[candidatecount].fromfile = f;
   468                 candidates[candidatecount].torow = row;
   469                 candidates[candidatecount].tofile = file;
   470                 candidatecount++;
   472                 /* capturing move */
   473                 memcpy(&(candidates[candidatecount]),
   474                     &(candidates[candidatecount-1]), sizeof(Move));
   475                 candidates[candidatecount].capture = 1;
   476                 candidatecount++;
   477             }
   478         }
   479     }
   481     if (threatcount) {
   482         *threatcount = 0;
   483     }
   486     _Bool result = 0;
   488     for (int i = 0 ; i < candidatecount ; i++) {
   489         if (validate_move_rules(gamestate, &(candidates[i]))
   490                 == VALID_MOVE_SEMANTICS) {
   491             result = 1;
   492             if (threats && threatcount) {
   493                 threats[(*threatcount)++] = candidates[i];
   494             }
   495         }
   496     }
   498     return result;
   499 }
   501 _Bool is_pinned(GameState *gamestate, Move *move) {
   502     uint8_t color = move->piece & COLOR_MASK;
   504     GameState simulation = gamestate_copy_sim(gamestate);
   505     Move simmove = *move;
   506     apply_move(&simulation, &simmove);
   508     uint8_t kingfile = 0, kingrow = 0;
   509     for (uint8_t row = 0 ; row < 8 ; row++) {
   510         for (uint8_t file = 0 ; file < 8 ; file++) {
   511             if (simulation.board[row][file] == (color|KING)) {
   512                 kingfile = file;
   513                 kingrow = row;
   514             }
   515         }
   516     }
   518     _Bool covered = is_covered(&simulation,
   519         kingrow, kingfile, opponent_color(color));
   520     gamestate_cleanup(&simulation);
   522     return covered;
   523 }
   525 _Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   526         uint8_t color, Move *threats, uint8_t *threatcount) {
   528     if (threatcount) {
   529         *threatcount = 0;
   530     }
   532     Move candidates[16];
   533     uint8_t candidatecount;
   534     if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   536         _Bool result = 0;
   537         uint8_t kingfile = 0, kingrow = 0;
   538         for (uint8_t row = 0 ; row < 8 ; row++) {
   539             for (uint8_t file = 0 ; file < 8 ; file++) {
   540                 if (gamestate->board[row][file] == (color|KING)) {
   541                     kingfile = file;
   542                     kingrow = row;
   543                 }
   544             }
   545         }
   547         for (uint8_t i = 0 ; i < candidatecount ; i++) {
   548             GameState simulation = gamestate_copy_sim(gamestate);
   549             Move simmove = candidates[i];
   550             apply_move(&simulation, &simmove);
   551             if (!is_covered(&simulation, kingrow, kingfile,
   552                     opponent_color(color))) {
   553                 result = 1;
   554                 if (threats && threatcount) {
   555                     threats[(*threatcount)++] = candidates[i];
   556                 }
   557             }
   558         }
   560         return result;
   561     } else {
   562         return 0;
   563     }
   564 }
   566 static int getlocation(GameState *gamestate, Move *move) {   
   568     uint8_t color = move->piece & COLOR_MASK;
   569     _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   571     Move threats[16], *threat = NULL;
   572     uint8_t threatcount;
   574     if (get_threats(gamestate, move->torow, move->tofile, color,
   575             threats, &threatcount)) {
   577         int reason = INVALID_POSITION;
   579         /* find threats for the specified position */
   580         for (uint8_t i = 0 ; i < threatcount ; i++) {            
   581             if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
   582                     == move->piece &&
   583                     (move->fromrow == POS_UNSPECIFIED ||
   584                     move->fromrow == threats[i].fromrow) &&
   585                     (move->fromfile == POS_UNSPECIFIED ||
   586                     move->fromfile == threats[i].fromfile)) {
   588                 if (threat) {
   589                     return AMBIGUOUS_MOVE;
   590                 } else {
   591                     /* found threat is no real threat */
   592                     if (is_pinned(gamestate, &(threats[i]))) {
   593                         reason = incheck?KING_IN_CHECK:PIECE_PINNED;
   594                     } else {
   595                         threat = &(threats[i]);
   596                     }
   597                 }
   598             }
   599         }
   601         /* can't threaten specified position */
   602         if (!threat) {
   603             return reason;
   604         }
   606         memcpy(move, threat, sizeof(Move));
   607         return VALID_MOVE_SYNTAX;
   608     } else {
   609         return INVALID_POSITION;
   610     }
   611 }
   613 int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   614     memset(move, 0, sizeof(Move));
   615     move->fromfile = POS_UNSPECIFIED;
   616     move->fromrow = POS_UNSPECIFIED;
   618     size_t len = strlen(mstr);
   619     if (len < 1 || len > 6) {
   620         return INVALID_MOVE_SYNTAX;
   621     }
   623     /* evaluate check/checkmate flags */
   624     if (mstr[len-1] == '+') {
   625         len--; mstr[len] = '\0';
   626         move->check = 1;
   627     } else if (mstr[len-1] == '#') {
   628         len--; mstr[len] = '\0';
   629         /* ignore - validation should set game state */
   630     }
   632     /* evaluate promotion */
   633     if (len > 3 && mstr[len-2] == '=') {
   634         move->promotion = getpiece(mstr[len-1]);
   635         if (!move->promotion) {
   636             return INVALID_MOVE_SYNTAX;
   637         } else {
   638             move->promotion |= color;
   639             len -= 2;
   640             mstr[len] = 0;
   641         }
   642     }
   644     if (len == 2) {
   645         /* pawn move (e.g. "e4") */
   646         move->piece = PAWN;
   647         move->tofile = fileidx(mstr[0]);
   648         move->torow = rowidx(mstr[1]);
   649     } else if (len == 3) {
   650         if (strcmp(mstr, "O-O") == 0) {
   651             /* king side castling */
   652             move->piece = KING;
   653             move->fromfile = fileidx('e');
   654             move->tofile = fileidx('g');
   655             move->fromrow = move->torow = color == WHITE ? 0 : 7;
   656         } else {
   657             /* move (e.g. "Nf3") */
   658             move->piece = getpiece(mstr[0]);
   659             move->tofile = fileidx(mstr[1]);
   660             move->torow = rowidx(mstr[2]);
   661         }
   662     } else if (len == 4) {
   663         move->piece = getpiece(mstr[0]);
   664         if (!move->piece) {
   665             move->piece = PAWN;
   666             move->fromfile = fileidx(mstr[0]);
   667         }
   668         if (mstr[1] == 'x') {
   669             /* capture (e.g. "Nxf3", "dxe5") */
   670             move->capture = 1;
   671         } else {
   672             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   673             if (isfile(mstr[1])) {
   674                 move->fromfile = fileidx(mstr[1]);
   675                 if (move->piece == PAWN) {
   676                     move->piece = 0;
   677                 }
   678             } else {
   679                 move->fromrow = rowidx(mstr[1]);
   680             }
   681         }
   682         move->tofile = fileidx(mstr[2]);
   683         move->torow = rowidx(mstr[3]);
   684     } else if (len == 5) {
   685         if (strcmp(mstr, "O-O-O") == 0) {
   686             /* queen side castling "O-O-O" */
   687             move->piece = KING;
   688             move->fromfile = fileidx('e');
   689             move->tofile = fileidx('c');
   690             move->fromrow = move->torow = color == WHITE ? 0 : 7;
   691         } else {
   692             move->piece = getpiece(mstr[0]);
   693             if (mstr[2] == 'x') {
   694                 move->capture = 1;
   695                 if (move->piece) {
   696                     /* capture (e.g. "Ndxf3") */
   697                     move->fromfile = fileidx(mstr[1]);
   698                 } else {
   699                     /* long notation capture (e.g. "e5xf6") */
   700                     move->piece = PAWN;
   701                     move->fromfile = fileidx(mstr[0]);
   702                     move->fromrow = rowidx(mstr[1]);
   703                 }
   704             } else {
   705                 /* long notation move (e.g. "Nc5a4") */
   706                 move->fromfile = fileidx(mstr[1]);
   707                 move->fromrow = rowidx(mstr[2]);
   708             }
   709             move->tofile = fileidx(mstr[3]);
   710             move->torow = rowidx(mstr[4]);
   711         }
   712     } else if (len == 6) {
   713         /* long notation capture (e.g. "Nc5xf3") */
   714         if (mstr[3] == 'x') {
   715             move->capture = 1;
   716             move->piece = getpiece(mstr[0]);
   717             move->fromfile = fileidx(mstr[1]);
   718             move->fromrow = rowidx(mstr[2]);
   719             move->tofile = fileidx(mstr[4]);
   720             move->torow = rowidx(mstr[5]);
   721         }
   722     }
   725     if (move->piece) {
   726         if (move->piece == PAWN
   727             && move->torow == (color==WHITE?7:0)
   728             && !move->promotion) {
   729             return NEED_PROMOTION;
   730         }
   732         move->piece |= color;
   733         if (move->fromfile == POS_UNSPECIFIED
   734             || move->fromrow == POS_UNSPECIFIED) {
   735             return getlocation(gamestate, move);
   736         } else {
   737             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   738         }
   739     } else {
   740         return INVALID_MOVE_SYNTAX;
   741     }
   742 }
   744 _Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   745         uint8_t color) {
   747     Move threats[16];
   748     uint8_t threatcount;
   749     if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   750         for (int i = 0 ; i < threatcount ; i++) {
   751             if (threats[i].piece != (color|KING)) {
   752                 return 1;
   753             }
   754         }
   755         return 0;
   756     } else {
   757         return 0;
   758     }
   759 }
   761 uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   762         uint8_t color) {
   763     if (!gameinfo->timecontrol) {
   764         return 0;
   765     }
   767     if (gamestate->movelist) {
   768         uint16_t time = gameinfo->time;
   769         suseconds_t micros = 0;
   771         MoveList *movelist = color == WHITE ?
   772             gamestate->movelist : gamestate->movelist->next;
   774         while (movelist) {
   775             time += gameinfo->addtime;
   777             struct movetimeval *movetime = &(movelist->move.movetime);
   778             if (movetime->tv_sec >= time) {
   779                 return 0;
   780             }
   782             time -= movetime->tv_sec;
   783             micros += movetime->tv_usec;
   785             movelist = movelist->next ? movelist->next->next : NULL;
   786         }
   788         time_t sec;
   789         movelist = gamestate->lastmove;
   790         if ((movelist->move.piece & COLOR_MASK) != color) {
   791             struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   792             struct timeval currenttstamp;
   793             gettimeofday(&currenttstamp, NULL);
   794             micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   795             sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   796             if (sec >= time) {
   797                 return 0;
   798             }
   800             time -= sec;
   801         }
   803         sec = micros / 1e6L;
   805         if (sec >= time) {
   806             return 0;
   807         }
   809         time -= sec;
   811         return time;
   812     } else {
   813         return gameinfo->time;
   814     }
   815 }

mercurial