diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index f2302c8..7e623de 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -429,6 +429,18 @@ class GameState: def __str__(self) -> str: ... def __repr__(self) -> str: ... + def possible_moves_for(self, start: Coordinate) -> List[Move]: + """ + Berechnet alle Züge, die aus der aktuellen Spielposition für den Fisch an der Koordinate möglich sind. + + Args: + start (Coordinate): Die Position des gewählten Fisch. + + Returns: + List[Move]: Die Liste der Züge. + """ + ... + def possible_moves(self) -> List[Move]: """ Berechnet alle Züge, die aus der aktuellen Spielposition für den aktuellen Spieler möglich sind. diff --git a/src/plugin2026/game_state.rs b/src/plugin2026/game_state.rs index 4c71d35..b6f7c60 100644 --- a/src/plugin2026/game_state.rs +++ b/src/plugin2026/game_state.rs @@ -32,6 +32,19 @@ impl GameState { pub fn __str__(&self) -> String {self.to_string()} pub fn __repr__(&self) -> String {format!("{:?}", self)} + pub fn possible_moves_for(&self, start: &Coordinate) -> Vec { + let mut moves: Vec = Vec::new(); + + for d in Direction::all_directions() { + moves.push(Move { start: start.to_owned(), direction: d }); + } + + moves + .into_iter() + .filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok()) + .collect() + } + pub fn possible_moves(&self) -> Vec { let mut moves: Vec = Vec::new(); let mut fish: Vec = Vec::new(); @@ -41,40 +54,20 @@ impl GameState { } for f in fish { - for d in Direction::all_directions() { - moves.push(Move { start: f.clone(), direction: d }); - } + moves.extend(self.possible_moves_for(&f)); } moves - .into_iter() - .filter(|m| RulesEngine::can_execute_move(&self.board, m).is_ok()) - .collect() } pub fn perform_move(&self, move_: &Move) -> Result { - RulesEngine::can_execute_move(&self.board, move_) - .map_err(|e| { - let full_error = e.to_string(); - let clean_error = full_error.strip_prefix("PiranhasError:").unwrap_or(&full_error).trim(); - PiranhasError::new_err(format!("Cannot execute move: {}", clean_error)) - })?; - - let target = RulesEngine::target_position(&self.board, move_); - let mut new_board = self.board.clone(); - new_board.map[target.y as usize][target.x as usize] = self.board.get_field(&move_.start).unwrap(); - new_board.map[move_.start.y as usize][move_.start.x as usize] = FieldType::Empty; + let mut new_game_state = self.clone(); + new_game_state.perform_move_mut(move_)?; - let new_state = GameState { - board: new_board, - turn: self.turn + 1, - last_move: Some(move_.clone()) - }; - - Ok(new_state) + Ok(new_game_state) } - + pub fn perform_move_mut(&mut self, move_: &Move) -> Result<(), PyErr> { RulesEngine::can_execute_move(&self.board, move_) diff --git a/src/plugin2026/rules_engine.rs b/src/plugin2026/rules_engine.rs index 4726993..5f9ccd1 100644 --- a/src/plugin2026/rules_engine.rs +++ b/src/plugin2026/rules_engine.rs @@ -61,21 +61,6 @@ impl RulesEngine { let distance = Self::move_distance(board, move_); let direction_fields = board.get_fields_in_direction(&move_.start, &move_.direction); let path_fields: Vec<_> = direction_fields.iter().take(distance - 1).cloned().collect(); // not including start or target -/* - println!("{}", move_); - println!("{}", move_.start); - println!("{}", move_.direction); - println!("{}", distance); - println!("{}", target_pos); - println!("{}", target_field); - for d in direction_fields { - print!("{} ", d); - } - println!(); - for d in &path_fields { - print!("{} ", *d); - } - println!();*/ for d in path_fields { let mut blocked_fields = this_team.get_fish_types(); diff --git a/src/plugin2026/test/board_test.rs b/src/plugin2026/test/board_test.rs index 82c789d..1ff3caa 100644 --- a/src/plugin2026/test/board_test.rs +++ b/src/plugin2026/test/board_test.rs @@ -1,43 +1,104 @@ #[cfg(test)] mod tests { - use crate::plugin2026::field_type::FieldType; - use crate::plugin2026::test::common::*; - use crate::plugin2026::utils::coordinate::Coordinate; - use crate::plugin2026::utils::direction::Direction; - + use crate::plugin2026::{ + field_type::FieldType, test::common::*, utils::{ + coordinate::Coordinate, + direction::Direction + } + }; + #[test] - pub fn test01() { + pub fn get_field_test() { let b = create_test_board(); - println!("{}", b); + assert_eq!(b.get_field(&Coordinate { x: 0, y: 4 }), Some(FieldType::OneS)); + assert_eq!(b.get_field(&Coordinate { x: 7, y: 0 }), Some(FieldType::TwoL)); + assert_eq!(b.get_field(&Coordinate { x: 3, y: 3 }), Some(FieldType::Empty)); + assert_eq!(b.get_field(&Coordinate { x: 6, y: 2 }), Some(FieldType::Squid)); + assert_eq!(b.get_field(&Coordinate { x: -2, y: 0 }), None); // out of bounds + } + + #[test] + pub fn get_fields_by_type_test() { + let mut b = create_test_board(); + + // remove squids + b.map[2][6] = FieldType::Empty; + b.map[7][3] = FieldType::Empty; + + let one_s_positions = vec![ + Coordinate {x: 0, y: 2}, + Coordinate {x: 9, y: 2}, + Coordinate {x: 0, y: 4}, + Coordinate {x: 0, y: 6}, + Coordinate {x: 9, y: 7}, + Coordinate {x: 0, y: 8}, + Coordinate {x: 9, y: 8}, + ]; - for variant in Direction::all_directions() { - let c = Coordinate {x: 2, y: 1}; - let f = b.get_fields_in_direction(&c, &variant); - - print!("[ "); - for field in &f { - print!("{} ", field); + assert_eq!(b.get_fields_by_type(FieldType::OneS), one_s_positions); + assert_eq!(b.get_fields_by_type(FieldType::Squid), vec![]); + } + + #[test] + pub fn get_fields_in_direction_test() { + let b = create_test_board(); + let start = Coordinate {x: 2, y: 6}; + + for d in Direction::all_directions() { + match d { + Direction::Up => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::Empty, FieldType::TwoS + ]), + Direction::UpRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Squid, FieldType::Empty, FieldType::TwoM + ]), + Direction::Right => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::Empty, FieldType::Empty, + FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneM + ]), + Direction::DownRight => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::Empty, FieldType::Empty, + FieldType::Squid, FieldType::Empty, FieldType::TwoS + ]), + Direction::Down => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::Empty, FieldType::Empty, + FieldType::Empty, FieldType::Empty, FieldType::TwoS + ]), + Direction::DownLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::OneS + ]), + Direction::Left => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::OneS + ]), + Direction::UpLeft => assert_eq!(b.get_fields_in_direction(&start, &d), vec![ + FieldType::Empty, FieldType::OneS + ]), } - print!("] {} {}", c, variant); - println!(); } + } - let mut sum = 0; + #[test] + pub fn get_fields_on_line_test() { + let b = create_test_board(); + let start = Coordinate {x: 2, y: 7}; + let direction = Direction::Right; - for fvarient in FieldType::all_field_types() { - let f = b.get_fields_by_type(fvarient); - - print!("[ "); - for field in &f { - print!("{} ", field); - } - print!("]"); - println!(); + assert_eq!(b.get_fields_on_line(&start, &direction), vec![ + FieldType::OneL, FieldType::Empty, FieldType::Empty, + FieldType::Squid, FieldType::Empty, FieldType::Empty, + FieldType::Empty, FieldType::Empty, FieldType::Empty, FieldType::OneS + ]); + } - sum += f.len(); - } + #[test] + pub fn get_fish_on_line_test() { + let b = create_test_board(); + let start = Coordinate {x: 2, y: 7}; + let direction = Direction::Right; - println!("{}", sum); + assert_eq!(b.get_fish_on_line(&start, &direction), vec![ + FieldType::OneL, FieldType::OneS + ]); } } diff --git a/src/plugin2026/test/common.rs b/src/plugin2026/test/common.rs index 2864940..f4e26ef 100644 --- a/src/plugin2026/test/common.rs +++ b/src/plugin2026/test/common.rs @@ -1,7 +1,5 @@ use crate::plugin2026::{ - board::Board, - field_type::FieldType, - utils::constants::PluginConstants + board::Board, field_type::FieldType, game_state::GameState, utils::constants::PluginConstants }; pub fn create_test_board() -> Board { @@ -36,5 +34,9 @@ pub fn create_test_board() -> Board { } } - Board::new(new_map) + Board {map: new_map} +} + +pub fn create_test_game_state() -> GameState { + GameState { board: create_test_board(), turn: 0, last_move: None } } \ No newline at end of file diff --git a/src/plugin2026/test/coordinate_test.rs b/src/plugin2026/test/coordinate_test.rs deleted file mode 100644 index 2556e30..0000000 --- a/src/plugin2026/test/coordinate_test.rs +++ /dev/null @@ -1,30 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::plugin2026::{ - utils::coordinate::Coordinate, - utils::vector::Vector - }; - - #[test] - pub fn test_eq() { - let coord01 = Coordinate {x: 1, y: 2}; - let coord02 = Coordinate {x: 1, y: 2}; - let coord03 = Coordinate {x: 2, y: 1}; - let coord04 = Coordinate {x: 1, y: 1}; - - assert_eq!(coord01, coord02); - assert_ne!(coord02, coord03); - assert_ne!(coord03, coord04); - } - - #[test] - pub fn test_add_vector() { - let coord01 = Coordinate {x: 1, y: 2}; - let coord02 = Coordinate {x: 1, y: 2}; - let vec01 = Vector {delta_x: 2, delta_y: 3}; - let vec02 = Vector {delta_x: -1, delta_y: 1}; - - println!("{}", coord01.add_vector(&vec01)); - println!("{}", coord02.add_vector(&vec02)); - } -} diff --git a/src/plugin2026/test/game_state_test.rs b/src/plugin2026/test/game_state_test.rs index fed1ec6..83ab624 100644 --- a/src/plugin2026/test/game_state_test.rs +++ b/src/plugin2026/test/game_state_test.rs @@ -1,4 +1,87 @@ #[cfg(test)] mod tests { - + use crate::plugin2026::{ + field_type::FieldType, r#move::Move, test::common::create_test_game_state, utils::{coordinate::Coordinate, direction::Direction} + }; + + #[test] + pub fn possible_moves_for_test() { + let state = create_test_game_state(); + + // 3 possible moves + let position01 = Coordinate {x: 0, y: 3}; + let poss01 = state.possible_moves_for(&position01); + + assert_eq!(poss01, vec![ + Move {start: position01.clone(), direction: Direction::UpRight}, + Move {start: position01.clone(), direction: Direction::Right}, + Move {start: position01.clone(), direction: Direction::DownRight}, + ]); + + // no possible move because out of bounds start + let position02 = Coordinate {x: 0, y: -1}; + let poss02 = state.possible_moves_for(&position02); + + assert_eq!(poss02, vec![]); + } + + #[test] + pub fn possible_moves_test() { + // this state's board has 48 (standard number) moves for team one (turn 0) + // team two would have 6 turns less from this position due to squids + let mut state = create_test_game_state(); + + assert_eq!(state.possible_moves().len(), 48); // team one + + state.turn = 1; + assert_eq!(state.possible_moves().len(), 42); // team two + } + + #[test] + pub fn perform_move_test() { + pyo3::prepare_freethreaded_python(); + + let mut state = create_test_game_state(); + + // correct move + let new_state = state.perform_move( &Move { + start: Coordinate { x: 0, y: 3 }, + direction: Direction::Right + }).unwrap(); + + assert_eq!(new_state.board.get_field(&Coordinate { x: 0, y: 3 }), Some(FieldType::Empty)); + assert_eq!(new_state.board.get_field(&Coordinate { x: 2, y: 3 }), Some(FieldType::OneL)); + + // illegal moves + let result = state.perform_move(&Move { + start: Coordinate { x: -1, y: 0 }, + direction: Direction::Right + }); + assert!(result.is_err(), "Move FROM out of bounds should fail, but succeeded"); + + let result = state.perform_move(&Move { + start: Coordinate { x: 0, y: 3 }, + direction: Direction::Left + }); + assert!(result.is_err(), "Move TO out of bounds should fail, but succeeded"); + + let result = state.perform_move(&Move { + start: Coordinate { x: 3, y: 3 }, + direction: Direction::Right + }); + assert!(result.is_err(), "Move FROM non-fish field should fail, but succeeded"); + + let result = state.perform_move(&Move { + start: Coordinate { x: 6, y: 0 }, + direction: Direction::Up + }); + assert!(result.is_err(), "Move TO squid field should fail, but succeeded"); + + state.board.map[3][2] = FieldType::TwoL; + let result = state.perform_move(&Move { + start: Coordinate { x: 2, y: 0 }, + direction: Direction::Up + }); + assert!(result.is_err(), "Move TO own-fish field should fail, but succeeded"); + } } diff --git a/src/plugin2026/test/mod.rs b/src/plugin2026/test/mod.rs index 7e89469..5d21ff2 100644 --- a/src/plugin2026/test/mod.rs +++ b/src/plugin2026/test/mod.rs @@ -3,8 +3,4 @@ mod common; #[cfg(test)] mod board_test; #[cfg(test)] -mod game_state_test; -#[cfg(test)] -mod coordinate_test; -#[cfg(test)] -mod rules_engine_test; \ No newline at end of file +mod game_state_test; \ No newline at end of file diff --git a/src/plugin2026/test/rules_engine_test.rs b/src/plugin2026/test/rules_engine_test.rs deleted file mode 100644 index 0dd4e7a..0000000 --- a/src/plugin2026/test/rules_engine_test.rs +++ /dev/null @@ -1,32 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::plugin2026::r#move::Move; - use crate::plugin2026::rules_engine::RulesEngine; - use crate::plugin2026::test::common::*; - use crate::plugin2026::utils::coordinate::Coordinate; - use crate::plugin2026::utils::direction::Direction; - - #[test] - pub fn test01() { - pyo3::prepare_freethreaded_python(); - - let b = create_test_board(); - - println!("{}", b); - - for variant in Direction::all_directions() { - let c = Coordinate {x: 2, y: 0}; - let move_ = Move {start: c, direction: variant}; - - println!("Move: {}", move_); - - if let Err(e) = RulesEngine::can_execute_move(&b, &move_) { - println!("Error occurred: {:?}", e); - } else { - println!("Alles super"); - } - - println!(); - } - } -}