| 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); |
| 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; |
| 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)) { |