Skip to content

Commit

Permalink
Add new methods for performing unchecked actions
Browse files Browse the repository at this point in the history
  • Loading branch information
maxblan committed Jan 23, 2024
1 parent e751072 commit 26af619
Show file tree
Hide file tree
Showing 10 changed files with 397 additions and 179 deletions.
46 changes: 43 additions & 3 deletions python/socha/_socha.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class Accelerate:
def perform(self, state: Any) -> Ship | BaseException(
AccelerationProblem): ...

def accelerate(self, ship: Any) -> Ship: ...
def perform_unchecked(self, state: Any) -> Ship: ...
def __str__(self) -> str: ...


Expand All @@ -145,6 +145,8 @@ class Advance:
def perform(self, state: GameState) -> Ship | BaseException(
AdvanceProblem): ...

def perform_unchecked(self, state: GameState) -> Ship: ...


class PushProblem:
MovementPointsMissing: Any
Expand All @@ -164,6 +166,8 @@ class Push:
def perform(self, state: GameState) -> (Ship, Ship) | BaseException(
PushProblem): ...

def perform_unchecked(self, state: GameState) -> (Ship, Ship): ...


class TurnProblem():
RotationOnSandbankNotAllowed: Any
Expand All @@ -180,6 +184,8 @@ class Turn:
def perform(self, state: GameState) -> Ship | BaseException(
TurnProblem): ...

def perform_unchecked(self, state: GameState) -> Ship: ...

def coal_cost(self, ship: Ship) -> int: ...


Expand Down Expand Up @@ -267,10 +273,43 @@ class Board:
def pickup_passenger(self, state: GameState) -> GameState: ...

def pickup_passenger_at_position(
self, pos: CubeCoordinates) -> Optional[Field]: ...
self, pos: CubeCoordinates) -> Optional[Field]:
"""
Picks up a passenger at the specified position using the CubeCoordinates.
Args:
pos (CubeCoordinates): The CubeCoordinates representing the position to check.
Returns:
Optional[Field]: The Field containing a passenger with a passenger count greater than 0,
or None if no such Field is found in any adjacent direction.
"""

def find_nearest_field_types(self, start_coordinates: CubeCoordinates,
field_type: FieldType) -> Set[CubeCoordinates]: ...
field_type: FieldType) -> Set[CubeCoordinates]:
"""
A function to find the nearest field(s) of a specific type from a starting point in a hexagonal grid.
Args:
start_coordinates (CubeCoordinates): A CubeCoordinates object representing the starting point for the search.
field_type (FieldType): A FieldType object representing the type of field being searched for.
Returns:
list of CubeCoordinates: A list of CubeCoordinates corresponding to the location of the nearest field(s) of the specified type.
Note:
This function will always return the coordinates of the nearest field(s) of the specified type, if such a field(s) exist.
If multiple fields of the same type are at the same minimum distance, it returns all of them.
If there isn't a field of the specified type or path to it, it will return an empty list.
Examples:
```python
from plugin import Board, CubeCoordinates, FieldType
board = Board()
board.find_nearest_field_types(CubeCoordinates(0, 0), FieldType.Water)
```
"""


class TeamPoints:
Expand Down Expand Up @@ -303,6 +342,7 @@ class GameState:
def calculate_points(self, ship: Ship) -> int: ...
def is_current_ship_on_current(self) -> bool: ...
def perform_move(self, move: Move) -> GameState: ...
def perform_move_unchecked(self, move: Move) -> GameState: ...
def advance_turn(self) -> GameState: ...
def effective_speed(self, ship: Ship) -> int: ...
def remove_passenger_at(self, coords: CubeCoordinates) -> bool: ...
Expand Down
98 changes: 44 additions & 54 deletions python/socha/api/networking/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,36 @@
from socha._socha import Field, FieldType, Move, TeamEnum, CubeCoordinates, GameState
from socha.api.protocol.protocol import Acceleration, Actions, Advance, Push, Turn, Board, Data, Water, Sandbank, Island, Passenger, Goal

from python.socha.api.protocol.protocol import Room


def _convert_field(field) -> _socha.Field:
if isinstance(field, Water):
return Field(FieldType.Water, None)
elif isinstance(field, Sandbank):
return Field(FieldType.Sandbank, None)
elif isinstance(field, Island):
return Field(FieldType.Island, None)
elif isinstance(field, Passenger):
return Field(FieldType.Passenger, _socha.Passenger(direction_from_string(field.direction), field.passenger))
elif isinstance(field, Goal):
return Field(FieldType.Goal, None)


def _convert_field_array(field_array) -> List[_socha.Field]:
return [_convert_field(field) for field in field_array.field]


def _convert_segment(segment) -> _socha.Segment:
con_fields = [_convert_field_array(field_array)
for field_array in segment.field_array]
con_center = _socha.CubeCoordinates(q=segment.center.q, r=segment.center.r)
return _socha.Segment(direction=direction_from_string(segment.direction), center=con_center, fields=con_fields)


def _convert_board(protocol_board: Board) -> _socha.Board:
"""
Converts a protocol Board to a usable game board for using in the logic.
:param protocol_board: A Board object in protocol format
:type protocol_board: Board
:return: A Board object in the format used by the game logic
:rtype: penguins.Board
"""
con_segments: List[_socha.Segment] = []
for segment in protocol_board.segment:
con_fields: List[List[_socha.Field]] = []
for field_array in segment.field_array:
con_row: List[_socha.Field] = []
for field in field_array.field:
if isinstance(field, Water):
con_row.append(Field(FieldType.Water, None))
elif isinstance(field, Sandbank):
con_row.append(Field(FieldType.Sandbank, None))
elif isinstance(field, Island):
con_row.append(Field(FieldType.Island, None))
elif isinstance(field, Passenger):
con_row.append(Field(
FieldType.Passenger, _socha.Passenger(direction_from_string(field.direction), field.passenger)))
elif isinstance(field, Goal):
con_row.append(Field(FieldType.Goal, None))
con_fields.append(con_row)
con_center: _socha.CubeCoordinates = CubeCoordinates(
q=segment.center.q, r=segment.center.r)
con_segments.append(_socha.Segment(direction=direction_from_string(
segment.direction), center=con_center, fields=con_fields))
con_segments = [_convert_segment(segment)
for segment in protocol_board.segment]
return _socha.Board(
segments=con_segments,
next_direction=direction_from_string(protocol_board.next_direction)
Expand Down Expand Up @@ -106,7 +105,7 @@ def _merge_advances(actions):
return new_actions


def if_last_game_state(message, last_game_state):
def if_last_game_state(message: Room, last_game_state):
try:
last_game_state.board = _convert_board(
message.data.class_binding.board)
Expand All @@ -120,27 +119,18 @@ def if_last_game_state(message, last_game_state):
return last_game_state.perform_move(last_move)
except Exception as e:
logging.warning(
f"An error occurred: {e}. Returning the last game state without changes.")
return last_game_state


def if_not_last_game_state(message) -> GameState:
first_position = CubeCoordinates(
q=message.data.class_binding.ship[0].position.q, r=message.data.class_binding.ship[0].position.r)
first_team = TeamEnum.One if message.data.class_binding.ship[
0].team == "ONE" else TeamEnum.Two
first_team = _socha.Ship(position=first_position, team=first_team)

second_position = CubeCoordinates(
q=message.data.class_binding.ship[1].position.q, r=message.data.class_binding.ship[1].position.r)
second_team = TeamEnum.One if message.data.class_binding.ship[
1].team == "ONE" else TeamEnum.Two
second_team = _socha.Ship(position=second_position, team=second_team)

return GameState(
board=_convert_board(message.data.class_binding.board),
turn=message.data.class_binding.turn,
current_ship=first_team,
other_ship=second_team,
last_move=None,
)
f"An error occurred: {e}.")
return if_not_last_game_state(message)


def if_not_last_game_state(message: Room) -> GameState:
ships = [
_socha.Ship(position=CubeCoordinates(q=ship.position.q, r=ship.position.r),
team=TeamEnum.One if ship.team == "ONE" else TeamEnum.Two)
for ship in message.data.class_binding.ship
]
return GameState(board=_convert_board(message.data.class_binding.board),
turn=message.data.class_binding.turn,
current_ship=ships[0],
other_ship=ships[1],
last_move=None)
35 changes: 33 additions & 2 deletions src/plugin/actions.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use pyo3::{IntoPy, PyObject};
use pyo3::{ IntoPy, PyErr, PyObject };
use pyo3::FromPyObject;
use pyo3::Python;

use crate::plugin::actions::push::Push;
use crate::plugin::actions::turn::Turn;

use self::{accelerate::Accelerate, advance::Advance};
use self::{ accelerate::Accelerate, advance::Advance };

use super::game_state::GameState;
use super::ship::Ship;

pub mod accelerate;
pub mod advance;
Expand All @@ -31,3 +34,31 @@ impl IntoPy<PyObject> for Action {
}
}

impl Action {
pub fn perform(
&self,
game_state: &mut GameState
) -> Result<(Option<Ship>, Option<Ship>), PyErr> {
match self {
Action::Accelerate(accelerate) =>
accelerate.perform(game_state).map(|ship| (Some(ship), None)),
Action::Advance(advance) => advance.perform(game_state).map(|ship| (Some(ship), None)),
Action::Push(push) =>
push.perform(game_state).map(|(ship1, ship2)| (Some(ship1), Some(ship2))),
Action::Turn(turn) => turn.perform(game_state).map(|ship| (Some(ship), None)),
}
}

pub fn perform_unchecked(&self, game_state: &mut GameState) -> (Option<Ship>, Option<Ship>) {
match self {
Action::Accelerate(accelerate) =>
(Some(accelerate.perform_unchecked(game_state)), None),
Action::Advance(advance) => (Some(advance.perform_unchecked(game_state)), None),
Action::Push(push) => {
let (ship1, ship2) = push.perform_unchecked(game_state);
(Some(ship1), Some(ship2))
}
Action::Turn(turn) => (Some(turn.perform_unchecked(game_state)), None),
}
}
}
22 changes: 22 additions & 0 deletions src/plugin/actions/accelerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ impl Accelerate {
}
}
}

fn accelerate(&self, ship: &mut Ship) -> Ship {
debug!("accelerate() called with ship: {:?}", ship);
let used_coal: i32 = self.acc.abs() - ship.free_acc;
Expand All @@ -80,6 +81,27 @@ impl Accelerate {
debug!("Acceleration completed and ship status: {:?}", ship);
return ship.clone();
}

pub fn perform_unchecked(&self, state: &GameState) -> Ship {
debug!("perform_unchecked() called with acc: {} and game state: {:?}", self.acc, state);
let mut ship: Ship = state.current_ship.clone();

let new_ship = self.accelerate_unchecked(&mut ship);

debug!("Ship accelerated successfully");
new_ship
}

fn accelerate_unchecked(&self, ship: &mut Ship) -> Ship {
debug!("accelerate_unchecked() called with ship: {:?}", ship);
let used_coal: i32 = self.acc.abs() - ship.free_acc;
ship.coal -= used_coal.max(0);
ship.free_acc = (-used_coal).max(0);
ship.accelerate_by(self.acc);
debug!("Unchecked acceleration completed and ship status: {:?}", ship);
ship.clone()
}

fn __repr__(&self) -> PyResult<String> {
debug!("__repr__ method called for Accelerate with acc: {}", self.acc);
Ok(format!("Accelerate({})", self.acc))
Expand Down
Loading

0 comments on commit 26af619

Please sign in to comment.