Skip to content

Commit 26af619

Browse files
author
maxblan
committed
Add new methods for performing unchecked actions
1 parent e751072 commit 26af619

File tree

10 files changed

+397
-179
lines changed

10 files changed

+397
-179
lines changed

python/socha/_socha.pyi

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ class Accelerate:
123123
def perform(self, state: Any) -> Ship | BaseException(
124124
AccelerationProblem): ...
125125

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

129129

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

148+
def perform_unchecked(self, state: GameState) -> Ship: ...
149+
148150

149151
class PushProblem:
150152
MovementPointsMissing: Any
@@ -164,6 +166,8 @@ class Push:
164166
def perform(self, state: GameState) -> (Ship, Ship) | BaseException(
165167
PushProblem): ...
166168

169+
def perform_unchecked(self, state: GameState) -> (Ship, Ship): ...
170+
167171

168172
class TurnProblem():
169173
RotationOnSandbankNotAllowed: Any
@@ -180,6 +184,8 @@ class Turn:
180184
def perform(self, state: GameState) -> Ship | BaseException(
181185
TurnProblem): ...
182186

187+
def perform_unchecked(self, state: GameState) -> Ship: ...
188+
183189
def coal_cost(self, ship: Ship) -> int: ...
184190

185191

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

269275
def pickup_passenger_at_position(
270-
self, pos: CubeCoordinates) -> Optional[Field]: ...
276+
self, pos: CubeCoordinates) -> Optional[Field]:
277+
"""
278+
Picks up a passenger at the specified position using the CubeCoordinates.
279+
280+
Args:
281+
pos (CubeCoordinates): The CubeCoordinates representing the position to check.
282+
283+
Returns:
284+
Optional[Field]: The Field containing a passenger with a passenger count greater than 0,
285+
or None if no such Field is found in any adjacent direction.
286+
"""
271287

272288
def find_nearest_field_types(self, start_coordinates: CubeCoordinates,
273-
field_type: FieldType) -> Set[CubeCoordinates]: ...
289+
field_type: FieldType) -> Set[CubeCoordinates]:
290+
"""
291+
A function to find the nearest field(s) of a specific type from a starting point in a hexagonal grid.
292+
293+
Args:
294+
start_coordinates (CubeCoordinates): A CubeCoordinates object representing the starting point for the search.
295+
field_type (FieldType): A FieldType object representing the type of field being searched for.
296+
297+
Returns:
298+
list of CubeCoordinates: A list of CubeCoordinates corresponding to the location of the nearest field(s) of the specified type.
299+
300+
Note:
301+
This function will always return the coordinates of the nearest field(s) of the specified type, if such a field(s) exist.
302+
If multiple fields of the same type are at the same minimum distance, it returns all of them.
303+
If there isn't a field of the specified type or path to it, it will return an empty list.
304+
305+
Examples:
306+
```python
307+
from plugin import Board, CubeCoordinates, FieldType
308+
309+
board = Board()
310+
board.find_nearest_field_types(CubeCoordinates(0, 0), FieldType.Water)
311+
```
312+
"""
274313

275314

276315
class TeamPoints:
@@ -303,6 +342,7 @@ class GameState:
303342
def calculate_points(self, ship: Ship) -> int: ...
304343
def is_current_ship_on_current(self) -> bool: ...
305344
def perform_move(self, move: Move) -> GameState: ...
345+
def perform_move_unchecked(self, move: Move) -> GameState: ...
306346
def advance_turn(self) -> GameState: ...
307347
def effective_speed(self, ship: Ship) -> int: ...
308348
def remove_passenger_at(self, coords: CubeCoordinates) -> bool: ...

python/socha/api/networking/utils.py

Lines changed: 44 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,37 +5,36 @@
55
from socha._socha import Field, FieldType, Move, TeamEnum, CubeCoordinates, GameState
66
from socha.api.protocol.protocol import Acceleration, Actions, Advance, Push, Turn, Board, Data, Water, Sandbank, Island, Passenger, Goal
77

8+
from python.socha.api.protocol.protocol import Room
9+
10+
11+
def _convert_field(field) -> _socha.Field:
12+
if isinstance(field, Water):
13+
return Field(FieldType.Water, None)
14+
elif isinstance(field, Sandbank):
15+
return Field(FieldType.Sandbank, None)
16+
elif isinstance(field, Island):
17+
return Field(FieldType.Island, None)
18+
elif isinstance(field, Passenger):
19+
return Field(FieldType.Passenger, _socha.Passenger(direction_from_string(field.direction), field.passenger))
20+
elif isinstance(field, Goal):
21+
return Field(FieldType.Goal, None)
22+
23+
24+
def _convert_field_array(field_array) -> List[_socha.Field]:
25+
return [_convert_field(field) for field in field_array.field]
26+
27+
28+
def _convert_segment(segment) -> _socha.Segment:
29+
con_fields = [_convert_field_array(field_array)
30+
for field_array in segment.field_array]
31+
con_center = _socha.CubeCoordinates(q=segment.center.q, r=segment.center.r)
32+
return _socha.Segment(direction=direction_from_string(segment.direction), center=con_center, fields=con_fields)
33+
834

935
def _convert_board(protocol_board: Board) -> _socha.Board:
10-
"""
11-
Converts a protocol Board to a usable game board for using in the logic.
12-
:param protocol_board: A Board object in protocol format
13-
:type protocol_board: Board
14-
:return: A Board object in the format used by the game logic
15-
:rtype: penguins.Board
16-
"""
17-
con_segments: List[_socha.Segment] = []
18-
for segment in protocol_board.segment:
19-
con_fields: List[List[_socha.Field]] = []
20-
for field_array in segment.field_array:
21-
con_row: List[_socha.Field] = []
22-
for field in field_array.field:
23-
if isinstance(field, Water):
24-
con_row.append(Field(FieldType.Water, None))
25-
elif isinstance(field, Sandbank):
26-
con_row.append(Field(FieldType.Sandbank, None))
27-
elif isinstance(field, Island):
28-
con_row.append(Field(FieldType.Island, None))
29-
elif isinstance(field, Passenger):
30-
con_row.append(Field(
31-
FieldType.Passenger, _socha.Passenger(direction_from_string(field.direction), field.passenger)))
32-
elif isinstance(field, Goal):
33-
con_row.append(Field(FieldType.Goal, None))
34-
con_fields.append(con_row)
35-
con_center: _socha.CubeCoordinates = CubeCoordinates(
36-
q=segment.center.q, r=segment.center.r)
37-
con_segments.append(_socha.Segment(direction=direction_from_string(
38-
segment.direction), center=con_center, fields=con_fields))
36+
con_segments = [_convert_segment(segment)
37+
for segment in protocol_board.segment]
3938
return _socha.Board(
4039
segments=con_segments,
4140
next_direction=direction_from_string(protocol_board.next_direction)
@@ -106,7 +105,7 @@ def _merge_advances(actions):
106105
return new_actions
107106

108107

109-
def if_last_game_state(message, last_game_state):
108+
def if_last_game_state(message: Room, last_game_state):
110109
try:
111110
last_game_state.board = _convert_board(
112111
message.data.class_binding.board)
@@ -120,27 +119,18 @@ def if_last_game_state(message, last_game_state):
120119
return last_game_state.perform_move(last_move)
121120
except Exception as e:
122121
logging.warning(
123-
f"An error occurred: {e}. Returning the last game state without changes.")
124-
return last_game_state
125-
126-
127-
def if_not_last_game_state(message) -> GameState:
128-
first_position = CubeCoordinates(
129-
q=message.data.class_binding.ship[0].position.q, r=message.data.class_binding.ship[0].position.r)
130-
first_team = TeamEnum.One if message.data.class_binding.ship[
131-
0].team == "ONE" else TeamEnum.Two
132-
first_team = _socha.Ship(position=first_position, team=first_team)
133-
134-
second_position = CubeCoordinates(
135-
q=message.data.class_binding.ship[1].position.q, r=message.data.class_binding.ship[1].position.r)
136-
second_team = TeamEnum.One if message.data.class_binding.ship[
137-
1].team == "ONE" else TeamEnum.Two
138-
second_team = _socha.Ship(position=second_position, team=second_team)
139-
140-
return GameState(
141-
board=_convert_board(message.data.class_binding.board),
142-
turn=message.data.class_binding.turn,
143-
current_ship=first_team,
144-
other_ship=second_team,
145-
last_move=None,
146-
)
122+
f"An error occurred: {e}.")
123+
return if_not_last_game_state(message)
124+
125+
126+
def if_not_last_game_state(message: Room) -> GameState:
127+
ships = [
128+
_socha.Ship(position=CubeCoordinates(q=ship.position.q, r=ship.position.r),
129+
team=TeamEnum.One if ship.team == "ONE" else TeamEnum.Two)
130+
for ship in message.data.class_binding.ship
131+
]
132+
return GameState(board=_convert_board(message.data.class_binding.board),
133+
turn=message.data.class_binding.turn,
134+
current_ship=ships[0],
135+
other_ship=ships[1],
136+
last_move=None)

src/plugin/actions.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
use pyo3::{IntoPy, PyObject};
1+
use pyo3::{ IntoPy, PyErr, PyObject };
22
use pyo3::FromPyObject;
33
use pyo3::Python;
44

55
use crate::plugin::actions::push::Push;
66
use crate::plugin::actions::turn::Turn;
77

8-
use self::{accelerate::Accelerate, advance::Advance};
8+
use self::{ accelerate::Accelerate, advance::Advance };
9+
10+
use super::game_state::GameState;
11+
use super::ship::Ship;
912

1013
pub mod accelerate;
1114
pub mod advance;
@@ -31,3 +34,31 @@ impl IntoPy<PyObject> for Action {
3134
}
3235
}
3336

37+
impl Action {
38+
pub fn perform(
39+
&self,
40+
game_state: &mut GameState
41+
) -> Result<(Option<Ship>, Option<Ship>), PyErr> {
42+
match self {
43+
Action::Accelerate(accelerate) =>
44+
accelerate.perform(game_state).map(|ship| (Some(ship), None)),
45+
Action::Advance(advance) => advance.perform(game_state).map(|ship| (Some(ship), None)),
46+
Action::Push(push) =>
47+
push.perform(game_state).map(|(ship1, ship2)| (Some(ship1), Some(ship2))),
48+
Action::Turn(turn) => turn.perform(game_state).map(|ship| (Some(ship), None)),
49+
}
50+
}
51+
52+
pub fn perform_unchecked(&self, game_state: &mut GameState) -> (Option<Ship>, Option<Ship>) {
53+
match self {
54+
Action::Accelerate(accelerate) =>
55+
(Some(accelerate.perform_unchecked(game_state)), None),
56+
Action::Advance(advance) => (Some(advance.perform_unchecked(game_state)), None),
57+
Action::Push(push) => {
58+
let (ship1, ship2) = push.perform_unchecked(game_state);
59+
(Some(ship1), Some(ship2))
60+
}
61+
Action::Turn(turn) => (Some(turn.perform_unchecked(game_state)), None),
62+
}
63+
}
64+
}

src/plugin/actions/accelerate.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ impl Accelerate {
7171
}
7272
}
7373
}
74+
7475
fn accelerate(&self, ship: &mut Ship) -> Ship {
7576
debug!("accelerate() called with ship: {:?}", ship);
7677
let used_coal: i32 = self.acc.abs() - ship.free_acc;
@@ -80,6 +81,27 @@ impl Accelerate {
8081
debug!("Acceleration completed and ship status: {:?}", ship);
8182
return ship.clone();
8283
}
84+
85+
pub fn perform_unchecked(&self, state: &GameState) -> Ship {
86+
debug!("perform_unchecked() called with acc: {} and game state: {:?}", self.acc, state);
87+
let mut ship: Ship = state.current_ship.clone();
88+
89+
let new_ship = self.accelerate_unchecked(&mut ship);
90+
91+
debug!("Ship accelerated successfully");
92+
new_ship
93+
}
94+
95+
fn accelerate_unchecked(&self, ship: &mut Ship) -> Ship {
96+
debug!("accelerate_unchecked() called with ship: {:?}", ship);
97+
let used_coal: i32 = self.acc.abs() - ship.free_acc;
98+
ship.coal -= used_coal.max(0);
99+
ship.free_acc = (-used_coal).max(0);
100+
ship.accelerate_by(self.acc);
101+
debug!("Unchecked acceleration completed and ship status: {:?}", ship);
102+
ship.clone()
103+
}
104+
83105
fn __repr__(&self) -> PyResult<String> {
84106
debug!("__repr__ method called for Accelerate with acc: {}", self.acc);
85107
Ok(format!("Accelerate({})", self.acc))

0 commit comments

Comments
 (0)