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