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  }