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(&currenttstamp, 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 }