test/bigtest.c

changeset 67
5da2cb5aea6b
parent 65
7dd4fd1e7071
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/bigtest.c	Mon Apr 24 21:01:41 2023 +0200
@@ -0,0 +1,808 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2014 Mike Becker. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "rules.h"
+#include "chess.h"
+#include <string.h>
+#include <stdlib.h>
+#include <sys/time.h>
+
+static GameState gamestate_copy_sim(GameState *gamestate) {
+    GameState simulation = *gamestate;
+    if (simulation.lastmove) {
+        MoveList *lastmovecopy = malloc(sizeof(MoveList));
+        *lastmovecopy = *(simulation.lastmove);
+        simulation.movelist = simulation.lastmove = lastmovecopy;
+    }
+
+    return simulation;
+}
+
+void gamestate_init(GameState *gamestate) {
+    memset(gamestate, 0, sizeof(GameState));
+    
+    Board initboard = {
+        {WROOK, WKNIGHT, WBISHOP, WQUEEN, WKING, WBISHOP, WKNIGHT, WROOK},
+        {WPAWN, WPAWN,   WPAWN,   WPAWN,  WPAWN, WPAWN,   WPAWN,   WPAWN},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {0,     0,       0,       0,      0,     0,       0,       0},
+        {BPAWN, BPAWN,   BPAWN,   BPAWN,  BPAWN, BPAWN,   BPAWN,   BPAWN},
+        {BROOK, BKNIGHT, BBISHOP, BQUEEN, BKING, BBISHOP, BKNIGHT, BROOK}
+    };
+    memcpy(gamestate->board, initboard, sizeof(Board));
+}
+
+void gamestate_cleanup(GameState *gamestate) {
+    MoveList *elem;
+    elem = gamestate->movelist;
+    while (elem) {
+        MoveList *cur = elem;
+        elem = elem->next;
+        free(cur);
+    };
+}
+
+/* MUST be called IMMEDIATLY after applying a move to work correctly */
+static void format_move(GameState *gamestate, Move *move) {
+    char *string = move->string;
+    
+    /* at least 8 characters should be available, wipe them out */
+    memset(string, 0, 8);
+    
+    /* special formats for castling */
+    if ((move->piece&PIECE_MASK) == KING &&
+            abs(move->tofile-move->fromfile) == 2) {
+        if (move->tofile==fileidx('c')) {
+            memcpy(string, "O-O-O", 5);
+        } else {
+            memcpy(string, "O-O", 3);
+        }
+    }
+
+    /* start by notating the piece character */
+    string[0] = getpiecechr(move->piece);
+    int idx = string[0] ? 1 : 0;
+    
+    /* find out how many source information we do need */
+    uint8_t piece = move->piece & PIECE_MASK;
+    if (piece == PAWN) {
+        if (move->capture) {
+            string[idx++] = filechr(move->fromfile);
+        }
+    } else if (piece != KING) {
+        Move threats[16];
+        uint8_t threatcount;
+        get_real_threats(gamestate, move->torow, move->tofile,
+            move->piece&COLOR_MASK, threats, &threatcount);
+        if (threatcount > 1) {
+            int ambrows = 0, ambfiles = 0;
+            for (uint8_t i = 0 ; i < threatcount ; i++) {
+                if (threats[i].fromrow == move->fromrow) {
+                    ambrows++;
+                }
+                if (threats[i].fromfile == move->fromfile) {
+                    ambfiles++;
+                }
+            }
+            /* ambiguous row, name file */
+            if (ambrows > 1) {
+                string[idx++] = filechr(move->fromfile);
+            }
+            /* ambiguous file, name row */
+            if (ambfiles > 1) {
+                string[idx++] = filechr(move->fromrow);
+            }
+        }
+    }
+    
+    /* capturing? */
+    if (move->capture) {
+        string[idx++] = 'x';
+    }
+    
+    /* destination */
+    string[idx++] = filechr(move->tofile);
+    string[idx++] = rowchr(move->torow);
+    
+    /* promotion? */
+    if (move->promotion) {
+        string[idx++] = '=';
+        string[idx++] = getpiecechr(move->promotion);
+    }
+    
+    /* check? */
+    if (move->check) {
+        /* works only, if this function is called when applying the move */
+        string[idx++] = gamestate->checkmate?'#':'+';
+    }
+}
+
+static void addmove(GameState* gamestate, Move *move) {
+    MoveList *elem = malloc(sizeof(MoveList));
+    elem->next = NULL;
+    elem->move = *move;
+    
+    struct timeval curtimestamp;
+    gettimeofday(&curtimestamp, NULL);
+    elem->move.timestamp.tv_sec = curtimestamp.tv_sec;
+    elem->move.timestamp.tv_usec = curtimestamp.tv_usec;
+    
+    if (gamestate->lastmove) {
+        struct movetimeval *lasttstamp = &(gamestate->lastmove->move.timestamp);
+        uint64_t sec = curtimestamp.tv_sec - lasttstamp->tv_sec;
+        suseconds_t micros;
+        if (curtimestamp.tv_usec < lasttstamp->tv_usec) {
+            micros = 1e6L-(lasttstamp->tv_usec - curtimestamp.tv_usec);
+            sec--;
+        } else {
+            micros = curtimestamp.tv_usec - lasttstamp->tv_usec;
+        }
+        
+        elem->move.movetime.tv_sec = sec;
+        elem->move.movetime.tv_usec = micros;
+        
+        gamestate->lastmove->next = elem;
+        gamestate->lastmove = elem;
+    } else {
+        elem->move.movetime.tv_usec = 0;
+        elem->move.movetime.tv_sec = 0;
+        gamestate->movelist = gamestate->lastmove = elem;
+    }
+}
+
+char getpiecechr(uint8_t piece) {
+    switch (piece & PIECE_MASK) {
+    case ROOK: return 'R';
+    case KNIGHT: return 'N';
+    case BISHOP: return 'B';
+    case QUEEN: return 'Q';
+    case KING: return 'K';
+    default: return '\0';
+    }
+}
+
+uint8_t getpiece(char c) {
+    switch (c) {
+        case 'R': return ROOK;
+        case 'N': return KNIGHT;
+        case 'B': return BISHOP;
+        case 'Q': return QUEEN;
+        case 'K': return KING;
+        default: return 0;
+    }
+}
+
+static void apply_move_impl(GameState *gamestate, Move *move, _Bool simulate) {
+    uint8_t piece = move->piece & PIECE_MASK;
+    uint8_t color = move->piece & COLOR_MASK;
+    
+    /* en passant capture */
+    if (move->capture && piece == PAWN &&
+        mdst(gamestate->board, move) == 0) {
+        gamestate->board[move->fromrow][move->tofile] = 0;
+    }
+    
+    /* remove old en passant threats */
+    for (uint8_t file = 0 ; file < 8 ; file++) {
+        gamestate->board[3][file] &= ~ENPASSANT_THREAT;
+        gamestate->board[4][file] &= ~ENPASSANT_THREAT;
+    }
+    
+    /* add new en passant threat */
+    if (piece == PAWN && (
+        (move->fromrow == 1 && move->torow == 3) ||
+        (move->fromrow == 6 && move->torow == 4))) {
+        move->piece |= ENPASSANT_THREAT;
+    }
+    
+    /* move (and maybe capture or promote) */
+    msrc(gamestate->board, move) = 0;
+    if (move->promotion) {
+        mdst(gamestate->board, move) = move->promotion;
+    } else {
+        mdst(gamestate->board, move) = move->piece;
+    }
+    
+    /* castling */
+    if (piece == KING && move->fromfile == fileidx('e')) {
+        
+        if (move->tofile == fileidx('g')) {
+            gamestate->board[move->torow][fileidx('h')] = 0;
+            gamestate->board[move->torow][fileidx('f')] = color|ROOK;
+        } else if (move->tofile == fileidx('c')) {
+            gamestate->board[move->torow][fileidx('a')] = 0;
+            gamestate->board[move->torow][fileidx('d')] = color|ROOK;
+        }
+    }
+
+    if (!simulate) {
+        if (!move->string[0]) {
+            format_move(gamestate, move);
+        }
+    }
+    /* add move, even in simulation (checkmate test needs it) */
+    addmove(gamestate, move);
+}
+
+void apply_move(GameState *gamestate, Move *move) {
+    apply_move_impl(gamestate, move, 0);
+}
+
+static int validate_move_rules(GameState *gamestate, Move *move) {
+    /* validate indices (don't trust opponent) */
+    if (!chkidx(move)) {
+        return INVALID_POSITION;
+    }
+    
+    /* must move */
+    if (move->fromfile == move->tofile && move->fromrow == move->torow) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* does piece exist */
+    if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK))
+           != (move->piece&(PIECE_MASK|COLOR_MASK))) {
+        return INVALID_POSITION;
+    }
+    
+    /* can't capture own pieces */
+    if ((mdst(gamestate->board, move) & COLOR_MASK)
+            == (move->piece & COLOR_MASK)) {
+        return RULES_VIOLATED;
+    }
+    
+    /* must capture, if and only if destination is occupied */
+    if ((mdst(gamestate->board, move) == 0 && move->capture) ||
+            (mdst(gamestate->board, move) != 0 && !move->capture)) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* validate individual rules */
+    _Bool chkrules;
+    switch (move->piece & PIECE_MASK) {
+    case PAWN:
+        chkrules = pawn_chkrules(gamestate, move) &&
+            !pawn_isblocked(gamestate, move);
+        break;
+    case ROOK:
+        chkrules = rook_chkrules(move) &&
+            !rook_isblocked(gamestate, move);
+        break;
+    case KNIGHT:
+        chkrules = knight_chkrules(move); /* knight is never blocked */
+        break;
+    case BISHOP:
+        chkrules = bishop_chkrules(move) &&
+            !bishop_isblocked(gamestate, move);
+        break;
+    case QUEEN:
+        chkrules = queen_chkrules(move) &&
+            !queen_isblocked(gamestate, move);
+        break;
+    case KING:
+        chkrules = king_chkrules(gamestate, move) &&
+            !king_isblocked(gamestate, move);
+        break;
+    default:
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED;
+}
+
+int validate_move(GameState *gamestate, Move *move) {
+    
+    int result = validate_move_rules(gamestate, move);
+    
+    /* cancel processing to save resources */
+    if (result != VALID_MOVE_SEMANTICS) {
+        return result;
+    }
+    
+    /* find kings for check validation */
+    uint8_t piececolor = (move->piece & COLOR_MASK);
+    
+    uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0;
+    for (uint8_t row = 0 ; row < 8 ; row++) {
+        for (uint8_t file = 0 ; file < 8 ; file++) {
+            if (gamestate->board[row][file] ==
+                    (piececolor == WHITE?WKING:BKING)) {
+                mykingfile = file;
+                mykingrow = row;
+            } else if (gamestate->board[row][file] ==
+                    (piececolor == WHITE?BKING:WKING)) {
+                opkingfile = file;
+                opkingrow = row;
+            }
+        }
+    }
+    
+    /* simulate move for check validation */
+    GameState simulation = gamestate_copy_sim(gamestate);
+    Move simmove = *move;
+    apply_move_impl(&simulation, &simmove, 1);
+    
+    /* don't move into or stay in check position */
+    if (is_covered(&simulation, mykingrow, mykingfile,
+        opponent_color(piececolor))) {
+        
+        gamestate_cleanup(&simulation);
+        if ((move->piece & PIECE_MASK) == KING) {
+            return KING_MOVES_INTO_CHECK;
+        } else {
+            /* last move is always not null in this case */
+            return gamestate->lastmove->move.check ?
+                KING_IN_CHECK : PIECE_PINNED;
+        }
+    }
+    
+    /* correct check and checkmate flags (move is still valid) */
+    Move threats[16];
+    uint8_t threatcount;
+    move->check = get_threats(&simulation, opkingrow, opkingfile,
+        piececolor, threats, &threatcount);
+    
+    if (move->check) {
+        /* determine possible escape fields */
+        _Bool canescape = 0;
+        for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
+            for (int df = -1 ; df <= 1 && !canescape ; df++) {
+                if (!(dr == 0 && df == 0)  &&
+                        isidx(opkingrow + dr) && isidx(opkingfile + df)) {
+                    
+                    /* escape field neither blocked nor covered */
+                    if ((simulation.board[opkingrow + dr][opkingfile + df]
+                            & COLOR_MASK) != opponent_color(piececolor)) {
+                        canescape |= !is_covered(&simulation,
+                            opkingrow + dr, opkingfile + df, piececolor);
+                    }
+                }
+            }
+        }
+        /* can't escape, can he capture? */
+        if (!canescape && threatcount == 1) {
+            canescape = is_attacked(&simulation, threats[0].fromrow,
+                threats[0].fromfile, opponent_color(piececolor));
+        }
+        
+        /* can't capture, can he block? */
+        if (!canescape && threatcount == 1) {
+            Move *threat = &(threats[0]);
+            uint8_t threatpiece = threat->piece & PIECE_MASK;
+            
+            /* knight, pawns and the king cannot be blocked */
+            if (threatpiece == BISHOP || threatpiece == ROOK
+                || threatpiece == QUEEN) {
+                if (threat->fromrow == threat->torow) {
+                    /* rook aspect (on row) */
+                    int d = threat->tofile > threat->fromfile ? 1 : -1;
+                    uint8_t file = threat->fromfile;
+                    while (!canescape && file != threat->tofile - d) {
+                        file += d;
+                        canescape |= is_protected(&simulation,
+                            threat->torow, file, opponent_color(piececolor));
+                    }
+                } else if (threat->fromfile == threat->tofile) {
+                    /* rook aspect (on file) */
+                    int d = threat->torow > threat->fromrow ? 1 : -1;
+                    uint8_t row = threat->fromrow;
+                    while (!canescape && row != threat->torow - d) {
+                        row += d;
+                        canescape |= is_protected(&simulation,
+                            row, threat->tofile, opponent_color(piececolor));
+                    }
+                } else {
+                    /* bishop aspect */
+                    int dr = threat->torow > threat->fromrow ? 1 : -1;
+                    int df = threat->tofile > threat->fromfile ? 1 : -1;
+
+                    uint8_t row = threat->fromrow;
+                    uint8_t file = threat->fromfile;
+                    while (!canescape && file != threat->tofile - df
+                        && row != threat->torow - dr) {
+                        row += dr;
+                        file += df;
+                        canescape |= is_protected(&simulation, row, file,
+                            opponent_color(piececolor));
+                    }
+                }
+            }
+        }
+            
+        if (!canescape) {
+            gamestate->checkmate = 1;
+        }
+    }
+    
+    gamestate_cleanup(&simulation);
+    
+    return VALID_MOVE_SEMANTICS;
+}
+
+_Bool get_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+    Move candidates[32];
+    int candidatecount = 0;
+    for (uint8_t r = 0 ; r < 8 ; r++) {
+        for (uint8_t f = 0 ; f < 8 ; f++) {
+            if ((gamestate->board[r][f] & COLOR_MASK) == color) {
+                // non-capturing move
+                memset(&(candidates[candidatecount]), 0, sizeof(Move));
+                candidates[candidatecount].piece = gamestate->board[r][f];
+                candidates[candidatecount].fromrow = r;
+                candidates[candidatecount].fromfile = f;
+                candidates[candidatecount].torow = row;
+                candidates[candidatecount].tofile = file;
+                candidatecount++;
+
+                // capturing move
+                memcpy(&(candidates[candidatecount]),
+                    &(candidates[candidatecount-1]), sizeof(Move));
+                candidates[candidatecount].capture = 1;
+                candidatecount++;
+            }
+        }
+    }
+
+    if (threatcount) {
+        *threatcount = 0;
+    }
+    
+    
+    _Bool result = 0;
+    
+    for (int i = 0 ; i < candidatecount ; i++) {
+        if (validate_move_rules(gamestate, &(candidates[i]))
+                == VALID_MOVE_SEMANTICS) {
+            result = 1;
+            if (threats && threatcount) {
+                threats[(*threatcount)++] = candidates[i];
+            }
+        }
+    }
+    
+    return result;
+}
+
+_Bool is_pinned(GameState *gamestate, Move *move) {
+    uint8_t color = move->piece & COLOR_MASK;
+
+    uint8_t kingfile = 0, kingrow = 0;
+    for (uint8_t row = 0 ; row < 8 ; row++) {
+        for (uint8_t file = 0 ; file < 8 ; file++) {
+            if (gamestate->board[row][file] == (color|KING)) {
+                kingfile = file;
+                kingrow = row;
+            }
+        }
+    }
+
+    GameState simulation = gamestate_copy_sim(gamestate);
+    Move simmove = *move;
+    apply_move(&simulation, &simmove);
+    _Bool covered = is_covered(&simulation,
+        kingrow, kingfile, opponent_color(color));
+    gamestate_cleanup(&simulation);
+    
+    return covered;
+}
+
+_Bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color, Move *threats, uint8_t *threatcount) {
+    
+    if (threatcount) {
+        *threatcount = 0;
+    }
+
+    Move candidates[16];
+    uint8_t candidatecount;
+    if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
+        
+        _Bool result = 0;
+        uint8_t kingfile = 0, kingrow = 0;
+        for (uint8_t row = 0 ; row < 8 ; row++) {
+            for (uint8_t file = 0 ; file < 8 ; file++) {
+                if (gamestate->board[row][file] == (color|KING)) {
+                    kingfile = file;
+                    kingrow = row;
+                }
+            }
+        }
+
+        for (uint8_t i = 0 ; i < candidatecount ; i++) {
+            GameState simulation = gamestate_copy_sim(gamestate);
+            Move simmove = candidates[i];
+            apply_move(&simulation, &simmove);
+            if (!is_covered(&simulation, kingrow, kingfile,
+                    opponent_color(color))) {
+                result = 1;
+                if (threats && threatcount) {
+                    threats[(*threatcount)++] = candidates[i];
+                }
+            }
+        }
+        
+        return result;
+    } else {
+        return 0;
+    }
+}
+
+static int getlocation(GameState *gamestate, Move *move) {   
+
+    uint8_t color = move->piece & COLOR_MASK;
+    _Bool incheck = gamestate->lastmove?gamestate->lastmove->move.check:0;
+    
+    Move threats[16], *threat = NULL;
+    uint8_t threatcount;
+    
+    if (get_threats(gamestate, move->torow, move->tofile, color,
+            threats, &threatcount)) {
+        
+        int reason = INVALID_POSITION;
+        
+        // find threats for the specified position
+        for (uint8_t i = 0 ; i < threatcount ; i++) {
+            if ((threats[i].piece & (PIECE_MASK | COLOR_MASK))
+                    == move->piece &&
+                    (move->fromrow == POS_UNSPECIFIED ||
+                    move->fromrow == threats[i].fromrow) &&
+                    (move->fromfile == POS_UNSPECIFIED ||
+                    move->fromfile == threats[i].fromfile)) {
+
+                if (threat) {
+                    return AMBIGUOUS_MOVE;
+                } else {
+                    // found threat is no real threat
+                    if (is_pinned(gamestate, &(threats[i]))) {
+                        reason = incheck?KING_IN_CHECK:PIECE_PINNED;
+                    } else {
+                        threat = &(threats[i]);
+                    }
+                }
+            }
+        }
+        
+        // can't threaten specified position
+        if (!threat) {
+            return reason;
+        }
+
+        memcpy(move, threat, sizeof(Move));
+        return VALID_MOVE_SYNTAX;
+    } else {
+        return INVALID_POSITION;
+    }
+}
+
+int eval_move(GameState *gamestate, char *mstr, Move *move, uint8_t color) {
+    memset(move, 0, sizeof(Move));
+    move->fromfile = POS_UNSPECIFIED;
+    move->fromrow = POS_UNSPECIFIED;
+
+    size_t len = strlen(mstr);
+    if (len < 1 || len > 6) {
+        return INVALID_MOVE_SYNTAX;
+    }
+    
+    /* evaluate check/checkmate flags */
+    if (mstr[len-1] == '+') {
+        len--; mstr[len] = '\0';
+        move->check = 1;
+    } else if (mstr[len-1] == '#') {
+        len--; mstr[len] = '\0';
+        /* ignore - validation should set game state */
+    }
+    
+    /* evaluate promotion */
+    if (len > 3 && mstr[len-2] == '=') {
+        move->promotion = getpiece(mstr[len-1]);
+        if (!move->promotion) {
+            return INVALID_MOVE_SYNTAX;
+        } else {
+            move->promotion |= color;
+            len -= 2;
+            mstr[len] = 0;
+        }
+    }
+    
+    if (len == 2) {
+        /* pawn move (e.g. "e4") */
+        move->piece = PAWN;
+        move->tofile = fileidx(mstr[0]);
+        move->torow = rowidx(mstr[1]);
+    } else if (len == 3) {
+        if (strcmp(mstr, "O-O") == 0) {
+            /* king side castling */
+            move->piece = KING;
+            move->fromfile = fileidx('e');
+            move->tofile = fileidx('g');
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
+        } else {
+            /* move (e.g. "Nf3") */
+            move->piece = getpiece(mstr[0]);
+            move->tofile = fileidx(mstr[1]);
+            move->torow = rowidx(mstr[2]);
+        }
+    } else if (len == 4) {
+        move->piece = getpiece(mstr[0]);
+        if (!move->piece) {
+            move->piece = PAWN;
+            move->fromfile = fileidx(mstr[0]);
+        }
+        if (mstr[1] == 'x') {
+            /* capture (e.g. "Nxf3", "dxe5") */
+            move->capture = 1;
+        } else {
+            /* move (e.g. "Ndf3", "N2c3", "e2e4") */
+            if (isfile(mstr[1])) {
+                move->fromfile = fileidx(mstr[1]);
+                if (move->piece == PAWN) {
+                    move->piece = 0;
+                }
+            } else {
+                move->fromrow = rowidx(mstr[1]);
+            }
+        }
+        move->tofile = fileidx(mstr[2]);
+        move->torow = rowidx(mstr[3]);
+    } else if (len == 5) {
+        if (strcmp(mstr, "O-O-O") == 0) {
+            /* queen side castling "O-O-O" */
+            move->piece = KING;
+            move->fromfile = fileidx('e');
+            move->tofile = fileidx('c');
+            move->fromrow = move->torow = color == WHITE ? 0 : 7;
+        } else {
+            move->piece = getpiece(mstr[0]);
+            if (mstr[2] == 'x') {
+                move->capture = 1;
+                if (move->piece) {
+                    /* capture (e.g. "Ndxf3") */
+                    move->fromfile = fileidx(mstr[1]);
+                } else {
+                    /* long notation capture (e.g. "e5xf6") */
+                    move->piece = PAWN;
+                    move->fromfile = fileidx(mstr[0]);
+                    move->fromrow = rowidx(mstr[1]);
+                }
+            } else {
+                /* long notation move (e.g. "Nc5a4") */
+                move->fromfile = fileidx(mstr[1]);
+                move->fromrow = rowidx(mstr[2]);
+            }
+            move->tofile = fileidx(mstr[3]);
+            move->torow = rowidx(mstr[4]);
+        }
+    } else if (len == 6) {
+        /* long notation capture (e.g. "Nc5xf3") */
+        if (mstr[3] == 'x') {
+            move->capture = 1;
+            move->piece = getpiece(mstr[0]);
+            move->fromfile = fileidx(mstr[1]);
+            move->fromrow = rowidx(mstr[2]);
+            move->tofile = fileidx(mstr[4]);
+            move->torow = rowidx(mstr[5]);
+        }
+    }
+
+    
+    if (move->piece) {
+        if (move->piece == PAWN
+            && move->torow == (color==WHITE?7:0)
+            && !move->promotion) {
+            return NEED_PROMOTION;
+        }
+        
+        move->piece |= color;
+        if (move->fromfile == POS_UNSPECIFIED
+            || move->fromrow == POS_UNSPECIFIED) {
+            return getlocation(gamestate, move);
+        } else {
+            return chkidx(move) ? VALID_MOVE_SYNTAX : INVALID_POSITION;
+        }
+    } else {
+        return INVALID_MOVE_SYNTAX;
+    }
+}
+
+_Bool is_protected(GameState *gamestate, uint8_t row, uint8_t file,
+        uint8_t color) {
+    
+    Move threats[16];
+    uint8_t threatcount;
+    if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
+        for (int i = 0 ; i < threatcount ; i++) {
+            if (threats[i].piece != (color|KING)) {
+                return 1;
+            }
+        }
+        return 0;
+    } else {
+        return 0;
+    }
+}
+
+uint16_t remaining_movetime(GameInfo *gameinfo, GameState *gamestate,
+        uint8_t color) {
+    if (!gameinfo->timecontrol) {
+        return 0;
+    }
+    
+    if (gamestate->movelist) {
+        uint16_t time = gameinfo->time;
+        suseconds_t micros = 0;
+        
+        MoveList *movelist = color == WHITE ?
+            gamestate->movelist : gamestate->movelist->next;
+        
+        while (movelist) {
+            time += gameinfo->addtime;
+            
+            struct movetimeval *movetime = &(movelist->move.movetime);
+            if (movetime->tv_sec >= time) {
+                return 0;
+            }
+            
+            time -= movetime->tv_sec;
+            micros += movetime->tv_usec;
+            
+            movelist = movelist->next ? movelist->next->next : NULL;
+        }
+        
+        time_t sec;
+        movelist = gamestate->lastmove;
+        if ((movelist->move.piece & COLOR_MASK) != color) {
+            struct movetimeval *lastmovetstamp = &(movelist->move.timestamp);
+            struct timeval currenttstamp;
+            gettimeofday(&currenttstamp, NULL);
+            micros += currenttstamp.tv_usec - lastmovetstamp->tv_usec;
+            sec = currenttstamp.tv_sec - lastmovetstamp->tv_sec;
+            if (sec >= time) {
+                return 0;
+            }
+            
+            time -= sec;
+        }
+        
+        sec = micros / 1e6L;
+        
+        if (sec >= time) {
+            return 0;
+        }
+
+        time -= sec;
+        
+        return time;
+    } else {
+        return gameinfo->time;
+    }
+}

mercurial