diff --git a/src/plugin/action/advance.rs b/src/plugin/action/advance.rs index 7e4e263..f93bd75 100644 --- a/src/plugin/action/advance.rs +++ b/src/plugin/action/advance.rs @@ -1,6 +1,6 @@ use pyo3::{pyclass, pymethods, PyErr}; -use crate::plugin::{errors::HUIError, field::Field, game_state::GameState}; +use crate::plugin::{errors::HUIError, field::Field, game_state::GameState, hare::Hare}; use super::card::Card; @@ -28,21 +28,44 @@ impl Advance { let current_field = state.board.get_field(player.position).unwrap(); if self.cards.is_empty() { - match current_field { - Field::Market | Field::Hare => { - return Err(HUIError::new_err("Cannot enter field without any cards")); - } - _ => { - state.update_player(player); - return Ok(()); - } + return self.handle_empty_cards(current_field, state, player); + } + + self.handle_cards(current_field, state, player) + } + + fn handle_empty_cards( + &self, + current_field: Field, + state: &mut GameState, + player: Hare, + ) -> Result<(), PyErr> { + match current_field { + Field::Market | Field::Hare => { + Err(HUIError::new_err("Cannot enter field without any cards")) + } + _ => { + state.update_player(player); + Ok(()) } } + } + fn handle_cards( + &self, + mut current_field: Field, + state: &mut GameState, + mut player: Hare, + ) -> Result<(), PyErr> { let mut last_card: Option<&Card> = None; let mut card_bought = false; - for card in &self.cards { + for (index, card) in self.cards.iter().enumerate() { + let remaining_cards = self + .cards + .get(index + 1..) + .map(|slice| slice.to_vec()) + .unwrap_or(Vec::new()); match current_field { Field::Market if card_bought => { return Err(HUIError::new_err("Only one card allowed to buy")); @@ -60,18 +83,20 @@ impl Advance { } last_card = Some(card); - let mut remaining_cards = self.cards.clone(); - - if let Some(position) = remaining_cards.iter().position(|c| c == card) { - remaining_cards.remove(position); - } else { - return Err(HUIError::new_err("Card not in list of cards"))?; - } - card.perform(state, remaining_cards)?; + card.perform(state, remaining_cards.clone())?; + player = state.clone_current_player(); } _ => Err(HUIError::new_err("Card cannot be played on this field"))?, } + + current_field = state.board.get_field(player.position).unwrap(); + if current_field == Field::Hare && remaining_cards.is_empty() && last_card.is_none() { + return Err(HUIError::new_err("Cannot enter field without any cards")); + } + if current_field == Field::Market && remaining_cards.is_empty() && !card_bought { + return Err(HUIError::new_err("Cannot enter field without any cards")); + } } state.update_player(player); diff --git a/src/plugin/board.rs b/src/plugin/board.rs index fc3e500..4868977 100644 --- a/src/plugin/board.rs +++ b/src/plugin/board.rs @@ -25,7 +25,7 @@ impl Board { /// Finds the index of the specified field within the given range. pub fn find_field(&self, field: Field, start: usize, end: usize) -> Option { - (start..end).find(|&i| self.track.get(i) == Some(&field)) + (start..=end).find(|&i| self.track.get(i) == Some(&field)) } /// Finds the previous occurrence of the specified field before the given index. diff --git a/src/plugin/constants.rs b/src/plugin/constants.rs index c9e59a1..9ce3c3c 100644 --- a/src/plugin/constants.rs +++ b/src/plugin/constants.rs @@ -1,5 +1,7 @@ use pyo3::*; +use super::action::card::Card; + #[pyclass] pub struct PluginConstants; @@ -13,4 +15,11 @@ impl PluginConstants { pub const ROUND_LIMIT: usize = 30; pub const LAST_LETTUCE_POSITION: usize = 57; + + pub const MARKET_SELECTION: [Card; 4] = [ + Card::FallBack, + Card::HurryAhead, + Card::EatSalad, + Card::SwapCarrots, + ]; } diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index 0909b5e..e14b005 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -2,7 +2,6 @@ use itertools::Itertools; use pyo3::*; use super::action::advance::Advance; -use super::action::card::Card; use super::action::eat_salad::EatSalad; use super::action::exchange_carrots::ExchangeCarrots; use super::action::fall_back::FallBack; @@ -149,29 +148,28 @@ impl GameState { let mut moves = Vec::new(); for distance in 1..=max_distance { - if let Some(Field::Hare) = self.board.get_field(current_player.position + distance) { - for k in 0..current_player.cards.len() { - for combination in current_player.cards.iter().combinations(k) { - moves.push(Move::new(Action::Advance(Advance::new( - distance, - combination.iter().map(|&c| *c).collect(), - )))); - } - } + for card in PluginConstants::MARKET_SELECTION { + moves.push(Move::new(Action::Advance(Advance::new( + distance, + vec![card], + )))); } - if self.board.get_field(current_player.position + distance) == Some(Field::Market) { - let cards = vec![ - Card::FallBack, - Card::HurryAhead, - Card::EatSalad, - Card::SwapCarrots, - ]; - for card in cards { + for k in 0..=current_player.cards.len() { + for permutation in current_player.cards.iter().permutations(k).unique() { moves.push(Move::new(Action::Advance(Advance::new( distance, - vec![card], + permutation.iter().map(|&c| *c).collect(), )))); + + for card in PluginConstants::MARKET_SELECTION { + let mut extended_permutaion = permutation.clone(); + extended_permutaion.push(&card); + moves.push(Move::new(Action::Advance(Advance::new( + distance, + extended_permutaion.iter().map(|&c| *c).collect(), + )))); + } } } @@ -180,6 +178,7 @@ impl GameState { moves .into_iter() + .unique() .filter(|m| m.perform(&mut self.clone()).is_ok()) .collect() } diff --git a/src/plugin/test.rs b/src/plugin/test.rs index 0a59305..64e2f61 100644 --- a/src/plugin/test.rs +++ b/src/plugin/test.rs @@ -2,3 +2,4 @@ pub mod advance_test; pub mod board_test; pub mod card_test; pub mod rules_test; +pub mod state_test; \ No newline at end of file diff --git a/src/plugin/test/advance_test.rs b/src/plugin/test/advance_test.rs index 42e7189..5145491 100644 --- a/src/plugin/test/advance_test.rs +++ b/src/plugin/test/advance_test.rs @@ -60,7 +60,7 @@ mod tests { assert!(result.is_ok()); let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 6); + assert_eq!(current_player.position, 2); } #[test] diff --git a/src/plugin/test/state_test.rs b/src/plugin/test/state_test.rs new file mode 100644 index 0000000..09d6601 --- /dev/null +++ b/src/plugin/test/state_test.rs @@ -0,0 +1,81 @@ +#[cfg(test)] +mod tests { + use std::vec; + + use crate::plugin::{ + action::{advance::Advance, card::Card, Action}, + board::Board, + field::Field, + game_state::GameState, + hare::{Hare, TeamEnum}, + r#move::Move, + }; + + fn create_player( + team: TeamEnum, + position: usize, + cards: Vec, + carrots: i32, + salads: i32, + ) -> Hare { + Hare::new( + team, + Some(cards), + Some(carrots), + Some(salads), + None, + Some(position), + ) + } + + fn create_board() -> Board { + Board::new(vec![ + Field::Start, + Field::Salad, + Field::Position2, + Field::Hare, + Field::Carrots, + Field::Market, + Field::Position1, + Field::Hare, + Field::Goal, + ]) + } + + #[test] + fn test_possible_advance_moves_with_one_card() { + let state = GameState::new( + create_board(), + 20, + create_player(TeamEnum::One, 2, vec![Card::EatSalad], 37, 1), + create_player(TeamEnum::Two, 6, vec![], 11, 1), + ); + let moves = state.possible_moves(); + assert!(moves.contains(&Move::new(Action::Advance(Advance::new( + 1, + vec![Card::EatSalad] + ))))); + } + + #[test] + fn test_possible_advance_moves_with_hurry_ahead_back_and_market() { + let state = GameState::new( + create_board(), + 20, + create_player( + TeamEnum::One, + 2, + vec![Card::HurryAhead, Card::FallBack], + 37, + 0, + ), + create_player(TeamEnum::Two, 6, vec![], 11, 0), + ); + let moves = state.possible_moves(); + + assert!(moves.contains(&Move::new(Action::Advance(Advance::new( + 1, + vec![Card::HurryAhead, Card::FallBack, Card::EatSalad] + ))))); + } +}