mirror of
https://github.com/epasveer/seer.git
synced 2026-08-29 16:40:42 +08:00
442 lines
19 KiB
Fortran
442 lines
19 KiB
Fortran
! ============================================
|
|
! Module: Move_Generation
|
|
! Purpose: Generate pseudo-legal and legal moves
|
|
! ============================================
|
|
MODULE Move_Generation
|
|
USE Chess_Types
|
|
USE Board_Utils
|
|
IMPLICIT NONE
|
|
PRIVATE
|
|
PUBLIC :: generate_moves ! Main function exposed
|
|
|
|
CONTAINS
|
|
|
|
! --- Helper to add a move to the list if array not full ---
|
|
SUBROUTINE add_move(move_list, num_moves, new_move)
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list
|
|
INTEGER, INTENT(INOUT) :: num_moves
|
|
TYPE(Move_Type), INTENT(IN) :: new_move
|
|
IF (num_moves < MAX_MOVES) THEN
|
|
num_moves = num_moves + 1
|
|
move_list(num_moves) = new_move
|
|
ELSE
|
|
PRINT *, "Warning: Move list full!"
|
|
! Handle error - maybe stop program or ignore move
|
|
END IF
|
|
END SUBROUTINE add_move
|
|
|
|
! --- Generate Pawn Moves ---
|
|
SUBROUTINE generate_pawn_moves(board, from_sq, move_list, num_moves)
|
|
TYPE(Board_Type), INTENT(IN) :: board
|
|
TYPE(Square_Type), INTENT(IN) :: from_sq
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list ! Array to store moves
|
|
INTEGER, INTENT(INOUT) :: num_moves ! Current count of moves in list
|
|
|
|
INTEGER :: r, f, dir, start_rank, promotion_rank, next_r, dbl_r, target_f
|
|
INTEGER :: player_color, opponent_color, target_color, target_piece
|
|
TYPE(Square_Type) :: to_sq, ep_sq
|
|
TYPE(Move_Type) :: new_move
|
|
LOGICAL :: can_promote
|
|
INTEGER, DIMENSION(4) :: promotion_options = (/ QUEEN, ROOK, BISHOP, KNIGHT /)
|
|
INTEGER :: i
|
|
|
|
r = from_sq%rank
|
|
f = from_sq%file
|
|
player_color = board%current_player
|
|
opponent_color = get_opponent_color(player_color)
|
|
|
|
IF (player_color == WHITE) THEN
|
|
dir = 1
|
|
start_rank = 2
|
|
promotion_rank = 8
|
|
ELSE
|
|
dir = -1
|
|
start_rank = 7
|
|
promotion_rank = 1
|
|
END IF
|
|
|
|
! 1. Single Push
|
|
next_r = r + dir
|
|
IF (sq_is_valid(next_r, f)) THEN
|
|
IF (board%squares_piece(next_r, f) == NO_PIECE) THEN
|
|
can_promote = (next_r == promotion_rank)
|
|
to_sq = file_rank_to_sq(f, next_r)
|
|
IF (can_promote) THEN
|
|
DO i = 1, 4
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = to_sq
|
|
new_move%promotion_piece = promotion_options(i)
|
|
new_move%captured_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END DO
|
|
ELSE
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = to_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
|
|
! 2. Double Push (only if single push was possible)
|
|
IF (r == start_rank) THEN
|
|
dbl_r = r + 2*dir
|
|
IF (sq_is_valid(dbl_r, f)) THEN
|
|
IF (board%squares_piece(dbl_r, f) == NO_PIECE) THEN
|
|
to_sq = file_rank_to_sq(f, dbl_r)
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = to_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
END IF
|
|
END IF
|
|
END IF
|
|
END IF
|
|
END IF
|
|
|
|
! 3. Captures (Diagonal)
|
|
IF (sq_is_valid(next_r, 1)) THEN ! Only need to check rank validity once
|
|
DO target_f = f-1, f+1, 2 ! Check f-1 and f+1
|
|
IF (sq_is_valid(next_r, target_f)) THEN
|
|
target_piece = board%squares_piece(next_r, target_f)
|
|
target_color = board%squares_color(next_r, target_f)
|
|
to_sq = file_rank_to_sq(target_f, next_r)
|
|
|
|
! Regular Capture
|
|
IF (target_piece /= NO_PIECE .AND. target_color == opponent_color) THEN
|
|
can_promote = (next_r == promotion_rank)
|
|
IF (can_promote) THEN
|
|
DO i = 1, 4
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = to_sq
|
|
new_move%promotion_piece = promotion_options(i)
|
|
new_move%captured_piece = target_piece
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END DO
|
|
ELSE
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = to_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = target_piece
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
! En Passant Capture
|
|
ELSE IF (board%ep_target_present .AND. &
|
|
next_r == board%ep_target_sq%rank .AND. &
|
|
target_f == board%ep_target_sq%file) THEN
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = board%ep_target_sq ! Move to EP target square
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = PAWN ! EP always captures a pawn
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .TRUE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
END IF
|
|
END DO
|
|
END IF
|
|
|
|
END SUBROUTINE generate_pawn_moves
|
|
|
|
! --- Generate Knight Moves ---
|
|
SUBROUTINE generate_knight_moves(board, from_sq, move_list, num_moves)
|
|
TYPE(Board_Type), INTENT(IN) :: board
|
|
TYPE(Square_Type), INTENT(IN) :: from_sq
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list
|
|
INTEGER, INTENT(INOUT) :: num_moves
|
|
|
|
INTEGER :: r, f, nr, nf, target_piece, target_color
|
|
INTEGER, DIMENSION(8,2) :: deltas
|
|
TYPE(Square_Type) :: to_sq
|
|
TYPE(Move_Type) :: new_move
|
|
INTEGER :: i
|
|
|
|
r = from_sq%rank
|
|
f = from_sq%file
|
|
|
|
deltas = RESHAPE((/ 2, 1, -1, -2, -2, -1, 1, 2, &
|
|
1, 2, 2, 1, -1, -2, -2, -1 /), (/8, 2/))
|
|
|
|
|
|
new_move%from_sq = from_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
|
|
DO i = 1, 8
|
|
nr = r + deltas(i, 1)
|
|
nf = f + deltas(i, 2)
|
|
IF (sq_is_valid(nr, nf)) THEN
|
|
target_piece = board%squares_piece(nr, nf)
|
|
target_color = board%squares_color(nr, nf)
|
|
to_sq = file_rank_to_sq(nf, nr)
|
|
new_move%to_sq = to_sq
|
|
|
|
IF (target_piece == NO_PIECE) THEN ! Move to empty square
|
|
new_move%captured_piece = NO_PIECE
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
ELSE IF (target_color /= board%current_player) THEN ! Capture
|
|
new_move%captured_piece = target_piece
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
END IF
|
|
END DO
|
|
END SUBROUTINE generate_knight_moves
|
|
|
|
|
|
! --- Generate Sliding Moves (Rook, Bishop, Queen) ---
|
|
SUBROUTINE generate_sliding_moves(board, from_sq, directions, num_dirs, move_list, num_moves)
|
|
TYPE(Board_Type), INTENT(IN) :: board
|
|
TYPE(Square_Type), INTENT(IN) :: from_sq
|
|
INTEGER, DIMENSION(num_dirs, 2), INTENT(IN) :: directions
|
|
INTEGER, INTENT(IN) :: num_dirs
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list
|
|
INTEGER, INTENT(INOUT) :: num_moves
|
|
|
|
INTEGER :: r, f, nr, nf, target_piece, target_color, dr, df
|
|
TYPE(Square_Type) :: to_sq
|
|
TYPE(Move_Type) :: new_move
|
|
INTEGER :: i
|
|
|
|
r = from_sq%rank
|
|
f = from_sq%file
|
|
|
|
new_move%from_sq = from_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
|
|
DO i = 1, num_dirs
|
|
dr = directions(i, 1)
|
|
df = directions(i, 2)
|
|
nr = r + dr
|
|
nf = f + df
|
|
DO WHILE (sq_is_valid(nr, nf))
|
|
target_piece = board%squares_piece(nr, nf)
|
|
target_color = board%squares_color(nr, nf)
|
|
to_sq = file_rank_to_sq(nf, nr)
|
|
new_move%to_sq = to_sq
|
|
|
|
IF (target_piece == NO_PIECE) THEN ! Move to empty square
|
|
new_move%captured_piece = NO_PIECE
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
ELSE ! Hit a piece
|
|
IF (target_color /= board%current_player) THEN ! Capture
|
|
new_move%captured_piece = target_piece
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
EXIT ! Stop searching this direction (path blocked)
|
|
END IF
|
|
|
|
nr = nr + dr ! Continue sliding
|
|
nf = nf + df
|
|
END DO
|
|
END DO
|
|
END SUBROUTINE generate_sliding_moves
|
|
|
|
! --- Generate King Moves (Including Castling placeholders) ---
|
|
SUBROUTINE generate_king_moves(board, from_sq, move_list, num_moves)
|
|
TYPE(Board_Type), INTENT(IN) :: board
|
|
TYPE(Square_Type), INTENT(IN) :: from_sq
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list
|
|
INTEGER, INTENT(INOUT) :: num_moves
|
|
|
|
INTEGER :: r, f, nr, nf, target_piece, target_color, back_rank
|
|
INTEGER, DIMENSION(8,2) :: deltas
|
|
TYPE(Square_Type) :: to_sq
|
|
TYPE(Move_Type) :: new_move
|
|
INTEGER :: i
|
|
LOGICAL :: can_castle_k, can_castle_q
|
|
|
|
r = from_sq%rank
|
|
f = from_sq%file
|
|
|
|
! 1. Normal Moves
|
|
deltas = RESHAPE((/ 1, 0, -1, 0, 1, 1, -1, -1, &
|
|
0, 1, 0, -1, 1, -1, 1, -1 /), (/8, 2/))
|
|
|
|
new_move%from_sq = from_sq
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%is_castling = .FALSE.
|
|
new_move%is_en_passant = .FALSE.
|
|
|
|
DO i = 1, 8
|
|
nr = r + deltas(i, 1)
|
|
nf = f + deltas(i, 2)
|
|
IF (sq_is_valid(nr, nf)) THEN
|
|
target_piece = board%squares_piece(nr, nf)
|
|
target_color = board%squares_color(nr, nf)
|
|
to_sq = file_rank_to_sq(nf, nr)
|
|
new_move%to_sq = to_sq
|
|
|
|
IF (target_piece == NO_PIECE) THEN ! Move to empty square
|
|
new_move%captured_piece = NO_PIECE
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
ELSE IF (target_color /= board%current_player) THEN ! Capture
|
|
new_move%captured_piece = target_piece
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
END IF
|
|
END DO
|
|
|
|
! 2. Castling (Pseudo-legal check: rights and empty squares)
|
|
! Legality check (squares not attacked) happens in generate_moves
|
|
IF (board%current_player == WHITE) THEN
|
|
back_rank = 1
|
|
can_castle_k = board%wc_k
|
|
can_castle_q = board%wc_q
|
|
ELSE
|
|
back_rank = 8
|
|
can_castle_k = board%bc_k
|
|
can_castle_q = board%bc_q
|
|
END IF
|
|
|
|
IF (r == back_rank .AND. f == 5) THEN ! King on e1/e8
|
|
! Kingside
|
|
IF (can_castle_k .AND. &
|
|
board%squares_piece(back_rank, 6) == NO_PIECE .AND. &
|
|
board%squares_piece(back_rank, 7) == NO_PIECE .AND. &
|
|
board%squares_piece(back_rank, 8) == ROOK .AND. & ! Check rook presence
|
|
board%squares_color(back_rank, 8) == board%current_player) THEN
|
|
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = file_rank_to_sq(7, back_rank) ! King to g1/g8
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = NO_PIECE
|
|
new_move%is_castling = .TRUE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
! Queenside
|
|
IF (can_castle_q .AND. &
|
|
board%squares_piece(back_rank, 4) == NO_PIECE .AND. &
|
|
board%squares_piece(back_rank, 3) == NO_PIECE .AND. &
|
|
board%squares_piece(back_rank, 2) == NO_PIECE .AND. &
|
|
board%squares_piece(back_rank, 1) == ROOK .AND. & ! Check rook presence
|
|
board%squares_color(back_rank, 1) == board%current_player) THEN
|
|
|
|
new_move%from_sq = from_sq
|
|
new_move%to_sq = file_rank_to_sq(3, back_rank) ! King to c1/c8
|
|
new_move%promotion_piece = NO_PIECE
|
|
new_move%captured_piece = NO_PIECE
|
|
new_move%is_castling = .TRUE.
|
|
new_move%is_en_passant = .FALSE.
|
|
CALL add_move(move_list, num_moves, new_move)
|
|
END IF
|
|
END IF
|
|
|
|
END SUBROUTINE generate_king_moves
|
|
|
|
|
|
! --- Generate All Pseudo-Legal Moves ---
|
|
SUBROUTINE generate_pseudo_moves(board, move_list, num_moves)
|
|
TYPE(Board_Type), INTENT(IN) :: board
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(INOUT) :: move_list ! Array to store moves
|
|
INTEGER, INTENT(OUT) :: num_moves ! Count of moves generated
|
|
|
|
INTEGER :: r, f, piece, color
|
|
TYPE(Square_Type) :: from_sq
|
|
! Define directions for sliding pieces here
|
|
INTEGER, DIMENSION(4, 2) :: bishop_dirs = RESHAPE((/ 1, 1, 1, -1, -1, 1, -1, -1 /), (/4, 2/))
|
|
INTEGER, DIMENSION(4, 2) :: rook_dirs = RESHAPE((/ 1, 0, -1, 0, 0, 1, 0, -1 /), (/4, 2/))
|
|
INTEGER, DIMENSION(8, 2) :: queen_dirs = RESHAPE([ 1, -1, 0, 0, 1, 1, -1, -1, &
|
|
0, 0, 1, -1, 1, -1, 1, -1 ], [8, 2])
|
|
|
|
num_moves = 0 ! Reset count
|
|
|
|
DO r = 1, BOARD_SIZE
|
|
DO f = 1, BOARD_SIZE
|
|
piece = board%squares_piece(r, f)
|
|
color = board%squares_color(r, f)
|
|
|
|
IF (color == board%current_player) THEN
|
|
from_sq = file_rank_to_sq(f, r)
|
|
SELECT CASE (piece)
|
|
CASE (PAWN)
|
|
CALL generate_pawn_moves(board, from_sq, move_list, num_moves)
|
|
CASE (KNIGHT)
|
|
CALL generate_knight_moves(board, from_sq, move_list, num_moves)
|
|
CASE (BISHOP)
|
|
CALL generate_sliding_moves(board, from_sq, bishop_dirs, 4, move_list, num_moves)
|
|
CASE (ROOK)
|
|
CALL generate_sliding_moves(board, from_sq, rook_dirs, 4, move_list, num_moves)
|
|
CASE (QUEEN)
|
|
CALL generate_sliding_moves(board, from_sq, queen_dirs, 8, move_list, num_moves)
|
|
CASE (KING)
|
|
CALL generate_king_moves(board, from_sq, move_list, num_moves)
|
|
END SELECT
|
|
END IF
|
|
END DO
|
|
END DO
|
|
END SUBROUTINE generate_pseudo_moves
|
|
|
|
|
|
! --- Generate Legal Moves (Filters Pseudo-Legal) ---
|
|
SUBROUTINE generate_moves(board, legal_move_list, num_legal_moves)
|
|
USE make_unmake
|
|
TYPE(Board_Type), INTENT(INOUT) :: board ! Needs INOUT for make/unmake
|
|
TYPE(Move_Type), DIMENSION(:), INTENT(OUT) :: legal_move_list
|
|
INTEGER, INTENT(OUT) :: num_legal_moves
|
|
|
|
TYPE(Move_Type), DIMENSION(MAX_MOVES) :: pseudo_moves
|
|
INTEGER :: num_pseudo_moves
|
|
INTEGER :: i
|
|
TYPE(Move_Type) :: current_move
|
|
TYPE(UnmakeInfo_Type) :: unmake_info
|
|
LOGICAL :: is_legal
|
|
INTEGER :: player_color, opponent_color
|
|
TYPE(Square_Type) :: king_sq, mid_sq
|
|
|
|
num_legal_moves = 0
|
|
player_color = board%current_player
|
|
opponent_color = get_opponent_color(player_color)
|
|
|
|
! 1. Generate all pseudo-legal moves
|
|
CALL generate_pseudo_moves(board, pseudo_moves, num_pseudo_moves)
|
|
|
|
! 2. Filter for legality
|
|
DO i = 1, num_pseudo_moves
|
|
current_move = pseudo_moves(i)
|
|
is_legal = .TRUE. ! Assume legal initially
|
|
|
|
! Special check for castling through check
|
|
IF (current_move%is_castling) THEN
|
|
king_sq = current_move%from_sq
|
|
IF (current_move%to_sq%file == 7) THEN ! Kingside
|
|
mid_sq = file_rank_to_sq(6, king_sq%rank)
|
|
ELSE ! Queenside (to_sq%file == 3)
|
|
mid_sq = file_rank_to_sq(4, king_sq%rank)
|
|
END IF
|
|
IF (is_square_attacked(board, king_sq, opponent_color) .OR. &
|
|
is_square_attacked(board, mid_sq, opponent_color) .OR. &
|
|
is_square_attacked(board, current_move%to_sq, opponent_color)) THEN
|
|
is_legal = .FALSE.
|
|
END IF
|
|
END IF
|
|
|
|
IF (is_legal) THEN
|
|
! 3. Make the move, check if king is safe, unmake the move
|
|
CALL make_move(board, current_move, unmake_info)
|
|
IF (.NOT. is_in_check(board, player_color)) THEN
|
|
! Add to legal move list
|
|
CALL add_move(legal_move_list, num_legal_moves, current_move)
|
|
END IF
|
|
CALL unmake_move(board, current_move, unmake_info)
|
|
END IF
|
|
END DO
|
|
|
|
END SUBROUTINE generate_moves
|
|
|
|
|
|
END MODULE Move_Generation
|