diff -r 3fff4c364ffc -r 12d8f0f6ef06 test/golden-master/bigtest.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/golden-master/bigtest.html Mon Oct 03 12:14:53 2022 +0200 @@ -0,0 +1,861 @@ + + + + c2html + + + + +
+  1 /*
+  2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+  3  *
+  4  * Copyright 2014 Mike Becker. All rights reserved.
+  5  *
+  6  * Redistribution and use in source and binary forms, with or without
+  7  * modification, are permitted provided that the following conditions are met:
+  8  *
+  9  *   1. Redistributions of source code must retain the above copyright
+ 10  *      notice, this list of conditions and the following disclaimer.
+ 11  *
+ 12  *   2. Redistributions in binary form must reproduce the above copyright
+ 13  *      notice, this list of conditions and the following disclaimer in the
+ 14  *      documentation and/or other materials provided with the distribution.
+ 15  *
+ 16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ 17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ 18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ 19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ 20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ 21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ 22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ 23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ 24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ 25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ 26  * POSSIBILITY OF SUCH DAMAGE.
+ 27  *
+ 28  */
+ 29 
+ 30 #include "rules.h"
+ 31 #include "chess.h"
+ 32 #include <string.h>
+ 33 #include <stdlib.h>
+ 34 #include <sys/time.h>
+ 35 
+ 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     }
+ 43 
+ 44     return simulation;
+ 45 }
+ 46 
+ 47 void gamestate_init(GameState *gamestate) {
+ 48     memset(gamestate, 0, sizeof(GameState));
+ 49     
+ 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 }
+ 62 
+ 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 }
+ 72 
+ 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;
+ 76     
+ 77     /* at least 8 characters should be available, wipe them out */
+ 78     memset(string, 0, 8);
+ 79     
+ 80     /* special formats for castling */
+ 81     if ((move->piece&PIECE_MASK) == KING &&
+ 82             abs(move->tofile-move->fromfile) == 2) {
+ 83         if (move->tofile==fileidx('c')) {
+ 84             memcpy(string, "O-O-O", 5);
+ 85         } else {
+ 86             memcpy(string, "O-O", 3);
+ 87         }
+ 88     }
+ 89 
+ 90     /* start by notating the piece character */
+ 91     string[0] = getpiecechr(move->piece);
+ 92     int idx = string[0] ? 1 : 0;
+ 93     
+ 94     /* find out how many source information we do need */
+ 95     uint8_t piece = move->piece & PIECE_MASK;
+ 96     if (piece == PAWN) {
+ 97         if (move->capture) {
+ 98             string[idx++] = filechr(move->fromfile);
+ 99         }
+100     } else if (piece != KING) {
+101         Move threats[16];
+102         uint8_t threatcount;
+103         get_real_threats(gamestate, move->torow, move->tofile,
+104             move->piece&COLOR_MASK, threats, &threatcount);
+105         if (threatcount > 1) {
+106             int ambrows = 0, ambfiles = 0;
+107             for (uint8_t i = 0 ; i < threatcount ; i++) {
+108                 if (threats[i].fromrow == move->fromrow) {
+109                     ambrows++;
+110                 }
+111                 if (threats[i].fromfile == move->fromfile) {
+112                     ambfiles++;
+113                 }
+114             }
+115             /* ambiguous row, name file */
+116             if (ambrows > 1) {
+117                 string[idx++] = filechr(move->fromfile);
+118             }
+119             /* ambiguous file, name row */
+120             if (ambfiles > 1) {
+121                 string[idx++] = filechr(move->fromrow);
+122             }
+123         }
+124     }
+125     
+126     /* capturing? */
+127     if (move->capture) {
+128         string[idx++] = 'x';
+129     }
+130     
+131     /* destination */
+132     string[idx++] = filechr(move->tofile);
+133     string[idx++] = rowchr(move->torow);
+134     
+135     /* promotion? */
+136     if (move->promotion) {
+137         string[idx++] = '=';
+138         string[idx++] = getpiecechr(move->promotion);
+139     }
+140     
+141     /* check? */
+142     if (move->check) {
+143         /* works only, if this function is called when applying the move */
+144         string[idx++] = gamestate->checkmate?'#':'+';
+145     }
+146 }
+147 
+148 static void addmove(GameState* gamestate, Move *move) {
+149     MoveList *elem = malloc(sizeof(MoveList));
+150     elem->next = NULL;
+151     elem->move = *move;
+152     
+153     struct timeval curtimestamp;
+154     gettimeofday(&curtimestamp, NULL);
+155     elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
+156     elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
+157     
+158     if (gamestate->lastmove) {
+159         struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
+160         uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
+161         suseconds_t micros;
+162         if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
+163             micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
+164             sec--;
+165         } else {
+166             micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
+167         }
+168         
+169         elem->move.movetime.tv_sec = sec;
+170         elem->move.movetime.tv_usec = micros;
+171         
+172         gamestate->lastmove->next = elem;
+173         gamestate->lastmove = elem;
+174     } else {
+175         elem->move.movetime.tv_usec = 0;
+176         elem->move.movetime.tv_sec = 0;
+177         gamestate->movelist = gamestate->lastmove = elem;
+178     }
+179 }
+180 
+181 char getpiecechr(uint8_t piece) {
+182     switch (piece & PIECE_MASK) {
+183     case ROOK: return 'R';
+184     case KNIGHT: return 'N';
+185     case BISHOP: return 'B';
+186     case QUEEN: return 'Q';
+187     case KING: return 'K';
+188     default: return '\0';
+189     }
+190 }
+191 
+192 uint8_t getpiece(char c) {
+193     switch (c) {
+194         case 'R': return ROOK;
+195         case 'N': return KNIGHT;
+196         case 'B': return BISHOP;
+197         case 'Q': return QUEEN;
+198         case 'K': return KING;
+199         default: return 0;
+200     }
+201 }
+202 
+203 static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
+204     uint8_t piece = move->piece & PIECE_MASK;
+205     uint8_t color = move->piece & COLOR_MASK;
+206     
+207     /* en passant capture */
+208     if (move->capture && piece == PAWN &&
+209         mdst(gamestate->board, move) == 0) {
+210         gamestate->board[move->fromrow][move->tofile] = 0;
+211     }
+212     
+213     /* remove old en passant threats */
+214     for (uint8_t file = 0 ; file < 8 ; file++) {
+215         gamestate->board[3][file] &= ~ENPASSANT_THREAT;
+216         gamestate->board[4][file] &= ~ENPASSANT_THREAT;
+217     }
+218     
+219     /* add new en passant threat */
+220     if (piece == PAWN && (
+221         (move->fromrow == 1 && move->torow == 3) ||
+222         (move->fromrow == 6 && move->torow == 4))) {
+223         move->piece |= ENPASSANT_THREAT;
+224     }
+225     
+226     /* move (and maybe capture or promote) */
+227     msrc(gamestate->board, move) = 0;
+228     if (move->promotion) {
+229         mdst(gamestate->board, move) = move->promotion;
+230     } else {
+231         mdst(gamestate->board, move) = move->piece;
+232     }
+233     
+234     /* castling */
+235     if (piece == KING && move->fromfile == fileidx('e')) {
+236         
+237         if (move->tofile == fileidx('g')) {
+238             gamestate->board[move->torow][fileidx('h')] = 0;
+239             gamestate->board[move->torow][fileidx('f')] = color|ROOK;
+240         } else if (move->tofile == fileidx('c')) {
+241             gamestate->board[move->torow][fileidx('a')] = 0;
+242             gamestate->board[move->torow][fileidx('d')] = color|ROOK;
+243         }
+244     }
+245 
+246     if (!simulate) {
+247         if (!move->string[0]) {
+248             format_move(gamestate, move);
+249         }
+250     }
+251     /* add move, even in simulation (checkmate test needs it) */
+252     addmove(gamestate, move);
+253 }
+254 
+255 void apply_move(GameState *gamestate, Move *move) {
+256     apply_move_impl(gamestate, move, 0);
+257 }
+258 
+259 static int validate_move_rules(GameState *gamestate, Move *move) {
+260     /* validate indices (don't trust opponent) */
+261     if (!chkidx(move)) {
+262         return INVALID_POSITION;
+263     }
+264     
+265     /* must move */
+266     if (move->fromfile == move->tofile && move->fromrow == move->torow) {
+267         return INVALID_MOVE_SYNTAX;
+268     }
+269     
+270     /* does piece exist */
+271     if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
+272            != (move->piece&(PIECE_MASK|COLOR_MASK))) {
+273         return INVALID_POSITION;
+274     }
+275     
+276     /* can't capture own pieces */
+277     if ((mdst(gamestate->board, move) & COLOR_MASK)
+278             == (move->piece & COLOR_MASK)) {
+279         return RULES_VIOLATED;
+280     }
+281     
+282     /* must capture, if and only if destination is occupied */
+283     if ((mdst(gamestate->board, move) == 0 && move->capture) ||
+284             (mdst(gamestate->board, move) != 0 && !move->capture)) {
+285         return INVALID_MOVE_SYNTAX;
+286     }
+287     
+288     /* validate individual rules */
+289     _Bool chkrules;
+290     switch (move->piece & PIECE_MASK) {
+291     case PAWN:
+292         chkrules = pawn_chkrules(gamestate, move) &&
+293             !pawn_isblocked(gamestate, move);
+294         break;
+295     case ROOK:
+296         chkrules = rook_chkrules(move) &&
+297             !rook_isblocked(gamestate, move);
+298         break;
+299     case KNIGHT:
+300         chkrules = knight_chkrules(move); /* knight is never blocked */
+301         break;
+302     case BISHOP:
+303         chkrules = bishop_chkrules(move) &&
+304             !bishop_isblocked(gamestate, move);
+305         break;
+306     case QUEEN:
+307         chkrules = queen_chkrules(move) &&
+308             !queen_isblocked(gamestate, move);
+309         break;
+310     case KING:
+311         chkrules = king_chkrules(gamestate, move) &&
+312             !king_isblocked(gamestate, move);
+313         break;
+314     default:
+315         return INVALID_MOVE_SYNTAX;
+316     }
+317     
+318     return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
+319 }
+320 
+321 int validate_move(GameState *gamestate, Move *move) {
+322     
+323     int result = validate_move_rules(gamestate, move);
+324     
+325     /* cancel processing to save resources */
+326     if (result != VALID_MOVE_SEMANTICS) {
+327         return result;
+328     }
+329     
+330     /* find kings for check validation */
+331     uint8_t piececolor = (move->piece & COLOR_MASK);
+332     
+333     uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
+334     for (uint8_t row = 0 ; row < 8 ; row++) {
+335         for (uint8_t file = 0 ; file < 8 ; file++) {
+336             if (gamestate->board[row][file] ==
+337                     (piececolor == WHITE?WKING:BKING)) {
+338                 mykingfile = file;
+339                 mykingrow = row;
+340             } else if (gamestate->board[row][file] ==
+341                     (piececolor == WHITE?BKING:WKING)) {
+342                 opkingfile = file;
+343                 opkingrow = row;
+344             }
+345         }
+346     }
+347     
+348     /* simulate move for check validation */
+349     GameState simulation = gamestate_copy_sim(gamestate);
+350     Move simmove = *move;
+351     apply_move_impl(&simulation, &simmove, 1);
+352     
+353     /* don't move into or stay in check position */
+354     if (is_covered(&simulation, mykingrow, mykingfile,
+355         opponent_color(piececolor))) {
+356         
+357         gamestate_cleanup(&simulation);
+358         if ((move->piece & PIECE_MASK) == KING) {
+359             return KING_MOVES_INTO_CHECK;
+360         } else {
+361             /* last move is always not null in this case */
+362             return gamestate->lastmove->move.check ?
+363                 KING_IN_CHECK : PIECE_PINNED;
+364         }
+365     }
+366     
+367     /* correct check and checkmate flags (move is still valid) */
+368     Move threats[16];
+369     uint8_t threatcount;
+370     move->check = get_threats(&simulation, opkingrow, opkingfile,
+371         piececolor, threats, &threatcount);
+372     
+373     if (move->check) {
+374         /* determine possible escape fields */
+375         _Bool canescape = 0;
+376         for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
+377             for (int df = -1 ; df <= 1 && !canescape ; df++) {
+378                 if (!(dr == 0 && df == 0)  &&
+379                         isidx(opkingrow + dr) && isidx(opkingfile + df)) {
+380                     
+381                     /* escape field neither blocked nor covered */
+382                     if ((simulation.board[opkingrow + dr][opkingfile + df]
+383                             & COLOR_MASK) != opponent_color(piececolor)) {
+384                         canescape |= !is_covered(&simulation,
+385                             opkingrow + dr, opkingfile + df, piececolor);
+386                     }
+387                 }
+388             }
+389         }
+390         /* can't escape, can he capture? */
+391         if (!canescape && threatcount == 1) {
+392             canescape = is_attacked(&simulation, threats[0].fromrow,
+393                 threats[0].fromfile, opponent_color(piececolor));
+394         }
+395         
+396         /* can't capture, can he block? */
+397         if (!canescape && threatcount == 1) {
+398             Move *threat = &(threats[0]);
+399             uint8_t threatpiece = threat->piece & PIECE_MASK;
+400             
+401             /* knight, pawns and the king cannot be blocked */
+402             if (threatpiece == BISHOP || threatpiece == ROOK
+403                 || threatpiece == QUEEN) {
+404                 if (threat->fromrow == threat->torow) {
+405                     /* rook aspect (on row) */
+406                     int d = threat->tofile > threat->fromfile ? 1 : -1;
+407                     uint8_t file = threat->fromfile;
+408                     while (!canescape && file != threat->tofile - d) {
+409                         file += d;
+410                         canescape |= is_protected(&simulation,
+411                             threat->torow, file, opponent_color(piececolor));
+412                     }
+413                 } else if (threat->fromfile == threat->tofile) {
+414                     /* rook aspect (on file) */
+415                     int d = threat->torow > threat->fromrow ? 1 : -1;
+416                     uint8_t row = threat->fromrow;
+417                     while (!canescape && row != threat->torow - d) {
+418                         row += d;
+419                         canescape |= is_protected(&simulation,
+420                             row, threat->tofile, opponent_color(piececolor));
+421                     }
+422                 } else {
+423                     /* bishop aspect */
+424                     int dr = threat->torow > threat->fromrow ? 1 : -1;
+425                     int df = threat->tofile > threat->fromfile ? 1 : -1;
+426 
+427                     uint8_t row = threat->fromrow;
+428                     uint8_t file = threat->fromfile;
+429                     while (!canescape && file != threat->tofile - df
+430                         && row != threat->torow - dr) {
+431                         row += dr;
+432                         file += df;
+433                         canescape |= is_protected(&simulation, row, file,
+434                             opponent_color(piececolor));
+435                     }
+436                 }
+437             }
+438         }
+439             
+440         if (!canescape) {
+441             gamestate->checkmate = 1;
+442         }
+443     }
+444     
+445     gamestate_cleanup(&simulation);
+446     
+447     return VALID_MOVE_SEMANTICS;
+448 }
+449 
+450 _Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+451         uint8_t color, Move *threats, uint8_t *threatcount) {
+452     Move candidates[32];
+453     int candidatecount = 0;
+454     for (uint8_t r = 0 ; r < 8 ; r++) {
+455         for (uint8_t f = 0 ; f < 8 ; f++) {
+456             if ((gamestate->board[r][f] & COLOR_MASK) == color) {
+457                 // non-capturing move
+458                 memset(&(candidates[candidatecount]), 0, sizeof(Move));
+459                 candidates[candidatecount].piece = gamestate->board[r][f];
+460                 candidates[candidatecount].fromrow = r;
+461                 candidates[candidatecount].fromfile = f;
+462                 candidates[candidatecount].torow = row;
+463                 candidates[candidatecount].tofile = file;
+464                 candidatecount++;
+465 
+466                 // capturing move
+467                 memcpy(&(candidates[candidatecount]),
+468                     &(candidates[candidatecount-1]), sizeof(Move));
+469                 candidates[candidatecount].capture = 1;
+470                 candidatecount++;
+471             }
+472         }
+473     }
+474 
+475     if (threatcount) {
+476         *threatcount = 0;
+477     }
+478     
+479     
+480     _Bool result = 0;
+481     
+482     for (int i = 0 ; i < candidatecount ; i++) {
+483         if (validate_move_rules(gamestate, &(candidates[i]))
+484                 == VALID_MOVE_SEMANTICS) {
+485             result = 1;
+486             if (threats && threatcount) {
+487                 threats[(*threatcount)++] = candidates[i];
+488             }
+489         }
+490     }
+491     
+492     return result;
+493 }
+494 
+495 _Bool is_pinned(GameState *gamestate, Move *move) {
+496     uint8_t color = move->piece & COLOR_MASK;
+497 
+498     uint8_t kingfile = 0, kingrow = 0;
+499     for (uint8_t row = 0 ; row < 8 ; row++) {
+500         for (uint8_t file = 0 ; file < 8 ; file++) {
+501             if (gamestate->board[row][file] == (color|KING)) {
+502                 kingfile = file;
+503                 kingrow = row;
+504             }
+505         }
+506     }
+507 
+508     GameState simulation = gamestate_copy_sim(gamestate);
+509     Move simmove = *move;
+510     apply_move(&simulation, &simmove);
+511     _Bool covered = is_covered(&simulation,
+512         kingrow, kingfile, opponent_color(color));
+513     gamestate_cleanup(&simulation);
+514     
+515     return covered;
+516 }
+517 
+518 _Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
+519         uint8_t color, Move *threats, uint8_t *threatcount) {
+520     
+521     if (threatcount) {
+522         *threatcount = 0;
+523     }
+524 
+525     Move candidates[16];
+526     uint8_t candidatecount;
+527     if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
+528         
+529         _Bool result = 0;
+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 (gamestate->board[row][file] == (color|KING)) {
+534                     kingfile = file;
+535                     kingrow = row;
+536                 }
+537             }
+538         }
+539 
+540         for (uint8_t i = 0 ; i < candidatecount ; i++) {
+541             GameState simulation = gamestate_copy_sim(gamestate);
+542             Move simmove = candidates[i];
+543             apply_move(&simulation, &simmove);
+544             if (!is_covered(&simulation, kingrow, kingfile,
+545                     opponent_color(color))) {
+546                 result = 1;
+547                 if (threats && threatcount) {
+548                     threats[(*threatcount)++] = candidates[i];
+549                 }
+550             }
+551         }
+552         
+553         return result;
+554     } else {
+555         return 0;
+556     }
+557 }
+558 
+559 static int getlocation(GameState *gamestate, Move *move) {   
+560 
+561     uint8_t color = move->piece & COLOR_MASK;
+562     _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
+563     
+564     Move threats[16], *threat = NULL;
+565     uint8_t threatcount;
+566     
+567     if (get_threats(gamestate, move->torow, move->tofile, color,
+568             threats, &threatcount)) {
+569         
+570         int reason = INVALID_POSITION;
+571         
+572         // find threats for the specified position
+573         for (uint8_t i = 0 ; i < threatcount ; i++) {
+574             if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
+575                     == move->piece &&
+576                     (move->fromrow == POS_UNSPECIFIED ||
+577                     move->fromrow == threats[i].fromrow) &&
+578                     (move->fromfile == POS_UNSPECIFIED ||
+579                     move->fromfile == threats[i].fromfile)) {
+580 
+581                 if (threat) {
+582                     return AMBIGUOUS_MOVE;
+583                 } else {
+584                     // found threat is no real threat
+585                     if (is_pinned(gamestate, &(threats[i]))) {
+586                         reason = incheck?KING_IN_CHECK:PIECE_PINNED;
+587                     } else {
+588                         threat = &(threats[i]);
+589                     }
+590                 }
+591             }
+592         }
+593         
+594         // can't threaten specified position
+595         if (!threat) {
+596             return reason;
+597         }
+598 
+599         memcpy(move, threat, sizeof(Move));
+600         return VALID_MOVE_SYNTAX;
+601     } else {
+602         return INVALID_POSITION;
+603     }
+604 }
+605 
+606 int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
+607     memset(move, 0, sizeof(Move));
+608     move->fromfile = POS_UNSPECIFIED;
+609     move->fromrow = POS_UNSPECIFIED;
+610 
+611     size_t len = strlen(mstr);
+612     if (len < 1 || len > 6) {
+613         return INVALID_MOVE_SYNTAX;
+614     }
+615     
+616     /* evaluate check/checkmate flags */
+617     if (mstr[len-1] == '+') {
+618         len--; mstr[len] = '\0';
+619         move->check = 1;
+620     } else if (mstr[len-1] == '#') {
+621         len--; mstr[len] = '\0';
+622         /* ignore - validation should set game state */
+623     }
+624     
+625     /* evaluate promotion */
+626     if (len > 3 && mstr[len-2] == '=') {
+627         move->promotion = getpiece(mstr[len-1]);
+628         if (!move->promotion) {
+629             return INVALID_MOVE_SYNTAX;
+630         } else {
+631             move->promotion |= color;
+632             len -= 2;
+633             mstr[len] = 0;
+634         }
+635     }
+636     
+637     if (len == 2) {
+638         /* pawn move (e.g. "e4") */
+639         move->piece = PAWN;
+640         move->tofile = fileidx(mstr[0]);
+641         move->torow = rowidx(mstr[1]);
+642     } else if (len == 3) {
+643         if (strcmp(mstr, "O-O") == 0) {
+644             /* king side castling */
+645             move->piece = KING;
+646             move->fromfile = fileidx('e');
+647             move->tofile = fileidx('g');
+648             move->fromrow = move->torow = color == WHITE ? 0 : 7;
+649         } else {
+650             /* move (e.g. "Nf3") */
+651             move->piece = getpiece(mstr[0]);
+652             move->tofile = fileidx(mstr[1]);
+653             move->torow = rowidx(mstr[2]);
+654         }
+655     } else if (len == 4) {
+656         move->piece = getpiece(mstr[0]);
+657         if (!move->piece) {
+658             move->piece = PAWN;
+659             move->fromfile = fileidx(mstr[0]);
+660         }
+661         if (mstr[1] == 'x') {
+662             /* capture (e.g. "Nxf3", "dxe5") */
+663             move->capture = 1;
+664         } else {
+665             /* move (e.g. "Ndf3", "N2c3", "e2e4") */
+666             if (isfile(mstr[1])) {
+667                 move->fromfile = fileidx(mstr[1]);
+668                 if (move->piece == PAWN) {
+669                     move->piece = 0;
+670                 }
+671             } else {
+672                 move->fromrow = rowidx(mstr[1]);
+673             }
+674         }
+675         move->tofile = fileidx(mstr[2]);
+676         move->torow = rowidx(mstr[3]);
+677     } else if (len == 5) {
+678         if (strcmp(mstr, "O-O-O") == 0) {
+679             /* queen side castling "O-O-O" */
+680             move->piece = KING;
+681             move->fromfile = fileidx('e');
+682             move->tofile = fileidx('c');
+683             move->fromrow = move->torow = color == WHITE ? 0 : 7;
+684         } else {
+685             move->piece = getpiece(mstr[0]);
+686             if (mstr[2] == 'x') {
+687                 move->capture = 1;
+688                 if (move->piece) {
+689                     /* capture (e.g. "Ndxf3") */
+690                     move->fromfile = fileidx(mstr[1]);
+691                 } else {
+692                     /* long notation capture (e.g. "e5xf6") */
+693                     move->piece = PAWN;
+694                     move->fromfile = fileidx(mstr[0]);
+695                     move->fromrow = rowidx(mstr[1]);
+696                 }
+697             } else {
+698                 /* long notation move (e.g. "Nc5a4") */
+699                 move->fromfile = fileidx(mstr[1]);
+700                 move->fromrow = rowidx(mstr[2]);
+701             }
+702             move->tofile = fileidx(mstr[3]);
+703             move->torow = rowidx(mstr[4]);
+704         }
+705     } else if (len == 6) {
+706         /* long notation capture (e.g. "Nc5xf3") */
+707         if (mstr[3] == 'x') {
+708             move->capture = 1;
+709             move->piece = getpiece(mstr[0]);
+710             move->fromfile = fileidx(mstr[1]);
+711             move->fromrow = rowidx(mstr[2]);
+712             move->tofile = fileidx(mstr[4]);
+713             move->torow = rowidx(mstr[5]);
+714         }
+715     }
+716 
+717     
+718     if (move->piece) {
+719         if (move->piece == PAWN
+720             && move->torow == (color==WHITE?7:0)
+721             && !move->promotion) {
+722             return NEED_PROMOTION;
+723         }
+724         
+725         move->piece |= color;
+726         if (move->fromfile == POS_UNSPECIFIED
+727             || move->fromrow == POS_UNSPECIFIED) {
+728             return getlocation(gamestate, move);
+729         } else {
+730             return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
+731         }
+732     } else {
+733         return INVALID_MOVE_SYNTAX;
+734     }
+735 }
+736 
+737 _Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
+738         uint8_t color) {
+739     
+740     Move threats[16];
+741     uint8_t threatcount;
+742     if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
+743         for (int i = 0 ; i < threatcount ; i++) {
+744             if (threats[i].piece != (color|KING)) {
+745                 return 1;
+746             }
+747         }
+748         return 0;
+749     } else {
+750         return 0;
+751     }
+752 }
+753 
+754 uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
+755         uint8_t color) {
+756     if (!gameinfo->timecontrol) {
+757         return 0;
+758     }
+759     
+760     if (gamestate->movelist) {
+761         uint16_t time = gameinfo->time;
+762         suseconds_t micros = 0;
+763         
+764         MoveList *movelist = color == WHITE ?
+765             gamestate->movelist : gamestate->movelist->next;
+766         
+767         while (movelist) {
+768             time += gameinfo->addtime;
+769             
+770             struct movetimeval *movetime = &(movelist->move.movetime);
+771             if (movetime->tv_sec >= time) {
+772                 return 0;
+773             }
+774             
+775             time -= movetime->tv_sec;
+776             micros += movetime->tv_usec;
+777             
+778             movelist = movelist->next ? movelist->next->next : NULL;
+779         }
+780         
+781         time_t sec;
+782         movelist = gamestate->lastmove;
+783         if ((movelist->move.piece & COLOR_MASK) != color) {
+784             struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
+785             struct timeval currenttstamp;
+786             gettimeofday(¤ttstamp, NULL);
+787             micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
+788             sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
+789             if (sec >= time) {
+790                 return 0;
+791             }
+792             
+793             time -= sec;
+794         }
+795         
+796         sec = micros / 1e6L;
+797         
+798         if (sec >= time) {
+799             return 0;
+800         }
+801 
+802         time -= sec;
+803         
+804         return time;
+805     } else {
+806         return gameinfo->time;
+807     }
+808 }
+
+ + +