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 }