| 124 move->movetime = cur - last; |
124 move->movetime = cur - last; |
| 125 } |
125 } |
| 126 } |
126 } |
| 127 |
127 |
| 128 size_t piece_moves_allowed(const GameState *gamestate, |
128 size_t piece_moves_allowed(const GameState *gamestate, |
| 129 Row r, File f, Move *moves) { |
129 Rank r, File f, Move *moves) { |
| 130 Piece p = piece_at(gamestate, r, f); |
130 Piece p = piece_at(gamestate, r, f); |
| 131 Color c = piece_color(p); |
131 Color c = piece_color(p); |
| 132 switch (piece_type(p)) { |
132 switch (piece_type(p)) { |
| 133 case KING: |
133 case KING: |
| 134 return king_moves_allowed(gamestate, c, r, f, moves); |
134 return king_moves_allowed(gamestate, c, r, f, moves); |
| 150 static bool check_stalemate(const GameState *gamestate) { |
150 static bool check_stalemate(const GameState *gamestate) { |
| 151 Color next_player = gamestate->movecount % 2 == 0 ? WHITE : BLACK; |
151 Color next_player = gamestate->movecount % 2 == 0 ? WHITE : BLACK; |
| 152 |
152 |
| 153 /* scan the board for pieces of the next player's color */ |
153 /* scan the board for pieces of the next player's color */ |
| 154 Move moves[QUEEN_MOVES_MAX]; |
154 Move moves[QUEEN_MOVES_MAX]; |
| 155 for (Row r = 0; r < 8; r++) { |
155 for (Rank r = 0; r < 8; r++) { |
| 156 for (File f = 0; f < 8; f++) { |
156 for (File f = 0; f < 8; f++) { |
| 157 if (piece_color(piece_at(gamestate, r, f)) == next_player |
157 if (piece_color(piece_at(gamestate, r, f)) == next_player |
| 158 && piece_moves_allowed(gamestate, r, f, moves) > 0) { |
158 && piece_moves_allowed(gamestate, r, f, moves) > 0) { |
| 159 return false; |
159 return false; |
| 160 } |
160 } |
| 201 /* count the available pieces */ |
201 /* count the available pieces */ |
| 202 unsigned piece_count[7] = {0}; |
202 unsigned piece_count[7] = {0}; |
| 203 unsigned op_piece_count[7] = {0}; |
203 unsigned op_piece_count[7] = {0}; |
| 204 bool has_bbishop = false, has_wbishop = false; |
204 bool has_bbishop = false, has_wbishop = false; |
| 205 bool op_has_bbishop = false, op_has_wbishop = false; |
205 bool op_has_bbishop = false, op_has_wbishop = false; |
| 206 for (Row r = 0 ; r < 8 ; r++) { |
206 for (Rank r = 0 ; r < 8 ; r++) { |
| 207 for (File f = 0 ; f < 8 ; f++) { |
207 for (File f = 0 ; f < 8 ; f++) { |
| 208 Piece p = piece_at(gamestate, r, f); |
208 Piece p = piece_at(gamestate, r, f); |
| 209 if (piece_color(p) == color) { |
209 if (piece_color(p) == color) { |
| 210 piece_count[piece_type(p)]++; |
210 piece_count[piece_type(p)]++; |
| 211 if (piece_type(p) == BISHOP) { |
211 if (piece_type(p) == BISHOP) { |
| 318 |
318 |
| 319 /* applies the move without recalculating gamestate flags */ |
319 /* applies the move without recalculating gamestate flags */ |
| 320 static void apply_move_internal(GameState *gamestate, Move *move) { |
320 static void apply_move_internal(GameState *gamestate, Move *move) { |
| 321 /* en passant capture */ |
321 /* en passant capture */ |
| 322 if (move->capture && piece_type(move->piece) == PAWN && |
322 if (move->capture && piece_type(move->piece) == PAWN && |
| 323 piece_at(gamestate, mdst(move)) == 0) { |
323 piece_at(gamestate, move->torank, move->tofile) == 0) { |
| 324 piece_remove(gamestate, move->fromrow, move->tofile); |
324 piece_remove(gamestate, move->fromrank, move->tofile); |
| 325 } |
325 } |
| 326 |
326 |
| 327 /* remove old en passant threats */ |
327 /* remove old en passant threats */ |
| 328 for (File file = 0 ; file < 8 ; file++) { |
328 for (File file = 0 ; file < 8 ; file++) { |
| 329 enpassant_threat_remove(gamestate, 3, file); |
329 enpassant_threat_remove(gamestate, 3, file); |
| 330 enpassant_threat_remove(gamestate, 4, file); |
330 enpassant_threat_remove(gamestate, 4, file); |
| 331 } |
331 } |
| 332 |
332 |
| 333 /* move (and maybe capture or promote) */ |
333 /* move (and maybe capture or promote) */ |
| 334 piece_remove(gamestate, msrc(move)); |
334 piece_remove(gamestate, move->fromrank, move->fromfile); |
| 335 if (move->promotion) { |
335 if (move->promotion) { |
| 336 piece_set(gamestate, mdst(move), move->promotion); |
336 piece_set(gamestate, move->torank, move->tofile, move->promotion); |
| 337 } else { |
337 } else { |
| 338 piece_set(gamestate, mdst(move), move->piece); |
338 piece_set(gamestate, move->torank, move->tofile, move->piece); |
| 339 } |
339 } |
| 340 |
340 |
| 341 /* add new en passant threat */ |
341 /* add new en passant threat */ |
| 342 if (piece_type(move->piece) == PAWN && ( |
342 if (piece_type(move->piece) == PAWN && ( |
| 343 (move->fromrow == 1 && move->torow == 3) || |
343 (move->fromrank == 1 && move->torank == 3) || |
| 344 (move->fromrow == 6 && move->torow == 4))) { |
344 (move->fromrank == 6 && move->torank == 4))) { |
| 345 enpassant_threat_add(gamestate, move->torow, move->tofile); |
345 enpassant_threat_add(gamestate, move->torank, move->tofile); |
| 346 } |
346 } |
| 347 |
347 |
| 348 /* castling */ |
348 /* castling */ |
| 349 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { |
349 if (piece_type(move->piece) == KING && move->fromfile == fileidx('e')) { |
| 350 const Color color = piece_color(move->piece); |
350 const Color color = piece_color(move->piece); |
| 351 if (move->tofile == fileidx('g')) { |
351 if (move->tofile == fileidx('g')) { |
| 352 gamestate->board[move->torow][fileidx('h')] = 0; |
352 gamestate->board[move->torank][fileidx('h')] = 0; |
| 353 gamestate->board[move->torow][fileidx('f')] = mkpiece(ROOK, color); |
353 gamestate->board[move->torank][fileidx('f')] = mkpiece(ROOK, color); |
| 354 } else if (move->tofile == fileidx('c')) { |
354 } else if (move->tofile == fileidx('c')) { |
| 355 gamestate->board[move->torow][fileidx('a')] = 0; |
355 gamestate->board[move->torank][fileidx('a')] = 0; |
| 356 gamestate->board[move->torow][fileidx('d')] = mkpiece(ROOK, color); |
356 gamestate->board[move->torank][fileidx('d')] = mkpiece(ROOK, color); |
| 357 } |
357 } |
| 358 } |
358 } |
| 359 |
359 |
| 360 /* add move to the moves array and the new position to the FEN array */ |
360 /* add move to the moves array and the new position to the FEN array */ |
| 361 if (gamestate->movecount == gamestate->movecapacity) { |
361 if (gamestate->movecount == gamestate->movecapacity) { |
| 419 |
419 |
| 420 /* find the opposing king */ |
420 /* find the opposing king */ |
| 421 Color piececolor = piece_color(move->piece); |
421 Color piececolor = piece_color(move->piece); |
| 422 Color oppcolor = opponent_color(piececolor); |
422 Color oppcolor = opponent_color(piececolor); |
| 423 File opkingfile = 0; |
423 File opkingfile = 0; |
| 424 Row opkingrow = 0; |
424 Rank opkingrank = 0; |
| 425 for (Row row = 0 ; row < 8 ; row++) { |
425 for (Rank rank = 0 ; rank < 8 ; rank++) { |
| 426 for (File file = 0 ; file < 8 ; file++) { |
426 for (File file = 0 ; file < 8 ; file++) { |
| 427 Piece p = piece_at(&simulation, row, file); |
427 Piece p = piece_at(&simulation, rank, file); |
| 428 if (p == mkpiece(KING, oppcolor)) { |
428 if (p == mkpiece(KING, oppcolor)) { |
| 429 opkingfile = file; |
429 opkingfile = file; |
| 430 opkingrow = row; |
430 opkingrank = rank; |
| 431 } |
431 } |
| 432 } |
432 } |
| 433 } |
433 } |
| 434 |
434 |
| 435 /* determine if the opposing king is now threatened */ |
435 /* determine if the opposing king is now threatened */ |
| 436 Move threats[16]; |
436 Move threats[16]; |
| 437 size_t threatcount; |
437 size_t threatcount; |
| 438 bool incheck = get_threats(&simulation, opkingrow, opkingfile, |
438 bool incheck = get_threats(&simulation, opkingrank, opkingfile, |
| 439 piececolor, threats, &threatcount); |
439 piececolor, threats, &threatcount); |
| 440 |
440 |
| 441 if (!incheck) { |
441 if (!incheck) { |
| 442 gamestate_cleanup(&simulation); |
442 gamestate_cleanup(&simulation); |
| 443 return 0; |
443 return 0; |
| 446 /* determine possible escape fields */ |
446 /* determine possible escape fields */ |
| 447 bool canescape = false; |
447 bool canescape = false; |
| 448 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
448 for (int dr = -1 ; dr <= 1 && !canescape ; dr++) { |
| 449 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
449 for (int df = -1 ; df <= 1 && !canescape ; df++) { |
| 450 if (dr == 0 && df == 0) continue; |
450 if (dr == 0 && df == 0) continue; |
| 451 Row er = opkingrow + dr; |
451 Rank er = opkingrank + dr; |
| 452 File ef = opkingfile + df; |
452 File ef = opkingfile + df; |
| 453 if (!isidx(er) || !isidx(ef)) continue; |
453 if (!isidx(er) || !isidx(ef)) continue; |
| 454 |
454 |
| 455 /* check if piece of the king's color blocks the field */ |
455 /* check if piece of the king's color blocks the field */ |
| 456 if (piece_color(simulation.board[er][ef]) == oppcolor) |
456 if (piece_color(simulation.board[er][ef]) == oppcolor) |
| 464 if (piece_color(simulation.board[er][ef]) == piececolor) { |
464 if (piece_color(simulation.board[er][ef]) == piececolor) { |
| 465 /* test if the king can fight back */ |
465 /* test if the king can fight back */ |
| 466 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
466 GameState sim_retaliate = gamestate_copy_sim(&simulation); |
| 467 Move move_retaliate = {0}; |
467 Move move_retaliate = {0}; |
| 468 move_retaliate.piece = mkpiece(KING, oppcolor); |
468 move_retaliate.piece = mkpiece(KING, oppcolor); |
| 469 move_retaliate.fromrow = opkingrow; |
469 move_retaliate.fromrank = opkingrank; |
| 470 move_retaliate.fromfile = opkingfile; |
470 move_retaliate.fromfile = opkingfile; |
| 471 move_retaliate.torow = er; |
471 move_retaliate.torank = er; |
| 472 move_retaliate.tofile = ef; |
472 move_retaliate.tofile = ef; |
| 473 move_retaliate.capture = true; |
473 move_retaliate.capture = true; |
| 474 apply_move_internal(&sim_retaliate, &move_retaliate); |
474 apply_move_internal(&sim_retaliate, &move_retaliate); |
| 475 canescape = !is_covered(&sim_retaliate, er, ef, piececolor); |
475 canescape = !is_covered(&sim_retaliate, er, ef, piececolor); |
| 476 gamestate_cleanup(&sim_retaliate); |
476 gamestate_cleanup(&sim_retaliate); |
| 483 } |
483 } |
| 484 |
484 |
| 485 /* can't escape, can the king be rescued? */ |
485 /* can't escape, can the king be rescued? */ |
| 486 if (!canescape && threatcount == 1) { |
486 if (!canescape && threatcount == 1) { |
| 487 canescape = is_protected(&simulation, |
487 canescape = is_protected(&simulation, |
| 488 threats[0].fromrow, threats[0].fromfile, oppcolor); |
488 threats[0].fromrank, threats[0].fromfile, oppcolor); |
| 489 } |
489 } |
| 490 |
490 |
| 491 /* can't capture, can he block? */ |
491 /* can't capture, can he block? */ |
| 492 if (!canescape && threatcount == 1) { |
492 if (!canescape && threatcount == 1) { |
| 493 Move *threat = &(threats[0]); |
493 Move *threat = &(threats[0]); |
| 494 unsigned tptype = piece_type(threat->piece); |
494 unsigned tptype = piece_type(threat->piece); |
| 495 |
495 |
| 496 /* knight, pawns and the king cannot be blocked */ |
496 /* knight, pawns and the king cannot be blocked */ |
| 497 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { |
497 if (tptype == BISHOP || tptype == ROOK || tptype == QUEEN) { |
| 498 if (threat->fromrow == threat->torow) { |
498 if (threat->fromrank == threat->torank) { |
| 499 /* rook aspect (on row) */ |
499 /* rook aspect (on rank) */ |
| 500 int d = threat->tofile > threat->fromfile ? 1 : -1; |
500 int d = threat->tofile > threat->fromfile ? 1 : -1; |
| 501 File file = threat->fromfile; |
501 File file = threat->fromfile; |
| 502 while (!canescape && file != threat->tofile - d) { |
502 while (!canescape && file != threat->tofile - d) { |
| 503 file += d; |
503 file += d; |
| 504 canescape |= is_protected(&simulation, |
504 canescape |= is_protected(&simulation, |
| 505 threat->torow, file, oppcolor); |
505 threat->torank, file, oppcolor); |
| 506 } |
506 } |
| 507 } else if (threat->fromfile == threat->tofile) { |
507 } else if (threat->fromfile == threat->tofile) { |
| 508 /* rook aspect (on file) */ |
508 /* rook aspect (on file) */ |
| 509 int d = threat->torow > threat->fromrow ? 1 : -1; |
509 int d = threat->torank > threat->fromrank ? 1 : -1; |
| 510 Row row = threat->fromrow; |
510 Rank rank = threat->fromrank; |
| 511 while (!canescape && row != threat->torow - d) { |
511 while (!canescape && rank != threat->torank - d) { |
| 512 row += d; |
512 rank += d; |
| 513 canescape |= is_protected(&simulation, |
513 canescape |= is_protected(&simulation, |
| 514 row, threat->tofile, oppcolor); |
514 rank, threat->tofile, oppcolor); |
| 515 } |
515 } |
| 516 } else { |
516 } else { |
| 517 /* bishop aspect */ |
517 /* bishop aspect */ |
| 518 int dr = threat->torow > threat->fromrow ? 1 : -1; |
518 int dr = threat->torank > threat->fromrank ? 1 : -1; |
| 519 int df = threat->tofile > threat->fromfile ? 1 : -1; |
519 int df = threat->tofile > threat->fromfile ? 1 : -1; |
| 520 |
520 |
| 521 Row row = threat->fromrow; |
521 Rank rank = threat->fromrank; |
| 522 File file = threat->fromfile; |
522 File file = threat->fromfile; |
| 523 while (!canescape && file != threat->tofile - df |
523 while (!canescape && file != threat->tofile - df |
| 524 && row != threat->torow - dr) { |
524 && rank != threat->torank - dr) { |
| 525 row += dr; |
525 rank += dr; |
| 526 file += df; |
526 file += df; |
| 527 canescape |= is_protected(&simulation, row, file, |
527 canescape |= is_protected(&simulation, rank, file, |
| 528 oppcolor); |
528 oppcolor); |
| 529 } |
529 } |
| 530 } |
530 } |
| 531 } |
531 } |
| 532 } |
532 } |
| 564 } |
564 } |
| 565 } else if (piece_type(move->piece) != KING) { |
565 } else if (piece_type(move->piece) != KING) { |
| 566 /* resolve ambiguities, if any */ |
566 /* resolve ambiguities, if any */ |
| 567 Move candidates[16]; |
567 Move candidates[16]; |
| 568 size_t ccount; |
568 size_t ccount; |
| 569 if (get_real_candidates(gamestate, move->torow, move->tofile, |
569 if (get_real_candidates(gamestate, move->torank, move->tofile, |
| 570 piece_color(move->piece), candidates, &ccount)) { |
570 piece_color(move->piece), candidates, &ccount)) { |
| 571 unsigned int ambrows = 0, ambfiles = 0, ambpiece = 0; |
571 unsigned int ambranks = 0, ambfiles = 0, ambpiece = 0; |
| 572 for (size_t i = 0 ; i < ccount ; i++) { |
572 for (size_t i = 0 ; i < ccount ; i++) { |
| 573 if (candidates[i].piece == move->piece) { |
573 if (candidates[i].piece == move->piece) { |
| 574 ambpiece++; |
574 ambpiece++; |
| 575 if (candidates[i].fromrow == move->fromrow) { |
575 if (candidates[i].fromrank == move->fromrank) { |
| 576 ambrows++; |
576 ambranks++; |
| 577 } |
577 } |
| 578 if (candidates[i].fromfile == move->fromfile) { |
578 if (candidates[i].fromfile == move->fromfile) { |
| 579 ambfiles++; |
579 ambfiles++; |
| 580 } |
580 } |
| 581 } |
581 } |
| 582 } |
582 } |
| 583 /* neither file, nor row are ambiguous, name file */ |
583 /* neither file, nor rank are ambiguous, name file */ |
| 584 if (ambpiece > 1 && ambrows == 1 && ambfiles == 1) { |
584 if (ambpiece > 1 && ambranks == 1 && ambfiles == 1) { |
| 585 /* this is most likely the case with Knights |
585 /* this is most likely the case with Knights |
| 586 * in diagonal opposition */ |
586 * in diagonal opposition */ |
| 587 string[idx++] = filechr(move->fromfile); |
587 string[idx++] = filechr(move->fromfile); |
| 588 } else { |
588 } else { |
| 589 /* ambiguous row, name file */ |
589 /* ambiguous rank, name file */ |
| 590 if (ambrows > 1) { |
590 if (ambranks > 1) { |
| 591 string[idx++] = filechr(move->fromfile); |
591 string[idx++] = filechr(move->fromfile); |
| 592 } |
592 } |
| 593 /* ambiguous file, name row */ |
593 /* ambiguous file, name rank */ |
| 594 if (ambfiles > 1) { |
594 if (ambfiles > 1) { |
| 595 string[idx++] = rowchr(move->fromrow); |
595 string[idx++] = rankchr(move->fromrank); |
| 596 } |
596 } |
| 597 } |
597 } |
| 598 } |
598 } |
| 599 } |
599 } |
| 600 |
600 |
| 624 |
624 |
| 625 static int validate_move_rules(const GameState *gamestate, const Move *move) { |
625 static int validate_move_rules(const GameState *gamestate, const Move *move) { |
| 626 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0); |
626 assert((move->piece & ~(PIECE_MASK|COLOR_MASK)) == 0); |
| 627 |
627 |
| 628 /* validate indices (don't trust opponent) */ |
628 /* validate indices (don't trust opponent) */ |
| 629 if (!isidx(move->fromrow) || !isidx(move->fromfile) || |
629 if (!isidx(move->fromrank) || !isidx(move->fromfile) || |
| 630 !isidx(move->torow) || !isidx(move->tofile)) { |
630 !isidx(move->torank) || !isidx(move->tofile)) { |
| 631 return INVALID_MOVE_SYNTAX; |
631 return INVALID_MOVE_SYNTAX; |
| 632 } |
632 } |
| 633 |
633 |
| 634 /* must move */ |
634 /* must move */ |
| 635 if (move->fromfile == move->tofile && move->fromrow == move->torow) { |
635 if (move->fromfile == move->tofile && move->fromrank == move->torank) { |
| 636 return RULES_VIOLATED; |
636 return RULES_VIOLATED; |
| 637 } |
637 } |
| 638 |
638 |
| 639 /* does piece exist */ |
639 /* does piece exist */ |
| 640 if (piece_at(gamestate, msrc(move)) != move->piece) { |
640 if (piece_at(gamestate, move->fromrank, move->fromfile) != move->piece) { |
| 641 return PIECE_NOT_FOUND; |
641 return PIECE_NOT_FOUND; |
| 642 } |
642 } |
| 643 |
643 |
| 644 /* is there any piece at the destination? */ |
644 /* is there any piece at the destination? */ |
| 645 Piece piece_at_dst = piece_at(gamestate, mdst(move)); |
645 Piece piece_at_dst = piece_at(gamestate, move->torank, move->tofile); |
| 646 |
646 |
| 647 /* can't capture own pieces */ |
647 /* can't capture own pieces */ |
| 648 if (piece_color(piece_at_dst) == piece_color(move->piece)) { |
648 if (piece_color(piece_at_dst) == piece_color(move->piece)) { |
| 649 return RULES_VIOLATED; |
649 return RULES_VIOLATED; |
| 650 } |
650 } |
| 704 Move simmove = *move; |
704 Move simmove = *move; |
| 705 apply_move_internal(&simulation, &simmove); |
705 apply_move_internal(&simulation, &simmove); |
| 706 Color piececolor = piece_color(move->piece); |
706 Color piececolor = piece_color(move->piece); |
| 707 Color oppcolor = opponent_color(piececolor); |
707 Color oppcolor = opponent_color(piececolor); |
| 708 File kingfile = 0; |
708 File kingfile = 0; |
| 709 Row kingrow = 0; |
709 Rank kingrank = 0; |
| 710 for (Row row = 0 ; row < 8 ; row++) { |
710 for (Rank rank = 0 ; rank < 8 ; rank++) { |
| 711 for (File file = 0 ; file < 8 ; file++) { |
711 for (File file = 0 ; file < 8 ; file++) { |
| 712 Piece p = piece_at(&simulation, row, file); |
712 Piece p = piece_at(&simulation, rank, file); |
| 713 if (p == mkpiece(KING, piececolor)) { |
713 if (p == mkpiece(KING, piececolor)) { |
| 714 kingfile = file; |
714 kingfile = file; |
| 715 kingrow = row; |
715 kingrank = rank; |
| 716 } |
716 } |
| 717 } |
717 } |
| 718 } |
718 } |
| 719 if (is_covered(&simulation, kingrow, kingfile, oppcolor)) { |
719 if (is_covered(&simulation, kingrank, kingfile, oppcolor)) { |
| 720 if (piece_type(move->piece) == KING) { |
720 if (piece_type(move->piece) == KING) { |
| 721 result = KING_MOVES_INTO_CHECK; |
721 result = KING_MOVES_INTO_CHECK; |
| 722 } else { |
722 } else { |
| 723 if (is_check_position(gamestate)) { |
723 if (is_check_position(gamestate)) { |
| 724 result = KING_IN_CHECK; |
724 result = KING_IN_CHECK; |
| 750 } |
750 } |
| 751 |
751 |
| 752 return VALID_MOVE_SEMANTICS; |
752 return VALID_MOVE_SEMANTICS; |
| 753 } |
753 } |
| 754 |
754 |
| 755 Piece piece_at(const GameState *gamestate, Row row, File file) { |
755 Piece piece_at(const GameState *gamestate, Rank rank, File file) { |
| 756 return gamestate->board[row][file] & (PIECE_MASK|COLOR_MASK); |
756 return gamestate->board[rank][file] & (PIECE_MASK|COLOR_MASK); |
| 757 } |
757 } |
| 758 |
758 |
| 759 void piece_set(GameState *gamestate, Row row, File file, Piece piece) { |
759 void piece_set(GameState *gamestate, Rank rank, File file, Piece piece) { |
| 760 gamestate->board[row][file] = piece; |
760 gamestate->board[rank][file] = piece; |
| 761 } |
761 } |
| 762 |
762 |
| 763 bool get_candidates(const GameState *gamestate, Row row, File file, |
763 bool get_candidates(const GameState *gamestate, Rank rank, File file, |
| 764 Color color, Move *moves, size_t *movecount) { |
764 Color color, Move *moves, size_t *movecount) { |
| 765 Move candidates[32]; |
765 Move candidates[32]; |
| 766 size_t ccount = 0; |
766 size_t ccount = 0; |
| 767 for (Row r = 0 ; r < 8 ; r++) { |
767 for (Rank r = 0 ; r < 8 ; r++) { |
| 768 for (File f = 0 ; f < 8 ; f++) { |
768 for (File f = 0 ; f < 8 ; f++) { |
| 769 Piece p = piece_at(gamestate, r, f); |
769 Piece p = piece_at(gamestate, r, f); |
| 770 if (piece_color(p) == color) { |
770 if (piece_color(p) == color) { |
| 771 /* non-capturing move */ |
771 /* non-capturing move */ |
| 772 memset(&(candidates[ccount]), 0, sizeof(Move)); |
772 memset(&(candidates[ccount]), 0, sizeof(Move)); |
| 773 candidates[ccount].piece = p; |
773 candidates[ccount].piece = p; |
| 774 candidates[ccount].fromrow = r; |
774 candidates[ccount].fromrank = r; |
| 775 candidates[ccount].fromfile = f; |
775 candidates[ccount].fromfile = f; |
| 776 candidates[ccount].torow = row; |
776 candidates[ccount].torank = rank; |
| 777 candidates[ccount].tofile = file; |
777 candidates[ccount].tofile = file; |
| 778 if (piece_type(p) == PAWN && (row == 0 || row == 7)) { |
778 if (piece_type(p) == PAWN && (rank == 0 || rank == 7)) { |
| 779 /* the exact piece for promotion does not matter */ |
779 /* the exact piece for promotion does not matter */ |
| 780 candidates[ccount].promotion = mkpiece(QUEEN, color); |
780 candidates[ccount].promotion = mkpiece(QUEEN, color); |
| 781 } |
781 } |
| 782 ccount++; |
782 ccount++; |
| 783 |
783 |
| 807 } |
807 } |
| 808 |
808 |
| 809 return result; |
809 return result; |
| 810 } |
810 } |
| 811 |
811 |
| 812 bool get_threats(const GameState *gamestate, Row row, File file, |
812 bool get_threats(const GameState *gamestate, Rank rank, File file, |
| 813 Color color, Move *threats, size_t *threatcount) { |
813 Color color, Move *threats, size_t *threatcount) { |
| 814 |
814 |
| 815 /* simulate a capturing move on the target position */ |
815 /* simulate a capturing move on the target position */ |
| 816 Color opcolor = opponent_color(color); |
816 Color opcolor = opponent_color(color); |
| 817 GameState simulation = gamestate_copy_sim(gamestate); |
817 GameState simulation = gamestate_copy_sim(gamestate); |
| 818 if (piece_color(piece_at(&simulation, row, file)) != opcolor) { |
818 if (piece_color(piece_at(&simulation, rank, file)) != opcolor) { |
| 819 /* set a fake pawn if the field is not occupied by the opponent */ |
819 /* set a fake pawn if the field is not occupied by the opponent */ |
| 820 piece_set(&simulation, row, file, mkpiece(PAWN, opcolor)); |
820 piece_set(&simulation, rank, file, mkpiece(PAWN, opcolor)); |
| 821 } |
821 } |
| 822 |
822 |
| 823 Move candidates[16]; |
823 Move candidates[16]; |
| 824 size_t ccount = 0; |
824 size_t ccount = 0; |
| 825 for (Row r = 0 ; r < 8 ; r++) { |
825 for (Rank r = 0 ; r < 8 ; r++) { |
| 826 for (File f = 0 ; f < 8 ; f++) { |
826 for (File f = 0 ; f < 8 ; f++) { |
| 827 Piece p = piece_at(&simulation, r, f); |
827 Piece p = piece_at(&simulation, r, f); |
| 828 if (piece_color(p) == color) { |
828 if (piece_color(p) == color) { |
| 829 memset(&(candidates[ccount]), 0, sizeof(Move)); |
829 memset(&(candidates[ccount]), 0, sizeof(Move)); |
| 830 candidates[ccount].piece = p; |
830 candidates[ccount].piece = p; |
| 831 candidates[ccount].fromrow = r; |
831 candidates[ccount].fromrank = r; |
| 832 candidates[ccount].fromfile = f; |
832 candidates[ccount].fromfile = f; |
| 833 candidates[ccount].torow = row; |
833 candidates[ccount].torank = rank; |
| 834 candidates[ccount].tofile = file; |
834 candidates[ccount].tofile = file; |
| 835 candidates[ccount].capture = true; |
835 candidates[ccount].capture = true; |
| 836 if (piece_type(p) == PAWN && (row == 0 || row == 7)) { |
836 if (piece_type(p) == PAWN && (rank == 0 || rank == 7)) { |
| 837 /* the exact piece for promotion does not matter */ |
837 /* the exact piece for promotion does not matter */ |
| 838 candidates[ccount].promotion = mkpiece(QUEEN, color); |
838 candidates[ccount].promotion = mkpiece(QUEEN, color); |
| 839 } |
839 } |
| 840 ccount++; |
840 ccount++; |
| 841 } |
841 } |
| 869 GameState simulation = gamestate_copy_sim(gamestate); |
869 GameState simulation = gamestate_copy_sim(gamestate); |
| 870 Move simmove = *move; |
870 Move simmove = *move; |
| 871 apply_move_internal(&simulation, &simmove); |
871 apply_move_internal(&simulation, &simmove); |
| 872 |
872 |
| 873 File kingfile = 0; |
873 File kingfile = 0; |
| 874 Row kingrow = 0; |
874 Rank kingrank = 0; |
| 875 for (Row row = 0 ; row < 8 ; row++) { |
875 for (Rank rank = 0 ; rank < 8 ; rank++) { |
| 876 for (File file = 0 ; file < 8 ; file++) { |
876 for (File file = 0 ; file < 8 ; file++) { |
| 877 if (piece_at(&simulation, row, file) == mkpiece(KING, color)) { |
877 if (piece_at(&simulation, rank, file) == mkpiece(KING, color)) { |
| 878 kingfile = file; |
878 kingfile = file; |
| 879 kingrow = row; |
879 kingrank = rank; |
| 880 } |
880 } |
| 881 } |
881 } |
| 882 } |
882 } |
| 883 |
883 |
| 884 bool covered = is_covered(&simulation, |
884 bool covered = is_covered(&simulation, |
| 885 kingrow, kingfile, opponent_color(color)); |
885 kingrank, kingfile, opponent_color(color)); |
| 886 gamestate_cleanup(&simulation); |
886 gamestate_cleanup(&simulation); |
| 887 |
887 |
| 888 return covered; |
888 return covered; |
| 889 } |
889 } |
| 890 |
890 |
| 891 bool get_real_candidates(const GameState *gamestate, Row row, File file, |
891 bool get_real_candidates(const GameState *gamestate, Rank rank, File file, |
| 892 Color color, Move *moves, size_t *movecount) { |
892 Color color, Move *moves, size_t *movecount) { |
| 893 |
893 |
| 894 if (movecount) { |
894 if (movecount) { |
| 895 *movecount = 0; |
895 *movecount = 0; |
| 896 } |
896 } |
| 897 |
897 |
| 898 Move candidates[16]; |
898 Move candidates[16]; |
| 899 size_t ccount; |
899 size_t ccount; |
| 900 if (get_candidates(gamestate, row, file, color, candidates, &ccount)) { |
900 if (get_candidates(gamestate, rank, file, color, candidates, &ccount)) { |
| 901 bool result = false; |
901 bool result = false; |
| 902 for (size_t i = 0 ; i < ccount ; i++) { |
902 for (size_t i = 0 ; i < ccount ; i++) { |
| 903 if (!is_pinned(gamestate, &candidates[i])) { |
903 if (!is_pinned(gamestate, &candidates[i])) { |
| 904 result = true; |
904 result = true; |
| 905 if (moves && movecount) { |
905 if (moves && movecount) { |
| 911 } else { |
911 } else { |
| 912 return false; |
912 return false; |
| 913 } |
913 } |
| 914 } |
914 } |
| 915 |
915 |
| 916 bool get_real_threats(const GameState *gamestate, Row row, File file, |
916 bool get_real_threats(const GameState *gamestate, Rank rank, File file, |
| 917 Color color, Move *threats, size_t *threatcount) { |
917 Color color, Move *threats, size_t *threatcount) { |
| 918 |
918 |
| 919 if (threatcount) { |
919 if (threatcount) { |
| 920 *threatcount = 0; |
920 *threatcount = 0; |
| 921 } |
921 } |
| 922 |
922 |
| 923 Move candidates[16]; |
923 Move candidates[16]; |
| 924 size_t ccount; |
924 size_t ccount; |
| 925 if (get_threats(gamestate, row, file, color, candidates, &ccount)) { |
925 if (get_threats(gamestate, rank, file, color, candidates, &ccount)) { |
| 926 bool result = false; |
926 bool result = false; |
| 927 for (size_t i = 0 ; i < ccount ; i++) { |
927 for (size_t i = 0 ; i < ccount ; i++) { |
| 928 if (!is_pinned(gamestate, &candidates[i])) { |
928 if (!is_pinned(gamestate, &candidates[i])) { |
| 929 result = true; |
929 result = true; |
| 930 if (threats && threatcount) { |
930 if (threats && threatcount) { |
| 948 |
948 |
| 949 Move candidates[16], *candidate = NULL; |
949 Move candidates[16], *candidate = NULL; |
| 950 size_t candidatecount; |
950 size_t candidatecount; |
| 951 |
951 |
| 952 /* determine all candidate moves and sort out the invalid ones */ |
952 /* determine all candidate moves and sort out the invalid ones */ |
| 953 if (get_candidates(gamestate, move->torow, move->tofile, color, |
953 if (get_candidates(gamestate, move->torank, move->tofile, color, |
| 954 candidates, &candidatecount)) { |
954 candidates, &candidatecount)) { |
| 955 |
955 |
| 956 bool found = false; |
956 bool found = false; |
| 957 |
957 |
| 958 for (size_t i = 0 ; i < candidatecount ; i++) { |
958 for (size_t i = 0 ; i < candidatecount ; i++) { |
| 959 /* filter by partial fromrow/fromfile information */ |
959 /* filter by partial fromrank/fromfile information */ |
| 960 if (candidates[i].piece == move->piece && |
960 if (candidates[i].piece == move->piece && |
| 961 (move->fromrow == POS_UNSPECIFIED || |
961 (move->fromrank == POS_UNSPECIFIED || |
| 962 move->fromrow == candidates[i].fromrow) && |
962 move->fromrank == candidates[i].fromrank) && |
| 963 (move->fromfile == POS_UNSPECIFIED || |
963 (move->fromfile == POS_UNSPECIFIED || |
| 964 move->fromfile == candidates[i].fromfile)) { |
964 move->fromfile == candidates[i].fromfile)) { |
| 965 |
965 |
| 966 /* found a candidate, here it does not matter if it's valid! */ |
966 /* found a candidate, here it does not matter if it's valid! */ |
| 967 found = true; |
967 found = true; |
| 992 return PIECE_NOT_FOUND; |
992 return PIECE_NOT_FOUND; |
| 993 } |
993 } |
| 994 } |
994 } |
| 995 |
995 |
| 996 /* found a candidate, copy the source location */ |
996 /* found a candidate, copy the source location */ |
| 997 move->fromrow = candidate->fromrow; |
997 move->fromrank = candidate->fromrank; |
| 998 move->fromfile = candidate->fromfile; |
998 move->fromfile = candidate->fromfile; |
| 999 return VALID_MOVE_SYNTAX; |
999 return VALID_MOVE_SYNTAX; |
| 1000 } else { |
1000 } else { |
| 1001 return PIECE_NOT_FOUND; |
1001 return PIECE_NOT_FOUND; |
| 1002 } |
1002 } |
| 1003 } |
1003 } |
| 1004 |
1004 |
| 1005 static int eval_move1(const char *pstr, Move *move, Color color) { |
1005 static int eval_move1(const char *pstr, Move *move, Color color) { |
| 1006 memset(move, 0, sizeof(Move)); |
1006 memset(move, 0, sizeof(Move)); |
| 1007 move->fromfile = POS_UNSPECIFIED; |
1007 move->fromfile = POS_UNSPECIFIED; |
| 1008 move->fromrow = POS_UNSPECIFIED; |
1008 move->fromrank = POS_UNSPECIFIED; |
| 1009 |
1009 |
| 1010 size_t len = strlen(pstr); |
1010 size_t len = strlen(pstr); |
| 1011 if (len < 1 || len > 6) { |
1011 if (len < 1 || len > 6) { |
| 1012 return INVALID_MOVE_SYNTAX; |
1012 return INVALID_MOVE_SYNTAX; |
| 1013 } |
1013 } |
| 1035 |
1035 |
| 1036 if (len == 2) { |
1036 if (len == 2) { |
| 1037 /* pawn move (e.g. "e4") */ |
1037 /* pawn move (e.g. "e4") */ |
| 1038 move->piece = mkpiece(PAWN, color); |
1038 move->piece = mkpiece(PAWN, color); |
| 1039 move->tofile = fileidx(mstr[0]); |
1039 move->tofile = fileidx(mstr[0]); |
| 1040 move->torow = rowidx(mstr[1]); |
1040 move->torank = rankidx(mstr[1]); |
| 1041 } else if (len == 3) { |
1041 } else if (len == 3) { |
| 1042 if (strcmp(mstr, "O-O") == 0) { |
1042 if (strcmp(mstr, "O-O") == 0) { |
| 1043 /* king side castling */ |
1043 /* king side castling */ |
| 1044 move->piece = mkpiece(KING, color); |
1044 move->piece = mkpiece(KING, color); |
| 1045 move->fromfile = fileidx('e'); |
1045 move->fromfile = fileidx('e'); |
| 1046 move->tofile = fileidx('g'); |
1046 move->tofile = fileidx('g'); |
| 1047 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
1047 move->fromrank = move->torank = color == WHITE ? 0 : 7; |
| 1048 } else { |
1048 } else { |
| 1049 /* move (e.g. "Nf3") */ |
1049 /* move (e.g. "Nf3") */ |
| 1050 move->piece = getpiece(mstr[0], color); |
1050 move->piece = getpiece(mstr[0], color); |
| 1051 move->tofile = fileidx(mstr[1]); |
1051 move->tofile = fileidx(mstr[1]); |
| 1052 move->torow = rowidx(mstr[2]); |
1052 move->torank = rankidx(mstr[2]); |
| 1053 } |
1053 } |
| 1054 } else if (len == 4) { |
1054 } else if (len == 4) { |
| 1055 move->piece = getpiece(mstr[0], color); |
1055 move->piece = getpiece(mstr[0], color); |
| 1056 if (move->piece == 0) { |
1056 if (move->piece == 0) { |
| 1057 move->piece = mkpiece(PAWN, color); |
1057 move->piece = mkpiece(PAWN, color); |
| 1068 if (piece_type(move->piece) == PAWN) { |
1068 if (piece_type(move->piece) == PAWN) { |
| 1069 /* invalidate the result */ |
1069 /* invalidate the result */ |
| 1070 move->piece = 0; |
1070 move->piece = 0; |
| 1071 } |
1071 } |
| 1072 } else { |
1072 } else { |
| 1073 move->fromrow = rowidx(mstr[1]); |
1073 move->fromrank = rankidx(mstr[1]); |
| 1074 } |
1074 } |
| 1075 } |
1075 } |
| 1076 move->tofile = fileidx(mstr[2]); |
1076 move->tofile = fileidx(mstr[2]); |
| 1077 move->torow = rowidx(mstr[3]); |
1077 move->torank = rankidx(mstr[3]); |
| 1078 } else if (len == 5) { |
1078 } else if (len == 5) { |
| 1079 if (strcmp(mstr, "O-O-O") == 0) { |
1079 if (strcmp(mstr, "O-O-O") == 0) { |
| 1080 /* queen side castling "O-O-O" */ |
1080 /* queen side castling "O-O-O" */ |
| 1081 move->piece = mkpiece(KING, color); |
1081 move->piece = mkpiece(KING, color); |
| 1082 move->fromfile = fileidx('e'); |
1082 move->fromfile = fileidx('e'); |
| 1083 move->tofile = fileidx('c'); |
1083 move->tofile = fileidx('c'); |
| 1084 move->fromrow = move->torow = color == WHITE ? 0 : 7; |
1084 move->fromrank = move->torank = color == WHITE ? 0 : 7; |
| 1085 } else { |
1085 } else { |
| 1086 move->piece = getpiece(mstr[0], color); |
1086 move->piece = getpiece(mstr[0], color); |
| 1087 if (mstr[2] == 'x') { |
1087 if (mstr[2] == 'x') { |
| 1088 move->capture = true; |
1088 move->capture = true; |
| 1089 if (move->piece) { |
1089 if (move->piece) { |
| 1090 /* capture (e.g. "Ndxf3" or "R1xh3") */ |
1090 /* capture (e.g. "Ndxf3" or "R1xh3") */ |
| 1091 if (isfile(mstr[1])) { |
1091 if (isfile(mstr[1])) { |
| 1092 move->fromfile = fileidx(mstr[1]); |
1092 move->fromfile = fileidx(mstr[1]); |
| 1093 } else if (isrow(mstr[1])) { |
1093 } else if (isrank(mstr[1])) { |
| 1094 move->fromrow = rowidx(mstr[1]); |
1094 move->fromrank = rankidx(mstr[1]); |
| 1095 } else { |
1095 } else { |
| 1096 return INVALID_MOVE_SYNTAX; |
1096 return INVALID_MOVE_SYNTAX; |
| 1097 } |
1097 } |
| 1098 } else { |
1098 } else { |
| 1099 /* long notation capture (e.g. "e5xf6") */ |
1099 /* long notation capture (e.g. "e5xf6") */ |
| 1100 move->piece = mkpiece(PAWN, color); |
1100 move->piece = mkpiece(PAWN, color); |
| 1101 move->fromfile = fileidx(mstr[0]); |
1101 move->fromfile = fileidx(mstr[0]); |
| 1102 move->fromrow = rowidx(mstr[1]); |
1102 move->fromrank = rankidx(mstr[1]); |
| 1103 } |
1103 } |
| 1104 } else { |
1104 } else { |
| 1105 /* long notation move (e.g. "Nc5a4") */ |
1105 /* long notation move (e.g. "Nc5a4") */ |
| 1106 move->fromfile = fileidx(mstr[1]); |
1106 move->fromfile = fileidx(mstr[1]); |
| 1107 move->fromrow = rowidx(mstr[2]); |
1107 move->fromrank = rankidx(mstr[2]); |
| 1108 } |
1108 } |
| 1109 move->tofile = fileidx(mstr[3]); |
1109 move->tofile = fileidx(mstr[3]); |
| 1110 move->torow = rowidx(mstr[4]); |
1110 move->torank = rankidx(mstr[4]); |
| 1111 } |
1111 } |
| 1112 } else if (len == 6) { |
1112 } else if (len == 6) { |
| 1113 /* long notation capture (e.g. "Nc5xf3") */ |
1113 /* long notation capture (e.g. "Nc5xf3") */ |
| 1114 if (mstr[3] == 'x') { |
1114 if (mstr[3] == 'x') { |
| 1115 move->capture = true; |
1115 move->capture = true; |
| 1116 move->piece = getpiece(mstr[0], color); |
1116 move->piece = getpiece(mstr[0], color); |
| 1117 move->fromfile = fileidx(mstr[1]); |
1117 move->fromfile = fileidx(mstr[1]); |
| 1118 move->fromrow = rowidx(mstr[2]); |
1118 move->fromrank = rankidx(mstr[2]); |
| 1119 move->tofile = fileidx(mstr[4]); |
1119 move->tofile = fileidx(mstr[4]); |
| 1120 move->torow = rowidx(mstr[5]); |
1120 move->torank = rankidx(mstr[5]); |
| 1121 } |
1121 } |
| 1122 } |
1122 } |
| 1123 |
1123 |
| 1124 |
1124 |
| 1125 if (!move->piece) { |
1125 if (!move->piece) { |
| 1126 return INVALID_MOVE_SYNTAX; |
1126 return INVALID_MOVE_SYNTAX; |
| 1127 } |
1127 } |
| 1128 |
1128 |
| 1129 if (piece_type(move->piece) == PAWN |
1129 if (piece_type(move->piece) == PAWN |
| 1130 && move->torow == (color==WHITE?7:0) |
1130 && move->torank == (color==WHITE?7:0) |
| 1131 && !move->promotion) { |
1131 && !move->promotion) { |
| 1132 return NEED_PROMOTION; |
1132 return NEED_PROMOTION; |
| 1133 } |
1133 } |
| 1134 |
1134 |
| 1135 /* up to this point |
1135 /* up to this point |
| 1136 * destination indices must be specified and valid |
1136 * destination indices must be specified and valid |
| 1137 * source indices must either be valid or unspecified |
1137 * source indices must either be valid or unspecified |
| 1138 */ |
1138 */ |
| 1139 if (!isidxr(move->fromrow) || !isidxr(move->fromfile) || |
1139 if (!isidxr(move->fromrank) || !isidxr(move->fromfile) || |
| 1140 !isidx(move->torow) || !isidx(move->tofile)) { |
1140 !isidx(move->torank) || !isidx(move->tofile)) { |
| 1141 return INVALID_MOVE_SYNTAX; |
1141 return INVALID_MOVE_SYNTAX; |
| 1142 } |
1142 } |
| 1143 |
1143 |
| 1144 return VALID_MOVE_SYNTAX; |
1144 return VALID_MOVE_SYNTAX; |
| 1145 } |
1145 } |
| 1147 static int eval_move2(const GameState *gamestate, |
1147 static int eval_move2(const GameState *gamestate, |
| 1148 const char *mstr, Color color, Move *move, bool lazy) { |
1148 const char *mstr, Color color, Move *move, bool lazy) { |
| 1149 int result = eval_move1(mstr, move, color); |
1149 int result = eval_move1(mstr, move, color); |
| 1150 if (result == VALID_MOVE_SYNTAX) { |
1150 if (result == VALID_MOVE_SYNTAX) { |
| 1151 if (move->fromfile == POS_UNSPECIFIED |
1151 if (move->fromfile == POS_UNSPECIFIED |
| 1152 || move->fromrow == POS_UNSPECIFIED) { |
1152 || move->fromrank == POS_UNSPECIFIED) { |
| 1153 result = getlocation(gamestate, move); |
1153 result = getlocation(gamestate, move); |
| 1154 } |
1154 } |
| 1155 if (result == VALID_MOVE_SYNTAX) { |
1155 if (result == VALID_MOVE_SYNTAX) { |
| 1156 /* correct check/checkmate flags */ |
1156 /* correct check/checkmate flags */ |
| 1157 if (lazy) { |
1157 if (lazy) { |
| 1184 int check_move(const char *mstr, Color color) { |
1184 int check_move(const char *mstr, Color color) { |
| 1185 Move move; |
1185 Move move; |
| 1186 return eval_move1(mstr, &move, color); |
1186 return eval_move1(mstr, &move, color); |
| 1187 } |
1187 } |
| 1188 |
1188 |
| 1189 bool is_protected(const GameState *gamestate, Row row, File file, Color color) { |
1189 bool is_protected(const GameState *gamestate, Rank rank, File file, Color color) { |
| 1190 Move candidates[16]; |
1190 Move candidates[16]; |
| 1191 size_t ccount; |
1191 size_t ccount; |
| 1192 /* we need all candidates - not only threats! */ |
1192 /* we need all candidates - not only threats! */ |
| 1193 if (get_candidates(gamestate, row, file, color, candidates, &ccount)) { |
1193 if (get_candidates(gamestate, rank, file, color, candidates, &ccount)) { |
| 1194 for (size_t i = 0 ; i < ccount ; i++) { |
1194 for (size_t i = 0 ; i < ccount ; i++) { |
| 1195 /* skip the king */ |
1195 /* skip the king */ |
| 1196 if (piece_type(candidates[i].piece) == KING) continue; |
1196 if (piece_type(candidates[i].piece) == KING) continue; |
| 1197 /* skip pinned pieces */ |
1197 /* skip pinned pieces */ |
| 1198 if (is_pinned(gamestate, &candidates[i])) continue; |
1198 if (is_pinned(gamestate, &candidates[i])) continue; |
| 1271 return snprintf(str, 6, "%02u:%02u", minutes, seconds); |
1271 return snprintf(str, 6, "%02u:%02u", minutes, seconds); |
| 1272 } |
1272 } |
| 1273 } |
1273 } |
| 1274 |
1274 |
| 1275 size_t filter_moves_allowed(const GameState *gamestate, |
1275 size_t filter_moves_allowed(const GameState *gamestate, |
| 1276 Color c, Row r, File f, Move *moves, moves_generator_func func) { |
1276 Color c, Rank r, File f, Move *moves, moves_generator_func func) { |
| 1277 |
1277 |
| 1278 /* worst case: the queen has the most moves */ |
1278 /* worst case: the queen has the most moves */ |
| 1279 Move candidates[QUEEN_MOVES_MAX]; |
1279 Move candidates[QUEEN_MOVES_MAX]; |
| 1280 size_t candidatecount = func(gamestate, c, r, f, candidates); |
1280 size_t candidatecount = func(gamestate, c, r, f, candidates); |
| 1281 size_t count = 0; |
1281 size_t count = 0; |