From 3cb91e4b559a765b7c0a1ba7d19efbb3652236cf Mon Sep 17 00:00:00 2001 From: YoEnte Date: Thu, 4 Sep 2025 10:07:04 +0200 Subject: [PATCH 1/4] update starter logic.py add new socha imports update comments to German --- logic.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/logic.py b/logic.py index 82972dd..4474715 100644 --- a/logic.py +++ b/logic.py @@ -1,12 +1,20 @@ -# not all imports are currently used, but they might be in the future and it shows all available functionalities +# Nicht alle Imports werden aktuell verwendet. +# Diese können in Zukunft aber sehr nützlich sein +# und zeigen außerdem alle Funktionalitäten der Software-Challenge Python API. import math import random import time from typing import Optional, Tuple from socha import ( - Field, + Coordinate, + Vector, + Direction, + FieldType, + TeamEnum, + Board, + Move, GameState, - Move + RulesEngine, ) from socha.api.networking.game_client import IClientHandler from socha.starter import Starter @@ -15,18 +23,20 @@ class Logic(IClientHandler): game_state: GameState - # this method is called every time the server is requesting a new move - # this method should always be implemented otherwise the client will be disqualified + # Diese Methode wird immer aufgerufen, wenn der Spielserver einen Zug vom Client anfordert. + # Sie muss implementiert sein, weil der Computerspieler sonst disqualifiziert wird. def calculate_move(self) -> Move: return random.choice(self.game_state.possible_moves()) - # this method is called every time the server has sent a new game state update - # this method should be implemented to keep the game state up to date + # Diese Methode wird jedes Mal aufgerufen, wenn der Server einen neunen Spielstand bereitstellt. + # Sie muss implentiert sein, damit die Spielstand Instanz auf dem neusten Stand bleibt. def on_update(self, state: GameState) -> None: self.game_state = state + # Die Klasse IClientHandler hält noch weitere Methoden, die durch bestimmte Aktionen des Servers ausgeführt werden. + # Weitere Informationen dazu gibt es in der verlinkten Dokumentation unter dem Submodul socha.api.networking.game_client. if __name__ == "__main__": Starter(logic=Logic()) - # if u wanna have more insights, u can set the logging level to debug: + # Wenn man mehr Debug Informationen aus dem Hintergrund der API haben möchte, kann das Log-Level auf debug gesetzt werden: # Starter(logic=Logic(), log_level=logging.DEBUG) From b21745b07a33e34ff9dac9c3c89c92ce69d49763 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Thu, 4 Sep 2025 11:43:13 +0200 Subject: [PATCH 2/4] feature: perform_move() add GameState.perform_move() and mutating variant --- python/socha/_socha.pyi | 30 ++++++++++++++++++++++ src/plugin2026/game_state.rs | 49 +++++++++++++++++++++++++++++++++--- 2 files changed, 75 insertions(+), 4 deletions(-) diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index e339fe3..e001e85 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -438,6 +438,33 @@ class GameState: """ ... + def perform_move(self, move: Move) -> GameState: + """ + Führt den gegebenen Zug auf dem Spielstand aus, insofern dieser ausführbar ist (**nicht mutierend**). + + Args: + move_ (Move): Der zuverwendene Zug. + + Returns: + Gamestate: Der neue Spielstand. + + Raises: + PiranhasError: Wenn der Zug nicht valide ist. + """ + ... + + def perform_move_mut(self, move: Move) -> None: + """ + Führt den gegebenen Zug auf dem Spielstand aus, insofern dieser ausführbar ist (**mutierend**). + + Args: + move_ (Move): Der zuverwendene Zug. + + Raises: + PiranhasError: Wenn der Zug nicht valide ist. + """ + ... + class RulesEngine: """ Stellt Methoden, die zur Überprüfung der Spielregeln dienen. @@ -498,6 +525,9 @@ class RulesEngine: Args: board (Board): Das Spielfeld. move_ (Move): Der Zug, der geprüft werden soll. + + Raises: + PiranhasError: Wenn der Zug nicht valide ist. """ ... diff --git a/src/plugin2026/game_state.rs b/src/plugin2026/game_state.rs index 152e8b7..4c71d35 100644 --- a/src/plugin2026/game_state.rs +++ b/src/plugin2026/game_state.rs @@ -1,10 +1,7 @@ use pyo3::*; use crate::plugin2026::{ - board::Board, - r#move::Move, - rules_engine::RulesEngine, - utils::{ + board::Board, errors::PiranhasError, field_type::FieldType, r#move::Move, rules_engine::RulesEngine, utils::{ coordinate::Coordinate, direction::Direction } @@ -54,6 +51,50 @@ impl GameState { .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 new_state = GameState { + board: new_board, + turn: self.turn + 1, + last_move: Some(move_.clone()) + }; + + Ok(new_state) + } + + pub fn perform_move_mut(&mut self, move_: &Move) -> Result<(), PyErr> { + + 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; + + self.board = new_board; + self.turn += 1; + self.last_move = Some(move_.clone()); + + Ok(()) + } } impl std::fmt::Display for GameState { From 9b85fe2b2ed016b88759a4c8bdcde8613d2a8647 Mon Sep 17 00:00:00 2001 From: YoEnte Date: Mon, 8 Sep 2025 15:30:05 +0200 Subject: [PATCH 3/4] fix: clarify describtion --- logic.py | 1 + python/socha/_socha.pyi | 2 ++ 2 files changed, 3 insertions(+) diff --git a/logic.py b/logic.py index 4474715..aff38b0 100644 --- a/logic.py +++ b/logic.py @@ -25,6 +25,7 @@ class Logic(IClientHandler): # Diese Methode wird immer aufgerufen, wenn der Spielserver einen Zug vom Client anfordert. # Sie muss implementiert sein, weil der Computerspieler sonst disqualifiziert wird. + # Damit ein Zug an den Spielserver übermittelt wird, muss dieser mit *return* von der Methode zurückgegeben werden. def calculate_move(self) -> Move: return random.choice(self.game_state.possible_moves()) diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index e001e85..f2302c8 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -441,6 +441,7 @@ class GameState: def perform_move(self, move: Move) -> GameState: """ Führt den gegebenen Zug auf dem Spielstand aus, insofern dieser ausführbar ist (**nicht mutierend**). + Dabei wird *kein* Zug an den Spielserver übermittelt. Args: move_ (Move): Der zuverwendene Zug. @@ -456,6 +457,7 @@ class GameState: def perform_move_mut(self, move: Move) -> None: """ Führt den gegebenen Zug auf dem Spielstand aus, insofern dieser ausführbar ist (**mutierend**). + Dabei wird *kein* Zug an den Spielserver übermittelt. Args: move_ (Move): Der zuverwendene Zug. From f270b8623210f858f3e4ecd3815792198121762b Mon Sep 17 00:00:00 2001 From: YoEnte Date: Mon, 8 Sep 2025 15:38:20 +0200 Subject: [PATCH 4/4] chore: remove old 2025 plugin from project --- python/socha/_socha2025.pyi | 552 ---------------------- src/lib2025.rs | 41 -- src/plugin2025.rs | 10 - src/plugin2025/action.rs | 55 --- src/plugin2025/action/advance.rs | 119 ----- src/plugin2025/action/card.rs | 186 -------- src/plugin2025/action/eat_salad.rs | 33 -- src/plugin2025/action/exchange_carrots.rs | 35 -- src/plugin2025/action/fall_back.rs | 32 -- src/plugin2025/board.rs | 57 --- src/plugin2025/constants.rs | 25 - src/plugin2025/errors.rs | 3 - src/plugin2025/field.rs | 42 -- src/plugin2025/game_state.rs | 291 ------------ src/plugin2025/hare.rs | 179 ------- src/plugin2025/move.rs | 44 -- src/plugin2025/rules_engine.rs | 123 ----- src/plugin2025/test.rs | 5 - src/plugin2025/test/advance_test.rs | 201 -------- src/plugin2025/test/board_test.rs | 80 ---- src/plugin2025/test/card_test.rs | 258 ---------- src/plugin2025/test/rules_test.rs | 105 ---- src/plugin2025/test/state_test.rs | 165 ------- 23 files changed, 2641 deletions(-) delete mode 100644 python/socha/_socha2025.pyi delete mode 100644 src/lib2025.rs delete mode 100644 src/plugin2025.rs delete mode 100644 src/plugin2025/action.rs delete mode 100644 src/plugin2025/action/advance.rs delete mode 100644 src/plugin2025/action/card.rs delete mode 100644 src/plugin2025/action/eat_salad.rs delete mode 100644 src/plugin2025/action/exchange_carrots.rs delete mode 100644 src/plugin2025/action/fall_back.rs delete mode 100644 src/plugin2025/board.rs delete mode 100644 src/plugin2025/constants.rs delete mode 100644 src/plugin2025/errors.rs delete mode 100644 src/plugin2025/field.rs delete mode 100644 src/plugin2025/game_state.rs delete mode 100644 src/plugin2025/hare.rs delete mode 100644 src/plugin2025/move.rs delete mode 100644 src/plugin2025/rules_engine.rs delete mode 100644 src/plugin2025/test.rs delete mode 100644 src/plugin2025/test/advance_test.rs delete mode 100644 src/plugin2025/test/board_test.rs delete mode 100644 src/plugin2025/test/card_test.rs delete mode 100644 src/plugin2025/test/rules_test.rs delete mode 100644 src/plugin2025/test/state_test.rs diff --git a/python/socha/_socha2025.pyi b/python/socha/_socha2025.pyi deleted file mode 100644 index 953efea..0000000 --- a/python/socha/_socha2025.pyi +++ /dev/null @@ -1,552 +0,0 @@ -from enum import Enum -from typing import List, Optional - -class Card(Enum): - """ - Eine Karte, die im Spiel verwendet werden kann. - - Attributes: - FallBack (int): Die Karte, die den Spieler zurückfallen lässt. - HurryAhead (int): Die Karte, die den Spieler vorrücken lässt. - EatSalad (int): Die Karte, die den Spieler Salat essen lässt. - SwapCarrots (int): Die Karte, die die Karotten der Spieler tauscht. - """ - - FallBack: int = 0 - HurryAhead: int = 1 - EatSalad: int = 2 - SwapCarrots: int = 3 - - def __init__(self) -> None: ... - def moves(self) -> bool: - """ - Gibt an, ob die Karte den Spieler bewegt. - - Args: - None - - Returns: - bool: True, wenn die Karte den Spieler bewegt, False sonst. - """ - ... - - def perform(self, state: GameState, remaining_cards: List[Card]) -> None: - """ - Führt die Karte aus.\n - Diese Methode **kann** den GameState **mutieren**, unabhängig vom Erfolg des Zuges. - - Args: - state (GameState): Der aktuelle Spielzustand. - remaining_cards (List[Card]): Die verbleibenden Karten. - - Raises: - HUIError: Ein Fehler, wenn die Karte nicht ausgeführt werden kann. - """ - ... - -class Advance: - """ - Eine Klasse, die einen Vorwärtszug im Spiel darstellt. - - Attributes: - distance (int): Die Anzahl der Felder, die der Spieler vorrücken soll. - cards (List[Card]): Die Karten, die während des Vorgangs gespielt werden. - """ - - distance: int - cards: List[Card] - - def __init__(self, distance: int, cards: List[Card]): ... - def perform(self, state: GameState) -> None: - """ - Führt den Vorwärtszug aus.\n - Diese Methode **kann** den GameState **mutieren**, unabhängig vom Erfolg des Zuges. - - Args: - state (GameState): Der aktuelle Zustand des Spiels. - - Raises: - HUIError: Wenn der Vorwärtszug nicht erfolgreich ausgeführt werden kann. - """ - ... - -class EatSalad: - def __init__(self) -> None: ... - def perform(self, state: GameState) -> None: - """ - Führt die Salat-Fressen Aktion aus.\n - Diese Methode **kann** den GameState **mutieren**, unabhängig vom Erfolg des Zuges. - - Args: - state (GameState): Der aktuelle Zustand des Spiels. - - Raises: - HUIError: Wenn der Salat nicht erfolgreich gegessen werden kann. - """ - ... - -class ExchangeCarrots: - amount: int - - def __init__(self, amount: int) -> None: ... - def perform(self, state: GameState) -> None: - """ - Führt die Karotten-Tausch-Aktion aus.\n - Diese Methode **kann** den GameState **mutieren**, unabhängig vom Erfolg des Zuges. - - Args: - state (GameState): Der aktuelle Zustand des Spiels. - - Raises: - HUIError: Wenn die Karotten nicht erfolgreich getauscht werden können. - """ - ... - -class FallBack: - def __init__(self) -> None: ... - def perform(self, state: GameState) -> None: ... - -class Field(Enum): - Position1: int = 0 - """ - Zahlfeld - """ - Position2: int = 1 - """ - Flaggenfeld - """ - Hedgehog: int = 2 - """ - Igelfeld: Hierauf kann nur rückwärts gezogen werden. - """ - Salad: int = 3 - """ - Salatfeld: Beim Betreten wird im nächsten Zug ein Salat gegessen. - """ - Carrots: int = 4 - """ - Karottenfeld: Hier dürfen Karotten getauscht werden. - """ - Hare: int = 5 - """ - Hasenfeld: Hier wird sofort eine Hasenkarte gespielt. - """ - Market: int = 6 - """ - Marktfeld: Hier wird eine Hasenkarte gekauft (Variation). - """ - Goal: int = 7 - """ - Zielfeld - """ - Start: int = 8 - """ - Startfeld - """ - -class Board: - """ - Ein Spielbrett, das die Felder des Spiels enthält. - - Attributes: - track (List[Field]): Die Liste der Felder, die das Spielbrett darstellen - """ - - track: list[Field] - - def __init__(self, track: list[Field]) -> None: ... - def get_field(self, index: int) -> Optional[Field]: - """ - Gibt das Feld am angegebenen Index zurück. - - Args: - index (int): Der Index des Feldes, das abgerufen werden soll. - - Returns: - Field: Das Feld am angegebenen Index, oder None, wenn außerhalb des gültigen Bereichs. - """ - ... - def find_field(self, field: Field, start: int, end: int) -> Optional[int]: - """ - Findet den ersten Index des angegebenen Feldes innerhalb des angegebenen Bereichs. - - Args: - field (Field): Das Feld, nach dem gesucht werden soll. - start (int): Der Startindex des Bereichs, in dem gesucht werden soll. - end (int): Der Endindex des Bereichs, in dem gesucht werden soll. - - Returns: - int: Der Index des Feldes, wenn gefunden, oder None, wenn nicht gefunden. - """ - ... - def get_previous_field(self, field: Field, index: int) -> Optional[int]: - """ - Findet die vorherige Vorkommen des angegebenen Feldes vor dem angegebenen Index. - - Args: - field (Field): Das Feld, nach dem gesucht werden soll. - index (int): Der Index, von dem aus gesucht werden soll. - - Returns: - int: Der Index des vorherigen Vorkommens des Feldes, oder None, wenn nicht gefunden. - """ - ... - def get_next_field(self, field: Field, index: int) -> Optional[int]: - """ - Findet das nächste Vorkommen des angegebenen Feldes nach dem angegebenen Index. - - Args: - field (Field): Das Feld, nach dem gesucht werden soll. - index (int): Der Index, von dem aus gesucht werden soll. - - Returns: - int: Der Index des nächsten Vorkommens des Feldes, oder None, wenn nicht gefunden. - """ - ... - -class TeamEnum(Enum): - One: int = 0 - """ - Team 1 - """ - Two: int = 1 - """ - Team 2 - """ - - def __repr__(self) -> str: ... - -class Hare: - """ - Repräsentiert einen Hasen im Spiel. - - Attribute: - team (TeamEnum): Das Team, dem der Hase angehört. - position (int): Die aktuelle Position des Hasen auf dem Brett. - salads (int): Die Anzahl der Salate, die der Hase hat. - carrots (int): Die Anzahl der Karotten, die der Hase hat. - last_move (Optional[Move]): Der letzte Zug, den der Hase gemacht hat. - cards (List[Card]): Die Karten, die der Hase hat. - """ - - team: TeamEnum - position: int - salads: int - carrots: int - last_move: Optional[Move] - cards: List[Card] - - def __init__( - self, - team: TeamEnum, - cards: Optional[List[Card]] = None, - carrots: Optional[int] = None, - salads: Optional[int] = None, - last_move: Optional[Move] = None, - position: Optional[int] = None, - ) -> None: ... - def is_in_goal(self) -> bool: - """ - Überprüft, ob der Hase im Ziel ist. - - Returns: - bool: True, wenn der Hase im Ziel ist, False sonst. - """ - ... - - def can_enter_goal(self) -> bool: - """ - Überprüft, ob der Hase das Ziel betreten kann. - - Returns: - bool: True, wenn der Hase das Ziel betreten kann, False sonst. - """ - ... - - def advance_by(self, state: GameState, distance: int, cards: List[Card]) -> None: - """ - Rückt den Hasen um eine bestimmte Entfernung vor. - - Args: - state (GameState): Der aktuelle Spielzustand. - distance (int): Die Entfernung, um die der Hase vorrücken soll. - cards (List[Card]): Die Karten, die während des Vorgangs gespielt werden. - - Raises: - HUIError: Wenn der Hase nicht vorrücken kann. - """ - ... - - def exchange_carrots(self, state: GameState, carrots: int) -> None: - """ - Tauscht Karotten mit dem Hasen. - - Args: - state (GameState): Der aktuelle Spielzustand. - carrots (int): Die Anzahl der Karotten, die getauscht werden sollen. - - Raises: - HUIError: Wenn die Karotten nicht getauscht werden können. - """ - ... - - def consume_carrots(self, state: GameState, carrots: int) -> None: - """ - Verbraucht Karotten vom Hasen. - - Args: - state (GameState): Der aktuelle Spielzustand. - carrots (int): Die Anzahl der Karotten, die verbraucht werden sollen. - - Raises: - HUIError: Wenn die Karotten nicht verbraucht werden können. - """ - ... - - def eat_salad(self, state: GameState) -> None: - """ - Lässt den Hasen einen Salat essen. - - Args: - state (GameState): Der aktuelle Spielzustand. - """ - ... - - def get_fall_back(self, state: GameState) -> Optional[int]: - """ - Gibt den nächsten möglich Index zurück, an dem der Hase zurückfallen kann. - - Args: - state (GameState): Der aktuelle Spielzustand. - - Returns: - Optional[int]: Die Rückfallposition des Hasen, oder None, wenn nicht gefunden. - """ - ... - - def fall_back(self, state: GameState) -> None: - """ - Lässt den Hasen zu einer vorherigen Position zurückfallen. - - Args: - state (GameState): Der aktuelle Spielzustand. - - Raises: - HUIError: Wenn der Hase nicht zurückfallen kann. - """ - ... - - def is_ahead(self, state: GameState) -> bool: - """ - Überprüft, ob der Hase vor dem anderen Spieler ist. - - Args: - state (GameState): Der aktuelle Spielzustand. - - Returns: - bool: True, wenn der Hase vor dem anderen Spieler ist, False sonst. - """ - ... - -class Move: - """ - Repräsentiert einen Zug im Spiel. - - Attribute: - action (Advance | EatSalad | ExchangeCarrots | FallBack): Die Aktion, die der Zug ausführt. - """ - - action: Advance | EatSalad | ExchangeCarrots | FallBack - - def __init__( - self, action: Advance | EatSalad | ExchangeCarrots | FallBack - ) -> None: ... - def perform(self, state: GameState) -> None: - """ - Führt den Zug aus. - - Args: - state (GameState): Der aktuelle Spielzustand. - - Raises: - HUIError: Wenn der Zug nicht ausgeführt werden kann. - """ - ... - def __repr__(self) -> str: ... - - def __eq__(self) -> bool: ... - -class GameState: - """ - Repräsentiert den aktuellen Zustand des Spiels. - - Attribute: - board (Board): Das Spielbrett. - turn (int): Die aktuelle Runde. - """ - - board: Board - turn: int - last_move: Optional[Move] - - def __init__( - self, board: Board, turn: int, player_one: Hare, player_two: Hare, last_move: Optional[Move] - ) -> None: ... - def perform_move(self, move: Move) -> GameState: - """ - Führt einen Zug aus und gibt den neuen Spielzustand zurück. - - Args: - move (Move): Der Zug, der ausgeführt werden soll. - - Returns: - GameState: Der neue Spielzustand. - - Raises: - HUIError: Wenn der Zug nicht ausgeführt werden kann. - """ - ... - - def clone_current_player(self) -> Hare: - """ - Gibt eine Kopie des aktuellen Spielers zurück. - - Returns: - Hare: Eine Kopie des aktuellen Spielers. - """ - ... - - def clone_other_player(self) -> Hare: - """ - Gibt eine Kopie des anderen Spielers zurück. - - Returns: - Hare: Eine Kopie des anderen Spielers. - """ - ... - - def update_player(self, player: Hare) -> None: - """ - Aktualisiert den Spieler. - - Args: - player (Hare): Der Spieler, der aktualisiert werden soll. - """ - ... - - def is_over(self) -> bool: - """ - Überprüft, ob das Spiel vorbei ist. - - Returns: - bool: True, wenn das Spiel vorbei ist, False sonst. - """ - ... - - def possible_moves_old(self) -> List[Move]: - """ - Gibt eine Liste aller möglichen Züge zurück. - - Returns: - List[Move]: Eine Liste aller möglichen Züge. - """ - ... - - def possible_moves(self) -> List[Move]: - """ - Gibt eine Liste aller möglichen Züge zurück. - - Returns: - List[Move]: Eine Liste aller möglichen Züge. - """ - ... - -class RulesEngine: - """ - Dient zur Überprüfung der Spielregeln. - """ - - @staticmethod - def calculates_carrots(distance: int) -> int: - """ - Berechnet die Anzahl der Karotten, die für einen Zug benötigt werden. - - Args: - distance (int): Die Entfernung, die zurückgelegt werden soll. - - Returns: - int: Die Anzahl der Karotten, die benötigt werden. - """ - ... - - @staticmethod - def can_exchange_carrots(board: Board, player: Hare, count: int) -> None: - """ - Überprüft, ob ein Spieler Karotten tauschen kann. - - Args: - board (Board): Das Spielbrett. - player (Hare): Der Spieler, der Karotten tauschen möchte. - count (int): Die Anzahl der Karotten, die getauscht werden sollen. - - Raises: - HUIError: Wenn der Spieler nicht genug Karotten hat oder wenn das Feld nicht ein Karottenfeld ist. - """ - ... - - @staticmethod - def can_eat_salad(board: Board, player: Hare) -> None: - """ - Überprüft, ob ein Spieler einen Salat essen kann. - - Args: - board (Board): Das Spielbrett. - player (Hare): Der Spieler, der einen Salat essen möchte. - - Raises: - HUIError: Wenn der Spieler keinen Salat hat oder wenn das Feld nicht ein Salatfeld ist. - """ - ... - - @staticmethod - def has_to_eat_salad(board: Board, player: Hare) -> None: - """ - Überprüft, ob ein Spieler einen Salat essen muss. - - Args: - board (Board): Das Spielbrett. - player (Hare): Der Spieler, der einen Salat essen muss. - - Raises: - Exception: Wenn der Spieler nicht genug Salate hat oder wenn das Feld nicht ein Salatfeld ist. - """ - ... - - @staticmethod - def can_move_to( - board: Board, - new_position: int, - player: Hare, - other_player: Hare, - cards: List[Card], - ) -> None: - """ - Überprüft, ob ein Spieler zu einem bestimmten Feld ziehen kann. - - Args: - board (Board): Das Spielbrett. - new_position (int): Die neue Position, zu der der Spieler ziehen möchte. - player (Hare): Der Spieler, der ziehen möchte. - other_player (Hare): Der andere Spieler. - cards (List[Card]): Die Karten, die der Spieler spielen möchte. - - Raises: - Exception: Wenn das Feld besetzt ist oder wenn der Spieler nicht genug Karotten hat. - """ - ... - -class PluginConstants: - NUM_FIELDS: int - INITIAL_SALADS: int - INITIAL_CARROTS: int - ROUND_LIMIT: int diff --git a/src/lib2025.rs b/src/lib2025.rs deleted file mode 100644 index b89f019..0000000 --- a/src/lib2025.rs +++ /dev/null @@ -1,41 +0,0 @@ -use plugin2025::rules_engine::RulesEngine; -use pyo3::*; -use types::PyModule; - -pub mod plugin2025; - -use crate::plugin2025::action::advance::Advance; -use crate::plugin2025::action::card::Card; -use crate::plugin2025::action::eat_salad::EatSalad; -use crate::plugin2025::action::exchange_carrots::ExchangeCarrots; -use crate::plugin2025::action::fall_back::FallBack; -use crate::plugin2025::board::Board; -use crate::plugin2025::constants::PluginConstants; -use crate::plugin2025::field::Field; -use crate::plugin2025::game_state::GameState; -use crate::plugin2025::hare::Hare; -use crate::plugin2025::hare::TeamEnum; -use crate::plugin2025::r#move::Move; - -#[pymodule] -fn _socha(m: &Bound<'_, PyModule>) -> PyResult<()> { - pyo3_log::init(); - - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - - m.add_class::()?; - - Ok(()) -} diff --git a/src/plugin2025.rs b/src/plugin2025.rs deleted file mode 100644 index 7fed764..0000000 --- a/src/plugin2025.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod action; -pub mod board; -pub mod constants; -pub mod errors; -pub mod field; -pub mod game_state; -pub mod r#move; -pub mod test; -pub mod hare; -pub mod rules_engine; diff --git a/src/plugin2025/action.rs b/src/plugin2025/action.rs deleted file mode 100644 index 4802192..0000000 --- a/src/plugin2025/action.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub mod advance; -pub mod card; -pub mod eat_salad; -pub mod exchange_carrots; -pub mod fall_back; - -use advance::Advance; -use eat_salad::EatSalad; -use exchange_carrots::ExchangeCarrots; -use fall_back::FallBack; - -use pyo3::*; - -use super::game_state::GameState; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, FromPyObject)] -pub enum Action { - Advance(Advance), - EatSalad(EatSalad), - ExchangeCarrots(ExchangeCarrots), - FallBack(FallBack), -} - -impl Action { - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - match self { - Self::Advance(advance) => advance.perform(state), - Self::EatSalad(eat_salad) => eat_salad.perform(state), - Self::ExchangeCarrots(exchange_carrots) => exchange_carrots.perform(state), - Self::FallBack(fall_back) => fall_back.perform(state), - } - } -} - -impl IntoPy for Action { - fn into_py(self, py: Python) -> PyObject { - match self { - Self::Advance(advance) => advance.into_py(py), - Self::EatSalad(eat_salad) => eat_salad.into_py(py), - Self::ExchangeCarrots(exchange_carrots) => exchange_carrots.into_py(py), - Self::FallBack(fall_back) => fall_back.into_py(py), - } - } -} - -impl std::fmt::Display for Action { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Advance(advance) => write!(f, "{}", advance), - Self::EatSalad(eat_salad) => write!(f, "{}", eat_salad), - Self::ExchangeCarrots(exchange_carrots) => write!(f, "{}", exchange_carrots), - Self::FallBack(fall_back) => write!(f, "{}", fall_back), - } - } -} diff --git a/src/plugin2025/action/advance.rs b/src/plugin2025/action/advance.rs deleted file mode 100644 index b39e33c..0000000 --- a/src/plugin2025/action/advance.rs +++ /dev/null @@ -1,119 +0,0 @@ -use pyo3::{pyclass, pymethods, PyErr}; - -use crate::plugin2025::{errors::HUIError, field::Field, game_state::GameState, hare::Hare}; - -use super::card::Card; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] -pub struct Advance { - #[pyo3(get, set)] - pub distance: usize, - #[pyo3(get, set)] - pub cards: Vec, -} - -#[pymethods] -impl Advance { - #[new] - #[must_use] - pub fn new(distance: usize, cards: Vec) -> Self { - Self { distance, cards } - } - - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - let mut player = state.clone_current_player(); - - player.advance_by(state, self.distance, self.cards.clone())?; - - let current_field = state.board.get_field(player.position).unwrap(); - if self.cards.is_empty() { - 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 (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")); - } - Field::Market => { - player.consume_carrots(state, 10)?; - card_bought = true; - player.cards.push(*card); - } - Field::Hare => { - if let Some(last) = last_card { - if !last.moves() { - return Err(HUIError::new_err("Card cannot be played")); - } - } - - last_card = Some(card); - - card.perform(state, remaining_cards.clone(), self.distance)?; - 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); - Ok(()) - } - - pub fn __repr__(&self) -> String { - format!("{:?}", self) - } -} - -impl std::fmt::Display for Advance { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Advance(distance={}, cards={:?})", - self.distance, self.cards - ) - } -} diff --git a/src/plugin2025/action/card.rs b/src/plugin2025/action/card.rs deleted file mode 100644 index 6cc5883..0000000 --- a/src/plugin2025/action/card.rs +++ /dev/null @@ -1,186 +0,0 @@ -use std::mem::swap; - -use pyo3::*; - -use crate::plugin2025::{ - constants::PluginConstants, errors::HUIError, field::Field, game_state::GameState, hare::Hare, - rules_engine::RulesEngine, -}; - -use super::Action; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Copy)] -pub enum Card { - FallBack, - HurryAhead, - EatSalad, - SwapCarrots, -} - -#[pymethods] -impl Card { - pub fn moves(&self) -> bool { - match self { - Card::FallBack | Card::HurryAhead => true, - Card::EatSalad | Card::SwapCarrots => false, - } - } - - fn move_to_field( - &self, - player: &mut Hare, - state: &mut GameState, - target_position: usize, - cards: Vec, - ) -> Result<(), PyErr> { - let distance = target_position as isize - player.position as isize; - - if target_position == PluginConstants::NUM_FIELDS - 1 && (player.carrots > 10 || player.salads > 0) { - return Err(HUIError::new_err("Too many carrots or salads to jump to goal")); - } - - RulesEngine::can_move_to( - &state.board, - distance, - player, - &state.clone_other_player(), - cards, - )?; - - player.position = (player.position as isize + distance) as usize; - - state.update_player(player.clone()); - Ok(()) - } - - fn play( - &self, - state: &mut GameState, - current: &mut Hare, - other: &mut Hare, - remaining_cards: Vec, - advance_distance: usize, - ) -> Result<(), PyErr> { - match self { - Card::FallBack => { - if current.position < other.position { - return Err(HUIError::new_err( - "You can only play this card if you are ahead of the other player", - )); - } - self.move_to_field( - current, - state, - other.position.saturating_sub(1), - remaining_cards, - )?; - } - Card::HurryAhead => { - if current.position > other.position { - return Err(HUIError::new_err( - "You can only play this card if you are behind the other player", - )); - } - // saturating add is here unnecessary because the board is finite and never larger than usize::MAX - self.move_to_field(current, state, other.position + 1, remaining_cards)?; - } - Card::EatSalad => { - if current.salads == 0 { - return Err(HUIError::new_err( - "You can only play this card if you have lettuce left", - )); - } - - current.eat_salad(state)? - } - Card::SwapCarrots => { - if current.position >= PluginConstants::LAST_LETTUCE_POSITION - || other.position >= PluginConstants::LAST_LETTUCE_POSITION - { - return Err(HUIError::new_err( - "You can only play this card if both players are before the last lettuce field", - )); - } - - let mut current_ok: bool = true; - if let Some(current_last_move) = ¤t.last_move - { - if let Action::Advance(current_advance) = ¤t_last_move.action - { - if current_advance.cards.contains(&Card::SwapCarrots) - && state.board.track[current.position - advance_distance] == Field::Hare - { - current_ok = false; - } - } - } - - let mut other_ok: bool = true; - if let Some(other_last_move) = &other.last_move - { - if let Action::Advance(other_advance) = &other_last_move.action - { - if other_advance.cards.contains(&Card::SwapCarrots) - && state.board.track[other.position] == Field::Hare - { - other_ok = false; - } - } - } - - if !current_ok || !other_ok { - return Err(HUIError::new_err( - "You can only play this card if the last similar swap card was not used in one of the last two turns", - )); - } - - swap(&mut current.carrots, &mut other.carrots); - } - } - Ok(()) - } - - pub fn perform(&self, state: &mut GameState, remaining_cards: Vec, advance_distance: usize) -> Result<(), PyErr> { - let mut current = state.clone_current_player(); - let mut other = state.clone_other_player(); - - let field = state - .board - .get_field(current.position) - .ok_or_else(|| HUIError::new_err("Field not found"))?; - - if field != Field::Hare { - return Err(HUIError::new_err( - "You can only play cards on the hare field", - )); - } - - let index = current - .cards - .iter() - .position(|card| card == self) - .ok_or_else(|| HUIError::new_err("Card not owned"))?; - - self.play(state, &mut current, &mut other, remaining_cards, advance_distance)?; - - current.cards.remove(index); - - state.update_player(current); - state.update_player(other); - - Ok(()) - } -} - -impl std::fmt::Display for Card { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - - match self { - Card::FallBack => write!(f, "Fallback Card"), - Card::HurryAhead => write!(f, "HurryAhead Card"), - Card::EatSalad => write!(f, "EatSalad Card"), - Card::SwapCarrots => write!(f, "SwapCarrot Card"), - } - } -} diff --git a/src/plugin2025/action/eat_salad.rs b/src/plugin2025/action/eat_salad.rs deleted file mode 100644 index cdb8088..0000000 --- a/src/plugin2025/action/eat_salad.rs +++ /dev/null @@ -1,33 +0,0 @@ -use pyo3::*; - -use crate::plugin2025::{game_state::GameState, rules_engine::RulesEngine}; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Default)] -pub struct EatSalad {} - -#[pymethods] -impl EatSalad { - #[new] - #[must_use] - pub fn new() -> Self { - Self {} - } - - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - let mut current = state.clone_current_player(); - RulesEngine::can_eat_salad(&state.board, ¤t)?; - current.eat_salad(state)?; - Ok(()) - } - - fn __repr__(&self) -> PyResult { - Ok(format!("{:?}", self)) - } -} - -impl std::fmt::Display for EatSalad { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "EatSalad") - } -} diff --git a/src/plugin2025/action/exchange_carrots.rs b/src/plugin2025/action/exchange_carrots.rs deleted file mode 100644 index 025846b..0000000 --- a/src/plugin2025/action/exchange_carrots.rs +++ /dev/null @@ -1,35 +0,0 @@ -use pyo3::*; - -use crate::plugin2025::game_state::GameState; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] -pub struct ExchangeCarrots { - #[pyo3(get, set)] - amount: i32, -} - -#[pymethods] -impl ExchangeCarrots { - #[new] - #[must_use] - pub fn new(amount: i32) -> Self { - Self { amount } - } - - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - let mut current = state.clone_current_player(); - current.exchange_carrots(state, self.amount)?; - Ok(()) - } - - fn __repr__(&self) -> PyResult { - Ok(format!("{:?}", self)) - } -} - -impl std::fmt::Display for ExchangeCarrots { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ExchangeCarrots(amount={})", self.amount) - } -} diff --git a/src/plugin2025/action/fall_back.rs b/src/plugin2025/action/fall_back.rs deleted file mode 100644 index dbb8291..0000000 --- a/src/plugin2025/action/fall_back.rs +++ /dev/null @@ -1,32 +0,0 @@ -use pyo3::*; - -use crate::plugin2025::game_state::GameState; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Default)] -pub struct FallBack {} - -#[pymethods] -impl FallBack { - #[new] - #[must_use] - pub fn new() -> Self { - Self {} - } - - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - let mut current = state.clone_current_player(); - current.fall_back(state)?; - Ok(()) - } - - fn __repr__(&self) -> PyResult { - Ok(format!("{:?}", self)) - } -} - -impl std::fmt::Display for FallBack { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "FallBack") - } -} diff --git a/src/plugin2025/board.rs b/src/plugin2025/board.rs deleted file mode 100644 index 4868977..0000000 --- a/src/plugin2025/board.rs +++ /dev/null @@ -1,57 +0,0 @@ -use pyo3::*; - -use super::field::Field; - -#[pyclass] -#[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Hash)] -pub struct Board { - #[pyo3(get)] - pub track: Vec, -} - -#[pymethods] -impl Board { - /// Creates a new board with the given fields. - #[new] - #[must_use] - pub fn new(track: Vec) -> Self { - Self { track } - } - - /// Returns the field at the specified index, or `None` if the index is out of bounds. - pub fn get_field(&self, index: usize) -> Option { - self.track.get(index).copied() - } - - /// 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)) - } - - /// Finds the previous occurrence of the specified field before the given index. - pub fn get_previous_field(&self, field: Field, index: usize) -> Option { - self.track.iter().take(index).rposition(|&f| f == field) - } - - /// Finds the next occurrence of the specified field after the given index. - pub fn get_next_field(&self, field: Field, index: usize) -> Option { - self.track - .iter() - .skip(index + 1) - .position(|&f| f == field) - .map(|i| i + index + 1) - } - - pub fn __repr__(&self) -> String { - format!("{:?}", self) - } -} - -impl std::fmt::Display for Board { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for field in &self.track { - write!(f, "{}", field)?; - } - Ok(()) - } -} diff --git a/src/plugin2025/constants.rs b/src/plugin2025/constants.rs deleted file mode 100644 index 9ce3c3c..0000000 --- a/src/plugin2025/constants.rs +++ /dev/null @@ -1,25 +0,0 @@ -use pyo3::*; - -use super::action::card::Card; - -#[pyclass] -pub struct PluginConstants; - -#[pymethods] -impl PluginConstants { - pub const NUM_FIELDS: usize = 65; - - pub const INITIAL_SALADS: i32 = 5; - pub const INITIAL_CARROTS: i32 = 68; - - 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/plugin2025/errors.rs b/src/plugin2025/errors.rs deleted file mode 100644 index 019b4a2..0000000 --- a/src/plugin2025/errors.rs +++ /dev/null @@ -1,3 +0,0 @@ -use pyo3::{exceptions::PyException, *}; - -create_exception!(_socha, HUIError, PyException); diff --git a/src/plugin2025/field.rs b/src/plugin2025/field.rs deleted file mode 100644 index 1435589..0000000 --- a/src/plugin2025/field.rs +++ /dev/null @@ -1,42 +0,0 @@ -use pyo3::*; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash, Copy)] -pub enum Field { - /// Zahlfeld - Position1, - /// Flaggenfeld - Position2, - /// Igelfeld: Hierauf kann nur rückwärts gezogen werden. - Hedgehog, - /// Salatfeld: Beim Betreteten wird im nächsten Zug ein Salat gegessen. - Salad, - /// Karottenfeld: Hier dürfen Karotten getauscht werden. - Carrots, - /// Hasenfeld: Hier wird sofort eine Hasenkarte gespielt. - Hare, - /// Marktfeld: Hier wird eine Hasenkarte gekauft (Variation). - Market, - /// Das Zielfeld. - Goal, - /// Das Startfeld - Start, -} - -// display - -impl std::fmt::Display for Field { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Field::Position1 => write!(f, "Pos1"), - Field::Position2 => write!(f, "Pos2"), - Field::Hedgehog => write!(f, "Hedgehog"), - Field::Salad => write!(f, "Salad"), - Field::Carrots => write!(f, "Carrot"), - Field::Hare => write!(f, "Hare"), - Field::Market => write!(f, "Market"), - Field::Goal => write!(f, "Goal"), - Field::Start => write!(f, "Start"), - } - } -} diff --git a/src/plugin2025/game_state.rs b/src/plugin2025/game_state.rs deleted file mode 100644 index d69fd41..0000000 --- a/src/plugin2025/game_state.rs +++ /dev/null @@ -1,291 +0,0 @@ -use itertools::Itertools; -use pyo3::*; - -use super::action::advance::Advance; -use super::action::eat_salad::EatSalad; -use super::action::exchange_carrots::ExchangeCarrots; -use super::action::fall_back::FallBack; -use super::action::card::Card; -use super::action::Action; -use super::board::Board; -use super::constants::PluginConstants; -use super::field::Field; -use super::hare::Hare; -use super::r#move::Move; - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] -pub struct GameState { - #[pyo3(get, set)] - pub board: Board, - #[pyo3(get, set)] - pub turn: usize, - player_one: Hare, - player_two: Hare, - #[pyo3(get, set)] - pub last_move: Option -} - -#[pymethods] -impl GameState { - #[new] - pub fn new(board: Board, turn: usize, player_one: Hare, player_two: Hare, last_move: Option) -> Self { - Self { - board, - turn, - player_one, - player_two, - last_move, - } - } - - pub fn perform_move(&self, r#move: &Move) -> Result { - let mut new_state = self.clone(); - r#move.perform(&mut new_state)?; - new_state.turn += 1; - - fn update_carrots(player: &mut Hare, opponent_position: usize, board: &Board) { - match board.get_field(player.position) { - Some(Field::Position1) if player.position > opponent_position => { - player.carrots += 10 - } - Some(Field::Position2) if player.position < opponent_position => { - player.carrots += 30 - } - _ => {} - } - } - - if new_state.turn % 2 == 0 { - update_carrots( - &mut new_state.player_one, - new_state.player_two.position, - &new_state.board, - ); - } else { - update_carrots( - &mut new_state.player_two, - new_state.player_one.position, - &new_state.board, - ); - } - - Ok(new_state) - } - - pub fn clone_current_player(&self) -> Hare { - if self.turn % 2 == 0 { - self.player_one.clone() - } else { - self.player_two.clone() - } - } - - pub fn clone_other_player(&self) -> Hare { - if self.turn % 2 != 0 { - self.player_one.clone() - } else { - self.player_two.clone() - } - } - - pub fn update_player(&mut self, player: Hare) { - if player.team == self.player_one.team { - self.player_one = player; - } else { - self.player_two = player; - } - } - - pub fn is_over(&self) -> bool { - let player_one_in_goal = self.player_one.is_in_goal(); - let player_two_in_goal = self.player_two.is_in_goal(); - let both_had_last_chance = self.turn % 2 == 0; - let rounds_exceeded = self.turn / 2 == PluginConstants::ROUND_LIMIT; - - player_one_in_goal || player_two_in_goal && both_had_last_chance || rounds_exceeded - } - - pub fn possible_moves_old(&self) -> Vec { - let mut moves = Vec::new(); - - moves.append(&mut self.possible_advance_moves_old()); - moves.append(&mut self.possible_eat_salad_moves()); - moves.append(&mut self.possible_exchange_carrots_moves()); - moves.append(&mut self.possible_fall_back_moves()); - - moves - } - - pub fn possible_moves(&self) -> Vec { - let mut moves = Vec::new(); - - moves.append(&mut self.possible_advance_moves()); - moves.append(&mut self.possible_eat_salad_moves()); - moves.append(&mut self.possible_exchange_carrots_moves()); - moves.append(&mut self.possible_fall_back_moves()); - - moves - } - - fn possible_exchange_carrots_moves(&self) -> Vec { - let moves: Vec = vec![ - Move::new(Action::ExchangeCarrots(ExchangeCarrots::new(-10))), - Move::new(Action::ExchangeCarrots(ExchangeCarrots::new(10))), - ]; - - moves - .into_iter() - .filter(|m| m.perform(&mut self.clone()).is_ok()) - .collect() - } - - fn possible_fall_back_moves(&self) -> Vec { - let moves: Vec = vec![Move::new(Action::FallBack(FallBack::new()))]; - - moves - .into_iter() - .filter(|m| m.perform(&mut self.clone()).is_ok()) - .collect() - } - - fn possible_eat_salad_moves(&self) -> Vec { - let moves: Vec = vec![Move::new(Action::EatSalad(EatSalad::new()))]; - - moves - .into_iter() - .filter(|m| m.perform(&mut self.clone()).is_ok()) - .collect() - } - - fn possible_advance_moves_old(&self) -> Vec { - - let current_player = self.clone_current_player(); - let max_distance = - (((-1.0 + (1 + 8 * current_player.carrots) as f64).sqrt()) / 2.0) as usize; - - let mut moves = Vec::new(); - - for distance in 1..=max_distance { - for card in PluginConstants::MARKET_SELECTION { - moves.push(Move::new(Action::Advance(Advance::new( - distance, - vec![card], - )))); - } - - 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, - 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(), - )))); - } - } - } - - moves.push(Move::new(Action::Advance(Advance::new(distance, vec![])))); - } - - moves - .into_iter() - .unique() - .filter(|m| m.perform(&mut self.clone()).is_ok()) - .collect() - } - - fn possible_advance_moves(&self) -> Vec { - - let current_player = self.clone_current_player(); - let max_distance = - (((-1.0 + (1 + 8 * current_player.carrots) as f64).sqrt()) / 2.0) as usize; - - - let mut card_permutations = Vec::new(); - - for k in 0..=current_player.cards.len() { - for permutation in current_player.cards.iter().permutations(k).unique() { - - // change permutation cards to owned - let owned_permutation: Vec = permutation.iter().map(|&card| *card).collect(); - - // if minimum one card in permutation, save permutation and add all market cards to it - if !owned_permutation.is_empty() { - card_permutations.push(owned_permutation.clone()); - - for card in PluginConstants::MARKET_SELECTION { - let mut extended_permutation = owned_permutation.clone(); - extended_permutation.push(card); // card is already owned - card_permutations.push(extended_permutation); - } - } - } - } - - let mut moves: Vec = Vec::new(); - - for distance in 1..=max_distance { - // destination of advance - let target_pos: usize = current_player.position + distance; - - // out of range, skip - if target_pos > self.board.track.len() - 1 { - continue; - } - - // destination field of advance - let target_field: Field = self.board.track[target_pos]; - - // add card / no card advances for each field type - match target_field { - Field::Hare => { - for permutation in &card_permutations { - moves.push(Move::new(Action::Advance(Advance::new( - distance, - permutation.to_vec(), - )))); - } - }, - Field::Market => { - for card in PluginConstants::MARKET_SELECTION { - moves.push(Move::new(Action::Advance(Advance::new( - distance, - vec![card], - )))); - } - }, - _ => { - moves.push(Move::new(Action::Advance(Advance::new(distance, vec![])))); - } - } - } - - moves - .into_iter() - .unique() - .filter(|m| m.perform(&mut self.clone()).is_ok()) - .collect() - } - - pub fn __repr__(&self) -> String { - format!("{:?}", self) - } -} - -impl std::fmt::Display for GameState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "GameState(board={}, turn={}, player_one={}, player_two={}, last_move={:?})", - self.board, self.turn, self.player_one, self.player_two, self.last_move - ) - } -} diff --git a/src/plugin2025/hare.rs b/src/plugin2025/hare.rs deleted file mode 100644 index 872ee07..0000000 --- a/src/plugin2025/hare.rs +++ /dev/null @@ -1,179 +0,0 @@ -use std::fmt; - -use pyo3::*; - -use super::{ - action::card::Card, constants::PluginConstants, errors::HUIError, field::Field, - game_state::GameState, r#move::Move, rules_engine::RulesEngine, -}; - -#[pyclass] -#[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Hash, Copy)] -pub enum TeamEnum { - One, - Two, -} - -impl TeamEnum { - pub fn __repr__(&self) -> String { - format!("{:?}", self) - } -} - -impl fmt::Display for TeamEnum { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TeamEnum::One => write!(f, "Team One"), - TeamEnum::Two => write!(f, "Team Two"), - } - } -} - -#[pyclass] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Hash)] -pub struct Hare { - #[pyo3(get, set)] - pub team: TeamEnum, - #[pyo3(get, set)] - pub position: usize, - #[pyo3(get, set)] - pub salads: i32, - #[pyo3(get, set)] - pub carrots: i32, - #[pyo3(get, set)] - pub last_move: Option, - #[pyo3(get)] - pub cards: Vec, -} - -#[pymethods] -impl Hare { - #[new] - #[must_use] - pub fn new( - team: TeamEnum, - cards: Option>, - carrots: Option, - salads: Option, - last_move: Option, - position: Option, - ) -> Self { - Self { - team, - cards: cards.unwrap_or_default(), - carrots: carrots.unwrap_or(PluginConstants::INITIAL_CARROTS), - salads: salads.unwrap_or(PluginConstants::INITIAL_SALADS), - last_move, - position: position.unwrap_or(0), - } - } - - pub fn is_in_goal(&self) -> bool { - self.position == PluginConstants::NUM_FIELDS - 1 - } - - pub fn can_enter_goal(&self) -> bool { - self.carrots <= 10 && self.salads == 0 - } - - pub fn advance_by( - &mut self, - state: &mut GameState, - distance: usize, - cards: Vec, - ) -> Result<(), PyErr> { - let needed_carrots = RulesEngine::calculates_carrots(distance); - - if self.carrots - needed_carrots < 0 { - return Err(HUIError::new_err("Not enough carrots")); - } - - RulesEngine::can_move_to( - &state.board, - distance as isize, - self, - &state.clone_other_player(), - cards, - )?; - - let new_position = self.position + distance; - - self.carrots -= needed_carrots; - self.position = new_position; - - state.update_player(self.clone()); - - Ok(()) - } - - pub fn exchange_carrots(&mut self, state: &mut GameState, carrots: i32) -> Result<(), PyErr> { - RulesEngine::can_exchange_carrots(&state.board, self, carrots)?; - self.carrots += carrots; - - state.update_player(self.clone()); - Ok(()) - } - - pub fn consume_carrots(&mut self, state: &mut GameState, carrots: i32) -> Result<(), PyErr> { - if self.carrots - carrots >= 0 { - self.carrots -= carrots; - - state.update_player(self.clone()); - Ok(()) - } else { - Err(HUIError::new_err("Not enough carrots")) - } - } - - pub fn eat_salad(&mut self, state: &mut GameState) -> Result<(), PyErr> { - self.salads -= 1; - self.carrots += if self.is_ahead(state) { 10 } else { 30 }; - - state.update_player(self.clone()); - Ok(()) - } - - pub fn get_fall_back(&self, state: &GameState) -> Option { - match state - .board - .get_previous_field(Field::Hedgehog, self.position) - { - Some(i) if state.clone_other_player().position != i => Some(i), - Some(_) => None, - None => None, - } - } - - pub fn fall_back(&mut self, state: &mut GameState) -> Result<(), PyErr> { - match self.get_fall_back(state) { - Some(i) => { - RulesEngine::has_to_eat_salad(&state.board, self)?; - - self.carrots += 10 * ((self.position - i) as i32); - self.position = i; - - state.update_player(self.clone()); - Ok(()) - } - None => Err(HUIError::new_err("Field not found")), - } - } - - pub fn is_ahead(&self, state: &GameState) -> bool { - self.position > state.clone_other_player().position - } - - pub fn __repr__(&self) -> String { - format!("{:?}", self) - } -} - -impl fmt::Display for Hare { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Hare(team={}, position={}, salads={}, carrots={}, last_move={:?}, cards={:?})", - self.team, self.position, self.salads, self.carrots, self.last_move, self.cards - ) - } -} diff --git a/src/plugin2025/move.rs b/src/plugin2025/move.rs deleted file mode 100644 index a3a758d..0000000 --- a/src/plugin2025/move.rs +++ /dev/null @@ -1,44 +0,0 @@ -use pyo3::*; - -use super::{action::Action, game_state::GameState}; - -#[pyclass] -#[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Hash)] -pub struct Move { - #[pyo3(get, set)] - pub action: Action, -} - -#[pymethods] -impl Move { - #[new] - #[must_use] - pub fn new(action: Action) -> Self { - Self { action } - } - - pub fn perform(&self, state: &mut GameState) -> Result<(), PyErr> { - let result = self.action.perform(state); - if result.is_ok() { - let mut player = state.clone_current_player(); - player.last_move = Some(self.clone()); - state.last_move = Some(self.clone()); - state.update_player(player); - } - result - } - - fn __repr__(&self) -> PyResult { - Ok(format!("Move(action={:?})", self.action)) - } - - fn __eq__(&self, other: &Move) -> PyResult { - Ok(self == other) - } -} - -impl std::fmt::Display for Move { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Move(action={})", self.action) - } -} diff --git a/src/plugin2025/rules_engine.rs b/src/plugin2025/rules_engine.rs deleted file mode 100644 index 03d7078..0000000 --- a/src/plugin2025/rules_engine.rs +++ /dev/null @@ -1,123 +0,0 @@ -use pyo3::*; - -use super::{ - action::{card::Card, eat_salad::EatSalad, Action}, - board::Board, - errors::HUIError, - field::Field, - hare::Hare, - r#move::Move, -}; - -#[pyclass] -pub struct RulesEngine; - -#[pymethods] -impl RulesEngine { - #[staticmethod] - pub fn calculates_carrots(distance: usize) -> i32 { - let distance_i32: i32 = distance.try_into().unwrap(); - (distance_i32 * (distance_i32 + 1)) / 2 - } - - #[staticmethod] - pub fn can_exchange_carrots(board: &Board, player: &Hare, count: i32) -> Result<(), PyErr> { - match board.get_field(player.position) { - Some(Field::Carrots) => { - if count != 10 && count != -10 { - return Err(HUIError::new_err("You can only exchange 10 carrots")); - } - if count == -10 && player.carrots < 10 { - return Err(HUIError::new_err("Not enough carrots")); - } - Ok(()) - } - Some(_) => Err(HUIError::new_err("Field is not a carrot field")), - None => Err(HUIError::new_err("Field not found")), - } - } - - #[staticmethod] - pub fn can_eat_salad(board: &Board, player: &Hare) -> Result<(), PyErr> { - if player.salads < 1 { - return Err(HUIError::new_err("No salad to eat")); - } - - match board.get_field(player.position) { - Some(Field::Salad) - if !matches!( - player.last_move, - Some(Move { - action: Action::EatSalad(_) - }) - ) => - { - Ok(()) - } - Some(Field::Salad) => Err(HUIError::new_err("Cannot eat salad twice in a row")), - Some(_) => Err(HUIError::new_err("Field is not a salad")), - None => Err(HUIError::new_err("Field not found")), - } - } - - #[staticmethod] - pub fn has_to_eat_salad(board: &Board, player: &Hare) -> Result<(), PyErr> { - match board.get_field(player.position) { - Some(Field::Salad) => { - if player.last_move - != Some(Move { - action: Action::EatSalad(EatSalad::new()), - }) - { - Err(HUIError::new_err("Cannot advance without eating salad")) - } else { - Ok(()) - } - } - Some(_) => Ok(()), - None => Ok(()), - } - } - - #[staticmethod] - pub fn can_move_to( - board: &Board, - distance: isize, - player: &Hare, - other_player: &Hare, - cards: Vec, - ) -> Result<(), PyErr> { - if distance == 0 { - return Err(HUIError::new_err("Advance distance cannot be 0")); - } - - let new_position = (player.position as isize + distance) as usize; - - if new_position == 0 { - return Err(HUIError::new_err("Cannot jump to position 0")); - } - - Self::has_to_eat_salad(board, player)?; - - let field = board - .get_field(new_position) - .ok_or_else(|| HUIError::new_err("Field not found"))?; - - if field != Field::Goal && new_position == other_player.position { - return Err(HUIError::new_err("Field is occupied by opponent")); - } - - match field { - Field::Hedgehog => Err(HUIError::new_err("Cannot advance on Hedgehog field")), - Field::Salad if player.salads > 0 => Ok(()), - Field::Salad => Err(HUIError::new_err("No salad to eat")), - Field::Hare if !cards.is_empty() => Ok(()), - Field::Hare => Err(HUIError::new_err("No card to play")), - Field::Market if player.carrots >= 10 && !cards.is_empty() => Ok(()), - Field::Market => Err(HUIError::new_err("Not enough carrots or no card to play")), - Field::Goal if player.carrots - RulesEngine::calculates_carrots(distance as usize) <= 10 && player.salads == 0 => Ok(()), - Field::Goal => Err(HUIError::new_err("Too many carrots or salads")), - _ => Ok(()), - } - } -} diff --git a/src/plugin2025/test.rs b/src/plugin2025/test.rs deleted file mode 100644 index 64e2f61..0000000 --- a/src/plugin2025/test.rs +++ /dev/null @@ -1,5 +0,0 @@ -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/plugin2025/test/advance_test.rs b/src/plugin2025/test/advance_test.rs deleted file mode 100644 index 2c1e085..0000000 --- a/src/plugin2025/test/advance_test.rs +++ /dev/null @@ -1,201 +0,0 @@ -#[cfg(test)] -mod tests { - use pyo3::Python; - - use crate::plugin2025::{ - action::{advance::Advance, card::Card}, - board::Board, - field::Field, - game_state::GameState, - hare::{Hare, TeamEnum}, - }; - - fn create_test_game_state() -> GameState { - let board = Board::new(vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Hare, - Field::Hedgehog, - Field::Market, - Field::Hare, - Field::Position1, - Field::Goal, - ]); - let player_one = Hare::new( - TeamEnum::One, - Some(vec![Card::FallBack, Card::EatSalad, Card::SwapCarrots]), - Some(60), - Some(3), - None, - Some(4), - ); - let player_two = Hare::new( - TeamEnum::Two, - Some(vec![Card::HurryAhead]), - Some(60), - Some(3), - None, - Some(3), - ); - GameState::new(board, 0, player_one, player_two, None) - } - - #[test] - fn test_new() { - let cards = vec![Card::FallBack]; - let advance = Advance::new(5, cards.clone()); - assert_eq!(advance.distance, 5); - assert_eq!(advance.cards, cards); - } - - #[test] - fn test_perform_success() { - let cards = vec![Card::FallBack]; - let advance = Advance::new(2, cards.clone()); - - let mut state = create_test_game_state(); - - let result = advance.perform(&mut state); - assert!(result.is_ok()); - - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 2); - } - - #[test] - fn test_perform_success_without_cards() { - let board = Board::new(vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Hare, - Field::Hedgehog, - Field::Market, - Field::Hare, - Field::Position1, - Field::Goal, - ]); - let player_one = Hare::new( - TeamEnum::One, - Some(vec![Card::FallBack, Card::EatSalad, Card::SwapCarrots]), - Some(60), - Some(3), - None, - Some(0), - ); - let player_two = Hare::new( - TeamEnum::Two, - Some(vec![Card::HurryAhead]), - Some(60), - Some(3), - None, - Some(0), - ); - - let mut state = GameState::new(board, 0, player_one, player_two, None); - - let advance = Advance::new(2, vec![]); - - let result = advance.perform(&mut state); - assert!(result.is_ok()); - - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 2); - } - - #[test] - fn test_perform_buy_card_success() { - let cards = vec![Card::HurryAhead]; - let advance = Advance::new(2, cards.clone()); - - let mut state = create_test_game_state(); - state.turn = 1; - - let result = advance.perform(&mut state); - assert!(result.is_ok()); - } - - #[test] - fn test_perform_buy_card_error() { - let cards = vec![Card::HurryAhead, Card::FallBack]; - let advance = Advance::new(2, cards.clone()); - - let mut state = create_test_game_state(); - state.turn = 1; - - let result = advance.perform(&mut state); - assert!(result.is_err()); - } - - #[test] - fn test_perform_cannot_play_card_error() { - let cards = vec![Card::EatSalad]; - let advance = Advance::new(2, cards.clone()); - - let mut state = create_test_game_state(); - let mut player = state.clone_current_player(); - player.cards = vec![Card::FallBack]; - state.update_player(player); - - let result = advance.perform(&mut state); - assert!(result.is_err()); - - pyo3::prepare_freethreaded_python(); - Python::with_gil(|_| { - assert_eq!(result.unwrap_err().to_string(), "HUIError: Card not owned"); - }) - } - - #[test] - fn test_perform_market_without_cards_error() { - let advance = Advance::new(1, vec![]); - - let mut state = create_test_game_state(); - - let result = advance.perform(&mut state); - assert!(result.is_err()); - - pyo3::prepare_freethreaded_python(); - Python::with_gil(|_| { - assert_eq!( - result.unwrap_err().to_string(), - "HUIError: Not enough carrots or no card to play" - ); - }) - } - - #[test] - fn test_perform_hare_without_cards_error() { - let advance = Advance::new(2, vec![]); - - let mut state = create_test_game_state(); - - let result = advance.perform(&mut state); - assert!(result.is_err()); - - pyo3::prepare_freethreaded_python(); - Python::with_gil(|_| { - assert_eq!(result.unwrap_err().to_string(), "HUIError: No card to play"); - }) - } - - #[test] - fn test_perform_market_with_multiple_cards() { - let cards = vec![Card::HurryAhead, Card::FallBack]; - let advance = Advance::new(1, cards.clone()); - - let mut state = create_test_game_state(); - - let result = advance.perform(&mut state); - assert!(result.is_err()); - - pyo3::prepare_freethreaded_python(); - Python::with_gil(|_| { - assert_eq!( - result.unwrap_err().to_string(), - "HUIError: Only one card allowed to buy" - ); - }) - } -} diff --git a/src/plugin2025/test/board_test.rs b/src/plugin2025/test/board_test.rs deleted file mode 100644 index 59aa44a..0000000 --- a/src/plugin2025/test/board_test.rs +++ /dev/null @@ -1,80 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::plugin2025::{board::Board, field::Field}; - - #[test] - fn test_new_board() { - let fields = vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Goal, - ]; - let board = Board::new(fields.clone()); - assert_eq!(board.track, fields); - } - - #[test] - fn test_get_field() { - let fields = vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Goal, - ]; - let board = Board::new(fields); - assert_eq!(board.get_field(0), Some(Field::Start)); - assert_eq!(board.get_field(2), Some(Field::Position2)); - assert_eq!(board.get_field(4), None); - } - - #[test] - fn test_find_field() { - let fields = vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Goal, - ]; - let board = Board::new(fields); - assert_eq!(board.find_field(Field::Position1, 0, 4), Some(1)); - assert_eq!(board.find_field(Field::Goal, 1, 4), Some(3)); - assert_eq!(board.find_field(Field::Hedgehog, 0, 4), None); - assert_eq!(board.find_field(Field::Position1, 2, 4), None); - } - - #[test] - fn test_get_previous_field() { - let fields = vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Position1, - Field::Goal, - ]; - let board = Board::new(fields); - assert_eq!(board.get_previous_field(Field::Position1, 3), Some(1)); - assert_eq!(board.get_previous_field(Field::Start, 4), Some(0)); - assert_eq!(board.get_previous_field(Field::Goal, 4), None); - assert_eq!(board.get_previous_field(Field::Position2, 2), None); - assert_eq!(board.get_previous_field(Field::Position2, 3), Some(2)); - } - - #[test] - fn test_get_next_field() { - let fields = vec![ - Field::Start, - Field::Position1, - Field::Position2, - Field::Position1, - Field::Goal, - ]; - let board = Board::new(fields); - assert_eq!(board.get_next_field(Field::Position1, 1), Some(3)); - assert_eq!(board.get_next_field(Field::Start, 0), None); - assert_eq!(board.get_next_field(Field::Position2, 2), None); - assert_eq!(board.get_next_field(Field::Goal, 3), Some(4)); - assert_eq!(board.get_next_field(Field::Goal, 4), None); - assert_eq!(board.get_next_field(Field::Position1, 2), Some(3)); - } -} diff --git a/src/plugin2025/test/card_test.rs b/src/plugin2025/test/card_test.rs deleted file mode 100644 index c6daaa4..0000000 --- a/src/plugin2025/test/card_test.rs +++ /dev/null @@ -1,258 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::plugin2025::{ - action::{advance::Advance, card::Card, Action}, - board::Board, - field::Field, - game_state::GameState, - hare::{Hare, TeamEnum}, - r#move::Move, - }; - - fn create_test_game_state() -> GameState { - let board = Board::new(vec![ - Field::Start, - Field::Carrots, - Field::Position2, - Field::Hare, - Field::Position1, - Field::Market, - Field::Carrots, - Field::Hare, - Field::Carrots, - Field::Hedgehog, - Field::Salad, - Field::Goal, - ]); - let player_one = Hare::new( - TeamEnum::One, - Some(vec![Card::FallBack, Card::EatSalad, Card::SwapCarrots]), - Some(30), - Some(3), - None, - Some(7), - ); - let player_two = Hare::new( - TeamEnum::Two, - Some(vec![Card::HurryAhead]), - Some(60), - Some(3), - None, - Some(3), - ); - GameState::new(board, 0, player_one, player_two, None) - } - - #[test] - fn test_fallback_card() { - let mut state = create_test_game_state(); - let fallback_card = Card::FallBack; - assert!(fallback_card - .perform(&mut state, vec![Card::EatSalad, Card::SwapCarrots], 0) - .is_ok()); - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 2); - } - - #[test] - fn test_hurryahead_card() { - let mut state = create_test_game_state(); - state.turn = 1; - let hurry_ahead_card: Card = Card::HurryAhead; - assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_ok()); - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 8); - - // test hurry ahead in goal with too many carrots (salads are ok but that was never the problem) - let mut state = create_test_game_state(); - state.turn = 1; - - let mut current_player = state.clone_current_player(); - current_player.salads = 0; - state.update_player(current_player); - let mut other_player = state.clone_other_player(); - other_player.position = 10; - state.update_player(other_player); - - let hurry_ahead_card: Card = Card::HurryAhead; - assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_err()); - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 3); - - // test hurry ahead in goal with low enough many carrots - let mut state = create_test_game_state(); - state.turn = 1; - - let mut current_player = state.clone_current_player(); - current_player.carrots = 3; - current_player.salads = 0; - state.update_player(current_player); - let mut other_player = state.clone_other_player(); - other_player.position = 10; - state.update_player(other_player); - - let hurry_ahead_card: Card = Card::HurryAhead; - assert!(hurry_ahead_card.perform(&mut state, vec![], 0).is_ok()); - let current_player = state.clone_current_player(); - assert_eq!(current_player.position, 11); - } - - #[test] - fn test_eatsalad_card() { - let mut state = create_test_game_state(); - let eat_salad_card = Card::EatSalad; - assert!(eat_salad_card - .perform(&mut state, vec![Card::FallBack, Card::SwapCarrots], 0) - .is_ok()); - let current_player = state.clone_current_player(); - assert_eq!(current_player.salads, 2); - } - - #[test] - fn test_swapcarrots_card_general() { - let mut state = create_test_game_state(); - - // modify player one - let mut player_one = state.clone_current_player(); - player_one.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 2, - cards: vec![], - }), - }); - - state.update_player(player_one); - - // modify player two - let mut player_two = state.clone_other_player(); - player_two.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 3, - cards: vec![], - }), - }); - - state.update_player(player_two); - - // test card - let swap_carrots_card = Card::SwapCarrots; - assert!(swap_carrots_card - .perform(&mut state, vec![Card::FallBack, Card::EatSalad], 1) - .is_ok()); - let current_player = state.clone_current_player(); - let other_player = state.clone_other_player(); - assert_eq!(current_player.carrots, 60); - assert_eq!(other_player.carrots, 30); - } - - #[test] - fn test_swapcarrots_card_bought_last_two_rounds() { - let mut state = create_test_game_state(); - - // modify player one - let mut player_one = state.clone_current_player(); - player_one.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 1, - cards: vec![Card::SwapCarrots], - }), - }); - - state.update_player(player_one); - - // modify player two - let mut player_two = state.clone_other_player(); - player_two.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 3, - cards: vec![], - }), - }); - - state.update_player(player_two); - - // test card - let swap_carrots_card = Card::SwapCarrots; - assert!(swap_carrots_card - .perform(&mut state, vec![Card::FallBack, Card::EatSalad], 2) - .is_ok()); - let current_player = state.clone_current_player(); - let other_player = state.clone_other_player(); - assert_eq!(current_player.carrots, 60); - assert_eq!(other_player.carrots, 30); - } - - #[test] - fn test_swapcarrots_card_played_last_two_rounds() { - let mut state = create_test_game_state(); - - // modify player one - let mut player_one = state.clone_current_player(); - player_one.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 2, - cards: vec![], - }), - }); - - state.update_player(player_one); - - // modify player two - let mut player_two = state.clone_other_player(); - player_two.last_move = Some(Move { - action: Action::Advance(Advance { - distance: 3, - cards: vec![Card::SwapCarrots], - }), - }); - - state.update_player(player_two); - - // test card - let swap_carrots_card = Card::SwapCarrots; - assert!(swap_carrots_card - .perform(&mut state, vec![Card::FallBack, Card::EatSalad], 1) - .is_err()); - } - - #[test] - fn test_play_card_not_owned() { - let mut state = create_test_game_state(); - state.turn = 1; - let card_not_owned = Card::FallBack; - let result = card_not_owned.perform(&mut state, vec![Card::HurryAhead], 0); - assert!(result.is_err()); - } - - #[test] - fn test_play_card_not_on_hare_field() { - let mut state = create_test_game_state(); - let card = Card::FallBack; - let mut current_player = state.clone_current_player(); - current_player.position = 1; - state.update_player(current_player); - let result = card.perform(&mut state, vec![Card::EatSalad, Card::SwapCarrots], 0); - assert!(result.is_err()); - } - - #[test] - fn test_invalid_field() { - let mut state = create_test_game_state(); - let invalid_card = Card::FallBack; - state.board.track.clear(); - let result = invalid_card.perform(&mut state, vec![Card::EatSalad, Card::SwapCarrots], 0); - assert!(result.is_err()); - } - - #[test] - fn test_no_salad_but_salad_card() { - let mut state = create_test_game_state(); - let card = Card::EatSalad; - let mut current_player = state.clone_current_player(); - current_player.salads = 0; - current_player.cards = vec![card]; - state.update_player(current_player); - let result = card.perform(&mut state, vec![], 0); - assert!(result.is_err()); - } -} diff --git a/src/plugin2025/test/rules_test.rs b/src/plugin2025/test/rules_test.rs deleted file mode 100644 index cfce5e6..0000000 --- a/src/plugin2025/test/rules_test.rs +++ /dev/null @@ -1,105 +0,0 @@ -#[cfg(test)] -mod tests { - use crate::plugin2025::{ - action::card::Card, - board::Board, - field::Field, - hare::{Hare, TeamEnum}, - rules_engine::RulesEngine, - }; - - fn create_player(team: TeamEnum, position: usize) -> Hare { - Hare::new( - team, - Some(vec![Card::FallBack, Card::EatSalad, Card::SwapCarrots]), - Some(60), - Some(3), - None, - Some(position), - ) - } - - fn create_board() -> Board { - Board::new(vec![ - Field::Start, - Field::Salad, - Field::Position2, - Field::Hare, - Field::Carrots, - Field::Market, - Field::Hare, - Field::Position1, - Field::Goal, - ]) - } - - #[test] - fn test_calculates_carrots() { - assert_eq!(RulesEngine::calculates_carrots(0), 0); - assert_eq!(RulesEngine::calculates_carrots(1), 1); - assert_eq!(RulesEngine::calculates_carrots(2), 3); - assert_eq!(RulesEngine::calculates_carrots(3), 6); - assert_eq!(RulesEngine::calculates_carrots(4), 10); - } - - #[test] - fn test_can_exchange_carrots() { - let board = create_board(); - let mut player = create_player(TeamEnum::One, 4); - - let result = RulesEngine::can_exchange_carrots(&board, &player, 10); - assert!(result.is_ok()); - - player.carrots = 5; - let result = RulesEngine::can_exchange_carrots(&board, &player, -10); - assert!(result.is_err()); - - player.carrots = 10; - let result = RulesEngine::can_exchange_carrots(&board, &player, -10); - assert!(result.is_ok()); - - let result = RulesEngine::can_exchange_carrots(&board, &player, 5); - assert!(result.is_err()); - } - - #[test] - fn test_can_eat_salad() { - let board = create_board(); - let player = create_player(TeamEnum::One, 1); - - let result = RulesEngine::can_eat_salad(&board, &player); - assert!(result.is_ok()); - } - - #[test] - fn test_can_advance_to() { - let board = create_board(); - let mut player_one = create_player(TeamEnum::One, 0); - let player_two = create_player(TeamEnum::Two, 2); - - assert!(RulesEngine::can_move_to(&board, 3, &player_one, &player_two, vec![Card::FallBack]).is_ok()); - - assert!(RulesEngine::can_move_to(&board, 2, &player_one, &player_two, vec![]).is_err()); - - player_one.carrots = 1; - assert!(RulesEngine::can_move_to(&board, 5, &player_one, &player_two, vec![]).is_err()); - - player_one.cards = vec![]; - player_one.carrots = 60; - assert!(RulesEngine::can_move_to(&board, 6, &player_one, &player_two, vec![]).is_err()); - - // goal - // too many salads and carrots - assert!(RulesEngine::can_move_to(&board, 8, &player_one, &player_two, vec![]).is_err()); - // salads ok, too many carrots - player_one.salads = 0; - assert!(RulesEngine::can_move_to(&board, 8, &player_one, &player_two, vec![]).is_err()); - // too many salads, carrots ok - player_one.carrots = 45; - player_one.salads = 3; - assert!(RulesEngine::can_move_to(&board, 8, &player_one, &player_two, vec![]).is_err()); - // all ok - player_one.salads = 0; - assert!(RulesEngine::can_move_to(&board, 8, &player_one, &player_two, vec![]).is_ok()); - } -} diff --git a/src/plugin2025/test/state_test.rs b/src/plugin2025/test/state_test.rs deleted file mode 100644 index edd7b69..0000000 --- a/src/plugin2025/test/state_test.rs +++ /dev/null @@ -1,165 +0,0 @@ -#[cfg(test)] -mod tests { - use std::vec; - - use crate::plugin2025::{ - action::{advance::Advance, card::Card, Action}, - board::Board, - field::Field, - game_state::GameState, - hare::{Hare, TeamEnum}, - r#move::Move, - rules_engine::RulesEngine, - }; - - 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::Carrots, - Field::Position2, - Field::Hare, - Field::Position1, - Field::Market, - Field::Carrots, - Field::Hare, - Field::Hedgehog, - Field::Salad, - 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), - None, - ); - 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), - None, - ); - let moves = state.possible_moves(); - - assert!(moves.contains(&Move::new(Action::Advance(Advance::new( - 1, - vec![Card::HurryAhead, Card::FallBack, Card::EatSalad] - ))))); - } - - #[test] - fn test_correct_carrot_update() { - let state_depth_0 = GameState::new( - create_board(), - 0, - create_player(TeamEnum::One, 0, vec![], 75, 0), - create_player(TeamEnum::Two, 0, vec![], 200, 0), - None, - ); - - // perform all poss moves for current player ("A") - let moves_depth_0 = state_depth_0.possible_moves(); - for move_depth_0 in moves_depth_0.iter() { - let depth_1 = state_depth_0.perform_move(move_depth_0); - assert!(depth_1.is_ok()); - - match depth_1 { - Ok(state_depth_1) => { - let moves_depth_1 = state_depth_1.possible_moves(); - let ref move_first_depth_1 = moves_depth_1[0]; - let ref move_last_depth_1 = moves_depth_1[moves_depth_1.len() - 1]; - - // performed player "A" on Pos1 or Pos2 Field -> calculate next depth (player B) - let on_pos1 = state_depth_1.board.get_field(state_depth_1.clone_other_player().position) == Some(Field::Position1); - let on_pos2 = state_depth_1.board.get_field(state_depth_1.clone_other_player().position) == Some(Field::Position2); - if on_pos1 || on_pos2 { - - let moved_distance = match &move_depth_0.action { - Action::Advance(advance) => advance.distance, - _ => 0, - }; - - let expected_carrots = state_depth_0.clone_current_player().carrots - RulesEngine::calculates_carrots(moved_distance); - - // player "A" should be missing the exact carrot amount for the distance - assert_eq!(expected_carrots, state_depth_1.clone_other_player().carrots); - - // first (shortest) poss move of player "B" gets performed -> A is (with this board) in front - let depth_2_first = state_depth_1.perform_move(move_first_depth_1); - assert!(depth_2_first.is_ok()); - match depth_2_first { - Ok(state_depth_2_first) => { - // "A" got the 10 ten extra carrots if on pos1 field and in front - if on_pos1 { - assert_eq!(expected_carrots + 10, state_depth_2_first.clone_current_player().carrots); - } - - // no carrots should have been added to "A" if on pos2 field and in front - if on_pos2 { - assert_eq!(expected_carrots, state_depth_2_first.clone_current_player().carrots); - } - } - Err(e) => println!("Error {e}") - } - - // last (farthest) poss move of player "B" gets performed -> A is (with this board) behind - let depth_2_last = state_depth_1.perform_move(move_last_depth_1); - assert!(depth_2_last.is_ok()); - match depth_2_last { - Ok(state_depth_2_last) => { - // no carrots should have been added to "A" if on pos1 field and behind - if on_pos1 { - assert_eq!(expected_carrots, state_depth_2_last.clone_current_player().carrots); - } - - // "A" got the 30 ten extra carrots if on pos2 field and behind - if on_pos2 { - assert_eq!(expected_carrots + 30, state_depth_2_last.clone_current_player().carrots); - } - } - Err(e) => println!("Error {e}") - } - } - }, - Err(e) => println!("Error {e}") - } - } - } -}