src/chess/rules.c

Wed, 29 Aug 2018 15:12:24 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 29 Aug 2018 15:12:24 +0200
changeset 66
f5cc75565f7c
parent 64
4eda5df55f86
child 67
c76e46970a59
permissions
-rw-r--r--

fixes ambiguity resolver in PGN output not resolving ambiguities for diagonally attacking Knights

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

mercurial