/* move-gen.c - move generation * * Copyright (C) 2024 Bruno Raoult ("br") * Licensed under the GNU General Public License v3.0 or later. * Some rights reserved. See COPYING. * * You should have received a copy of the GNU General Public License along with this * program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later * */ #include #include "bitops.h" #include "bug.h" #include "chessdefs.h" #include "board.h" #include "bitboard.h" #include "piece.h" #include "position.h" #include "move.h" #include "hyperbola-quintessence.h" #include "attack.h" #include "move-gen.h" /** * pseudo_is_legal() - check if a move is legal. * @pos: position * @move: move_t to verify * * @return: true if move is valid, false otherwise. */ bool pseudo_is_legal(const pos_t *pos, const move_t move) { color_t us = pos->turn, them = OPPONENT(us); square_t from = move_from(move), to = move_to(move), king = pos->king[us], sq; piece_type_t piece = PIECE(pos->board[from]); bitboard_t kingbb = pos->bb[us][KING], tmp; /* (1) - King * For castling, we already tested intermediate squares attacks * in pseudo move generation, so we only care destination square here. * Attention: We need to exclude king from occupation bitboard ! */ if (piece == KING) { bitboard_t occ = pos_occ(pos) ^ kingbb; return !sq_attackers(pos, occ, to, them); } /* (2) - King is in check * Double-check is already handled, as only K moves were generated. * Here, allowed moves are only on King-attacker line, including * attacker.We can move a piece on * in pseudo move generation, so we only care destination square here. */ if (pos->checkers) { if (popcount64(pos->checkers) == 1) { /* one checker */ square_t checker = ctz64(pos->checkers); bitboard_t between = bb_between[king][checker]; return mask(to) & between; } return false; /* double check */ } /* (3) - pinned pieces * We verify here that pinned piece P stays on line King-P. */ if (mask(from) & pos->blockers & pos->bb[us][ALL_PIECES]) { bitboard_t line = bb_line[from][king]; return line & mask(to); /* to is not on pin line */ } /* (4) - En-passant * We only care the situation where our King and enemy R/Q are on * 5th relative rank. To do so, we create an occupation bb without * the 2 pawns. */ if (is_enpassant(move)) { /* from rank bitboard */ bitboard_t rank5 = bb_sqrank[from]; /* enemy rooks/queens on from rank */ bitboard_t rooks = (pos->bb[them][ROOK] | pos->bb[them][QUEEN]) & rank5; if ((kingbb & rank5) && rooks) { /* K and enemy R/Q on rank */ /* captured pawn square (beside from square) */ square_t captured = sq_make(sq_file(pos->en_passant), sq_rank(from)); /* occupation bitboard without the two "disappearing" pawns */ bitboard_t occ = pos_occ(pos) ^ mask(from) ^ mask(captured); bit_for_each64(sq, tmp, rooks) /* check all rooks/queens */ if (hyperbola_rank_moves(occ, sq) & kingbb) return false; } return true; } return true; } /** * pos_next_legal() - get next legal move in position. * @pos: position * @start: &int, starting position in move list * * Get next valid move in @pos move list, from move @start, or MOVE_NONE. * @start is set to next non-checked move in pseudo-legal list. * Position pseudo-legal moves must be already calculated before calling this function. * * @return: move, or MOVE_NONE if no move. */ move_t pos_next_legal(const pos_t *pos, int *start) { const int nmoves = pos->moves.nmoves; const move_t *moves = pos->moves.move; move_t move; while (*start < nmoves) { if (pseudo_is_legal(pos, (move = moves[(*start)++] ))) return move; } return MOVE_NONE; } /** * pos_all_legal() - get the list of legal moves from pseudo-legal. * @pos: position * @dest: destination &movelist_t * * The pseudo-legal moves must be already calculated before calling this function. * No check is done on @dest limits. * * @Return: @dest */ movelist_t *pos_all_legal(const pos_t *pos, movelist_t *dest) { int tmp = dest->nmoves = 0; move_t move; //int tmp = 0; while ((move = pos_next_legal(pos, &tmp)) != MOVE_NONE) dest->move[dest->nmoves++] = move; return dest; } /** * pos_gen_pseudomoves() - generate position pseudo-legal moves * @pos: position * * Generate all @pos pseudo moves for player-to-move. * The @pos->moves table is filled with the moves. * * Only a few validity checks are done here (i.e. moves are not generated): * - castling, if king is in check * - castling, if king passes an enemy-controlled square (not final square). * When immediately known, a few move flags are also applied in these cases: * - castling: M_CASTLE_{K,Q} * - pawn capture (excl. en-passant): M_CAPTURE * - en-passant: M_EN_PASSANT * - pawn double push: M_DPUSH * - promotion: M_PROMOTION * - promotion and capture * * TODO: move code to specific functions (especially castling, pawn push/capture) * * @Return: The total number of moves. */ int pos_gen_pseudomoves(pos_t *pos) { color_t us = pos->turn; color_t them = OPPONENT(us); bitboard_t my_pieces = pos->bb[us][ALL_PIECES]; bitboard_t not_my_pieces = ~my_pieces; bitboard_t enemy_pieces = pos->bb[them][ALL_PIECES]; bitboard_t occ = my_pieces | enemy_pieces; bitboard_t empty = ~occ; bitboard_t movebits, from_pawns; bitboard_t tmp1, tmp2; move_t *moves = pos->moves.move; int nmoves = pos->moves.nmoves; int from, to; bug_on(nmoves != 0); /* king - MUST BE FIRST (we stop if doubler check) */ from = pos->king[us]; movebits = bb_king_moves(not_my_pieces, from); bit_for_each64(to, tmp1, movebits & empty) { moves[nmoves++] = move_make(from, to); } bit_for_each64(to, tmp1, movebits & enemy_pieces) { moves[nmoves++] = move_make_capture(from, to); } if (popcount64(pos->checkers) > 1) /* double check, we stop here */ return (pos->moves.nmoves = nmoves); /* sliding pieces */ bit_for_each64(from, tmp1, pos->bb[us][BISHOP] | pos->bb[us][QUEEN]) { movebits = hyperbola_bishop_moves(occ, from) & not_my_pieces; bit_for_each64(to, tmp2, movebits & empty) { moves[nmoves++] = move_make(from, to); } bit_for_each64(to, tmp2, movebits & enemy_pieces) { moves[nmoves++] = move_make_capture(from, to); } } bit_for_each64(from, tmp1, pos->bb[us][ROOK] | pos->bb[us][QUEEN]) { // printf("rook=%d/%s\n", from, sq_to_string(from)); movebits = hyperbola_rook_moves(occ, from) & not_my_pieces; bit_for_each64(to, tmp2, movebits & empty) { moves[nmoves++] = move_make(from, to); } bit_for_each64(to, tmp2, movebits & enemy_pieces) { moves[nmoves++] = move_make_capture(from, to); } } /* knight */ bit_for_each64(from, tmp1, pos->bb[us][KNIGHT]) { movebits = bb_knight_moves(not_my_pieces, from); bit_for_each64(to, tmp2, movebits & empty) { moves[nmoves++] = move_make(from, to); } bit_for_each64(to, tmp2, movebits & enemy_pieces) { moves[nmoves++] = move_make_capture(from, to); } } /* pawn: relative rank and files */ bitboard_t rel_rank7 = bb_rel_rank(RANK_7, us); bitboard_t rel_rank3 = bb_rel_rank(RANK_3, us); /* pawn: ranks 2-6 push 1 and 2 squares */ movebits = pawn_shift_up(pos->bb[us][PAWN] & ~rel_rank7, us) & empty; bit_for_each64(to, tmp1, movebits) { from = pawn_push_up(to, them); /* reverse push */ //printf("push %d->%d %s->%s", from, to, sq_to_string(from), sq_to_string(to)); moves[nmoves++] = move_make(from, to); } /* possible second push */ movebits = pawn_shift_up(movebits & rel_rank3, us) & empty; bit_for_each64(to, tmp1, movebits) { from = pawn_push_up(pawn_push_up(to, them), them); moves[nmoves++] = move_make_flags(from, to, M_DPUSH); } /* pawn: ranks 2-6 captures left */ from_pawns = pos->bb[us][PAWN] & ~rel_rank7; // & ~rel_filea; movebits = pawn_shift_upleft(from_pawns, us) & enemy_pieces; bit_for_each64(to, tmp1, movebits) { from = pawn_push_upleft(to, them); /* reverse capture */ moves[nmoves++] = move_make_capture(from, to); } /* pawn: ranks 2-6 captures right */ from_pawns = pos->bb[us][PAWN] & ~rel_rank7; // & ~rel_fileh; movebits = pawn_shift_upright(from_pawns, us) & enemy_pieces; bit_for_each64(to, tmp1, movebits) { from = pawn_push_upright(to, them); moves[nmoves++] = move_make_capture(from, to); } /* pawn: en-passant */ if ((to = pos->en_passant) != SQUARE_NONE) { movebits = mask(to); from_pawns = pos->bb[us][PAWN] & (pawn_shift_upleft(movebits, them) | pawn_shift_upright(movebits, them)); bit_for_each64(from, tmp1, from_pawns) { moves[nmoves++] = move_make_enpassant(from, to); } } /* pawn: rank 7 push */ movebits = pawn_shift_up(pos->bb[us][PAWN] & rel_rank7, us) & empty; bit_for_each64(to, tmp1, movebits) { from = pawn_push_up(to, them); /* reverse push */ moves[nmoves++] = move_make_promote(from, to, QUEEN); moves[nmoves++] = move_make_promote(from, to, ROOK); moves[nmoves++] = move_make_promote(from, to, BISHOP); moves[nmoves++] = move_make_promote(from, to, KNIGHT); } /* pawn promotion: rank 7 captures left */ from_pawns = pos->bb[us][PAWN] & rel_rank7; // & ~rel_filea; movebits = pawn_shift_upleft(from_pawns, us) & enemy_pieces; bit_for_each64(to, tmp1, movebits) { from = pawn_push_upleft(to, them); /* reverse capture */ moves[nmoves++] = move_make_promote_capture(from, to, QUEEN); moves[nmoves++] = move_make_promote_capture(from, to, ROOK); moves[nmoves++] = move_make_promote_capture(from, to, BISHOP); moves[nmoves++] = move_make_promote_capture(from, to, KNIGHT); } /* pawn: rank 7 captures right */ from_pawns = pos->bb[us][PAWN] & rel_rank7; // & ~rel_fileh; movebits = pawn_shift_upright(from_pawns, us) & enemy_pieces; bit_for_each64(to, tmp1, movebits) { from = pawn_push_upright(to, them); /* reverse capture */ moves[nmoves++] = move_make_promote_capture(from, to, QUEEN); moves[nmoves++] = move_make_promote_capture(from, to, ROOK); moves[nmoves++] = move_make_promote_capture(from, to, BISHOP); moves[nmoves++] = move_make_promote_capture(from, to, KNIGHT); } /* castle - Attention ! Castling flags are assumed correct */ if (!pos->checkers) { bitboard_t rel_rank1 = bb_rel_rank(RANK_1, us); from = pos->king[us]; square_t from_square[2] = { E1, E8 }; /* verify king is on E1/E8 */ bug_on(can_castle(pos->castle, us) && from != from_square[us]); /* For castle, we check the opponent attacks on squares between from and to. * To square attack check will be done in gen_is_legal. */ if (can_oo(pos->castle, us)) { bitboard_t occmask = rel_rank1 & (FILE_Fbb | FILE_Gbb); if (!(occ & occmask) && !sq_attackers(pos, occ, from+1, them)) { /* f1/f8 */ moves[nmoves++] = move_make_flags(from, from + 2, M_CASTLE_K); } } if (can_ooo(pos->castle, us)) { bitboard_t occmask = rel_rank1 & (FILE_Bbb | FILE_Cbb | FILE_Dbb); if (!(occ & occmask) && !sq_attackers(pos, occ, from-1, them)) { /* d1/d8 */ moves[nmoves++] = move_make_flags(from, from - 2, M_CASTLE_Q); } } } /* TODO * DONE. pawn ranks 2-6 advance (1 push, + 2 squares for rank 2) * DONE. pawns rank 7 advance + promotions * DONE. pawns ranks 2-6 captures, left and right * DONE. pawns en-passant (with capture) * DONE. pawns rank 7 capture + promotion * DONE. castle * * add function per piece, and type, for easier debug * */ return (pos->moves.nmoves = nmoves); }