From fba4e78c21bdd1bcbba08f6a0cf58664f2b8a4d5 Mon Sep 17 00:00:00 2001 From: Edgardo Carreras Date: Sat, 27 Jul 2024 00:38:05 -0400 Subject: [PATCH] Finish adding takes to pawn pieces --- .github/workflows/release.yaml | 2 +- src/board.rs | 64 ++++++++++++++++++----- src/main.rs | 92 +++++++++++++++++----------------- src/pieces/bishop.rs | 4 ++ src/pieces/king.rs | 4 ++ src/pieces/knight.rs | 4 ++ src/pieces/mod.rs | 1 + src/pieces/pawn.rs | 90 +++++++++++++++++++++++++++++++-- src/pieces/queen.rs | 4 ++ src/pieces/rook.rs | 4 ++ 10 files changed, 205 insertions(+), 64 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 5bfe136..17702a8 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,7 +7,7 @@ on: env: # update with the name of the main binary - binary: rusty_chess + binary: rusty-chess add_binaries_to_github_release: true #itch_target: / diff --git a/src/board.rs b/src/board.rs index 080cc2b..9d0227f 100644 --- a/src/board.rs +++ b/src/board.rs @@ -127,19 +127,30 @@ impl CheckerBoard { pub fn piece_at(&self, position: &BoardPosition) -> Option<&Box> { self.pieces.get(position) } - pub fn move_piece(&mut self, from: &BoardPosition, to: &BoardPosition) { + fn force_move_piece(&mut self, from: &BoardPosition, to: &BoardPosition) { + let piece = self.pieces.remove(from); + if let Some(from_piece) = piece { + self.pieces.insert(to.clone(), from_piece); + } + } + + pub fn move_piece(&mut self, from: &BoardPosition, to: &BoardPosition) -> Vec { + let mut updated_positions = Vec::with_capacity(3); + if !self.is_valid_move(from, to) { + return updated_positions; + } let piece = self.pieces.remove(from); if let Some(p) = piece { - if self.active_turn() != p.color() { - return; - } self.moves.push(BoardMove::new( p.piece_type().clone(), from.clone(), to.clone(), )); self.pieces.insert(to.clone(), p); + updated_positions.push(from.clone()); + updated_positions.push(to.clone()); } + updated_positions } pub fn get_possible_moves(&self, from: &BoardPosition) -> Vec { @@ -151,7 +162,7 @@ impl CheckerBoard { .into_iter() .filter(|pos| { let mut prediction_board = self.clone(); - prediction_board.move_piece(from, &pos); + prediction_board.force_move_piece(from, &pos); !prediction_board.is_checked(piece.color()) }) .collect(), @@ -235,8 +246,8 @@ impl CheckerBoard { turns[self.moves.len() % turns.len()] } - pub fn is_valid_move(&self, from: &BoardPosition, to:&BoardPosition) -> bool { - if let Some(piece) = self.pieces.get(from) { + pub fn is_valid_move(&self, from: &BoardPosition, to: &BoardPosition) -> bool { + if let Some(piece) = self.pieces.get(from) { if piece.color() != self.active_turn() { return false; } @@ -300,12 +311,12 @@ mod chess_board_tests { } #[test] - fn it_can_move_pieces() { + fn it_can_force_move_pieces() { let mut board = CheckerBoard::new(); let position = BoardPosition::new(0, 0); board.spawn(&position, PieceType::Pawn, PieceColor::White); let new_position = BoardPosition::new(1, 1); - board.move_piece(&position, &new_position); + board.force_move_piece(&position, &new_position); let piece_at_old_pos = board.piece_at(&position); assert!(piece_at_old_pos.is_none()); let piece_at_new_pos = board.piece_at(&new_position).unwrap(); @@ -343,6 +354,7 @@ mod chess_board_tests { assert_eq!(last_move.from(), &board_pos!["b2"]); assert_eq!(last_move.to(), &board_pos!["b3"]); } + #[test] fn non_eight_row_is_not_last_row_for_white() { let board = CheckerBoard::new(); @@ -673,16 +685,44 @@ mod chess_board_tests { #[test] fn incorrect_move_is_invalid() { - let board = CheckerBoard::default(); + let board = CheckerBoard::default(); assert!(!board.is_valid_move(&board_pos!("e2"), &board_pos!("e7"))); } - #[test] + #[test] fn out_of_order_move_is_invalid() { - let board = CheckerBoard::default(); + let board = CheckerBoard::default(); assert!(!board.is_valid_move(&board_pos!("e7"), &board_pos!("e6"))); } + #[test] + fn can_not_move_if_move_is_invalid() { + let mut board = CheckerBoard::default(); + board.move_piece(&board_pos!("e7"), &board_pos!("e6")); + assert!(board.piece_at(&board_pos!("e6")).is_none()); + board.move_piece(&board_pos!("e2"), &board_pos!("c4")); + assert!(board.piece_at(&board_pos!("c4")).is_none()); + } + + #[test] + fn move_piece_returns_empty_list_when_invalid_pos() { + let mut board = CheckerBoard::default(); + let updated_pos = board.move_piece(&board_pos!("e7"), &board_pos!("e6")); + assert!(updated_pos.is_empty()); + assert!(board.piece_at(&board_pos!("e6")).is_none()); + let updated_pos = board.move_piece(&board_pos!("e2"), &board_pos!("c4")); + assert!(updated_pos.is_empty()); + assert!(board.piece_at(&board_pos!("c4")).is_none()); + } + + #[test] + fn move_piece_returns_list_of_updated_positions() { + let mut board = CheckerBoard::default(); + let updated_pos = board.move_piece(&board_pos!("a2"), &board_pos!("a3")); + assert!(updated_pos.contains(&board_pos!("a2"))); + assert!(updated_pos.contains(&board_pos!("a3"))); + } + fn assert_all_pos_have_pieces( board: CheckerBoard, rook_positions: impl Iterator, diff --git a/src/main.rs b/src/main.rs index 809ead2..06c2979 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use crate::board_position_marker::{add_board_pos_markers_sprite, BoardPositionMa use bevy::asset::AssetMetaCheck; use bevy::ecs::bundle::DynamicBundle; use bevy::prelude::*; -use bevy_mod_picking::debug::DebugPickingMode; use bevy_mod_picking::prelude::{Drag, DragEnd, DragStart, Drop, Listener, On, Pickable, Pointer}; use bevy_mod_picking::{low_latency_window_plugin, DefaultPickingPlugins, PickableBundle}; use board_ui_factory::BoardUiFactory; @@ -47,6 +46,7 @@ fn main() { #[cfg(feature = "debug")] { use bevy_inspector_egui::quick::WorldInspectorPlugin; + use bevy_mod_picking::debug::DebugPickingMode; app.add_plugins(WorldInspectorPlugin::new()); app.insert_resource(DebugPickingMode::Normal); } @@ -104,19 +104,18 @@ fn setup( to = Some(pos.0.clone()) } if let (Some(from), Some(to)) = (from, to) { - println!( - "From {:?}, To: {:?}", - from, to - ); - if !board_ui_factory.board.is_valid_move(&from, &to) { - let transform = board_ui_factory.get_pos_transform(&from); - commands.entity(event.dropped).insert(transform); - } else { - board_ui_factory.board.move_piece(&from, &to); - let transform = board_ui_factory.get_pos_transform(&to); - commands.entity(event.dropped).insert(transform); - commands.entity(event.dropped).insert(BoardPieceComponent(to)); - } + println!("From {:?}, To: {:?}", from, to); + if !board_ui_factory.board.is_valid_move(&from, &to) { + let transform = board_ui_factory.get_pos_transform(&from); + commands.entity(event.dropped).insert(transform); + } else { + board_ui_factory.board.move_piece(&from, &to); + let transform = board_ui_factory.get_pos_transform(&to); + commands.entity(event.dropped).insert(transform); + commands + .entity(event.dropped) + .insert(BoardPieceComponent(to)); + } } for marker in marker_query.iter() { commands.entity(marker).despawn(); @@ -164,39 +163,38 @@ fn setup( }), On::>::target_insert(Pickable::default()), On::>::run( - |event: Listener>, - mut commands: Commands, - mut board_ui_factory: ResMut, - query: Query<&BoardPieceComponent>, - marker_query: Query>| { - let mut from: Option = None; - let mut to: Option = None; - for pos in query.get(event.dropped).into_iter() { - from = Some(pos.0.clone()) - } - for pos in query.get(event.target).into_iter() { - to = Some(pos.0.clone()) - } - if let (Some(from), Some(to)) = (from, to) { - println!( - "From {:?}, To: {:?}", - from, to - ); - if !board_ui_factory.board.is_valid_move(&from, &to) { - let transform = board_ui_factory.get_pos_transform(&from); - commands.entity(event.dropped).insert(transform); - } else { - board_ui_factory.board.move_piece(&from, &to); - let transform = board_ui_factory.get_pos_transform(&to); - commands.entity(event.dropped).insert(transform); - commands.entity(event.target).despawn(); - commands.entity(event.dropped).insert(BoardPieceComponent(to)); - } - } - for marker in marker_query.iter() { - commands.entity(marker).despawn(); - } - }, + |event: Listener>, + mut commands: Commands, + mut board_ui_factory: ResMut, + query: Query<&BoardPieceComponent>, + marker_query: Query>| { + let mut from: Option = None; + let mut to: Option = None; + for pos in query.get(event.dropped).into_iter() { + from = Some(pos.0.clone()) + } + for pos in query.get(event.target).into_iter() { + to = Some(pos.0.clone()) + } + if let (Some(from), Some(to)) = (from, to) { + println!("From {:?}, To: {:?}", from, to); + if !board_ui_factory.board.is_valid_move(&from, &to) { + let transform = board_ui_factory.get_pos_transform(&from); + commands.entity(event.dropped).insert(transform); + } else { + board_ui_factory.board.move_piece(&from, &to); + let transform = board_ui_factory.get_pos_transform(&to); + commands.entity(event.dropped).insert(transform); + commands.entity(event.target).despawn(); + commands + .entity(event.dropped) + .insert(BoardPieceComponent(to)); + } + } + for marker in marker_query.iter() { + commands.entity(marker).despawn(); + } + }, ), )); } diff --git a/src/pieces/bishop.rs b/src/pieces/bishop.rs index 542dd8e..bcca683 100644 --- a/src/pieces/bishop.rs +++ b/src/pieces/bishop.rs @@ -33,6 +33,10 @@ impl Piece for Bishop { fn is_opponent(&self, color: &PieceColor) -> bool { self.color() != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + todo!() + } } #[cfg(test)] mod bishop_test { diff --git a/src/pieces/king.rs b/src/pieces/king.rs index ad5bedc..eaad057 100644 --- a/src/pieces/king.rs +++ b/src/pieces/king.rs @@ -48,6 +48,10 @@ impl Piece for King { fn is_opponent(&self, color: &PieceColor) -> bool { &self.color != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + todo!() + } } #[cfg(test)] diff --git a/src/pieces/knight.rs b/src/pieces/knight.rs index 0676279..7269f3b 100644 --- a/src/pieces/knight.rs +++ b/src/pieces/knight.rs @@ -42,6 +42,10 @@ impl Piece for Knight { fn is_opponent(&self, color: &PieceColor) -> bool { &self.color != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + todo!() + } } #[cfg(test)] diff --git a/src/pieces/mod.rs b/src/pieces/mod.rs index ebdb006..205e5aa 100644 --- a/src/pieces/mod.rs +++ b/src/pieces/mod.rs @@ -20,6 +20,7 @@ pub trait Piece: CloneBox + Send + Sync { fn piece_type(&self) -> &PieceType; fn get_all_moves(&self, board: &CheckerBoard, from: &BoardPosition) -> Vec; fn is_opponent(&self, color: &PieceColor) -> bool; + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec; } pub trait CloneBox { diff --git a/src/pieces/pawn.rs b/src/pieces/pawn.rs index 0fe5cd6..b963101 100644 --- a/src/pieces/pawn.rs +++ b/src/pieces/pawn.rs @@ -205,7 +205,7 @@ impl Piece for Pawn { let possible_forward_moves = self.get_most_forward_moves(from, board); let possible_takes = self.get_possible_take_positions(from, board); let mut moves = - self.get_possible_moves(board, &from, possible_forward_moves, possible_takes); + self.get_possible_moves(board, from, possible_forward_moves, possible_takes); let possible_en_passant = self.get_possible_en_passant_take(from, board.get_last_move()); if let Some(en_passant_take) = possible_en_passant { moves.push(en_passant_take); @@ -216,6 +216,24 @@ impl Piece for Pawn { fn is_opponent(&self, color: &PieceColor) -> bool { &self.color != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + let mut takes = Vec::with_capacity(1); + if board.piece_at(to).is_some() { + takes.push(to.clone()) + } + if let Some(en_passant) = self.get_possible_en_passant_take(from, board.get_last_move()) { + if &en_passant == to { + match self.color() { + PieceColor::White => + takes.push(BoardPosition::new(to.x(), to.y() - 1)), + PieceColor::Black => + takes.push(BoardPosition::new(to.x(), to.y() + 1)) + } + } + } + takes + } } #[cfg(test)] @@ -455,9 +473,10 @@ mod white_pawn_tests { #[test] fn cant_en_passant_if_black_didnt_pass_from_seventh_row() { let b6 = BoardPiece::build(PieceType::Pawn, PieceColor::Black, "b6"); - let c5 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "c5"); - let pieces = vec![b6, c5]; + let c4 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "c4"); + let pieces = vec![b6, c4]; let mut board = CheckerBoard::with_pieces(pieces); + board.move_piece(&board_pos!["c4"], &board_pos!["c5"]); board.move_piece(&board_pos!["b6"], &board_pos!["b5"]); let moves = board.get_possible_moves(&board_pos!("c5")); assert!(!moves.contains(&board_pos!("b6"))); @@ -475,7 +494,6 @@ mod white_pawn_tests { assert!(moves.contains(&board_pos!("b6"))); } - #[test] fn cant_move_into_a_check() { let e4 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "e4"); @@ -486,6 +504,39 @@ mod white_pawn_tests { let moves = board.get_possible_moves(&board_pos!("e4")); assert!(!moves.contains(&board_pos!("e5"))); } + + #[test] + fn takes_is_empty_when_no_takes_in_move() { + let board = CheckerBoard::default(); + let pawn = Pawn::new(PieceColor::White); + let takes = pawn.takes(&board, &board_pos!("e2"), &board_pos!("e4")); + assert!(takes.is_empty()); + } + + #[test] + fn takes_is_has_pos_of_taken_piece() { + let e4 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "e4"); + let d5 = BoardPiece::build(PieceType::Pawn, PieceColor::Black, "d5"); + let pieces = vec![e4, d5]; + let board = CheckerBoard::with_pieces(pieces); + + let pawn = Pawn::new(PieceColor::White); + let takes = pawn.takes(&board, &board_pos!("e4"), &board_pos!("d5")); + assert_eq!(takes, vec![board_pos!("d5")]); + } + + #[test] + fn takes_return_position_from_pawn_taken() { + let c5 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "c4"); + let b6 = BoardPiece::build(PieceType::Pawn, PieceColor::Black, "b7"); + let pieces = vec![b6, c5]; + let mut board = CheckerBoard::with_pieces(pieces); + board.move_piece(&board_pos!["c4"], &board_pos!["c5"]); + board.move_piece(&board_pos!["b7"], &board_pos!["b5"]); + let pawn = Pawn::new(PieceColor::White); + let takes = pawn.takes(&board, &board_pos!("c5"), &board_pos!("b6")); + assert!(takes.contains(&board_pos!("b5"))); + } } #[cfg(test)] @@ -729,4 +780,35 @@ mod black_pawn_tests { let moves = board.get_possible_moves(&board_pos!("d4")); assert!(moves.contains(&board_pos!("c3"))); } + + #[test] + fn takes_is_empty_when_no_takes_in_move() { + let board = CheckerBoard::default(); + let pawn = Pawn::new(PieceColor::Black); + let takes = pawn.takes(&board, &board_pos!("e7"), &board_pos!("e5")); + assert!(takes.is_empty()); + } + + #[test] + fn takes_is_has_pos_of_taken_piece() { + let e4 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "e4"); + let d5 = BoardPiece::build(PieceType::Pawn, PieceColor::Black, "d5"); + let pieces = vec![e4, d5]; + let board = CheckerBoard::with_pieces(pieces); + let pawn = Pawn::new(PieceColor::Black); + let takes = pawn.takes(&board, &board_pos!("d5"), &board_pos!("e4")); + assert_eq!(takes, vec![board_pos!("e4")]); + } + + #[test] + fn takes_return_position_from_pawn_en_passant_taken() { + let d4 = BoardPiece::build(PieceType::Pawn, PieceColor::Black, "d4"); + let c2 = BoardPiece::build(PieceType::Pawn, PieceColor::White, "c2"); + let pieces = vec![d4, c2]; + let mut board = CheckerBoard::with_pieces(pieces); + board.move_piece(&board_pos!["c2"], &board_pos!["c4"]); + let pawn = Pawn::new(PieceColor::Black); + let takes = pawn.takes(&board, &board_pos!("d4"), &board_pos!("c3")); + assert!(takes.contains(&board_pos!("c4"))); + } } diff --git a/src/pieces/queen.rs b/src/pieces/queen.rs index 70fb64a..f9180f5 100644 --- a/src/pieces/queen.rs +++ b/src/pieces/queen.rs @@ -40,6 +40,10 @@ impl Piece for Queen { fn is_opponent(&self, color: &PieceColor) -> bool { self.color() != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + todo!() + } } #[cfg(test)] diff --git a/src/pieces/rook.rs b/src/pieces/rook.rs index 3f97e05..9aabfa0 100644 --- a/src/pieces/rook.rs +++ b/src/pieces/rook.rs @@ -33,6 +33,10 @@ impl Piece for Rook { fn is_opponent(&self, color: &PieceColor) -> bool { &self.color != color } + + fn takes(&self, board: &CheckerBoard, from: &BoardPosition, to: &BoardPosition) -> Vec { + todo!() + } } #[cfg(test)]