src/chess/rules.c

Tue, 18 Sep 2018 15:02:08 +0200

author
Mike Becker <universe@uap-core.de>
date
Tue, 18 Sep 2018 15:02:08 +0200
changeset 69
c8f2c280cff7
parent 68
b34de5ce7d0e
permissions
-rw-r--r--

adds unicode support

     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 BETWEEN validating AND 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         string[idx++] = gamestate->checkmate?'#':'+';
   158     }
   159 }
   161 static void addmove(GameState* gamestate, Move *move) {
   162     MoveList *elem = malloc(sizeof(MoveList));
   163     elem->next = NULL;
   164     elem->move = *move;
   166     struct timeval curtimestamp;
   167     gettimeofday(&curtimestamp, NULL);
   168     elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
   169     elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
   171     if (gamestate->lastmove) {
   172         struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
   173         uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
   174         suseconds_t micros;
   175         if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
   176             micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
   177             sec--;
   178         } else {
   179             micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
   180         }
   182         elem->move.movetime.tv_sec = sec;
   183         elem->move.movetime.tv_usec = micros;
   185         gamestate->lastmove->next = elem;
   186         gamestate->lastmove = elem;
   187         gamestate->movecount++;
   188     } else {
   189         elem->move.movetime.tv_usec = 0;
   190         elem->move.movetime.tv_sec = 0;
   191         gamestate->movelist = gamestate->lastmove = elem;
   192         gamestate->movecount = 1;
   193     }
   194 }
   196 char getpiecechr(uint8_t piece) {
   197     switch (piece & PIECE_MASK) {
   198     case ROOK: return 'R';
   199     case KNIGHT: return 'N';
   200     case BISHOP: return 'B';
   201     case QUEEN: return 'Q';
   202     case KING: return 'K';
   203     default: return '\0';
   204     }
   205 }
   207 unsigned char* getpieceunicode(uint8_t piece) {
   208     switch (piece & PIECE_MASK) {
   209     case PAWN: return "\u265f";
   210     case ROOK: return "\u265c";
   211     case KNIGHT: return "\u265e";
   212     case BISHOP: return "\u265d";
   213     case QUEEN: return "\u265b";
   214     case KING: return "\u265a";
   215     default: return "";
   216     }
   217 }
   219 uint8_t getpiece(char c) {
   220     switch (c) {
   221         case 'R': return ROOK;
   222         case 'N': return KNIGHT;
   223         case 'B': return BISHOP;
   224         case 'Q': return QUEEN;
   225         case 'K': return KING;
   226         default: return 0;
   227     }
   228 }
   230 static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
   231     /* format move before moving (s.t. ambiguities can be resolved) */
   232     if (!simulate) {
   233         if (!move->string[0]) {
   234             format_move(gamestate, move);
   235         }
   236     }
   238     uint8_t piece = move->piece & PIECE_MASK;
   239     uint8_t color = move->piece & COLOR_MASK;
   241     /* en passant capture */
   242     if (move->capture && piece == PAWN &&
   243         mdst(gamestate->board, move) == 0) {
   244         gamestate->board[move->fromrow][move->tofile] = 0;
   245     }
   247     /* remove old en passant threats */
   248     for (uint8_t file = 0 ; file < 8 ; file++) {
   249         gamestate->board[3][file] &= ~ENPASSANT_THREAT;
   250         gamestate->board[4][file] &= ~ENPASSANT_THREAT;
   251     }
   253     /* add new en passant threat */
   254     if (piece == PAWN && (
   255         (move->fromrow == 1 && move->torow == 3) ||
   256         (move->fromrow == 6 && move->torow == 4))) {
   257         move->piece |= ENPASSANT_THREAT;
   258     }
   260     /* move (and maybe capture or promote) */
   261     msrc(gamestate->board, move) = 0;
   262     if (move->promotion) {
   263         mdst(gamestate->board, move) = move->promotion;
   264     } else {
   265         mdst(gamestate->board, move) = move->piece;
   266     }
   268     /* castling */
   269     if (piece == KING && move->fromfile == fileidx('e')) {
   270         if (move->tofile == fileidx('g')) {
   271             gamestate->board[move->torow][fileidx('h')] = 0;
   272             gamestate->board[move->torow][fileidx('f')] = color|ROOK;
   273         } else if (move->tofile == fileidx('c')) {
   274             gamestate->board[move->torow][fileidx('a')] = 0;
   275             gamestate->board[move->torow][fileidx('d')] = color|ROOK;
   276         }
   277     }
   279     /* add move, even in simulation (checkmate test needs it) */
   280     addmove(gamestate, move);
   281 }
   283 void apply_move(GameState *gamestate, Move *move) {
   284     apply_move_impl(gamestate, move, 0);
   285 }
   287 static int validate_move_rules(GameState *gamestate, Move *move) {
   288     /* validate indices (don't trust opponent) */
   289     if (!chkidx(move)) {
   290         return INVALID_POSITION;
   291     }
   293     /* must move */
   294     if (move->fromfile == move->tofile && move->fromrow == move->torow) {
   295         return INVALID_MOVE_SYNTAX;
   296     }
   298     /* does piece exist */
   299     if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
   300            != (move->piece&(PIECE_MASK|COLOR_MASK))) {
   301         return INVALID_POSITION;
   302     }
   304     /* can't capture own pieces */
   305     if ((mdst(gamestate->board, move) & COLOR_MASK)
   306             == (move->piece & COLOR_MASK)) {
   307         return RULES_VIOLATED;
   308     }
   310     /* must capture, if and only if destination is occupied */
   311     if ((mdst(gamestate->board, move) == 0 && move->capture) ||
   312             (mdst(gamestate->board, move) != 0 && !move->capture)) {
   313         return INVALID_MOVE_SYNTAX;
   314     }
   316     /* validate individual rules */
   317     _Bool chkrules;
   318     switch (move->piece & PIECE_MASK) {
   319     case PAWN:
   320         chkrules = pawn_chkrules(gamestate, move) &&
   321             !pawn_isblocked(gamestate, move);
   322         break;
   323     case ROOK:
   324         chkrules = rook_chkrules(move) &&
   325             !rook_isblocked(gamestate, move);
   326         break;
   327     case KNIGHT:
   328         chkrules = knight_chkrules(move); /* knight is never blocked */
   329         break;
   330     case BISHOP:
   331         chkrules = bishop_chkrules(move) &&
   332             !bishop_isblocked(gamestate, move);
   333         break;
   334     case QUEEN:
   335         chkrules = queen_chkrules(move) &&
   336             !queen_isblocked(gamestate, move);
   337         break;
   338     case KING:
   339         chkrules = king_chkrules(gamestate, move) &&
   340             !king_isblocked(gamestate, move);
   341         break;
   342     default:
   343         return INVALID_MOVE_SYNTAX;
   344     }
   346     return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
   347 }
   349 int validate_move(GameState *gamestate, Move *move) {
   351     int result = validate_move_rules(gamestate, move);
   353     /* cancel processing to save resources */
   354     if (result != VALID_MOVE_SEMANTICS) {
   355         return result;
   356     }
   358     /* simulate move for check validation */
   359     GameState simulation = gamestate_copy_sim(gamestate);
   360     Move simmove = *move;
   361     apply_move_impl(&simulation, &simmove, 1);
   363     /* find kings for check validation */
   364     uint8_t piececolor = (move->piece & COLOR_MASK);
   366     uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
   367     for (uint8_t row = 0 ; row < 8 ; row++) {
   368         for (uint8_t file = 0 ; file < 8 ; file++) {
   369             if (simulation.board[row][file] ==
   370                     (piececolor == WHITE?WKING:BKING)) {
   371                 mykingfile = file;
   372                 mykingrow = row;
   373             } else if (simulation.board[row][file] ==
   374                     (piececolor == WHITE?BKING:WKING)) {
   375                 opkingfile = file;
   376                 opkingrow = row;
   377             }
   378         }
   379     }
   381     /* don't move into or stay in check position */
   382     if (is_covered(&simulation, mykingrow, mykingfile,
   383         opponent_color(piececolor))) {
   385         gamestate_cleanup(&simulation);
   386         if ((move->piece & PIECE_MASK) == KING) {
   387             return KING_MOVES_INTO_CHECK;
   388         } else {
   389             /* last move is always not null in this case */
   390             return gamestate->lastmove->move.check ?
   391                 KING_IN_CHECK : PIECE_PINNED;
   392         }
   393     }
   395     /* correct check and checkmate flags (move is still valid) */
   396     Move threats[16];
   397     uint8_t threatcount;
   398     move->check = get_threats(&simulation, opkingrow, opkingfile,
   399         piececolor, threats, &threatcount);
   401     if (move->check) {
   402         /* determine possible escape fields */
   403         _Bool canescape = 0;
   404         for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
   405             for (int df = -1 ; df <= 1 && !canescape ; df++) {
   406                 if (!(dr == 0 && df == 0)  &&
   407                         isidx(opkingrow + dr) && isidx(opkingfile + df)) {
   409                     /* escape field neither blocked nor covered */
   410                     if ((simulation.board[opkingrow + dr][opkingfile + df]
   411                             & COLOR_MASK) != opponent_color(piececolor)) {
   412                         canescape |= !is_covered(&simulation,
   413                             opkingrow + dr, opkingfile + df, piececolor);
   414                     }
   415                 }
   416             }
   417         }
   418         /* can't escape, can he capture? */
   419         if (!canescape && threatcount == 1) {
   420             canescape = is_attacked(&simulation, threats[0].fromrow,
   421                 threats[0].fromfile, opponent_color(piececolor));
   422         }
   424         /* can't capture, can he block? */
   425         if (!canescape && threatcount == 1) {
   426             Move *threat = &(threats[0]);
   427             uint8_t threatpiece = threat->piece & PIECE_MASK;
   429             /* knight, pawns and the king cannot be blocked */
   430             if (threatpiece == BISHOP || threatpiece == ROOK
   431                 || threatpiece == QUEEN) {
   432                 if (threat->fromrow == threat->torow) {
   433                     /* rook aspect (on row) */
   434                     int d = threat->tofile > threat->fromfile ? 1 : -1;
   435                     uint8_t file = threat->fromfile;
   436                     while (!canescape && file != threat->tofile - d) {
   437                         file += d;
   438                         canescape |= is_protected(&simulation,
   439                             threat->torow, file, opponent_color(piececolor));
   440                     }
   441                 } else if (threat->fromfile == threat->tofile) {
   442                     /* rook aspect (on file) */
   443                     int d = threat->torow > threat->fromrow ? 1 : -1;
   444                     uint8_t row = threat->fromrow;
   445                     while (!canescape && row != threat->torow - d) {
   446                         row += d;
   447                         canescape |= is_protected(&simulation,
   448                             row, threat->tofile, opponent_color(piececolor));
   449                     }
   450                 } else {
   451                     /* bishop aspect */
   452                     int dr = threat->torow > threat->fromrow ? 1 : -1;
   453                     int df = threat->tofile > threat->fromfile ? 1 : -1;
   455                     uint8_t row = threat->fromrow;
   456                     uint8_t file = threat->fromfile;
   457                     while (!canescape && file != threat->tofile - df
   458                         && row != threat->torow - dr) {
   459                         row += dr;
   460                         file += df;
   461                         canescape |= is_protected(&simulation, row, file,
   462                             opponent_color(piececolor));
   463                     }
   464                 }
   465             }
   466         }
   468         if (!canescape) {
   469             gamestate->checkmate = 1;
   470         }
   471     }
   473     gamestate_cleanup(&simulation);
   475     return VALID_MOVE_SEMANTICS;
   476 }
   478 _Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
   479         uint8_t color, Move *threats, uint8_t *threatcount) {
   480     Move candidates[32];
   481     int candidatecount = 0;
   482     for (uint8_t r = 0 ; r < 8 ; r++) {
   483         for (uint8_t f = 0 ; f < 8 ; f++) {
   484             if ((gamestate->board[r][f] & COLOR_MASK) == color) {
   485                 /* non-capturing move */
   486                 memset(&(candidates[candidatecount]), 0, sizeof(Move));
   487                 candidates[candidatecount].piece = gamestate->board[r][f];
   488                 candidates[candidatecount].fromrow = r;
   489                 candidates[candidatecount].fromfile = f;
   490                 candidates[candidatecount].torow = row;
   491                 candidates[candidatecount].tofile = file;
   492                 candidatecount++;
   494                 /* capturing move */
   495                 memcpy(&(candidates[candidatecount]),
   496                     &(candidates[candidatecount-1]), sizeof(Move));
   497                 candidates[candidatecount].capture = 1;
   498                 candidatecount++;
   499             }
   500         }
   501     }
   503     if (threatcount) {
   504         *threatcount = 0;
   505     }
   508     _Bool result = 0;
   510     for (int i = 0 ; i < candidatecount ; i++) {
   511         if (validate_move_rules(gamestate, &(candidates[i]))
   512                 == VALID_MOVE_SEMANTICS) {
   513             result = 1;
   514             if (threats && threatcount) {
   515                 threats[(*threatcount)++] = candidates[i];
   516             }
   517         }
   518     }
   520     return result;
   521 }
   523 _Bool is_pinned(GameState *gamestate, Move *move) {
   524     uint8_t color = move->piece & COLOR_MASK;
   526     GameState simulation = gamestate_copy_sim(gamestate);
   527     Move simmove = *move;
   528     apply_move(&simulation, &simmove);
   530     uint8_t kingfile = 0, kingrow = 0;
   531     for (uint8_t row = 0 ; row < 8 ; row++) {
   532         for (uint8_t file = 0 ; file < 8 ; file++) {
   533             if (simulation.board[row][file] == (color|KING)) {
   534                 kingfile = file;
   535                 kingrow = row;
   536             }
   537         }
   538     }
   540     _Bool covered = is_covered(&simulation,
   541         kingrow, kingfile, opponent_color(color));
   542     gamestate_cleanup(&simulation);
   544     return covered;
   545 }
   547 _Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
   548         uint8_t color, Move *threats, uint8_t *threatcount) {
   550     if (threatcount) {
   551         *threatcount = 0;
   552     }
   554     Move candidates[16];
   555     uint8_t candidatecount;
   556     if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
   558         _Bool result = 0;
   559         uint8_t kingfile = 0, kingrow = 0;
   560         for (uint8_t row = 0 ; row < 8 ; row++) {
   561             for (uint8_t file = 0 ; file < 8 ; file++) {
   562                 if (gamestate->board[row][file] == (color|KING)) {
   563                     kingfile = file;
   564                     kingrow = row;
   565                 }
   566             }
   567         }
   569         for (uint8_t i = 0 ; i < candidatecount ; i++) {
   570             GameState simulation = gamestate_copy_sim(gamestate);
   571             Move simmove = candidates[i];
   572             apply_move(&simulation, &simmove);
   573             if (!is_covered(&simulation, kingrow, kingfile,
   574                     opponent_color(color))) {
   575                 result = 1;
   576                 if (threats && threatcount) {
   577                     threats[(*threatcount)++] = candidates[i];
   578                 }
   579             }
   580         }
   582         return result;
   583     } else {
   584         return 0;
   585     }
   586 }
   588 static int getlocation(GameState *gamestate, Move *move) {   
   590     uint8_t color = move->piece & COLOR_MASK;
   591     uint8_t piece = move->piece & PIECE_MASK;
   592     _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
   594     Move threats[16], *threat = NULL;
   595     uint8_t threatcount;
   597     if (get_threats(gamestate, move->torow, move->tofile, color,
   598             threats, &threatcount)) {
   600         int reason = INVALID_POSITION;
   602         /* find threats for the specified position */
   603         for (uint8_t i = 0 ; i < threatcount ; i++) {            
   604             if ((threats[i].piece & PIECE_MASK) == piece
   605                     && (threats[i].piece & COLOR_MASK) == color &&
   606                     (move->fromrow == POS_UNSPECIFIED ||
   607                     move->fromrow == threats[i].fromrow) &&
   608                     (move->fromfile == POS_UNSPECIFIED ||
   609                     move->fromfile == threats[i].fromfile)) {
   611                 if (threat) {
   612                     return AMBIGUOUS_MOVE;
   613                 } else {
   614                     /* found threat is no real threat */
   615                     if (is_pinned(gamestate, &(threats[i]))) {
   616                         reason = incheck?KING_IN_CHECK:
   617                             (piece==KING?KING_MOVES_INTO_CHECK:PIECE_PINNED);
   618                     } else {
   619                         threat = &(threats[i]);
   620                     }
   621                 }
   622             }
   623         }
   625         /* can't threaten specified position */
   626         if (!threat) {
   627             return reason;
   628         }
   630         memcpy(move, threat, sizeof(Move));
   631         return VALID_MOVE_SYNTAX;
   632     } else {
   633         return INVALID_POSITION;
   634     }
   635 }
   637 int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
   638     memset(move, 0, sizeof(Move));
   639     move->fromfile = POS_UNSPECIFIED;
   640     move->fromrow = POS_UNSPECIFIED;
   642     size_t len = strlen(mstr);
   643     if (len < 1 || len > 6) {
   644         return INVALID_MOVE_SYNTAX;
   645     }
   647     /* evaluate check/checkmate flags */
   648     if (mstr[len-1] == '+') {
   649         len--; mstr[len] = '\0';
   650         move->check = 1;
   651     } else if (mstr[len-1] == '#') {
   652         len--; mstr[len] = '\0';
   653         /* ignore - validation should set game state */
   654     }
   656     /* evaluate promotion */
   657     if (len > 3 && mstr[len-2] == '=') {
   658         move->promotion = getpiece(mstr[len-1]);
   659         if (!move->promotion) {
   660             return INVALID_MOVE_SYNTAX;
   661         } else {
   662             move->promotion |= color;
   663             len -= 2;
   664             mstr[len] = 0;
   665         }
   666     }
   668     if (len == 2) {
   669         /* pawn move (e.g. "e4") */
   670         move->piece = PAWN;
   671         move->tofile = fileidx(mstr[0]);
   672         move->torow = rowidx(mstr[1]);
   673     } else if (len == 3) {
   674         if (strcmp(mstr, "O-O") == 0) {
   675             /* king side castling */
   676             move->piece = KING;
   677             move->fromfile = fileidx('e');
   678             move->tofile = fileidx('g');
   679             move->fromrow = move->torow = color == WHITE ? 0 : 7;
   680         } else {
   681             /* move (e.g. "Nf3") */
   682             move->piece = getpiece(mstr[0]);
   683             move->tofile = fileidx(mstr[1]);
   684             move->torow = rowidx(mstr[2]);
   685         }
   686     } else if (len == 4) {
   687         move->piece = getpiece(mstr[0]);
   688         if (!move->piece) {
   689             move->piece = PAWN;
   690             move->fromfile = fileidx(mstr[0]);
   691         }
   692         if (mstr[1] == 'x') {
   693             /* capture (e.g. "Nxf3", "dxe5") */
   694             move->capture = 1;
   695         } else {
   696             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
   697             if (isfile(mstr[1])) {
   698                 move->fromfile = fileidx(mstr[1]);
   699                 if (move->piece == PAWN) {
   700                     move->piece = 0;
   701                 }
   702             } else {
   703                 move->fromrow = rowidx(mstr[1]);
   704             }
   705         }
   706         move->tofile = fileidx(mstr[2]);
   707         move->torow = rowidx(mstr[3]);
   708     } else if (len == 5) {
   709         if (strcmp(mstr, "O-O-O") == 0) {
   710             /* queen side castling "O-O-O" */
   711             move->piece = KING;
   712             move->fromfile = fileidx('e');
   713             move->tofile = fileidx('c');
   714             move->fromrow = move->torow = color == WHITE ? 0 : 7;
   715         } else {
   716             move->piece = getpiece(mstr[0]);
   717             if (mstr[2] == 'x') {
   718                 move->capture = 1;
   719                 if (move->piece) {
   720                     /* capture (e.g. "Ndxf3") */
   721                     move->fromfile = fileidx(mstr[1]);
   722                 } else {
   723                     /* long notation capture (e.g. "e5xf6") */
   724                     move->piece = PAWN;
   725                     move->fromfile = fileidx(mstr[0]);
   726                     move->fromrow = rowidx(mstr[1]);
   727                 }
   728             } else {
   729                 /* long notation move (e.g. "Nc5a4") */
   730                 move->fromfile = fileidx(mstr[1]);
   731                 move->fromrow = rowidx(mstr[2]);
   732             }
   733             move->tofile = fileidx(mstr[3]);
   734             move->torow = rowidx(mstr[4]);
   735         }
   736     } else if (len == 6) {
   737         /* long notation capture (e.g. "Nc5xf3") */
   738         if (mstr[3] == 'x') {
   739             move->capture = 1;
   740             move->piece = getpiece(mstr[0]);
   741             move->fromfile = fileidx(mstr[1]);
   742             move->fromrow = rowidx(mstr[2]);
   743             move->tofile = fileidx(mstr[4]);
   744             move->torow = rowidx(mstr[5]);
   745         }
   746     }
   749     if (move->piece) {
   750         if (move->piece == PAWN
   751             && move->torow == (color==WHITE?7:0)
   752             && !move->promotion) {
   753             return NEED_PROMOTION;
   754         }
   756         move->piece |= color;
   757         if (move->fromfile == POS_UNSPECIFIED
   758             || move->fromrow == POS_UNSPECIFIED) {
   759             return getlocation(gamestate, move);
   760         } else {
   761             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
   762         }
   763     } else {
   764         return INVALID_MOVE_SYNTAX;
   765     }
   766 }
   768 _Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
   769         uint8_t color) {
   771     Move threats[16];
   772     uint8_t threatcount;
   773     if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
   774         for (int i = 0 ; i < threatcount ; i++) {
   775             if (threats[i].piece != (color|KING)) {
   776                 return 1;
   777             }
   778         }
   779         return 0;
   780     } else {
   781         return 0;
   782     }
   783 }
   785 uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
   786         uint8_t color) {
   787     if (!gameinfo->timecontrol) {
   788         return 0;
   789     }
   791     if (gamestate->movelist) {
   792         uint16_t time = gameinfo->time;
   793         suseconds_t micros = 0;
   795         MoveList *movelist = color == WHITE ?
   796             gamestate->movelist : gamestate->movelist->next;
   798         while (movelist) {
   799             time += gameinfo->addtime;
   801             struct movetimeval *movetime = &(movelist->move.movetime);
   802             if (movetime->tv_sec >= time) {
   803                 return 0;
   804             }
   806             time -= movetime->tv_sec;
   807             micros += movetime->tv_usec;
   809             movelist = movelist->next ? movelist->next->next : NULL;
   810         }
   812         time_t sec;
   813         movelist = gamestate->lastmove;
   814         if ((movelist->move.piece & COLOR_MASK) != color) {
   815             struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
   816             struct timeval currenttstamp;
   817             gettimeofday(&currenttstamp, NULL);
   818             micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
   819             sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
   820             if (sec >= time) {
   821                 return 0;
   822             }
   824             time -= sec;
   825         }
   827         sec = micros / 1e6L;
   829         if (sec >= time) {
   830             return 0;
   831         }
   833         time -= sec;
   835         return time;
   836     } else {
   837         return gameinfo->time;
   838     }
   839 }

mercurial