src/chess/rules.c

changeset 160
f87832cba8b8
parent 158
52d452d0e7bf
equal deleted inserted replaced
159:07c7be5661f4 160:f87832cba8b8
38 38
39 #include <string.h> 39 #include <string.h>
40 #include <stdio.h> 40 #include <stdio.h>
41 #include <stdlib.h> 41 #include <stdlib.h>
42 #include <sys/time.h> 42 #include <sys/time.h>
43 #include <assert.h>
43 44
44 void gamestate_init(GameState *gamestate) { 45 void gamestate_init(GameState *gamestate) {
45 memset(gamestate, 0, sizeof(GameState)); 46 memset(gamestate, 0, sizeof(GameState));
46 47
47 // TODO: implement feature - game can be started from arbitrary position 48 // TODO: implement feature - game can be started from arbitrary position
99 } 100 }
100 101
101 return simulation; 102 return simulation;
102 } 103 }
103 104
104 uint8_t current_color(GameState *gamestate) { 105 Color current_color(GameState *gamestate) {
105 return (gamestate->movecount % 2 == 0) ? WHITE : BLACK; 106 return (gamestate->movecount % 2 == 0) ? WHITE : BLACK;
106 } 107 }
107 108
108 /* MUST be called BETWEEN validating AND applying a move to work correctly */ 109 /* MUST be called BETWEEN validating AND applying a move to work correctly */
109 static void format_move(GameState *gamestate, Move *move) { 110 static void format_move(GameState *gamestate, Move *move) {
111 112
112 /* at least 8 characters should be available, wipe them out */ 113 /* at least 8 characters should be available, wipe them out */
113 memset(string, 0, 8); 114 memset(string, 0, 8);
114 115
115 unsigned int idx; 116 unsigned int idx;
116 if ((move->piece&PIECE_MASK) == KING && 117 if (piece_type(move->piece) == KING &&
117 abs(move->tofile-move->fromfile) == 2) { 118 abs(move->tofile-move->fromfile) == 2) {
118 /* special formats for castling */ 119 /* special formats for castling */
119 if (move->tofile==fileidx('c')) { 120 if (move->tofile==fileidx('c')) {
120 memcpy(string, "O-O-O", 5); 121 memcpy(string, "O-O-O", 5);
121 idx = 5; 122 idx = 5;
127 /* start by notating the piece character */ 128 /* start by notating the piece character */
128 string[0] = getpiecechr(move->piece); 129 string[0] = getpiecechr(move->piece);
129 idx = string[0] ? 1 : 0; 130 idx = string[0] ? 1 : 0;
130 131
131 /* find out how many source information we do need */ 132 /* find out how many source information we do need */
132 uint8_t piece = move->piece & PIECE_MASK; 133 if (piece_type(move->piece) == PAWN) {
133 if (piece == PAWN) {
134 if (move->capture) { 134 if (move->capture) {
135 string[idx++] = filechr(move->fromfile); 135 string[idx++] = filechr(move->fromfile);
136 } 136 }
137 } else if (piece != KING) { 137 } else if (piece_type(move->piece) != KING) {
138 /* resolve ambiguities, if any */ 138 /* resolve ambiguities, if any */
139 Move threats[16]; 139 Move threats[16];
140 uint8_t threatcount; 140 size_t threatcount;
141 if (get_threats(gamestate, move->torow, move->tofile, 141 if (get_threats(gamestate, move->torow, move->tofile,
142 move->piece&COLOR_MASK, threats, &threatcount)) { 142 piece_color(move->piece), threats, &threatcount)) {
143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; 143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0;
144 for (uint8_t i = 0 ; i < threatcount ; i++) { 144 for (size_t i = 0 ; i < threatcount ; i++) {
145 if (threats[i].piece == move->piece) { 145 if (threats[i].piece == move->piece) {
146 ambpiece++; 146 ambpiece++;
147 if (threats[i].fromrow == move->fromrow) { 147 if (threats[i].fromrow == move->fromrow) {
148 ambrows++; 148 ambrows++;
149 } 149 }
220 move->movetime.tv_usec = (int32_t) micros; 220 move->movetime.tv_usec = (int32_t) micros;
221 } 221 }
222 } 222 }
223 } 223 }
224 224
225 char getpiecechr(uint8_t piece) { 225 char getpiecechr(Piece piece) {
226 switch (piece & PIECE_MASK) { 226 switch (piece_type(piece)) {
227 case ROOK: return 'R'; 227 case ROOK: return 'R';
228 case KNIGHT: return 'N'; 228 case KNIGHT: return 'N';
229 case BISHOP: return 'B'; 229 case BISHOP: return 'B';
230 case QUEEN: return 'Q'; 230 case QUEEN: return 'Q';
231 case KING: return 'K'; 231 case KING: return 'K';
232 default: return '\0'; 232 default: return '\0';
233 } 233 }
234 } 234 }
235 235
236 char* getpieceunicode(uint8_t piece) { 236 char* getpieceunicode(Piece piece) {
237 if ((piece & COLOR_MASK) == WHITE) { 237 if (piece_color(piece) == WHITE) {
238 switch (piece & PIECE_MASK) { 238 switch (piece_type(piece)) {
239 case PAWN: return "\u2659"; 239 case PAWN: return "\u2659";
240 case ROOK: return "\u2656"; 240 case ROOK: return "\u2656";
241 case KNIGHT: return "\u2658"; 241 case KNIGHT: return "\u2658";
242 case BISHOP: return "\u2657"; 242 case BISHOP: return "\u2657";
243 case QUEEN: return "\u2655"; 243 case QUEEN: return "\u2655";
244 case KING: return "\u2654"; 244 case KING: return "\u2654";
245 default: return ""; 245 default: return "";
246 } 246 }
247 } else { 247 } else {
248 switch (piece & PIECE_MASK) { 248 switch (piece_type(piece)) {
249 case PAWN: return "\u265f"; 249 case PAWN: return "\u265f";
250 case ROOK: return "\u265c"; 250 case ROOK: return "\u265c";
251 case KNIGHT: return "\u265e"; 251 case KNIGHT: return "\u265e";
252 case BISHOP: return "\u265d"; 252 case BISHOP: return "\u265d";
253 case QUEEN: return "\u265b"; 253 case QUEEN: return "\u265b";
255 default: return ""; 255 default: return "";
256 } 256 }
257 } 257 }
258 } 258 }
259 259
260 uint8_t getpiece(char c) { 260 Piece getpiece(char c, Color color) {
261 switch (c) { 261 switch (c) {
262 case 'R': return ROOK; 262 case 'R': return mkpiece(ROOK, color);
263 case 'N': return KNIGHT; 263 case 'N': return mkpiece(KNIGHT, color);
264 case 'B': return BISHOP; 264 case 'B': return mkpiece(BISHOP, color);
265 case 'Q': return QUEEN; 265 case 'Q': return mkpiece(QUEEN, color);
266 case 'K': return KING; 266 case 'K': return mkpiece(KING, color);
267 default: return 0; 267 default: return 0;
268 } 268 }
269 } 269 }
270 270
271 // TODO: check if we really need this "simulate" flag for performance 271 // TODO: check if we really need this "simulate" flag for performance
274 if (!simulate) { 274 if (!simulate) {
275 if (!move->string[0]) { 275 if (!move->string[0]) {
276 format_move(gamestate, move); 276 format_move(gamestate, move);
277 } 277 }
278 } 278 }
279 279
280 uint8_t piece = move->piece & PIECE_MASK;
281 uint8_t color = move->piece & COLOR_MASK;
282
283 /* en passant capture */ 280 /* en passant capture */
284 if (move->capture && piece == PAWN && 281 if (move->capture && piece_type(move->piece) == PAWN &&
285 mdst(gamestate->board, move) == 0) { 282 piece_at(gamestate, mdst(move)) == 0) {
286 gamestate->board[move->fromrow][move->tofile] = 0; 283 piece_remove(gamestate, move->fromrow, move->tofile);
287 } 284 }
288 285
289 /* remove old en passant threats */ 286 /* remove old en passant threats */
290 for (uint8_t file = 0 ; file < 8 ; file++) { 287 for (File file = 0 ; file < 8 ; file++) {
291 gamestate->board[3][file] &= ~ENPASSANT_THREAT; 288 enpassant_threat_remove(gamestate, 3, file);
292 gamestate->board[4][file] &= ~ENPASSANT_THREAT; 289 enpassant_threat_remove(gamestate, 4, file);
293 } 290 }
294 291
292 /* move (and maybe capture or promote) */
293 piece_remove(gamestate, msrc(move));
294 if (move->promotion) {
295 piece_set(gamestate, mdst(move), move->promotion);
296 } else {
297 piece_set(gamestate, mdst(move), move->piece);
298 }
299
295 /* add new en passant threat */ 300 /* add new en passant threat */
296 if (piece == PAWN && ( 301 if (piece_type(move->piece) == PAWN && (
297 (move->fromrow == 1 && move->torow == 3) || 302 (move->fromrow == 1 && move->torow == 3) ||
298 (move->fromrow == 6 && move->torow == 4))) { 303 (move->fromrow == 6 && move->torow == 4))) {
299 move->piece |= ENPASSANT_THREAT; 304 enpassant_threat_add(gamestate, move->torow, move->tofile);
300 }
301
302 /* move (and maybe capture or promote) */
303 msrc(gamestate->board, move) = 0;
304 if (move->promotion) {
305 mdst(gamestate->board, move) = move->promotion;
306 } else {
307 mdst(gamestate->board, move) = move->piece;
308 } 305 }
309 306
310 /* castling */ 307 /* castling */
311 if (piece == KING && move->fromfile == fileidx('e')) { 308 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) {
309 const Color color = piece_color(move->piece);
312 if (move->tofile == fileidx('g')) { 310 if (move->tofile == fileidx('g')) {
313 gamestate->board[move->torow][fileidx('h')] = 0; 311 gamestate->board[move->torow][fileidx('h')] = 0;
314 gamestate->board[move->torow][fileidx('f')] = color|ROOK; 312 gamestate->board[move->torow][fileidx('f')] = mkpiece(ROOK, color);
315 } else if (move->tofile == fileidx('c')) { 313 } else if (move->tofile == fileidx('c')) {
316 gamestate->board[move->torow][fileidx('a')] = 0; 314 gamestate->board[move->torow][fileidx('a')] = 0;
317 gamestate->board[move->torow][fileidx('d')] = color|ROOK; 315 gamestate->board[move->torow][fileidx('d')] = mkpiece(ROOK, color);
318 } 316 }
319 } 317 }
320 318
321 /* add move to the moves array and the new position to the FEN array */ 319 /* add move to the moves array and the new position to the FEN array */
322 if (gamestate->movecount == gamestate->movecapacity) { 320 if (gamestate->movecount == gamestate->movecapacity) {
361 apply_move_impl(replay, &(gamestate->moves[i]), true); 359 apply_move_impl(replay, &(gamestate->moves[i]), true);
362 } 360 }
363 } 361 }
364 362
365 static int validate_move_rules(GameState *gamestate, Move *move) { 363 static int validate_move_rules(GameState *gamestate, Move *move) {
364 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0);
365
366 /* validate indices (don't trust opponent) */ 366 /* validate indices (don't trust opponent) */
367 if (!isidx(move->fromrow) || !isidx(move->fromfile) || 367 if (!isidx(move->fromrow) || !isidx(move->fromfile) ||
368 !isidx(move->torow) || !isidx(move->tofile)) { 368 !isidx(move->torow) || !isidx(move->tofile)) {
369 return INVALID_MOVE_SYNTAX; 369 return INVALID_MOVE_SYNTAX;
370 } 370 }
373 if (move->fromfile == move->tofile && move->fromrow == move->torow) { 373 if (move->fromfile == move->tofile && move->fromrow == move->torow) {
374 return INVALID_MOVE_SYNTAX; 374 return INVALID_MOVE_SYNTAX;
375 } 375 }
376 376
377 /* does piece exist */ 377 /* does piece exist */
378 if ((msrc(gamestate->board, move)&(PIECE_MASK|COLOR_MASK)) 378 if (piece_at(gamestate, msrc(move)) != move->piece) {
379 != (move->piece&(PIECE_MASK|COLOR_MASK))) {
380 return PIECE_NOT_FOUND; 379 return PIECE_NOT_FOUND;
381 } 380 }
381
382 /* is there any piece at the destination? */
383 Piece piece_at_dst = piece_at(gamestate, mdst(move));
382 384
383 /* can't capture own pieces */ 385 /* can't capture own pieces */
384 if ((mdst(gamestate->board, move) & COLOR_MASK) 386 if (piece_color(piece_at_dst) == piece_color(move->piece)) {
385 == (move->piece & COLOR_MASK)) {
386 return RULES_VIOLATED; 387 return RULES_VIOLATED;
387 } 388 }
388 389
389 /* must capture, if and only if destination is occupied... */ 390 /* must capture, if and only if destination is occupied... */
390 if ((mdst(gamestate->board, move) == 0 && move->capture) || 391 if (!((piece_at_dst == 0) ^ move->capture)) {
391 (mdst(gamestate->board, move) != 0 && !move->capture)) {
392 /* ... or the capture happens en passant */ 392 /* ... or the capture happens en passant */
393 if (!move->capture || (move->piece & PIECE_MASK) != PAWN || 393 if (!move->capture || piece_type(move->piece) != PAWN ||
394 !(gamestate->board[move->fromrow][move->tofile] 394 !enpassant_threat_exists(gamestate,
395 & ENPASSANT_THREAT)) { 395 move->fromrow, move->tofile)) {
396 return INVALID_MOVE_SYNTAX; 396 return INVALID_MOVE_SYNTAX;
397 } 397 }
398 } 398 }
399 399
400 /* validate individual rules */ 400 /* validate individual rules */
401 bool chkrules; 401 bool chkrules;
402 switch (move->piece & PIECE_MASK) { 402 switch (piece_type(move->piece)) {
403 case PAWN: 403 case PAWN:
404 chkrules = pawn_chkrules(gamestate, move) && 404 chkrules = pawn_chkrules(gamestate, move) &&
405 !pawn_isblocked(gamestate, move); 405 !pawn_isblocked(gamestate, move);
406 break; 406 break;
407 case ROOK: 407 case ROOK:
443 GameState simulation = gamestate_copy_sim(gamestate); 443 GameState simulation = gamestate_copy_sim(gamestate);
444 Move simmove = *move; 444 Move simmove = *move;
445 apply_move_impl(&simulation, &simmove, true); 445 apply_move_impl(&simulation, &simmove, true);
446 446
447 /* find kings for check validation */ 447 /* find kings for check validation */
448 uint8_t piececolor = (move->piece & COLOR_MASK); 448 Color piececolor = piece_color(move->piece);
449 uint8_t oppcolor = opponent_color(piececolor); 449 Color oppcolor = opponent_color(piececolor);
450 450
451 uint8_t mykingfile = 0, mykingrow = 0, opkingfile = 0, opkingrow = 0; 451 File mykingfile = 0, opkingfile = 0;
452 for (uint8_t row = 0 ; row < 8 ; row++) { 452 Row mykingrow = 0, opkingrow = 0;
453 for (uint8_t file = 0 ; file < 8 ; file++) { 453 for (Row row = 0 ; row < 8 ; row++) {
454 if (simulation.board[row][file] == 454 for (File file = 0 ; file < 8 ; file++) {
455 (piececolor == WHITE?WKING:BKING)) { 455 Piece p = piece_at(&simulation, row, file);
456 if (p == mkpiece(KING, piececolor)) {
456 mykingfile = file; 457 mykingfile = file;
457 mykingrow = row; 458 mykingrow = row;
458 } else if (simulation.board[row][file] == 459 } else if (p == mkpiece(KING, oppcolor)) {
459 (piececolor == WHITE?BKING:WKING)) {
460 opkingfile = file; 460 opkingfile = file;
461 opkingrow = row; 461 opkingrow = row;
462 } 462 }
463 } 463 }
464 } 464 }
465 465
466 /* don't move into or stay in check position */ 466 /* don't move into or stay in check position */
467 if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) { 467 if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) {
468 gamestate_cleanup(&simulation); 468 gamestate_cleanup(&simulation);
469 if ((move->piece & PIECE_MASK) == KING) { 469 if (piece_type(move->piece) == KING) {
470 return KING_MOVES_INTO_CHECK; 470 return KING_MOVES_INTO_CHECK;
471 } else { 471 } else {
472 /* last move is always not null in this case */ 472 /* last move is always not null in this case */
473 return last_move(gamestate).check ? 473 return last_move(gamestate).check ?
474 KING_IN_CHECK : PIECE_PINNED; 474 KING_IN_CHECK : PIECE_PINNED;
475 } 475 }
476 } 476 }
477 477
478 /* correct check and checkmate flags (move is still valid) */ 478 /* correct check and checkmate flags (move is still valid) */
479 Move threats[16]; 479 Move threats[16];
480 uint8_t threatcount; 480 size_t threatcount;
481 move->check = get_threats(&simulation, opkingrow, opkingfile, 481 move->check = get_threats(&simulation, opkingrow, opkingfile,
482 piececolor, threats, &threatcount); 482 piececolor, threats, &threatcount);
483 483
484 if (move->check) { 484 if (move->check) {
485 /* determine possible escape fields */ 485 /* determine possible escape fields */
487 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { 487 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
488 for (int df = -1 ; df <= 1 && !canescape ; df++) { 488 for (int df = -1 ; df <= 1 && !canescape ; df++) {
489 if (dr == 0 && df == 0) continue; 489 if (dr == 0 && df == 0) continue;
490 if (!isidx(opkingrow + dr)) continue; 490 if (!isidx(opkingrow + dr)) continue;
491 if (!isidx(opkingfile + df)) continue; 491 if (!isidx(opkingfile + df)) continue;
492 uint8_t er = opkingrow + dr; 492 Row er = opkingrow + dr;
493 uint8_t ef = opkingfile + df; 493 File ef = opkingfile + df;
494 494
495 /* check if escape field is already covered (threatened) */ 495 /* check if escape field is already covered (threatened) */
496 if (is_covered(&simulation, er, ef, piececolor)) 496 if (is_covered(&simulation, er, ef, piececolor))
497 continue; 497 continue;
498 498
499 /* check if piece of the king's color blocks the field */ 499 /* check if piece of the king's color blocks the field */
500 if ((simulation.board[er][ef] & COLOR_MASK) == oppcolor) 500 if (piece_color(simulation.board[er][ef]) == oppcolor)
501 continue; 501 continue;
502 502
503 /* check if an attacking piece blocks the field */ 503 /* check if an attacking piece blocks the field */
504 if ((simulation.board[er][ef] & COLOR_MASK) == piececolor) { 504 if (piece_color(simulation.board[er][ef]) == piececolor) {
505 /* test if the king can fight back */ 505 /* test if the king can fight back */
506 GameState sim_retaliate = gamestate_copy_sim(&simulation); 506 GameState sim_retaliate = gamestate_copy_sim(&simulation);
507 Move move_retaliate = {0}; 507 Move move_retaliate = {0};
508 move_retaliate.piece = (oppcolor|KING); 508 move_retaliate.piece = mkpiece(KING, oppcolor);
509 move_retaliate.fromrow = opkingrow; 509 move_retaliate.fromrow = opkingrow;
510 move_retaliate.fromfile = opkingfile; 510 move_retaliate.fromfile = opkingfile;
511 move_retaliate.torow = er; 511 move_retaliate.torow = er;
512 move_retaliate.tofile = ef; 512 move_retaliate.tofile = ef;
513 move_retaliate.capture = 1; 513 move_retaliate.capture = 1;
528 } 528 }
529 529
530 /* can't capture, can he block? */ 530 /* can't capture, can he block? */
531 if (!canescape && threatcount == 1) { 531 if (!canescape && threatcount == 1) {
532 Move *threat = &(threats[0]); 532 Move *threat = &(threats[0]);
533 uint8_t threatpiece = threat->piece & PIECE_MASK; 533 unsigned tptype = piece_type(threat->piece);
534 534
535 /* knight, pawns and the king cannot be blocked */ 535 /* knight, pawns and the king cannot be blocked */
536 if (threatpiece == BISHOP || threatpiece == ROOK 536 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) {
537 || threatpiece == QUEEN) {
538 if (threat->fromrow == threat->torow) { 537 if (threat->fromrow == threat->torow) {
539 /* rook aspect (on row) */ 538 /* rook aspect (on row) */
540 int d = threat->tofile > threat->fromfile ? 1 : -1; 539 int d = threat->tofile > threat->fromfile ? 1 : -1;
541 uint8_t file = threat->fromfile; 540 File file = threat->fromfile;
542 while (!canescape && file != threat->tofile - d) { 541 while (!canescape && file != threat->tofile - d) {
543 file += d; 542 file += d;
544 canescape |= is_protected(&simulation, 543 canescape |= is_protected(&simulation,
545 threat->torow, file, oppcolor); 544 threat->torow, file, oppcolor);
546 } 545 }
547 } else if (threat->fromfile == threat->tofile) { 546 } else if (threat->fromfile == threat->tofile) {
548 /* rook aspect (on file) */ 547 /* rook aspect (on file) */
549 int d = threat->torow > threat->fromrow ? 1 : -1; 548 int d = threat->torow > threat->fromrow ? 1 : -1;
550 uint8_t row = threat->fromrow; 549 Row row = threat->fromrow;
551 while (!canescape && row != threat->torow - d) { 550 while (!canescape && row != threat->torow - d) {
552 row += d; 551 row += d;
553 canescape |= is_protected(&simulation, 552 canescape |= is_protected(&simulation,
554 row, threat->tofile, oppcolor); 553 row, threat->tofile, oppcolor);
555 } 554 }
556 } else { 555 } else {
557 /* bishop aspect */ 556 /* bishop aspect */
558 int dr = threat->torow > threat->fromrow ? 1 : -1; 557 int dr = threat->torow > threat->fromrow ? 1 : -1;
559 int df = threat->tofile > threat->fromfile ? 1 : -1; 558 int df = threat->tofile > threat->fromfile ? 1 : -1;
560 559
561 uint8_t row = threat->fromrow; 560 Row row = threat->fromrow;
562 uint8_t file = threat->fromfile; 561 File file = threat->fromfile;
563 while (!canescape && file != threat->tofile - df 562 while (!canescape && file != threat->tofile - df
564 && row != threat->torow - dr) { 563 && row != threat->torow - dr) {
565 row += dr; 564 row += dr;
566 file += df; 565 file += df;
567 canescape |= is_protected(&simulation, row, file, 566 canescape |= is_protected(&simulation, row, file,
568 oppcolor); 567 oppcolor);
569 } 568 }
579 gamestate_cleanup(&simulation); 578 gamestate_cleanup(&simulation);
580 579
581 return VALID_MOVE_SEMANTICS; 580 return VALID_MOVE_SEMANTICS;
582 } 581 }
583 582
584 bool get_threats(GameState *gamestate, uint8_t row, uint8_t file, 583 Piece piece_at(const GameState *gamestate, Row row, File file) {
585 uint8_t color, Move *threats, uint8_t *threatcount) { 584 return gamestate->board[row][file] & (PIECE_MASK|COLOR_MASK);
585 }
586
587 void piece_set(GameState *gamestate, Row row, File file, Piece piece) {
588 gamestate->board[row][file] = piece;
589 }
590
591 bool get_threats(GameState *gamestate, Row row, File file,
592 Color color, Move *threats, size_t *threatcount) {
586 Move candidates[32]; 593 Move candidates[32];
587 int candidatecount = 0; 594 size_t ccount = 0;
588 for (uint8_t r = 0 ; r < 8 ; r++) { 595 for (Row r = 0 ; r < 8 ; r++) {
589 for (uint8_t f = 0 ; f < 8 ; f++) { 596 for (File f = 0 ; f < 8 ; f++) {
590 if ((gamestate->board[r][f] & COLOR_MASK) == color) { 597 if (piece_color(gamestate->board[r][f]) == color) {
591 /* non-capturing move */ 598 /* non-capturing move */
592 memset(&(candidates[candidatecount]), 0, sizeof(Move)); 599 memset(&(candidates[ccount]), 0, sizeof(Move));
593 candidates[candidatecount].piece = gamestate->board[r][f]; 600 Piece p = piece_at(gamestate, r, f);
594 candidates[candidatecount].fromrow = r; 601 candidates[ccount].piece = p;
595 candidates[candidatecount].fromfile = f; 602 candidates[ccount].fromrow = r;
596 candidates[candidatecount].torow = row; 603 candidates[ccount].fromfile = f;
597 candidates[candidatecount].tofile = file; 604 candidates[ccount].torow = row;
598 if ((gamestate->board[r][f]&PIECE_MASK) == PAWN 605 candidates[ccount].tofile = file;
599 && (row == 0 || row == 7)) { 606 if (piece_type(p) == PAWN && (row == 0 || row == 7)) {
600 /* the exact piece for promotion does not matter */ 607 /* the exact piece for promotion does not matter */
601 candidates[candidatecount].promotion = color|QUEEN; 608 candidates[ccount].promotion = mkpiece(QUEEN, color);
602 } 609 }
603 candidatecount++; 610 ccount++;
604 611
605 /* capturing move */ 612 /* capturing move */
606 memcpy(&(candidates[candidatecount]), 613 memcpy(&(candidates[ccount]),
607 &(candidates[candidatecount-1]), sizeof(Move)); 614 &(candidates[ccount-1]), sizeof(Move));
608 candidates[candidatecount].capture = 1; 615 candidates[ccount].capture = 1;
609 candidatecount++; 616 ccount++;
610 } 617 }
611 } 618 }
612 } 619 }
613 620
614 if (threatcount) { 621 if (threatcount) {
616 } 623 }
617 624
618 625
619 bool result = false; 626 bool result = false;
620 627
621 for (int i = 0 ; i < candidatecount ; i++) { 628 for (size_t i = 0 ; i < ccount ; i++) {
622 if (validate_move_rules(gamestate, &(candidates[i])) 629 if (validate_move_rules(gamestate, &(candidates[i]))
623 == VALID_MOVE_SEMANTICS) { 630 == VALID_MOVE_SEMANTICS) {
624 result = true; 631 result = true;
625 if (threats && threatcount) { 632 if (threats && threatcount) {
626 threats[(*threatcount)++] = candidates[i]; 633 threats[(*threatcount)++] = candidates[i];
630 637
631 return result; 638 return result;
632 } 639 }
633 640
634 bool is_pinned(GameState *gamestate, Move *move) { 641 bool is_pinned(GameState *gamestate, Move *move) {
635 uint8_t color = move->piece & COLOR_MASK; 642 Color color = piece_color(move->piece);
636 643
637 GameState simulation = gamestate_copy_sim(gamestate); 644 GameState simulation = gamestate_copy_sim(gamestate);
638 Move simmove = *move; 645 Move simmove = *move;
639 apply_move(&simulation, &simmove); 646 apply_move(&simulation, &simmove);
640 647
641 uint8_t kingfile = 0, kingrow = 0; 648 File kingfile = 0;
642 for (uint8_t row = 0 ; row < 8 ; row++) { 649 Row kingrow = 0;
643 for (uint8_t file = 0 ; file < 8 ; file++) { 650 for (Row row = 0 ; row < 8 ; row++) {
651 for (File file = 0 ; file < 8 ; file++) {
644 if (simulation.board[row][file] == (color|KING)) { 652 if (simulation.board[row][file] == (color|KING)) {
645 kingfile = file; 653 kingfile = file;
646 kingrow = row; 654 kingrow = row;
647 } 655 }
648 } 656 }
653 gamestate_cleanup(&simulation); 661 gamestate_cleanup(&simulation);
654 662
655 return covered; 663 return covered;
656 } 664 }
657 665
658 bool get_real_threats(GameState *gamestate, uint8_t row, uint8_t file, 666 bool get_real_threats(GameState *gamestate, Row row, File file,
659 uint8_t color, Move *threats, uint8_t *threatcount) { 667 Color color, Move *threats, size_t *threatcount) {
660 668
661 if (threatcount) { 669 if (threatcount) {
662 *threatcount = 0; 670 *threatcount = 0;
663 } 671 }
664 672
665 Move candidates[16]; 673 Move candidates[16];
666 uint8_t candidatecount; 674 size_t candidatecount;
667 if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) { 675 if (get_threats(gamestate, row, file, color, candidates, &candidatecount)) {
668 676
669 bool result = false; 677 bool result = false;
670 uint8_t kingfile = 0, kingrow = 0; 678 File kingfile = 0;
671 for (uint8_t r = 0 ; r < 8 ; r++) { 679 Row kingrow = 0;
672 for (uint8_t f = 0 ; f < 8 ; f++) { 680 for (Row r = 0 ; r < 8 ; r++) {
681 for (File f = 0 ; f < 8 ; f++) {
673 if (gamestate->board[r][f] == (color|KING)) { 682 if (gamestate->board[r][f] == (color|KING)) {
674 kingfile = f; 683 kingfile = f;
675 kingrow = r; 684 kingrow = r;
676 } 685 }
677 } 686 }
678 } 687 }
679 688
680 for (uint8_t i = 0 ; i < candidatecount ; i++) { 689 for (size_t i = 0 ; i < candidatecount ; i++) {
681 // TODO: check if we really need full-blown simulations here 690 // TODO: check if we really need full-blown simulations here
682 GameState simulation = gamestate_copy_sim(gamestate); 691 GameState simulation = gamestate_copy_sim(gamestate);
683 Move simmove = candidates[i]; 692 Move simmove = candidates[i];
684 apply_move(&simulation, &simmove); 693 apply_move(&simulation, &simmove);
685 if (!is_covered(&simulation, kingrow, kingfile, 694 if (!is_covered(&simulation, kingrow, kingfile,
698 } 707 }
699 } 708 }
700 709
701 static int getlocation(GameState *gamestate, Move *move) { 710 static int getlocation(GameState *gamestate, Move *move) {
702 711
703 uint8_t color = move->piece & COLOR_MASK; 712 Color color = piece_color(move->piece);
704 uint8_t piece = move->piece & PIECE_MASK;
705 bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false; 713 bool incheck = gamestate->movecount > 0 ? last_move(gamestate).check:false;
706 714
707 Move threats[16], *threat = NULL; 715 Move threats[16], *threat = NULL;
708 uint8_t threatcount; 716 size_t threatcount;
709 717
710 /* determine all threats and sort out the unreal threats on our own */ 718 /* determine all threats and sort out the unreal threats on our own */
711 if (get_threats(gamestate, move->torow, move->tofile, color, 719 if (get_threats(gamestate, move->torow, move->tofile, color,
712 threats, &threatcount)) { 720 threats, &threatcount)) {
713 721
714 bool found = false; 722 bool found = false;
715 723
716 /* find threats for the specified position */ 724 /* find threats for the specified position */
717 for (uint8_t i = 0 ; i < threatcount ; i++) { 725 for (size_t i = 0 ; i < threatcount ; i++) {
718 if (threats[i].piece == move->piece && 726 if (threats[i].piece == move->piece &&
719 (move->fromrow == POS_UNSPECIFIED || 727 (move->fromrow == POS_UNSPECIFIED ||
720 move->fromrow == threats[i].fromrow) && 728 move->fromrow == threats[i].fromrow) &&
721 (move->fromfile == POS_UNSPECIFIED || 729 (move->fromfile == POS_UNSPECIFIED ||
722 move->fromfile == threats[i].fromfile)) { 730 move->fromfile == threats[i].fromfile)) {
737 } 745 }
738 746
739 /* can't threaten specified position */ 747 /* can't threaten specified position */
740 if (!threat) { 748 if (!threat) {
741 if (found) { 749 if (found) {
742 if (piece == KING) { 750 if (piece_type(move->piece) == KING) {
743 return KING_MOVES_INTO_CHECK; 751 return KING_MOVES_INTO_CHECK;
744 } else if (incheck) { 752 } else if (incheck) {
745 return KING_IN_CHECK; 753 return KING_IN_CHECK;
746 } else { 754 } else {
747 return PIECE_PINNED; 755 return PIECE_PINNED;
758 } else { 766 } else {
759 return PIECE_NOT_FOUND; 767 return PIECE_NOT_FOUND;
760 } 768 }
761 } 769 }
762 770
763 static int eval_move1(const char *pstr, Move *move, uint8_t color) { 771 static int eval_move1(const char *pstr, Move *move, Color color) {
764 memset(move, 0, sizeof(Move)); 772 memset(move, 0, sizeof(Move));
765 move->fromfile = POS_UNSPECIFIED; 773 move->fromfile = POS_UNSPECIFIED;
766 move->fromrow = POS_UNSPECIFIED; 774 move->fromrow = POS_UNSPECIFIED;
767 775
768 size_t len = strlen(pstr); 776 size_t len = strlen(pstr);
778 move->check = true; 786 move->check = true;
779 } 787 }
780 788
781 /* evaluate promotion */ 789 /* evaluate promotion */
782 if (len > 3 && mstr[len-2] == '=') { 790 if (len > 3 && mstr[len-2] == '=') {
783 move->promotion = getpiece(mstr[len-1]); 791 move->promotion = getpiece(mstr[len-1], color);
784 if (!move->promotion) { 792 if (!move->promotion) {
785 return INVALID_MOVE_SYNTAX; 793 return INVALID_MOVE_SYNTAX;
786 } else { 794 } else {
787 move->promotion |= color;
788 len -= 2; 795 len -= 2;
789 mstr[len] = 0; 796 mstr[len] = 0;
790 } 797 }
791 } 798 }
792 799
793 if (len == 2) { 800 if (len == 2) {
794 /* pawn move (e.g. "e4") */ 801 /* pawn move (e.g. "e4") */
795 move->piece = PAWN; 802 move->piece = mkpiece(PAWN, color);
796 move->tofile = fileidx(mstr[0]); 803 move->tofile = fileidx(mstr[0]);
797 move->torow = rowidx(mstr[1]); 804 move->torow = rowidx(mstr[1]);
798 } else if (len == 3) { 805 } else if (len == 3) {
799 if (strcmp(mstr, "O-O") == 0) { 806 if (strcmp(mstr, "O-O") == 0) {
800 /* king side castling */ 807 /* king side castling */
801 move->piece = KING; 808 move->piece = mkpiece(KING, color);
802 move->fromfile = fileidx('e'); 809 move->fromfile = fileidx('e');
803 move->tofile = fileidx('g'); 810 move->tofile = fileidx('g');
804 move->fromrow = move->torow = color == WHITE ? 0 : 7; 811 move->fromrow = move->torow = color == WHITE ? 0 : 7;
805 } else { 812 } else {
806 /* move (e.g. "Nf3") */ 813 /* move (e.g. "Nf3") */
807 move->piece = getpiece(mstr[0]); 814 move->piece = getpiece(mstr[0], color);
808 move->tofile = fileidx(mstr[1]); 815 move->tofile = fileidx(mstr[1]);
809 move->torow = rowidx(mstr[2]); 816 move->torow = rowidx(mstr[2]);
810 } 817 }
811 } else if (len == 4) { 818 } else if (len == 4) {
812 move->piece = getpiece(mstr[0]); 819 move->piece = getpiece(mstr[0], color);
813 if (!move->piece) { 820 if (move->piece == 0) {
814 move->piece = PAWN; 821 move->piece = mkpiece(PAWN, color);
815 move->fromfile = fileidx(mstr[0]); 822 move->fromfile = fileidx(mstr[0]);
816 } 823 }
817 if (mstr[1] == 'x') { 824 if (mstr[1] == 'x') {
818 /* capture (e.g. "Nxf3", "dxe5") */ 825 /* capture (e.g. "Nxf3", "dxe5") */
819 move->capture = 1; 826 move->capture = 1;
820 } else { 827 } else {
821 /* move (e.g. "Ndf3", "N2c3", "e2e4") */ 828 /* move (e.g. "Ndf3", "N2c3", "e2e4") */
822 if (isfile(mstr[1])) { 829 if (isfile(mstr[1])) {
823 move->fromfile = fileidx(mstr[1]); 830 move->fromfile = fileidx(mstr[1]);
824 if (move->piece == PAWN) { 831 /* when the piece is a pawn, second char cannot be a file */
832 if (piece_type(move->piece) == PAWN) {
833 /* invalidate the result */
825 move->piece = 0; 834 move->piece = 0;
826 } 835 }
827 } else { 836 } else {
828 move->fromrow = rowidx(mstr[1]); 837 move->fromrow = rowidx(mstr[1]);
829 } 838 }
831 move->tofile = fileidx(mstr[2]); 840 move->tofile = fileidx(mstr[2]);
832 move->torow = rowidx(mstr[3]); 841 move->torow = rowidx(mstr[3]);
833 } else if (len == 5) { 842 } else if (len == 5) {
834 if (strcmp(mstr, "O-O-O") == 0) { 843 if (strcmp(mstr, "O-O-O") == 0) {
835 /* queen side castling "O-O-O" */ 844 /* queen side castling "O-O-O" */
836 move->piece = KING; 845 move->piece = mkpiece(KING, color);
837 move->fromfile = fileidx('e'); 846 move->fromfile = fileidx('e');
838 move->tofile = fileidx('c'); 847 move->tofile = fileidx('c');
839 move->fromrow = move->torow = color == WHITE ? 0 : 7; 848 move->fromrow = move->torow = color == WHITE ? 0 : 7;
840 } else { 849 } else {
841 move->piece = getpiece(mstr[0]); 850 move->piece = getpiece(mstr[0], color);
842 if (mstr[2] == 'x') { 851 if (mstr[2] == 'x') {
843 move->capture = 1; 852 move->capture = 1;
844 if (move->piece) { 853 if (move->piece) {
845 /* capture (e.g. "Ndxf3" or "R1xh3") */ 854 /* capture (e.g. "Ndxf3" or "R1xh3") */
846 if (isfile(mstr[1])) { 855 if (isfile(mstr[1])) {
850 } else { 859 } else {
851 return INVALID_MOVE_SYNTAX; 860 return INVALID_MOVE_SYNTAX;
852 } 861 }
853 } else { 862 } else {
854 /* long notation capture (e.g. "e5xf6") */ 863 /* long notation capture (e.g. "e5xf6") */
855 move->piece = PAWN; 864 move->piece = mkpiece(PAWN, color);
856 move->fromfile = fileidx(mstr[0]); 865 move->fromfile = fileidx(mstr[0]);
857 move->fromrow = rowidx(mstr[1]); 866 move->fromrow = rowidx(mstr[1]);
858 } 867 }
859 } else { 868 } else {
860 /* long notation move (e.g. "Nc5a4") */ 869 /* long notation move (e.g. "Nc5a4") */
866 } 875 }
867 } else if (len == 6) { 876 } else if (len == 6) {
868 /* long notation capture (e.g. "Nc5xf3") */ 877 /* long notation capture (e.g. "Nc5xf3") */
869 if (mstr[3] == 'x') { 878 if (mstr[3] == 'x') {
870 move->capture = 1; 879 move->capture = 1;
871 move->piece = getpiece(mstr[0]); 880 move->piece = getpiece(mstr[0], color);
872 move->fromfile = fileidx(mstr[1]); 881 move->fromfile = fileidx(mstr[1]);
873 move->fromrow = rowidx(mstr[2]); 882 move->fromrow = rowidx(mstr[2]);
874 move->tofile = fileidx(mstr[4]); 883 move->tofile = fileidx(mstr[4]);
875 move->torow = rowidx(mstr[5]); 884 move->torow = rowidx(mstr[5]);
876 } 885 }
879 888
880 if (!move->piece) { 889 if (!move->piece) {
881 return INVALID_MOVE_SYNTAX; 890 return INVALID_MOVE_SYNTAX;
882 } 891 }
883 892
884 if (move->piece == PAWN 893 if (piece_type(move->piece) == PAWN
885 && move->torow == (color==WHITE?7:0) 894 && move->torow == (color==WHITE?7:0)
886 && !move->promotion) { 895 && !move->promotion) {
887 return NEED_PROMOTION; 896 return NEED_PROMOTION;
888 } 897 }
889
890 move->piece |= color;
891 898
892 /* up to this point 899 /* up to this point
893 * destination indices must be specified and valid 900 * destination indices must be specified and valid
894 * source indices must either be valid or unspecified 901 * source indices must either be valid or unspecified
895 */ 902 */
900 907
901 return VALID_MOVE_SYNTAX; 908 return VALID_MOVE_SYNTAX;
902 } 909 }
903 910
904 int eval_move(GameState *gamestate, const char *mstr, 911 int eval_move(GameState *gamestate, const char *mstr,
905 Move *move, uint8_t color) { 912 Move *move, Color color) {
906 int result = eval_move1(mstr, move, color); 913 int result = eval_move1(mstr, move, color);
907 if (result == VALID_MOVE_SYNTAX) { 914 if (result == VALID_MOVE_SYNTAX) {
908 if (move->fromfile == POS_UNSPECIFIED 915 if (move->fromfile == POS_UNSPECIFIED
909 || move->fromrow == POS_UNSPECIFIED) { 916 || move->fromrow == POS_UNSPECIFIED) {
910 return getlocation(gamestate, move); 917 return getlocation(gamestate, move);
911 } 918 }
912 } 919 }
913 return result; 920 return result;
914 } 921 }
915 922
916 int check_move(const char *mstr, uint8_t color) { 923 int check_move(const char *mstr, Color color) {
917 Move move; 924 Move move;
918 return eval_move1(mstr, &move, color); 925 return eval_move1(mstr, &move, color);
919 } 926 }
920 927
921 bool is_protected(GameState *gamestate, uint8_t row, uint8_t file, 928 bool is_protected(GameState *gamestate, Row row, File file, Color color) {
922 uint8_t color) {
923
924 Move threats[16]; 929 Move threats[16];
925 uint8_t threatcount; 930 size_t threatcount;
926 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { 931 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
927 for (int i = 0 ; i < threatcount ; i++) { 932 for (size_t i = 0 ; i < threatcount ; i++) {
928 if (threats[i].piece != (color|KING)) { 933 if (threats[i].piece != (color|KING)) {
929 return true; 934 return true;
930 } 935 }
931 } 936 }
932 return false; 937 return false;
933 } else { 938 } else {
934 return false; 939 return false;
935 } 940 }
936 } 941 }
937 942
938 uint16_t remaining_movetime(GameState *gamestate, uint8_t color) { 943 uint16_t remaining_movetime(GameState *gamestate, Color color) {
939 unsigned move_number = gamestate->movecount; 944 unsigned move_number = gamestate->movecount;
940 if (color == BLACK) { 945 if (color == BLACK) {
941 move_number |= 1; 946 move_number |= 1;
942 } else { 947 } else {
943 move_number = (move_number + 1) & ~1; 948 move_number = (move_number + 1) & ~1;

mercurial