diff --git a/Cargo.toml b/Cargo.toml index 04525b5..40711a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "_socha" -version = "2.1.1" +version = "2.1.2" edition = "2021" [lib] diff --git a/logic.py b/logic.py index 8820bf7..2d92c7e 100644 --- a/logic.py +++ b/logic.py @@ -1,4 +1,6 @@ import logging +import random +from typing import List # not all imports are currently used, but they might be in the future and it shows all available functionalities from socha import Accelerate, AccelerationProblem, Advance, AdvanceInfo, AdvanceProblem, Board from socha import CartesianCoordinate, CubeCoordinates, CubeDirection, Field, FieldType, GameState @@ -14,10 +16,11 @@ class Logic(IClientHandler): # this method should always be implemented otherwise the client will be disqualified def calculate_move(self) -> Move: logging.info("Calculate move...") - return Move([Advance(1)]) + possible_moves: List[Move] = self.game_state.possible_moves() + return possible_moves[random.randint(0, len(possible_moves) - 1)] # this method is called every time the server has sent a new game state update - # this method should be implemented to keep + # this method should be implemented to keep the game state up to date def on_update(self, state: GameState): self.game_state = state diff --git a/pyproject.toml b/pyproject.toml index 0c70168..afd2ffe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "socha" -version = "2.1.1" +version = "2.1.2" authors = [{ name = "maxblan", email = "stu222782@mail.uni-kiel.de" }] description = "This is the package for the Software-Challenge Germany 2023. This Season the game will be 'Hey, danke für den Fisch' a.k.a. 'Penguins' in short." readme = "README.md" diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index 76fbd36..06baff9 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -21,7 +21,7 @@ class PluginConstants: MIN_ISLANDS: int -class CartesianCoordinate(object): +class CartesianCoordinate: x: int y: int @@ -31,7 +31,7 @@ class CartesianCoordinate(object): def from_index(index: int) -> CartesianCoordinate: ... -class CubeCoordinates(object): +class CubeCoordinates: q: int r: int s: int @@ -103,6 +103,10 @@ class Ship: def max_acc(self) -> int: ... def accelerate_by(self, diff: int) -> None: ... def read_resolve(self) -> None: ... + def resolve_direction(self, reverse: bool) -> None: ... + def update_position(self, distance: int, + advance_info: AdvanceInfo) -> None: ... + def __str__(self) -> str: ... @@ -212,6 +216,7 @@ class Segment: def tip(self) -> CubeCoordinates: ... def get(self, coordinates: CubeCoordinates) -> Optional[Field]: ... + def set(self, coordinates: CubeCoordinates, field: Field) -> None: ... def local_to_global( self, coordinates: CubeCoordinates) -> CubeCoordinates: ... @@ -243,6 +248,9 @@ class Board: def get_field_in_direction( self, direction: CubeDirection, coords: CubeCoordinates) -> Optional[Field]: ... + def set_field_in_direction( + self, direction: CubeDirection, coords: CubeCoordinates, field: Field) -> None: ... + def get_coordinate_by_index( self, segment_index: int, x_index: int, y_index: int) -> CubeCoordinates: ... @@ -363,6 +371,9 @@ class GameState: def possible_actions(self, rank: int) -> List[Accelerate | Advance | Push | Turn]: ... + def coal_for_action(self, action: Accelerate | Advance | + Push | Turn) -> int: ... + def can_move(self) -> bool: ... def is_over(self) -> bool: ... def is_winner(self, ship: Ship) -> bool: ... diff --git a/src/lib.rs b/src/lib.rs index 845e3dd..78ce623 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,20 +3,22 @@ pub mod plugin; use pyo3::prelude::*; use pyo3::exceptions::PyException; -use plugin::coordinate::CubeCoordinates; -use plugin::field::{ FieldType, Passenger }; -use plugin::game_state::TeamPoints; -use plugin::game_state::AdvanceInfo; +use crate::plugin::coordinate::CartesianCoordinate; +use crate::plugin::coordinate::CubeCoordinates; +use crate::plugin::coordinate::CubeDirection; +use crate::plugin::game_state::TeamPoints; +use crate::plugin::game_state::AdvanceInfo; use crate::plugin::actions::accelerate::Accelerate; use crate::plugin::actions::advance::Advance; use crate::plugin::actions::push::Push; use crate::plugin::actions::turn::Turn; -use plugin::ship::TeamEnum; +use crate::plugin::ship::TeamEnum; use crate::plugin::board::Board; use crate::plugin::constants::PluginConstants; -use crate::plugin::coordinate::{ CartesianCoordinate, CubeDirection }; +use crate::plugin::field::FieldType; +use crate::plugin::field::Passenger; use crate::plugin::field::Field; use crate::plugin::game_state::GameState; use crate::plugin::r#move::Move; diff --git a/src/plugin/actions/advance.rs b/src/plugin/actions/advance.rs index 511331e..2e2689e 100644 --- a/src/plugin/actions/advance.rs +++ b/src/plugin/actions/advance.rs @@ -32,7 +32,7 @@ impl Advance { let valid_distance = self.validate_distance(&state, &ship)?; let advance_info = state.calculate_advance_info( &ship.position, - &ship.direction(!valid_distance), + &ship.resolve_direction(!valid_distance), ship.movement ); let advance_possible = (advance_info.distance() as i32) >= self.distance.abs(); diff --git a/src/plugin/board.rs b/src/plugin/board.rs index 62d628b..73c0306 100644 --- a/src/plugin/board.rs +++ b/src/plugin/board.rs @@ -76,6 +76,20 @@ impl Board { self.get(&(coords.clone() + direction.vector())) } + pub fn set_field_in_direction( + &mut self, + direction: &CubeDirection, + coords: &CubeCoordinates, + field: Field + ) { + for segment in &mut self.segments { + if segment.contains(*coords) { + segment.set(*coords + direction.vector(), field); + break; + } + } + } + pub fn get_coordinate_by_index( &self, segment_index: usize, diff --git a/src/plugin/coordinate.rs b/src/plugin/coordinate.rs index c8ed5e9..447a317 100644 --- a/src/plugin/coordinate.rs +++ b/src/plugin/coordinate.rs @@ -115,6 +115,10 @@ impl CubeCoordinates { pub fn distance_to(&self, other: &CubeCoordinates) -> i32 { ((self.q - other.q).abs() + (self.r - other.r).abs() + (self.s - other.s).abs()) / 2 } + + pub fn __repr__(&self) -> String { + format!("CubeCoordinates({}, {}, {})", self.q, self.r, self.s) + } } impl Add for CubeCoordinates { @@ -285,8 +289,15 @@ impl CubeDirection { } } - pub fn __hash__(&self) -> i32 { - self.ordinal() + pub fn __repr__(&self) -> String { + format!("CubeDirection::{}", match self { + CubeDirection::Right => "Right", + CubeDirection::DownRight => "DownRight", + CubeDirection::DownLeft => "DownLeft", + CubeDirection::Left => "Left", + CubeDirection::UpLeft => "UpLeft", + CubeDirection::UpRight => "UpRight", + }) } } diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index 765e8cc..544657a 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -19,7 +19,7 @@ use crate::plugin::r#move::Move; use crate::plugin::ship::Ship; use crate::plugin::errors::advance_errors::AdvanceProblem; -use super::field::Field; +use super::field::{ Field, Passenger }; use super::ship::TeamEnum; #[pyclass] @@ -349,27 +349,34 @@ impl GameState { } pub fn remove_passenger_at(&mut self, coord: CubeCoordinates) -> bool { - CubeDirection::VALUES.iter().any(|&d| { - self.board - .get_field_in_direction(&d, &coord) - .and_then(|mut field| { - field.passenger.as_mut().and_then(|passenger| { - if passenger.passenger > 0 && passenger.direction == d.opposite() { - passenger.passenger -= 1; - Some(()) - } else { - None - } - }) - }) - .is_some() - }) + for &d in CubeDirection::VALUES.iter() { + if let Some(field) = self.board.get_field_in_direction(&d, &coord) { + if let Some(passenger) = field.passenger { + if passenger.passenger > 0 && passenger.direction == d.opposite() { + let updated_passenger = Passenger { + passenger: passenger.passenger - 1, + direction: passenger.direction, + }; + self.board.set_field_in_direction( + &d, + &coord, + Field::new(field.field_type, Some(updated_passenger)) + ); + return true; + } + } + } + } + false } pub fn pick_up_passenger_current_ship(&mut self) { if self.effective_speed(self.current_ship) < 2 { if self.remove_passenger_at(self.current_ship.position) { self.current_ship.passengers += 1; + self.current_ship.points = self + .ship_points(self.current_ship) + .expect("Could not calculate ship points"); } } } @@ -378,6 +385,9 @@ impl GameState { if self.effective_speed(self.other_ship) < 2 { if self.remove_passenger_at(self.other_ship.position) { self.other_ship.passengers += 1; + self.other_ship.points = self + .ship_points(self.other_ship) + .expect("Could not calculate other ship's points"); } } } @@ -630,6 +640,19 @@ impl GameState { actions } + pub fn coal_for_action(&self, action: Action) -> usize { + match action { + Action::Accelerate(acc) => { + (acc.acc.abs() as usize) - (self.current_ship.free_acc as usize) + } + Action::Turn(dir) => { + let turn_count: i32 = self.current_ship.direction.turn_count_to(dir.direction); + (turn_count.abs() as usize) - (self.current_ship.free_turns as usize) + } + Action::Push(_) | Action::Advance(_) => { 0 } + } + } + pub fn can_move(&self) -> bool { let current_ship_can_advance: bool = !self.possible_advances().is_empty(); @@ -722,6 +745,29 @@ mod tests { GameState::new(Board::new(segment, CubeDirection::Right), 0, team_one, team_two, None) } + #[test] + fn test_remove_passenger_at() { + let mut segment = vec![ + create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right) + ]; + let team_one = create_ship(CubeCoordinates::new(0, -1), TeamEnum::One); + let team_two = create_ship(CubeCoordinates::new(-1, 1), TeamEnum::Two); + segment[0].set( + CubeCoordinates::new(0, 0), + Field::new(FieldType::Passenger, Some(Passenger::new(CubeDirection::UpLeft, 1))) + ); + let mut game_state = create_game_state(segment, team_one, team_two); + + assert_eq!(game_state.current_ship.passengers, 0); + assert_eq!(game_state.current_ship.points, 0); + game_state.pick_up_passenger_current_ship(); + assert_eq!(game_state.current_ship.passengers, 1); + assert_eq!(game_state.current_ship.points, 6); + game_state.pick_up_passenger_current_ship(); + assert_eq!(game_state.current_ship.passengers, 1); + assert_eq!(game_state.current_ship.points, 6); + } + #[test] fn find_possible_moves_returns_correct_count() { let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; diff --git a/src/plugin/segment.rs b/src/plugin/segment.rs index eba503a..5242da7 100644 --- a/src/plugin/segment.rs +++ b/src/plugin/segment.rs @@ -41,6 +41,17 @@ impl Segment { .cloned() } + pub fn set(&mut self, coordinates: CubeCoordinates, field: Field) { + let local: CubeCoordinates = self.global_to_local(coordinates); + + let local_cart: CartesianCoordinate = self.array_coords(local); + if let Some(row) = self.fields.get_mut(local_cart.x as usize) { + if let Some(cell) = row.get_mut(local_cart.y as usize) { + *cell = field; + } + } + } + pub fn local_to_global(&self, coordinates: CubeCoordinates) -> CubeCoordinates { coordinates.rotated_by(CubeDirection::Right.turn_count_to(self.direction)) + self.center } diff --git a/src/plugin/ship.rs b/src/plugin/ship.rs index 8bdccf7..9142835 100644 --- a/src/plugin/ship.rs +++ b/src/plugin/ship.rs @@ -89,7 +89,7 @@ impl Ship { self.movement = self.speed; } - pub fn direction(&self, reverse: bool) -> CubeDirection { + pub fn resolve_direction(&self, reverse: bool) -> CubeDirection { if reverse { self.direction.opposite() } else { self.direction } }