use crate::core::Move10;
use crate::board::{PieceMapping, Occupied};
use crate::rules::castling::CastlingLogic;
const KNIGHT_OFFSETS: &[(i8, i8)] = &[
( 2, 1), ( 2, -1), (-2, 1), (-2, -1),
( 1, 2), ( 1, -2), (-1, 2), (-1, -2),
];
const KING_OFFSETS: &[(i8, i8)] = &[
( 1, 0), ( 1, 1), ( 0, 1), (-1, 1),
(-1, 0), (-1, -1), ( 0, -1), ( 1, -1),
];
const ORTHO_DIRS: &[(i8, i8)] = &[( 1, 0), (-1, 0), ( 0, 1), ( 0, -1)];
const DIAG_DIRS: &[(i8, i8)] = &[( 1, 1), ( 1, -1), (-1, 1), (-1, -1)];
#[inline]
fn sq_to_coords(sq: u8) -> (i8, i8) {
((sq >> 3) as i8, (sq & 7) as i8)
}
#[inline]
fn coords_to_sq(rank: i8, file: i8) -> u8 {
((rank as u8) << 3) | (file as u8)
}
#[inline]
fn on_board(rank: i8, file: i8) -> bool {
(0..8).contains(&rank) && (0..8).contains(&file)
}
#[inline]
fn piece_type(pid: u8) -> u8 {
pid
}
#[inline]
fn is_pawn(pid: u8) -> bool {
(0..8).contains(&pid) || (16..24).contains(&pid)
}
#[inline]
fn color_of(pid: u8) -> u8 {
if pid < 16 { 0 } else { 1 }
}
#[inline]
fn pawn_home_rank(color: u8) -> i8 {
if color == 0 { 1 } else { 6 }
}
#[inline]
fn pawn_dir(color: u8) -> i8 {
if color == 0 { 1 } else { -1 }
}
#[inline]
fn is_ep_target(dest: u8, ep_target: Option<u8>) -> bool {
if let Some(t) = ep_target {
t == dest
} else {
false
}
}
pub struct MoveGenerator;
impl MoveGenerator {
pub fn generate(
mapping: &PieceMapping,
occupied: Occupied,
captured_bits: u32,
en_passant_target: Option<u8>,
side: u8,
) -> Vec<Move10> {
let mut moves = Vec::new();
let start_pid = if side == 0 { 0 } else { 16 };
let end_pid = start_pid + 16;
for pid in start_pid..end_pid {
if let Some(src_sq) = mapping.piece_square[pid as usize] {
let sq = src_sq;
let (r, f) = sq_to_coords(sq);
let piece = piece_type(pid);
match piece {
p if is_pawn(p) => {
Self::pawn_moves(pid, r, f, mapping, occupied, en_passant_target, &mut moves);
}
10..=11 | 26..=27 => {
Self::knight_moves(pid, r, f, mapping, occupied, &mut moves);
}
12..=13 | 28..=29 => {
Self::bishop_moves(pid, r, f, mapping, occupied, &mut moves);
}
8..=9 | 24..=25 => {
Self::rook_moves(pid, r, f, mapping, occupied, &mut moves);
}
14 | 30 => {
Self::rook_moves(pid, r, f, mapping, occupied, &mut moves);
Self::bishop_moves(pid, r, f, mapping, occupied, &mut moves);
}
15 | 31 => {
Self::king_moves(pid, r, f, mapping, occupied, side, en_passant_target, &mut moves);
}
_ => { }
}
}
}
moves
}
fn pawn_moves(
pid: u8,
r: i8,
f: i8,
mapping: &PieceMapping,
occupied: Occupied,
ep_target: Option<u8>,
moves: &mut Vec<Move10>,
) {
let side = color_of(pid);
let dir = pawn_dir(side);
let r1 = r + dir;
let f1 = f;
if on_board(r1, f1) {
let sq1 = coords_to_sq(r1, f1);
if (occupied >> sq1) & 1 == 0 {
let pid_within = pid % 16;
moves.push(Move10::new(pid_within, sq1));
let home_rank = pawn_home_rank(side);
if r == home_rank {
let r2 = r + 2 * dir;
if on_board(r2, f1) {
let sq2 = coords_to_sq(r2, f1);
if (occupied >> sq2) & 1 == 0 {
let between = coords_to_sq(r + dir, f);
if (occupied >> between) & 1 == 0 {
moves.push(Move10::new(pid_within, sq2));
}
}
}
}
}
}
for df in &[-1, 1] {
let rf = r + dir;
let ff = f + df;
if on_board(rf, ff) {
let dest_sq = coords_to_sq(rf, ff);
let pid_within = pid % 16;
if is_ep_target(dest_sq, ep_target) {
moves.push(Move10::new(pid_within, dest_sq));
} else if let Some(opp_pid) = mapping.who_on_square(dest_sq) {
if color_of(opp_pid) != side {
moves.push(Move10::new(pid_within, dest_sq));
}
}
}
}
}
fn knight_moves(
pid: u8,
r: i8,
f: i8,
mapping: &PieceMapping,
occupied: Occupied,
moves: &mut Vec<Move10>,
) {
let side = color_of(pid);
let pid_within = pid % 16;
for &(dr, df) in KNIGHT_OFFSETS {
let nr = r + dr;
let nf = f + df;
if on_board(nr, nf) {
let nsq = coords_to_sq(nr, nf);
if let Some(other) = mapping.who_on_square(nsq) {
if color_of(other) == side {
continue;
}
}
moves.push(Move10::new(pid_within, nsq));
}
}
}
fn bishop_moves(
pid: u8,
r: i8,
f: i8,
mapping: &PieceMapping,
occupied: Occupied,
moves: &mut Vec<Move10>,
) {
let side = color_of(pid);
let pid_within = pid % 16;
for &(dr, df) in DIAG_DIRS {
let mut nr = r + dr;
let mut nf = f + df;
while on_board(nr, nf) {
let sq = coords_to_sq(nr, nf);
if let Some(other) = mapping.who_on_square(sq) {
if color_of(other) != side {
moves.push(Move10::new(pid_within, sq));
}
break;
} else {
moves.push(Move10::new(pid_within, sq));
}
nr += dr;
nf += df;
}
}
}
fn rook_moves(
pid: u8,
r: i8,
f: i8,
mapping: &PieceMapping,
occupied: Occupied,
moves: &mut Vec<Move10>,
) {
let side = color_of(pid);
let pid_within = pid % 16;
for &(dr, df) in ORTHO_DIRS {
let mut nr = r + dr;
let mut nf = f + df;
while on_board(nr, nf) {
let sq = coords_to_sq(nr, nf);
if let Some(other) = mapping.who_on_square(sq) {
if color_of(other) != side {
moves.push(Move10::new(pid_within, sq));
}
break;
} else {
moves.push(Move10::new(pid_within, sq));
}
nr += dr;
nf += df;
}
}
}
fn king_moves(
pid: u8,
r: i8,
f: i8,
mapping: &PieceMapping,
occupied: Occupied,
side: u8,
ep_target: Option<u8>,
moves: &mut Vec<Move10>,
) {
let pid_within = pid % 16;
for &(dr, df) in KING_OFFSETS {
let nr = r + dr;
let nf = f + df;
if on_board(nr, nf) {
let sq = coords_to_sq(nr, nf);
if let Some(other) = mapping.who_on_square(sq) {
if color_of(other) != side {
moves.push(Move10::new(pid_within, sq));
}
} else {
moves.push(Move10::new(pid_within, sq));
}
}
}
if side == 0 {
if r == 0 && f == 4 {
if CastlingLogic::can_white_kingside(mapping, occupied) {
moves.push(Move10::new(pid_within, 6)); }
if CastlingLogic::can_white_queenside(mapping, occupied) {
moves.push(Move10::new(pid_within, 2)); }
}
} else {
if r == 7 && f == 4 {
if CastlingLogic::can_black_kingside(mapping, occupied) {
moves.push(Move10::new(pid_within, 62)); }
if CastlingLogic::can_black_queenside(mapping, occupied) {
moves.push(Move10::new(pid_within, 58)); }
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::board::{encode_square, init_chess_positions, PieceMapping};
#[test]
fn simple_pawn_push_capture() {
let mut mapping = PieceMapping::new_empty();
let mut occupied: u64 = 0;
mapping.place_piece(0, encode_square(1, 1));
occupied |= 1u64 << 9;
mapping.place_piece(17, encode_square(2, 2));
occupied |= 1u64 << 18;
let moves = MoveGenerator::generate(&mapping, occupied, 0, None, 0);
let mut found = false;
for mv in moves {
if mv.piece_id() == 0 && mv.dest() == 17 {
found = true;
}
if mv.piece_id() == 0 && mv.dest() == 18 {
found = true;
}
}
assert!(found);
}
#[test]
fn knight_jumps_and_blocking() {
let mut mapping = PieceMapping::new_empty();
let mut occupied: u64 = 0;
mapping.place_piece(10, encode_square(0, 6));
occupied |= 1u64 << 6;
mapping.place_piece(5, encode_square(2, 5));
occupied |= 1u64 << 21;
mapping.place_piece(20, encode_square(1, 4));
occupied |= 1u64 << 12;
let moves = MoveGenerator::generate(&mapping, occupied, 0, None, 0);
let mut found_capture = false;
for mv in moves {
if mv.piece_id() == 10 && mv.dest() == 12 {
found_capture = true;
}
}
assert!(found_capture);
}
#[test]
fn sliding_rook_moves() {
let mut mapping = PieceMapping::new_empty();
let mut occupied: u64 = 0;
mapping.place_piece(8, encode_square(3, 3));
occupied |= 1u64 << 27;
mapping.place_piece(3, encode_square(5, 3));
occupied |= 1u64 << 43;
mapping.place_piece(19, encode_square(1, 3));
occupied |= 1u64 << 11;
let moves = MoveGenerator::generate(&mapping, occupied, 0, None, 0);
let mut saw_d5 = false;
let mut saw_d2 = false;
for mv in moves {
if mv.piece_id() == 8 && mv.dest() == 35 {
saw_d5 = true;
}
if mv.piece_id() == 8 && mv.dest() == 11 {
saw_d2 = true;
}
}
assert!(saw_d5 && saw_d2);
}
#[test]
fn castling_generation() {
let pos = init_chess_positions();
let mut mapping = PieceMapping::new_empty();
let mut occupied: u64 = 0;
for &(pid, sq) in &pos {
mapping.place_piece(pid, sq);
occupied |= 1u64 << (sq as u64);
}
mapping.remove_piece(13);
mapping.remove_piece(11);
occupied &= !(1u64 << encode_square(0, 5)); occupied &= !(1u64 << encode_square(0, 6));
let moves_wh = MoveGenerator::generate(&mapping, occupied, 0, None, 0);
let mut found_ck = false;
for mv in moves_wh {
if mv.piece_id() == 15 && mv.dest() == 6 {
found_ck = true;
}
}
assert!(found_ck);
}
}