src/chess/rules.c

changeset 163
2a6d83f4677e
parent 161
3ff96fec144a
equal deleted inserted replaced
162:f0fc70b6f8f9 163:2a6d83f4677e
77 gamestate->fen = NULL; 77 gamestate->fen = NULL;
78 } 78 }
79 gamestate->movecount = gamestate->movecapacity = 0; 79 gamestate->movecount = gamestate->movecapacity = 0;
80 } 80 }
81 81
82 static GameState gamestate_copy_sim(GameState *gamestate) { 82 static GameState gamestate_copy_sim(const GameState *gamestate) {
83 GameState simulation = *gamestate; 83 GameState simulation = *gamestate;
84 84
85 /* create new move and position lists for the simulation */ 85 /* create new move and position lists for the simulation */
86 simulation.movecapacity = 4; 86 simulation.movecapacity = 4;
87 simulation.movecount = 0; 87 simulation.movecount = 0;
100 } 100 }
101 101
102 return simulation; 102 return simulation;
103 } 103 }
104 104
105 Color current_color(GameState *gamestate) { 105 Color current_color(const GameState *gamestate) {
106 return (gamestate->movecount % 2 == 0) ? WHITE : BLACK; 106 return (gamestate->movecount % 2 == 0) ? WHITE : BLACK;
107 } 107 }
108 108
109 /* 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 */
110 static void format_move(GameState *gamestate, Move *move) { 110 static void format_move(const GameState *gamestate, Move *move) {
111 char *string = &(move->string[0]); 111 char *string = &(move->string[0]);
112 112
113 /* at least 8 characters should be available, wipe them out */ 113 /* at least 8 characters should be available, wipe them out */
114 memset(string, 0, 8); 114 memset(string, 0, 8);
115 115
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_type(move->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 candidates[16];
140 size_t threatcount; 140 size_t ccount;
141 if (get_threats(gamestate, move->torow, move->tofile, 141 if (get_candidates(gamestate, move->torow, move->tofile,
142 piece_color(move->piece), threats, &threatcount)) { 142 piece_color(move->piece), candidates, &ccount)) {
143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; 143 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0;
144 for (size_t i = 0 ; i < threatcount ; i++) { 144 for (size_t i = 0 ; i < ccount ; i++) {
145 if (threats[i].piece == move->piece) { 145 if (candidates[i].piece == move->piece) {
146 ambpiece++; 146 ambpiece++;
147 if (threats[i].fromrow == move->fromrow) { 147 if (candidates[i].fromrow == move->fromrow) {
148 ambrows++; 148 ambrows++;
149 } 149 }
150 if (threats[i].fromfile == move->fromfile) { 150 if (candidates[i].fromfile == move->fromfile) {
151 ambfiles++; 151 ambfiles++;
152 } 152 }
153 } 153 }
154 } 154 }
155 /* neither file, nor row are ambiguous, name file */ 155 /* neither file, nor row are ambiguous, name file */
185 string[idx++] = getpiecechr(move->promotion); 185 string[idx++] = getpiecechr(move->promotion);
186 } 186 }
187 } 187 }
188 188
189 /* check? */ 189 /* check? */
190 if (move->check) { 190 if (move->checkmate) {
191 string[idx++] = gamestate->checkmate?'#':'+'; 191 string[idx++] = '#';
192 } else if (move->check) {
193 string[idx++] = '+';
192 } 194 }
193 } 195 }
194 196
195 static void calc_movetime(GameState *gamestate, Move *move) { 197 static void calc_movetime(GameState *gamestate, Move *move) {
196 struct timeval curtimestamp; 198 struct timeval curtimestamp;
267 case 'K': return mkpiece(KING, color); 269 case 'K': return mkpiece(KING, color);
268 default: return 0; 270 default: return 0;
269 } 271 }
270 } 272 }
271 273
272 // TODO: check if we really need this "simulate" flag for performance 274 void apply_move(GameState *gamestate, Move *move) {
273 static void apply_move_impl(GameState *gamestate, Move *move, bool simulate) {
274 /* format move before moving (s.t. ambiguities can be resolved) */
275 if (!simulate) {
276 if (!move->string[0]) {
277 format_move(gamestate, move);
278 }
279 }
280
281 /* en passant capture */ 275 /* en passant capture */
282 if (move->capture && piece_type(move->piece) == PAWN && 276 if (move->capture && piece_type(move->piece) == PAWN &&
283 piece_at(gamestate, mdst(move)) == 0) { 277 piece_at(gamestate, mdst(move)) == 0) {
284 piece_remove(gamestate, move->fromrow, move->tofile); 278 piece_remove(gamestate, move->fromrow, move->tofile);
285 } 279 }
340 calc_movetime(gamestate, melem); 334 calc_movetime(gamestate, melem);
341 } 335 }
342 336
343 /* important: only "add" the move after calculating the time! */ 337 /* important: only "add" the move after calculating the time! */
344 gamestate->movecount++; 338 gamestate->movecount++;
345 } 339
346 340 /* did this move checkmate the other king? */
347 void apply_move(GameState *gamestate, Move *move) { 341 gamestate->checkmate = move->checkmate;
348 apply_move_impl(gamestate, move, false); 342 }
349 } 343
350 344 void gamestate_at_move(const GameState *gamestate,
351 void gamestate_at_move(GameState *gamestate,
352 unsigned move_number, GameState *replay) { 345 unsigned move_number, GameState *replay) {
353 gamestate_init(replay); 346 gamestate_init(replay);
354 memcpy(&replay->info, &gamestate->info, sizeof(GameInfo)); 347 memcpy(&replay->info, &gamestate->info, sizeof(GameInfo));
355 replay->review = true; 348 replay->review = true;
356 if (move_number > gamestate->movecount) { 349 if (move_number > gamestate->movecount) {
357 move_number = gamestate->movecount; 350 move_number = gamestate->movecount;
358 } 351 }
359 for (unsigned i = 0 ; i < move_number ; i++) { 352 for (unsigned i = 0 ; i < move_number ; i++) {
360 apply_move_impl(replay, &(gamestate->moves[i]), true); 353 apply_move(replay, &(gamestate->moves[i]));
361 } 354 }
362 } 355 }
363 356
364 static int validate_move_rules(GameState *gamestate, Move *move) { 357 /* return 0 = no check, 1 = check, 2 = checkmate */
358 static int determine_check_or_checkmate(
359 const GameState *gamestate, const Move *move) {
360
361 /* simulate the move */
362 GameState simulation = gamestate_copy_sim(gamestate);
363 Move simmove = *move;
364 apply_move(&simulation, &simmove);
365
366 /* find the opposing king */
367 Color piececolor = piece_color(move->piece);
368 Color oppcolor = opponent_color(piececolor);
369 File opkingfile = 0;
370 Row opkingrow = 0;
371 for (Row row = 0 ; row < 8 ; row++) {
372 for (File file = 0 ; file < 8 ; file++) {
373 Piece p = piece_at(&simulation, row, file);
374 if (p == mkpiece(KING, oppcolor)) {
375 opkingfile = file;
376 opkingrow = row;
377 }
378 }
379 }
380
381 /* determine if the opposing king is now threatened */
382 Move threats[16];
383 size_t threatcount;
384 bool incheck = get_threats(&simulation, opkingrow, opkingfile,
385 piececolor, threats, &threatcount);
386
387 if (!incheck) {
388 gamestate_cleanup(&simulation);
389 return 0;
390 }
391
392 /* determine possible escape fields */
393 bool canescape = false;
394 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
395 for (int df = -1 ; df <= 1 && !canescape ; df++) {
396 if (dr == 0 && df == 0) continue;
397 Row er = opkingrow + dr;
398 File ef = opkingfile + df;
399 if (!isidx(er) || !isidx(ef)) continue;
400
401 /* check if piece of the king's color blocks the field */
402 if (piece_color(simulation.board[er][ef]) == oppcolor)
403 continue;
404
405 /* check if escape field is already covered (threatened) */
406 if (is_covered(&simulation, er, ef, piececolor))
407 continue;
408
409 /* check if an attacking piece blocks the field */
410 if (piece_color(simulation.board[er][ef]) == piececolor) {
411 /* test if the king can fight back */
412 GameState sim_retaliate = gamestate_copy_sim(&simulation);
413 Move move_retaliate = {0};
414 move_retaliate.piece = mkpiece(KING, oppcolor);
415 move_retaliate.fromrow = opkingrow;
416 move_retaliate.fromfile = opkingfile;
417 move_retaliate.torow = er;
418 move_retaliate.tofile = ef;
419 move_retaliate.capture = true;
420 apply_move(&sim_retaliate, &move_retaliate);
421 canescape = !is_covered(&sim_retaliate, er, ef, piececolor);
422 gamestate_cleanup(&sim_retaliate);
423 continue;
424 }
425
426 /* the field is not covered and unoccupied */
427 canescape = true;
428 }
429 }
430
431 /* can't escape, can the king be rescued? */
432 if (!canescape && threatcount == 1) {
433 canescape = is_protected(&simulation,
434 threats[0].fromrow, threats[0].fromfile, oppcolor);
435 }
436
437 /* can't capture, can he block? */
438 if (!canescape && threatcount == 1) {
439 Move *threat = &(threats[0]);
440 unsigned tptype = piece_type(threat->piece);
441
442 /* knight, pawns and the king cannot be blocked */
443 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) {
444 if (threat->fromrow == threat->torow) {
445 /* rook aspect (on row) */
446 int d = threat->tofile > threat->fromfile ? 1 : -1;
447 File file = threat->fromfile;
448 while (!canescape && file != threat->tofile - d) {
449 file += d;
450 canescape |= is_protected(&simulation,
451 threat->torow, file, oppcolor);
452 }
453 } else if (threat->fromfile == threat->tofile) {
454 /* rook aspect (on file) */
455 int d = threat->torow > threat->fromrow ? 1 : -1;
456 Row row = threat->fromrow;
457 while (!canescape && row != threat->torow - d) {
458 row += d;
459 canescape |= is_protected(&simulation,
460 row, threat->tofile, oppcolor);
461 }
462 } else {
463 /* bishop aspect */
464 int dr = threat->torow > threat->fromrow ? 1 : -1;
465 int df = threat->tofile > threat->fromfile ? 1 : -1;
466
467 Row row = threat->fromrow;
468 File file = threat->fromfile;
469 while (!canescape && file != threat->tofile - df
470 && row != threat->torow - dr) {
471 row += dr;
472 file += df;
473 canescape |= is_protected(&simulation, row, file,
474 oppcolor);
475 }
476 }
477 }
478 }
479 gamestate_cleanup(&simulation);
480 return canescape ? 1 : 2;
481 }
482
483 static int validate_move_rules(const GameState *gamestate, const Move *move) {
365 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0); 484 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0);
366 485
367 /* validate indices (don't trust opponent) */ 486 /* validate indices (don't trust opponent) */
368 if (!isidx(move->fromrow) || !isidx(move->fromfile) || 487 if (!isidx(move->fromrow) || !isidx(move->fromfile) ||
369 !isidx(move->torow) || !isidx(move->tofile)) { 488 !isidx(move->torow) || !isidx(move->tofile)) {
370 return INVALID_MOVE_SYNTAX; 489 return INVALID_MOVE_SYNTAX;
371 } 490 }
372 491
373 /* must move */ 492 /* must move */
374 if (move->fromfile == move->tofile && move->fromrow == move->torow) { 493 if (move->fromfile == move->tofile && move->fromrow == move->torow) {
375 return INVALID_MOVE_SYNTAX; 494 return INVALID_MOVE_SYNTAX;
376 } 495 }
377 496
378 /* does piece exist */ 497 /* does piece exist */
379 if (piece_at(gamestate, msrc(move)) != move->piece) { 498 if (piece_at(gamestate, msrc(move)) != move->piece) {
380 return PIECE_NOT_FOUND; 499 return PIECE_NOT_FOUND;
381 } 500 }
382 501
383 /* is there any piece at the destination? */ 502 /* is there any piece at the destination? */
384 Piece piece_at_dst = piece_at(gamestate, mdst(move)); 503 Piece piece_at_dst = piece_at(gamestate, mdst(move));
385 504
386 /* can't capture own pieces */ 505 /* can't capture own pieces */
387 if (piece_color(piece_at_dst) == piece_color(move->piece)) { 506 if (piece_color(piece_at_dst) == piece_color(move->piece)) {
388 return RULES_VIOLATED; 507 return RULES_VIOLATED;
389 } 508 }
390 509
395 !enpassant_threat_exists(gamestate, 514 !enpassant_threat_exists(gamestate,
396 move->fromrow, move->tofile)) { 515 move->fromrow, move->tofile)) {
397 return INVALID_MOVE_SYNTAX; 516 return INVALID_MOVE_SYNTAX;
398 } 517 }
399 } 518 }
400 519
401 /* validate individual rules */ 520 /* validate individual rules */
402 bool chkrules; 521 bool chkrules;
403 switch (piece_type(move->piece)) { 522 switch (piece_type(move->piece)) {
404 case PAWN: 523 case PAWN:
405 chkrules = pawn_chkrules(gamestate, move) && 524 chkrules = pawn_chkrules(gamestate, move) &&
406 !pawn_isblocked(gamestate, move); 525 !pawn_isblocked(gamestate, move);
407 break; 526 break;
408 case ROOK: 527 case ROOK:
409 chkrules = rook_chkrules(move) && 528 chkrules = rook_chkrules(move) &&
410 !rook_isblocked(gamestate, move); 529 !rook_isblocked(gamestate, move);
411 break; 530 break;
412 case KNIGHT: 531 case KNIGHT:
413 chkrules = knight_chkrules(move); /* knight is never blocked */ 532 chkrules = knight_chkrules(move) &&
414 break; 533 !knight_isblocked(gamestate, move);
415 case BISHOP: 534 break;
416 chkrules = bishop_chkrules(move) && 535 case BISHOP:
417 !bishop_isblocked(gamestate, move); 536 chkrules = bishop_chkrules(move) &&
418 break; 537 !bishop_isblocked(gamestate, move);
419 case QUEEN: 538 break;
420 chkrules = queen_chkrules(move) && 539 case QUEEN:
421 !queen_isblocked(gamestate, move); 540 chkrules = queen_chkrules(move) &&
422 break; 541 !queen_isblocked(gamestate, move);
423 case KING: 542 break;
424 chkrules = king_chkrules(gamestate, move) && 543 case KING:
425 !king_isblocked(gamestate, move); 544 chkrules = king_chkrules(gamestate, move) &&
426 break; 545 !king_isblocked(gamestate, move);
427 default: 546 break;
428 return INVALID_MOVE_SYNTAX; 547 default:
429 } 548 return INVALID_MOVE_SYNTAX;
430 549 }
431 return chkrules ? VALID_MOVE_SEMANTICS : RULES_VIOLATED; 550
432 }
433
434 int validate_move(GameState *gamestate, Move *move) {
435
436 int result = validate_move_rules(gamestate, move);
437
438 /* cancel processing to save resources */ 551 /* cancel processing to save resources */
439 if (result != VALID_MOVE_SEMANTICS) { 552 if (!chkrules) {
440 return result; 553 return RULES_VIOLATED;
441 } 554 }
442 555
443 /* simulate move for check validation */ 556 /* test if the move would expose our own king */
444 GameState simulation = gamestate_copy_sim(gamestate); 557 GameState simulation = gamestate_copy_sim(gamestate);
445 Move simmove = *move; 558 Move simmove = *move;
446 apply_move_impl(&simulation, &simmove, true); 559 apply_move(&simulation, &simmove);
447
448 /* find kings for check validation */
449 Color piececolor = piece_color(move->piece); 560 Color piececolor = piece_color(move->piece);
450 Color oppcolor = opponent_color(piececolor); 561 Color oppcolor = opponent_color(piececolor);
451 562 File kingfile = 0;
452 File mykingfile = 0, opkingfile = 0; 563 Row kingrow = 0;
453 Row mykingrow = 0, opkingrow = 0;
454 for (Row row = 0 ; row < 8 ; row++) { 564 for (Row row = 0 ; row < 8 ; row++) {
455 for (File file = 0 ; file < 8 ; file++) { 565 for (File file = 0 ; file < 8 ; file++) {
456 Piece p = piece_at(&simulation, row, file); 566 Piece p = piece_at(&simulation, row, file);
457 if (p == mkpiece(KING, piececolor)) { 567 if (p == mkpiece(KING, piececolor)) {
458 mykingfile = file; 568 kingfile = file;
459 mykingrow = row; 569 kingrow = row;
460 } else if (p == mkpiece(KING, oppcolor)) { 570 }
461 opkingfile = file; 571 }
462 opkingrow = row; 572 }
463 } 573 int result = VALID_MOVE_SEMANTICS;
464 } 574 if (is_covered(&simulation, kingrow, kingfile, oppcolor)) {
465 }
466
467 /* don't move into or stay in check position */
468 if (is_covered(&simulation, mykingrow, mykingfile, oppcolor)) {
469 gamestate_cleanup(&simulation); 575 gamestate_cleanup(&simulation);
470 if (piece_type(move->piece) == KING) { 576 if (piece_type(move->piece) == KING) {
471 return KING_MOVES_INTO_CHECK; 577 result = KING_MOVES_INTO_CHECK;
472 } else { 578 } else {
473 if (gamestate->moves[gamestate->movecount - 1].check) { 579 if (is_check_position(gamestate)) {
474 return KING_IN_CHECK; 580 result = KING_IN_CHECK;
475 } else { 581 } else {
476 return PIECE_PINNED; 582 result = PIECE_PINNED;
477 } 583 }
478 } 584 }
479 } 585 }
480
481 /* correct check and checkmate flags (move is still valid) */
482 Move threats[16];
483 size_t threatcount;
484 move->check = get_threats(&simulation, opkingrow, opkingfile,
485 piececolor, threats, &threatcount);
486
487 if (move->check) {
488 /* determine possible escape fields */
489 bool canescape = false;
490 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) {
491 for (int df = -1 ; df <= 1 && !canescape ; df++) {
492 if (dr == 0 && df == 0) continue;
493 if (!isidx(opkingrow + dr)) continue;
494 if (!isidx(opkingfile + df)) continue;
495 Row er = opkingrow + dr;
496 File ef = opkingfile + df;
497
498 /* check if escape field is already covered (threatened) */
499 if (is_covered(&simulation, er, ef, piececolor))
500 continue;
501
502 /* check if piece of the king's color blocks the field */
503 if (piece_color(simulation.board[er][ef]) == oppcolor)
504 continue;
505
506 /* check if an attacking piece blocks the field */
507 if (piece_color(simulation.board[er][ef]) == piececolor) {
508 /* test if the king can fight back */
509 GameState sim_retaliate = gamestate_copy_sim(&simulation);
510 Move move_retaliate = {0};
511 move_retaliate.piece = mkpiece(KING, oppcolor);
512 move_retaliate.fromrow = opkingrow;
513 move_retaliate.fromfile = opkingfile;
514 move_retaliate.torow = er;
515 move_retaliate.tofile = ef;
516 move_retaliate.capture = 1;
517 apply_move_impl(&sim_retaliate, &move_retaliate, true);
518 canescape = !is_covered(&sim_retaliate, er, ef, piececolor);
519 gamestate_cleanup(&sim_retaliate);
520 } else {
521 /* the field is not covered and unoccupied */
522 canescape = true;
523 }
524 }
525 }
526
527 /* can't escape, can the king be rescued? */
528 if (!canescape && threatcount == 1) {
529 canescape = is_protected(&simulation,
530 threats[0].fromrow, threats[0].fromfile, oppcolor);
531 }
532
533 /* can't capture, can he block? */
534 if (!canescape && threatcount == 1) {
535 Move *threat = &(threats[0]);
536 unsigned tptype = piece_type(threat->piece);
537
538 /* knight, pawns and the king cannot be blocked */
539 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) {
540 if (threat->fromrow == threat->torow) {
541 /* rook aspect (on row) */
542 int d = threat->tofile > threat->fromfile ? 1 : -1;
543 File file = threat->fromfile;
544 while (!canescape && file != threat->tofile - d) {
545 file += d;
546 canescape |= is_protected(&simulation,
547 threat->torow, file, oppcolor);
548 }
549 } else if (threat->fromfile == threat->tofile) {
550 /* rook aspect (on file) */
551 int d = threat->torow > threat->fromrow ? 1 : -1;
552 Row row = threat->fromrow;
553 while (!canescape && row != threat->torow - d) {
554 row += d;
555 canescape |= is_protected(&simulation,
556 row, threat->tofile, oppcolor);
557 }
558 } else {
559 /* bishop aspect */
560 int dr = threat->torow > threat->fromrow ? 1 : -1;
561 int df = threat->tofile > threat->fromfile ? 1 : -1;
562
563 Row row = threat->fromrow;
564 File file = threat->fromfile;
565 while (!canescape && file != threat->tofile - df
566 && row != threat->torow - dr) {
567 row += dr;
568 file += df;
569 canescape |= is_protected(&simulation, row, file,
570 oppcolor);
571 }
572 }
573 }
574 }
575
576 if (!canescape) {
577 gamestate->checkmate = true;
578 }
579 }
580
581 gamestate_cleanup(&simulation); 586 gamestate_cleanup(&simulation);
587
588 return result;
589 }
590
591 int validate_move(const GameState *gamestate, const Move *move) {
592 int result = validate_move_rules(gamestate, move);
593 if (result != VALID_MOVE_SEMANTICS) {
594 return result;
595 }
596
597 /* validate check and checkmate flags */
598 int cocm = determine_check_or_checkmate(gamestate, move);
599 if (cocm == 2) {
600 if (!move->checkmate) {
601 return MISSING_CHECKMATE;
602 }
603 } else if (cocm == 1) {
604 if (!move->check) {
605 return MISSING_CHECK;
606 }
607 } else if (move->checkmate) {
608 return INVALID_CHECKMATE;
609 } else if (move->check) {
610 return INVALID_CHECK;
611 }
612
582 613
583 return VALID_MOVE_SEMANTICS; 614 return VALID_MOVE_SEMANTICS;
584 } 615 }
585 616
586 Piece piece_at(const GameState *gamestate, Row row, File file) { 617 Piece piece_at(const GameState *gamestate, Row row, File file) {
589 620
590 void piece_set(GameState *gamestate, Row row, File file, Piece piece) { 621 void piece_set(GameState *gamestate, Row row, File file, Piece piece) {
591 gamestate->board[row][file] = piece; 622 gamestate->board[row][file] = piece;
592 } 623 }
593 624
594 bool get_threats(GameState *gamestate, Row row, File file, 625 bool get_candidates(const GameState *gamestate, Row row, File file,
595 Color color, Move *threats, size_t *threatcount) { 626 Color color, Move *moves, size_t *movecount) {
596 Move candidates[32]; 627 Move candidates[32];
597 size_t ccount = 0; 628 size_t ccount = 0;
598 for (Row r = 0 ; r < 8 ; r++) { 629 for (Row r = 0 ; r < 8 ; r++) {
599 for (File f = 0 ; f < 8 ; f++) { 630 for (File f = 0 ; f < 8 ; f++) {
600 if (piece_color(gamestate->board[r][f]) == color) { 631 Piece p = piece_at(gamestate, r, f);
632 if (piece_color(p) == color) {
601 /* non-capturing move */ 633 /* non-capturing move */
602 memset(&(candidates[ccount]), 0, sizeof(Move)); 634 memset(&(candidates[ccount]), 0, sizeof(Move));
603 Piece p = piece_at(gamestate, r, f);
604 candidates[ccount].piece = p; 635 candidates[ccount].piece = p;
605 candidates[ccount].fromrow = r; 636 candidates[ccount].fromrow = r;
606 candidates[ccount].fromfile = f; 637 candidates[ccount].fromfile = f;
607 candidates[ccount].torow = row; 638 candidates[ccount].torow = row;
608 candidates[ccount].tofile = file; 639 candidates[ccount].tofile = file;
613 ccount++; 644 ccount++;
614 645
615 /* capturing move */ 646 /* capturing move */
616 memcpy(&(candidates[ccount]), 647 memcpy(&(candidates[ccount]),
617 &(candidates[ccount-1]), sizeof(Move)); 648 &(candidates[ccount-1]), sizeof(Move));
618 candidates[ccount].capture = 1; 649 candidates[ccount].capture = true;
619 ccount++; 650 ccount++;
620 } 651 }
621 } 652 }
622 } 653 }
623 654
624 if (threatcount) { 655 if (movecount) {
625 *threatcount = 0; 656 *movecount = 0;
626 } 657 }
627 658
628
629 bool result = false; 659 bool result = false;
630 660
631 for (size_t i = 0 ; i < ccount ; i++) { 661 for (size_t i = 0 ; i < ccount ; i++) {
632 if (validate_move_rules(gamestate, &(candidates[i])) 662 if (validate_move_rules(gamestate, &(candidates[i]))
633 == VALID_MOVE_SEMANTICS) { 663 == VALID_MOVE_SEMANTICS) {
634 result = true; 664 result = true;
665 if (moves && movecount) {
666 moves[(*movecount)++] = candidates[i];
667 }
668 }
669 }
670
671 return result;
672 }
673
674 bool get_threats(const GameState *gamestate, Row row, File file,
675 Color color, Move *threats, size_t *threatcount) {
676
677 /* simulate a capturing move on the target position */
678 Color opcolor = opponent_color(color);
679 GameState simulation = gamestate_copy_sim(gamestate);
680 if (piece_color(piece_at(&simulation, row, file)) != opcolor) {
681 /* set a fake pawn if the field is not occupied by the opponent */
682 piece_set(&simulation, row, file, mkpiece(PAWN, opcolor));
683 }
684
685 Move candidates[16];
686 size_t ccount = 0;
687 for (Row r = 0 ; r < 8 ; r++) {
688 for (File f = 0 ; f < 8 ; f++) {
689 Piece p = piece_at(&simulation, r, f);
690 if (piece_color(p) == color) {
691 memset(&(candidates[ccount]), 0, sizeof(Move));
692 candidates[ccount].piece = p;
693 candidates[ccount].fromrow = r;
694 candidates[ccount].fromfile = f;
695 candidates[ccount].torow = row;
696 candidates[ccount].tofile = file;
697 candidates[ccount].capture = true;
698 ccount++;
699 }
700 }
701 }
702
703 if (threatcount) {
704 *threatcount = 0;
705 }
706
707 bool result = false;
708
709 for (size_t i = 0 ; i < ccount ; i++) {
710 if (validate_move_rules(&simulation, &(candidates[i]))
711 == VALID_MOVE_SEMANTICS) {
712 result = true;
635 if (threats && threatcount) { 713 if (threats && threatcount) {
636 threats[(*threatcount)++] = candidates[i]; 714 threats[(*threatcount)++] = candidates[i];
637 } 715 }
638 } 716 }
639 } 717 }
718
719 gamestate_cleanup(&simulation);
640 720
641 return result; 721 return result;
642 } 722 }
643 723
644 bool is_pinned(GameState *gamestate, Move *move) { 724 bool is_pinned(const GameState *gamestate, const Move *move) {
645 Color color = piece_color(move->piece); 725 Color color = piece_color(move->piece);
646 726
647 GameState simulation = gamestate_copy_sim(gamestate); 727 GameState simulation = gamestate_copy_sim(gamestate);
648 Move simmove = *move; 728 Move simmove = *move;
649 apply_move(&simulation, &simmove); 729 apply_move(&simulation, &simmove);
664 gamestate_cleanup(&simulation); 744 gamestate_cleanup(&simulation);
665 745
666 return covered; 746 return covered;
667 } 747 }
668 748
669 bool get_real_threats(GameState *gamestate, Row row, File file, 749 bool get_real_threats(const GameState *gamestate, Row row, File file,
670 Color color, Move *threats, size_t *threatcount) { 750 Color color, Move *threats, size_t *threatcount) {
671 751
672 if (threatcount) { 752 if (threatcount) {
673 *threatcount = 0; 753 *threatcount = 0;
674 } 754 }
708 } else { 788 } else {
709 return false; 789 return false;
710 } 790 }
711 } 791 }
712 792
713 static int getlocation(GameState *gamestate, Move *move) { 793 static int getlocation(const GameState *gamestate, Move *move) {
714 794
715 Color color = piece_color(move->piece); 795 Color color = piece_color(move->piece);
716 bool incheck = false; 796 bool incheck = false;
717 if (gamestate->movecount > 0) { 797 if (gamestate->movecount > 0) {
718 incheck = gamestate->moves[gamestate->movecount - 1].check; 798 incheck = is_check_position(gamestate);
719 } 799 }
720 800
721 Move threats[16], *threat = NULL; 801 Move candidates[16], *candidate = NULL;
722 size_t threatcount; 802 size_t candidatecount;
723 803
724 /* determine all threats and sort out the unreal threats on our own */ 804 /* determine all candidate moves and sort out the invalid ones */
725 if (get_threats(gamestate, move->torow, move->tofile, color, 805 if (get_candidates(gamestate, move->torow, move->tofile, color,
726 threats, &threatcount)) { 806 candidates, &candidatecount)) {
727 807
728 bool found = false; 808 bool found = false;
729 809
730 /* find threats for the specified position */ 810 for (size_t i = 0 ; i < candidatecount ; i++) {
731 for (size_t i = 0 ; i < threatcount ; i++) { 811 /* filter by partial fromrow/fromfile information */
732 if (threats[i].piece == move->piece && 812 if (candidates[i].piece == move->piece &&
733 (move->fromrow == POS_UNSPECIFIED || 813 (move->fromrow == POS_UNSPECIFIED ||
734 move->fromrow == threats[i].fromrow) && 814 move->fromrow == candidates[i].fromrow) &&
735 (move->fromfile == POS_UNSPECIFIED || 815 (move->fromfile == POS_UNSPECIFIED ||
736 move->fromfile == threats[i].fromfile)) { 816 move->fromfile == candidates[i].fromfile)) {
737 817
738 /* found a threat, here it does not matter if it's valid! */ 818 /* found a candidate, here it does not matter if it's valid! */
739 found = true; 819 found = true;
740 820
741 /* discard pinned pieces */ 821 /* discard pinned pieces */
742 if (!is_pinned(gamestate, &(threats[i]))) { 822 if (!is_pinned(gamestate, &(candidates[i]))) {
743 if (threat) { 823 if (candidate) {
744 /* we've already found a valid threat */ 824 /* we've already found a valid candidate */
745 return AMBIGUOUS_MOVE; 825 return AMBIGUOUS_MOVE;
746 } else { 826 } else {
747 threat = &(threats[i]); 827 candidate = &(candidates[i]);
748 } 828 }
749 } 829 }
750 } 830 }
751 } 831 }
752 832
753 /* can't threaten specified position */ 833 /* no valid candidate left */
754 if (!threat) { 834 if (!candidate) {
755 if (found) { 835 if (found) {
756 if (piece_type(move->piece) == KING) { 836 if (piece_type(move->piece) == KING) {
757 return KING_MOVES_INTO_CHECK; 837 return KING_MOVES_INTO_CHECK;
758 } else if (incheck) { 838 } else if (incheck) {
759 return KING_IN_CHECK; 839 return KING_IN_CHECK;
763 } else { 843 } else {
764 return PIECE_NOT_FOUND; 844 return PIECE_NOT_FOUND;
765 } 845 }
766 } 846 }
767 847
768 /* found a threat, copy the source location */ 848 /* found a candidate, copy the source location */
769 move->fromrow = threat->fromrow; 849 move->fromrow = candidate->fromrow;
770 move->fromfile = threat->fromfile; 850 move->fromfile = candidate->fromfile;
771 return VALID_MOVE_SYNTAX; 851 return VALID_MOVE_SYNTAX;
772 } else { 852 } else {
773 return PIECE_NOT_FOUND; 853 return PIECE_NOT_FOUND;
774 } 854 }
775 } 855 }
788 868
789 /* evaluate check/checkmate flags */ 869 /* evaluate check/checkmate flags */
790 if (mstr[len-1] == '+' || mstr[len-1] == '#') { 870 if (mstr[len-1] == '+' || mstr[len-1] == '#') {
791 len--; mstr[len] = '\0'; 871 len--; mstr[len] = '\0';
792 move->check = true; 872 move->check = true;
873 move->checkmate = mstr[len-1] == '#';
793 } 874 }
794 875
795 /* evaluate promotion */ 876 /* evaluate promotion */
796 if (len > 3 && mstr[len-2] == '=') { 877 if (len > 3 && mstr[len-2] == '=') {
797 move->promotion = getpiece(mstr[len-1], color); 878 move->promotion = getpiece(mstr[len-1], color);
827 move->piece = mkpiece(PAWN, color); 908 move->piece = mkpiece(PAWN, color);
828 move->fromfile = fileidx(mstr[0]); 909 move->fromfile = fileidx(mstr[0]);
829 } 910 }
830 if (mstr[1] == 'x') { 911 if (mstr[1] == 'x') {
831 /* capture (e.g. "Nxf3", "dxe5") */ 912 /* capture (e.g. "Nxf3", "dxe5") */
832 move->capture = 1; 913 move->capture = true;
833 } else { 914 } else {
834 /* move (e.g. "Ndf3", "N2c3", "e2e4") */ 915 /* move (e.g. "Ndf3", "N2c3", "e2e4") */
835 if (isfile(mstr[1])) { 916 if (isfile(mstr[1])) {
836 move->fromfile = fileidx(mstr[1]); 917 move->fromfile = fileidx(mstr[1]);
837 /* when the piece is a pawn, second char cannot be a file */ 918 /* when the piece is a pawn, second char cannot be a file */
853 move->tofile = fileidx('c'); 934 move->tofile = fileidx('c');
854 move->fromrow = move->torow = color == WHITE ? 0 : 7; 935 move->fromrow = move->torow = color == WHITE ? 0 : 7;
855 } else { 936 } else {
856 move->piece = getpiece(mstr[0], color); 937 move->piece = getpiece(mstr[0], color);
857 if (mstr[2] == 'x') { 938 if (mstr[2] == 'x') {
858 move->capture = 1; 939 move->capture = true;
859 if (move->piece) { 940 if (move->piece) {
860 /* capture (e.g. "Ndxf3" or "R1xh3") */ 941 /* capture (e.g. "Ndxf3" or "R1xh3") */
861 if (isfile(mstr[1])) { 942 if (isfile(mstr[1])) {
862 move->fromfile = fileidx(mstr[1]); 943 move->fromfile = fileidx(mstr[1]);
863 } else if (isrow(mstr[1])) { 944 } else if (isrow(mstr[1])) {
880 move->torow = rowidx(mstr[4]); 961 move->torow = rowidx(mstr[4]);
881 } 962 }
882 } else if (len == 6) { 963 } else if (len == 6) {
883 /* long notation capture (e.g. "Nc5xf3") */ 964 /* long notation capture (e.g. "Nc5xf3") */
884 if (mstr[3] == 'x') { 965 if (mstr[3] == 'x') {
885 move->capture = 1; 966 move->capture = true;
886 move->piece = getpiece(mstr[0], color); 967 move->piece = getpiece(mstr[0], color);
887 move->fromfile = fileidx(mstr[1]); 968 move->fromfile = fileidx(mstr[1]);
888 move->fromrow = rowidx(mstr[2]); 969 move->fromrow = rowidx(mstr[2]);
889 move->tofile = fileidx(mstr[4]); 970 move->tofile = fileidx(mstr[4]);
890 move->torow = rowidx(mstr[5]); 971 move->torow = rowidx(mstr[5]);
912 } 993 }
913 994
914 return VALID_MOVE_SYNTAX; 995 return VALID_MOVE_SYNTAX;
915 } 996 }
916 997
917 int eval_move(GameState *gamestate, const char *mstr, 998 int eval_move2(const GameState *gamestate,
918 Move *move, Color color) { 999 const char *mstr, Color color, Move *move, bool lazy) {
919 int result = eval_move1(mstr, move, color); 1000 int result = eval_move1(mstr, move, color);
920 if (result == VALID_MOVE_SYNTAX) { 1001 if (result == VALID_MOVE_SYNTAX) {
921 if (move->fromfile == POS_UNSPECIFIED 1002 if (move->fromfile == POS_UNSPECIFIED
922 || move->fromrow == POS_UNSPECIFIED) { 1003 || move->fromrow == POS_UNSPECIFIED) {
923 return getlocation(gamestate, move); 1004 result = getlocation(gamestate, move);
1005 }
1006 if (result == VALID_MOVE_SYNTAX) {
1007 /* correct check/checkmate flags */
1008 if (lazy) {
1009 move->check = move->checkmate = false;
1010 switch (determine_check_or_checkmate(gamestate, move)) {
1011 case 2: move->checkmate = true;
1012 case 1: move->check = true;
1013 }
1014 }
1015
1016 /* format the move string */
1017 format_move(gamestate, move);
924 } 1018 }
925 } 1019 }
926 return result; 1020 return result;
1021 }
1022
1023 int eval_move_lazy(const GameState *gamestate,
1024 const char *mstr, Color color, Move *move) {
1025 return eval_move2(gamestate, mstr, color, move, true);
1026 }
1027
1028 int eval_move(const GameState *gamestate,
1029 const char *mstr, Color color, Move *move) {
1030 return eval_move2(gamestate, mstr, color, move, false);
927 } 1031 }
928 1032
929 int check_move(const char *mstr, Color color) { 1033 int check_move(const char *mstr, Color color) {
930 Move move; 1034 Move move;
931 return eval_move1(mstr, &move, color); 1035 return eval_move1(mstr, &move, color);
932 } 1036 }
933 1037
934 bool is_protected(GameState *gamestate, Row row, File file, Color color) { 1038 bool is_protected(const GameState *gamestate, Row row, File file, Color color) {
935 Move threats[16]; 1039 Move threats[16];
936 size_t threatcount; 1040 size_t threatcount;
937 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) { 1041 if (get_real_threats(gamestate, row, file, color, threats, &threatcount)) {
938 for (size_t i = 0 ; i < threatcount ; i++) { 1042 for (size_t i = 0 ; i < threatcount ; i++) {
939 if (threats[i].piece != (color|KING)) { 1043 if (threats[i].piece != (color|KING)) {
944 } else { 1048 } else {
945 return false; 1049 return false;
946 } 1050 }
947 } 1051 }
948 1052
949 uint16_t remaining_movetime(GameState *gamestate, Color color) { 1053 uint16_t remaining_movetime(const GameState *gamestate, Color color) {
950 unsigned move_number = gamestate->movecount; 1054 unsigned move_number = gamestate->movecount;
951 if (color == BLACK) { 1055 if (color == BLACK) {
952 move_number |= 1; 1056 move_number |= 1;
953 } else { 1057 } else {
954 move_number = (move_number + 1) & ~1; 1058 move_number = (move_number + 1) & ~1;
955 } 1059 }
956 return remaining_movetime2(gamestate, move_number); 1060 return remaining_movetime2(gamestate, move_number);
957 } 1061 }
958 1062
959 uint16_t remaining_movetime2(GameState *gamestate, unsigned move_number) { 1063 uint16_t remaining_movetime2(const GameState *gamestate, unsigned move_number) {
960 if (!gamestate->info.timecontrol) { 1064 if (!gamestate->info.timecontrol) {
961 return 0; 1065 return 0;
962 } 1066 }
963 1067
964 unsigned total_time = gamestate->info.time; 1068 unsigned total_time = gamestate->info.time;
1021 } else { 1125 } else {
1022 return snprintf(str, 6, "%02u:%02u", minutes, seconds); 1126 return snprintf(str, 6, "%02u:%02u", minutes, seconds);
1023 } 1127 }
1024 } 1128 }
1025 1129
1026 bool check_threefold_repetition(GameState *gamestate) { 1130 bool check_threefold_repetition(const GameState *gamestate) {
1027 // TODO: implement threefold repetition detection 1131 // TODO: implement threefold repetition detection
1028 return false; 1132 return false;
1029 } 1133 }

mercurial