use crate::games::chess::board::Square;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PromotionPiece {
Queen,
Rook,
Bishop,
Knight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChessMove {
pub from: Square,
pub to: Square,
pub promotion: Option<PromotionPiece>,
}
impl ChessMove {
pub fn new(from: Square, to: Square) -> Self {
Self {
from,
to,
promotion: None,
}
}
pub fn new_with_promotion(from: Square, to: Square, promotion: PromotionPiece) -> Self {
Self {
from,
to,
promotion: Some(promotion),
}
}
#[inline]
pub fn from_rank(&self) -> u8 {
self.from.rank()
}
#[inline]
pub fn from_file(&self) -> u8 {
self.from.file()
}
#[inline]
pub fn to_rank(&self) -> u8 {
self.to.rank()
}
#[inline]
pub fn to_file(&self) -> u8 {
self.to.file()
}
fn compute_move_plane(&self) -> usize {
let from_rank = self.from_rank() as i8;
let from_file = self.from_file() as i8;
let to_rank = self.to_rank() as i8;
let to_file = self.to_file() as i8;
let delta_rank = to_rank - from_rank;
let delta_file = to_file - from_file;
let knight_moves = [
(2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1), ];
for (i, &(dr, df)) in knight_moves.iter().enumerate() {
if delta_rank == dr && delta_file == df {
return 56 + i;
}
}
if let Some(promo) = self.promotion {
if matches!(
promo,
PromotionPiece::Knight | PromotionPiece::Bishop | PromotionPiece::Rook
) {
let piece_offset = match promo {
PromotionPiece::Knight => 0,
PromotionPiece::Bishop => 3,
PromotionPiece::Rook => 6,
_ => unreachable!(),
};
let direction = match delta_file {
-1 => 0, 0 => 1, 1 => 2, _ => panic!("Invalid promotion move"),
};
return 64 + piece_offset + direction;
}
}
let direction_index = if delta_rank > 0 && delta_file == 0 {
0 } else if delta_rank > 0 && delta_file > 0 && delta_rank == delta_file {
1 } else if delta_rank == 0 && delta_file > 0 {
2 } else if delta_rank < 0 && delta_file > 0 && -delta_rank == delta_file {
3 } else if delta_rank < 0 && delta_file == 0 {
4 } else if delta_rank < 0 && delta_file < 0 && delta_rank == delta_file {
5 } else if delta_rank == 0 && delta_file < 0 {
6 } else if delta_rank > 0 && delta_file < 0 && delta_rank == -delta_file {
7 } else {
panic!(
"Invalid queen-like move: delta_rank={}, delta_file={}",
delta_rank, delta_file
);
};
let distance = delta_rank.abs().max(delta_file.abs()) as usize;
assert!(
(1..=7).contains(&distance),
"Invalid distance: {}",
distance
);
direction_index * 7 + (distance - 1)
}
fn decode_move_plane(from: Square, plane: usize) -> (Square, Option<PromotionPiece>) {
let from_rank = (from.rank()) as i8;
let from_file = (from.file()) as i8;
let (delta_rank, delta_file, promotion) = if plane < 56 {
let direction = plane / 7;
let distance = (plane % 7 + 1) as i8;
let (dr, df) = match direction {
0 => (1, 0), 1 => (1, 1), 2 => (0, 1), 3 => (-1, 1), 4 => (-1, 0), 5 => (-1, -1), 6 => (0, -1), 7 => (1, -1), _ => unreachable!(),
};
(dr * distance, df * distance, None)
} else if plane < 64 {
let knight_index = plane - 56;
let (dr, df) = match knight_index {
0 => (2, 1),
1 => (1, 2),
2 => (-1, 2),
3 => (-2, 1),
4 => (-2, -1),
5 => (-1, -2),
6 => (1, -2),
7 => (2, -1),
_ => unreachable!(),
};
(dr, df, None)
} else {
let promo_index = plane - 64;
let piece = match promo_index / 3 {
0 => PromotionPiece::Knight,
1 => PromotionPiece::Bishop,
2 => PromotionPiece::Rook,
_ => unreachable!(),
};
let direction = promo_index % 3;
let df = match direction {
0 => -1, 1 => 0, 2 => 1, _ => unreachable!(),
};
(1, df, Some(piece))
};
let to_rank = from_rank + delta_rank;
let to_file = from_file + delta_file;
let to_rank = to_rank.clamp(0, 7) as u8;
let to_file = to_file.clamp(0, 7) as u8;
let to = to_rank * 8 + to_file;
(Square(to), promotion)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_square_indices() {
let mv = ChessMove::new(Square(0), Square(16)); assert_eq!(mv.from_rank(), 0);
assert_eq!(mv.from_file(), 0);
let mv = ChessMove::new(Square(63), Square(47)); assert_eq!(mv.from_rank(), 7);
assert_eq!(mv.from_file(), 7);
let mv = ChessMove::new(Square(12), Square(28)); assert_eq!(mv.from_rank(), 1);
assert_eq!(mv.from_file(), 4);
}
#[test]
fn test_knight_move_encoding() {
let _mv = ChessMove::new(Square(28), Square(45));
}
#[test]
fn test_queen_move_encoding() {
let _mv = ChessMove::new(Square(12), Square(28));
}
#[test]
fn test_promotion_encoding() {
let _mv = ChessMove::new_with_promotion(Square(52), Square(60), PromotionPiece::Knight);
}
}