src/chess/rules.c

Wed, 11 Jun 2014 16:54:20 +0200

author
Mike Becker <universe@uap-core.de>
date
Wed, 11 Jun 2014 16:54:20 +0200
changeset 49
02c509a44e98
parent 48
0cedda2544da
child 50
41017d0a72c5
permissions
-rw-r--r--

logging string representation of moves in short algebraic notation

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

mercurial