Skip to content

Commit

Permalink
fix: advance logic and move generation
Browse files Browse the repository at this point in the history
Co-authored-by: YoEnte <[email protected]>
  • Loading branch information
maxblan and YoEnte committed Jan 1, 2025
1 parent 12bf9eb commit a4bd343
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 39 deletions.
61 changes: 43 additions & 18 deletions src/plugin/action/advance.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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"));
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize> {
(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.
Expand Down
9 changes: 9 additions & 0 deletions src/plugin/constants.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use pyo3::*;

use super::action::card::Card;

#[pyclass]
pub struct PluginConstants;

Expand All @@ -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,
];
}
37 changes: 18 additions & 19 deletions src/plugin/game_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
))));
}
}
}

Expand All @@ -180,6 +178,7 @@ impl GameState {

moves
.into_iter()
.unique()
.filter(|m| m.perform(&mut self.clone()).is_ok())
.collect()
}
Expand Down
1 change: 1 addition & 0 deletions src/plugin/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod advance_test;
pub mod board_test;
pub mod card_test;
pub mod rules_test;
pub mod state_test;
2 changes: 1 addition & 1 deletion src/plugin/test/advance_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
81 changes: 81 additions & 0 deletions src/plugin/test/state_test.rs
Original file line number Diff line number Diff line change
@@ -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<Card>,
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]
)))));
}
}

0 comments on commit a4bd343

Please sign in to comment.