diff --git a/.github/workflows/continuous_deployment.yml b/.github/workflows/cd.yml similarity index 96% rename from .github/workflows/continuous_deployment.yml rename to .github/workflows/cd.yml index b025ad1..fa7f801 100644 --- a/.github/workflows/continuous_deployment.yml +++ b/.github/workflows/cd.yml @@ -1,10 +1,8 @@ -name: continuous deployment +name: CD on: release: types: [published] - branches: - - master permissions: contents: read @@ -96,7 +94,7 @@ jobs: release: name: Release runs-on: ubuntu-latest - if: "startsWith(github.ref, 'refs/tags/')" + if: startsWith(github.ref, 'refs/tags/') needs: [linux, windows, macos, sdist] steps: - uses: actions/download-artifact@v3 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..1a5c26c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened] + +permissions: read-all + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + # Clippy - Rust linter + - name: Install Rust toolchain + run: rustup toolchain install stable + - name: Run Clippy + run: rustup component add clippy && cargo clippy -- -D warnings + + # Install dependencies and run unittests within the virtual environment + - name: Setup and run tests in virtual environment + run: | + python -m venv venv + source venv/bin/activate + pip install ruff + ruff . + pip install maturin + maturin develop + python -m unittest discover -s tests -p '*.py' + + + # Build and test steps for Rust + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose diff --git a/.github/workflows/convert.yml b/.github/workflows/convert.yml index bac2cf9..5005522 100644 --- a/.github/workflows/convert.yml +++ b/.github/workflows/convert.yml @@ -1,7 +1,7 @@ name: convert readme on: push: - branches: [ master ] + branches: [master] jobs: convert: runs-on: ubuntu-latest @@ -20,4 +20,4 @@ jobs: git add docs/index.rst git commit -m "docs: auto update index.rst" git push - continue-on-error: true \ No newline at end of file + continue-on-error: true diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml deleted file mode 100644 index faf9b3d..0000000 --- a/.github/workflows/push.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: build and test - -on: [push, pull_request] - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose diff --git a/logic.py b/logic.py index 2d92c7e..70cab2b 100644 --- a/logic.py +++ b/logic.py @@ -1,10 +1,32 @@ 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 -from socha import Move, Passenger, Push, PushProblem, Segment, Ship, TeamEnum, TeamPoints, Turn, TurnProblem +from socha import ( + Accelerate, + AccelerationProblem, + Advance, + AdvanceInfo, + AdvanceProblem, + Board, + CartesianCoordinate, + CubeCoordinates, + CubeDirection, + Field, + FieldType, + GameState, + Move, + Passenger, + Push, + PushProblem, + Segment, + Ship, + TeamEnum, + TeamPoints, + Turn, + TurnProblem, +) from socha.api.networking.game_client import IClientHandler from socha.starter import Starter diff --git a/pyproject.toml b/pyproject.toml index 4014428..c13ef7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,19 +11,19 @@ readme = "README.md" requires-python = ">=3.10" dependencies = ["xsdata==22.9"] classifiers = [ - "License :: OSI Approved :: MIT License", - "Operating System :: MacOS", - "Operating System :: Microsoft :: Windows", - "Operating System :: POSIX :: Linux", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python", - "Programming Language :: Rust", - "Typing :: Typed", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python", + "Programming Language :: Rust", + "Typing :: Typed", ] homepage = "https://software-challenge.de/" repository = "https://github.com/FalconsSky/socha-python-client" @@ -37,3 +37,6 @@ python-source = "python" module-name = "socha._socha" exclude = [".github", "docs", "tests", "logic.py", "readthedocs.yaml"] features = ["pyo3/extension-module"] + +[tool.ruff] +exclude = ["logic.py", "python/socha/__init__.py"] diff --git a/python/socha/_socha.pyi b/python/socha/_socha.pyi index 7d0b6cf..6256e5c 100644 --- a/python/socha/_socha.pyi +++ b/python/socha/_socha.pyi @@ -334,7 +334,6 @@ class GameState: other_ship: Ship, last_move: Optional[Move]) -> None: ... def determine_ahead_team(self) -> Ship: ... - def ship_advance_points(self, ship: Ship) -> int: ... def calculate_points(self, ship: Ship) -> int: ... def is_current_ship_on_current(self) -> bool: ... def perform_action(self, action: Accelerate | Advance | diff --git a/python/socha/api/networking/game_client.py b/python/socha/api/networking/game_client.py index 222d1cf..6952fb3 100644 --- a/python/socha/api/networking/game_client.py +++ b/python/socha/api/networking/game_client.py @@ -1,6 +1,7 @@ """ This module handles the communication with the api and the students' logic. """ + import gc import logging import sys @@ -9,10 +10,33 @@ from typing import List, Union from socha._socha import GameState, Move -from socha.api.networking.utils import handle_move, if_last_game_state, if_not_last_game_state +from socha.api.networking.utils import ( + handle_move, + if_last_game_state, + if_not_last_game_state, +) from socha.api.networking.xml_protocol_interface import XMLProtocolInterface -from socha.api.protocol.protocol import State, Error, Join, Joined, JoinPrepared, JoinRoom, Room, Result, MoveRequest, \ - Left, Errorpacket, Authenticate, Prepared, Slot, Prepare, Observe, Cancel, Observed, Step, Pause +from socha.api.protocol.protocol import ( + Authenticate, + Cancel, + Error, + Join, + Joined, + JoinPrepared, + JoinRoom, + Left, + MoveRequest, + Observe, + Observed, + Pause, + Prepare, + Prepared, + Result, + Room, + Slot, + State, + Step, +) from socha.api.protocol.protocol_packet import ProtocolPacket @@ -76,7 +100,7 @@ def on_game_left(self) -> None: If the client is running on survive mode it'll be running until shut downed manually. """ - def while_disconnected(self, player_client: 'GameClient') -> None: + def while_disconnected(self, player_client: "GameClient") -> None: """ The client loop will keep calling this method while there is no active connection to a game server. This can be used to do tasks after a game is finished and the server left. @@ -86,7 +110,7 @@ def while_disconnected(self, player_client: 'GameClient') -> None: :return: True if the client should shut down. False if the client should continue to run. """ - def on_create_game(self, game_client: 'GameClient') -> None: + def on_create_game(self, game_client: "GameClient") -> None: """ This method will be called if the client is in admin mode and the client has authenticated with the server. The client can now create a game. @@ -94,7 +118,9 @@ def on_create_game(self, game_client: 'GameClient') -> None: :param game_client: The client that is in admin mode. """ - def on_prepared(self, game_client: 'GameClient', room_id: str, reservations: List[str]) -> None: + def on_prepared( + self, game_client: "GameClient", room_id: str, reservations: List[str] + ) -> None: """ This method will be called if the client is in admin mode and the client has created a game. @@ -103,12 +129,12 @@ def on_prepared(self, game_client: 'GameClient', room_id: str, reservations: Lis :param reservations: The reservations of the game. """ - def on_observed(self, game_client: 'GameClient', room_id: str): + def on_observed(self, game_client: "GameClient", room_id: str): """ This method will be called if the client is in admin mode and the client is observing a game. :param game_client: The client that is in admin mode. - :param room_id: The room id of the game. + :param room_id: The room id of the game. """ def while_waiting(self) -> None: @@ -123,8 +149,18 @@ class GameClient(XMLProtocolInterface): The PlayerClient handles all incoming and outgoing objects accordingly to their types. """ - def __init__(self, host: str, port: int, handler: IClientHandler, reservation: str, - room_id: str, password: str, auto_reconnect: bool, survive: bool, headless: bool): + def __init__( + self, + host: str, + port: int, + handler: IClientHandler, + reservation: str, + room_id: str, + password: str, + auto_reconnect: bool, + survive: bool, + headless: bool, + ): super().__init__(host, port) self._game_handler = handler self.reservation = reservation @@ -152,9 +188,9 @@ def authenticate(self, password: str): def create_game(self, player_1: Slot, player_2: Slot, game_type: str, pause: bool): logging.info( - f"Creating game with {player_1}, {player_2} and game type '{game_type}'") - self.send(Prepare(game_type=game_type, - pause=pause, slot=[player_1, player_2])) + f"Creating game with {player_1}, {player_2} and game type '{game_type}'" + ) + self.send(Prepare(game_type=game_type, pause=pause, slot=[player_1, player_2])) def observe(self, room_id: str): logging.info(f"Observing game room '{room_id}'") @@ -188,46 +224,44 @@ def _on_object(self, message): """ if isinstance(message, Joined): - logging.log( - 15, f"Game joined received with room id '{message.room_id}'") + logging.log(15, f"Game joined received with room id '{message.room_id}'") self._game_handler.on_game_joined(room_id=message.room_id) elif isinstance(message, Left): - logging.log( - 15, f"Game left received with room id '{message.room_id}'") + logging.log(15, f"Game left received with room id '{message.room_id}'") self._game_handler.on_game_left() elif isinstance(message, Prepared): logging.log( - 15, f"Game prepared received with reservation '{message.reservation}'") + 15, f"Game prepared received with reservation '{message.reservation}'" + ) self._game_handler.on_prepared( - game_client=self, room_id=message.room_id, reservations=message.reservation) + game_client=self, + room_id=message.room_id, + reservations=message.reservation, + ) elif isinstance(message, Observed): - logging.log( - 15, f"Game observing received with room id '{message.room_id}'") - self._game_handler.on_observed( - game_client=self, room_id=message.room_id) + logging.log(15, f"Game observing received with room id '{message.room_id}'") + self._game_handler.on_observed(game_client=self, room_id=message.room_id) elif isinstance(message, Room) and not self.headless: room_id = message.room_id if isinstance(message.data.class_binding, Error): logging.error( - f"An error occurred while handling the request: {message}") + f"An error occurred while handling the request: {message}" + ) self._game_handler.on_error(str(message)) self.stop() elif isinstance(message.data.class_binding, MoveRequest): - logging.log( - 15, f"Move request received for room id '{room_id}'") + logging.log(15, f"Move request received for room id '{room_id}'") self._on_move_request(room_id) elif isinstance(message.data.class_binding, State): logging.log(15, f"State received for room id '{room_id}'") self._on_state(message) elif isinstance(message.data.class_binding, Result): logging.log(15, f"Result received for room id '{room_id}'") - self._game_handler.history[-1].append( - message.data.class_binding) + self._game_handler.history[-1].append(message.data.class_binding) self._game_handler.on_game_over(message.data.class_binding) else: - logging.log( - 15, f"Room message received for room id '{room_id}'") + logging.log(15, f"Room message received for room id '{room_id}'") self._game_handler.on_room_message(message.data.class_binding) else: room_id = message.room_id @@ -240,7 +274,8 @@ def _on_move_request(self, room_id): if move_response: response = handle_move(move_response) logging.info( - f"Sent {move_response} after {round(time.time() - start_time, ndigits=3)} seconds.") + f"Sent {move_response} after {round(time.time() - start_time, ndigits=3)} seconds." + ) self.send_message_to_room(room_id, response) else: logging.error(f"{move_response} is not a valid move.") @@ -288,15 +323,15 @@ def _handle_left(self): self.first_time = True self.network_interface.close() if self.survive: - logging.info("The server left. Client is in survive mode and keeps running.\n" - "Please shutdown the client manually.") + logging.info( + "The server left. Client is in survive mode and keeps running.\n" + "Please shutdown the client manually." + ) self._game_handler.while_disconnected(player_client=self) if self.auto_reconnect: - logging.info( - "The server left. Client tries to reconnect to the server.") + logging.info("The server left. Client tries to reconnect to the server.") for _ in range(3): - logging.info( - f"Try to establish a connection with the server...") + logging.info("Try to establish a connection with the server...") try: self.connect() if self.network_interface.connected: @@ -305,7 +340,8 @@ def _handle_left(self): except Exception as e: logging.exception(e) logging.info( - "The client couldn't reconnect due to a previous error.") + "The client couldn't reconnect due to a previous error." + ) self.stop() time.sleep(1) self.join() @@ -334,14 +370,13 @@ def _client_loop(self): else: self._on_object(response) while_waiting = threading.Thread( - target=self._game_handler.while_waiting) + target=self._game_handler.while_waiting + ) while_waiting.start() gc.collect() elif self.running: - logging.error( - f"Received a object of unknown class: {response}") - raise NotImplementedError( - "Received object of unknown class.") + logging.error(f"Received a object of unknown class: {response}") + raise NotImplementedError("Received object of unknown class.") else: self._game_handler.while_disconnected(player_client=self) diff --git a/python/socha/api/networking/utils.py b/python/socha/api/networking/utils.py index e54d801..565ba31 100644 --- a/python/socha/api/networking/utils.py +++ b/python/socha/api/networking/utils.py @@ -1,8 +1,21 @@ from typing import List from socha import _socha -from socha._socha import Field, FieldType, Move, TeamEnum, CubeCoordinates, GameState -from socha.api.protocol.protocol import Acceleration, Actions, Advance, Push, Ship, Turn, Board, Data, Water, Sandbank, Island, Passenger, Goal +from socha._socha import CubeCoordinates, Field, FieldType, GameState, Move, TeamEnum +from socha.api.protocol.protocol import ( + Acceleration, + Actions, + Advance, + Board, + Data, + Goal, + Island, + Passenger, + Push, + Sandbank, + Turn, + Water, +) def _convert_board(protocol_board: Board) -> _socha.Board: @@ -26,18 +39,30 @@ def _convert_board(protocol_board: Board) -> _socha.Board: 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))) + 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)) + 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, + ) + ) return _socha.Board( segments=con_segments, - next_direction=direction_from_string(protocol_board.next_direction) + next_direction=direction_from_string(protocol_board.next_direction), ) @@ -75,10 +100,22 @@ def direction_to_string(cube_direction: _socha.CubeDirection) -> str: def handle_move(move_response): actions = move_response.actions - protocol_actions = [Acceleration(acc=a.acc) if isinstance(a, _socha.Accelerate) - else Advance(distance=a.distance) if isinstance(a, _socha.Advance) - else Push(direction=direction_to_string(a.direction)) if isinstance(a, _socha.Push) - else Turn(direction=direction_to_string(a.direction)) for a in actions] + protocol_actions = [ + ( + Acceleration(acc=a.acc) + if isinstance(a, _socha.Accelerate) + else ( + Advance(distance=a.distance) + if isinstance(a, _socha.Advance) + else ( + Push(direction=direction_to_string(a.direction)) + if isinstance(a, _socha.Push) + else Turn(direction=direction_to_string(a.direction)) + ) + ) + ) + for a in actions + ] return Data(class_value="move", actions=Actions(actions=protocol_actions)) @@ -98,7 +135,9 @@ def _merge_advances(actions): """ new_actions = [] for i in range(len(actions) - 1): - if isinstance(actions[i], _socha.Advance) and isinstance(actions[i + 1], _socha.Advance): + if isinstance(actions[i], _socha.Advance) and isinstance( + actions[i + 1], _socha.Advance + ): actions[i + 1].distance += actions[i].distance actions[i] = None new_actions = [a for a in actions if a is not None] @@ -118,17 +157,32 @@ def if_last_game_state(message, last_game_state) -> GameState: GameState: The constructed game state from the message. """ try: - last_game_state.board = _convert_board( - message.data.class_binding.board) + last_game_state.board = _convert_board(message.data.class_binding.board) actions = message.data.class_binding.last_move.actions.actions - new_actions = _merge_advances([_socha.Accelerate(acc=a.acc) if isinstance(a, Acceleration) - else _socha.Advance(distance=a.distance) if isinstance(a, Advance) - else _socha.Push(direction=direction_from_string(a.direction)) if isinstance(a, Push) - else _socha.Turn(direction=direction_from_string(a.direction)) for a in actions]) + new_actions = _merge_advances( + [ + ( + _socha.Accelerate(acc=a.acc) + if isinstance(a, Acceleration) + else ( + _socha.Advance(distance=a.distance) + if isinstance(a, Advance) + else ( + _socha.Push(direction=direction_from_string(a.direction)) + if isinstance(a, Push) + else _socha.Turn( + direction=direction_from_string(a.direction) + ) + ) + ) + ) + for a in actions + ] + ) last_move = Move(actions=new_actions) return last_game_state.perform_move(last_move) - except Exception as e: + except Exception: return if_not_last_game_state(message) @@ -143,26 +197,36 @@ def if_not_last_game_state(message) -> GameState: Returns: GameState: The constructed game state from the message. """ + def create_ship(ship_data, team_enum_value) -> _socha.Ship: """Helper function to create a ship from the ship data.""" - position = CubeCoordinates( - q=ship_data.position.q, r=ship_data.position.r) + position = CubeCoordinates(q=ship_data.position.q, r=ship_data.position.r) team = TeamEnum.One if team_enum_value == "ONE" else TeamEnum.Two - return _socha.Ship(position=position, team=team, coal=ship_data.coal, - passengers=ship_data.passengers, points=ship_data.points, - speed=ship_data.speed, free_turns=ship_data.free_turns, - direction=direction_from_string(ship_data.direction)) + return _socha.Ship( + position=position, + team=team, + coal=ship_data.coal, + passengers=ship_data.passengers, + points=ship_data.points, + speed=ship_data.speed, + free_turns=ship_data.free_turns, + direction=direction_from_string(ship_data.direction), + ) first_ship_data, second_ship_data = message.data.class_binding.ship first_ship = create_ship(first_ship_data, first_ship_data.team) second_ship = create_ship(second_ship_data, second_ship_data.team) - current_team_enum = TeamEnum.One if message.data.class_binding.current_team == "ONE" else TeamEnum.Two + current_team_enum = ( + TeamEnum.One + if message.data.class_binding.current_team == "ONE" + else TeamEnum.Two + ) return GameState( board=_convert_board(message.data.class_binding.board), turn=message.data.class_binding.turn, current_ship=first_ship if current_team_enum == TeamEnum.One else second_ship, other_ship=second_ship if current_team_enum == TeamEnum.One else first_ship, - last_move=None + last_move=None, ) diff --git a/python/socha/api/networking/xml_protocol_interface.py b/python/socha/api/networking/xml_protocol_interface.py index 1d4e3f9..04d45da 100644 --- a/python/socha/api/networking/xml_protocol_interface.py +++ b/python/socha/api/networking/xml_protocol_interface.py @@ -1,11 +1,21 @@ """ Here are all incoming byte streams and all outgoing protocol objects handheld. """ + import contextlib import logging -from typing import Iterator, Callable, Any -from socha._socha import TeamEnum +from typing import Any, Callable, Iterator +from socha._socha import TeamEnum +from socha.api.networking.network_socket import NetworkSocket +from socha.api.protocol.protocol import ( + Close, + Error, + MoveRequest, + Result, + WelcomeMessage, +) +from socha.api.protocol.protocol_packet import ProtocolPacket from xsdata.formats.dataclass.context import XmlContext from xsdata.formats.dataclass.parsers import XmlParser from xsdata.formats.dataclass.parsers.config import ParserConfig @@ -13,9 +23,6 @@ from xsdata.formats.dataclass.serializers import XmlSerializer from xsdata.formats.dataclass.serializers.config import SerializerConfig -from socha.api.networking.network_socket import NetworkSocket -from socha.api.protocol.protocol import * - def custom_class_factory(clazz, params: dict): if clazz.__name__ == "Data": @@ -25,7 +32,8 @@ def custom_class_factory(clazz, params: dict): ... if params.get("class_value") == "welcomeMessage": welcome_message = WelcomeMessage( - TeamEnum.One if params.get("name") == "ONE" else TeamEnum.Two) + TeamEnum.One if params.get("name") == "ONE" else TeamEnum.Two + ) return clazz(class_binding=welcome_message, **params) elif params.get("class_value") == "memento": state_object = params.get("state") @@ -34,12 +42,17 @@ def custom_class_factory(clazz, params: dict): move_request_object = MoveRequest() return clazz(class_binding=move_request_object, **params) elif params.get("class_value") == "result": - result_object = Result(definition=params.get("definition"), scores=params.get("scores"), - winner=params.get("winner")) + result_object = Result( + definition=params.get("definition"), + scores=params.get("scores"), + winner=params.get("winner"), + ) return clazz(class_binding=result_object, **params) elif params.get("class_value") == "error": - error_object = Error(message=params.get( - "message"), originalMessage=params.get("original_message")) + error_object = Error( + message=params.get("message"), + originalMessage=params.get("original_message"), + ) return clazz(class_binding=error_object, **params) return clazz(**params) @@ -62,10 +75,10 @@ def __init__(self, host: str, port: int): context = XmlContext() deserialize_config = ParserConfig(class_factory=custom_class_factory) self.deserializer = XmlParser( - handler=XmlEventHandler, context=context, config=deserialize_config) + handler=XmlEventHandler, context=context, config=deserialize_config + ) - serialize_config = SerializerConfig( - pretty_print=True, xml_declaration=False) + serialize_config = SerializerConfig(pretty_print=True, xml_declaration=False) self.serializer = XmlSerializer(config=serialize_config) def connect(self): @@ -97,13 +110,13 @@ def _receive(self): cls = self._deserialize_object(receiving) return cls except OSError: - logging.error( - "An OSError occurred while receiving data from the server.") + logging.error("An OSError occurred while receiving data from the server.") self.running = False raise except Exception as e: logging.error( - "An error occurred while receiving data from the server: %s", e) + "An error occurred while receiving data from the server: %s", e + ) self.running = False raise @@ -117,8 +130,11 @@ def send(self, obj: ProtocolPacket) -> None: raise ValueError("Cannot send `None` to server") with self._encode_context() as encode: - shipment = PROTOCOL_PREFIX + \ - encode(obj) if self.first_time is True else encode(obj) + shipment = ( + PROTOCOL_PREFIX + encode(obj) + if self.first_time is True + else encode(obj) + ) try: self.network_interface.send(shipment) diff --git a/python/socha/api/protocol/protocol.py b/python/socha/api/protocol/protocol.py index ec3b43c..b02d5ee 100644 --- a/python/socha/api/protocol/protocol.py +++ b/python/socha/api/protocol/protocol.py @@ -1,9 +1,18 @@ from dataclasses import dataclass, field from typing import List, Optional -from socha.api.protocol.protocol_packet import ProtocolPacket, AdminLobbyRequest, ResponsePacket, LobbyRequest -from socha.api.protocol.room_message import RoomMessage, ObservableRoomMessage, RoomOrchestrationMessage from socha._socha import TeamEnum +from socha.api.protocol.protocol_packet import ( + AdminLobbyRequest, + LobbyRequest, + ProtocolPacket, + ResponsePacket, +) +from socha.api.protocol.room_message import ( + ObservableRoomMessage, + RoomMessage, + RoomOrchestrationMessage, +) @dataclass @@ -16,14 +25,14 @@ class Meta: metadata={ "name": "class", "type": "Attribute", - } + }, ) reservation_code: Optional[str] = field( default=None, metadata={ "name": "reservationCode", "type": "Attribute", - } + }, ) @@ -36,55 +45,14 @@ class Meta: default=None, metadata={ "type": "Attribute", - } + }, ) original_request: Optional[OriginalRequest] = field( default=None, metadata={ "name": "originalRequest", "type": "Element", - } - ) - - -@dataclass -class OriginalRequest(ProtocolPacket): - class Meta: - name = "originalRequest" - - class_value: Optional[str] = field( - default=None, - metadata={ - "name": "class", - "type": "Attribute", - } - ) - reservation_code: Optional[str] = field( - default=None, - metadata={ - "name": "reservationCode", - "type": "Attribute", - } - ) - - -@dataclass -class Errorpacket(ProtocolPacket): - class Meta: - name = "errorpacket" - - message: Optional[str] = field( - default=None, - metadata={ - "type": "Attribute", - } - ) - original_request: Optional[OriginalRequest] = field( - default=None, - metadata={ - "name": "originalRequest", - "type": "Element", - } + }, ) @@ -102,7 +70,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -129,7 +97,7 @@ class Meta: @dataclass class Authenticate(AdminLobbyRequest): """ - Authenticates a client as administrator to _send AdminLobbyRequest`s. \n + Authenticates a client as administrator to _send AdminLobbyRequest`s. *Is not answered if successful.* """ @@ -140,7 +108,7 @@ class Meta: default=None, metadata={ "type": "Attribute", - } + }, ) @@ -158,7 +126,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -176,14 +144,14 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) player_count: Optional[int] = field( default=None, metadata={ "name": "playerCount", "type": "Attribute", - } + }, ) @@ -201,7 +169,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -219,13 +187,13 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) pause: Optional[bool] = field( default=None, metadata={ "type": "Attribute", - } + }, ) @@ -243,20 +211,20 @@ class Meta: metadata={ "name": "displayName", "type": "Attribute", - } + }, ) can_timeout: Optional[bool] = field( default=None, metadata={ "name": "canTimeout", "type": "Attribute", - } + }, ) reserved: Optional[bool] = field( default=None, metadata={ "type": "Attribute", - } + }, ) @@ -276,7 +244,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -295,19 +263,19 @@ class Meta: metadata={ "name": "gameType", "type": "Attribute", - } + }, ) pause: Optional[bool] = field( default=None, metadata={ "type": "Attribute", - } + }, ) slot: List[Slot] = field( default_factory=list, metadata={ "type": "Element", - } + }, ) @@ -337,7 +305,7 @@ class Meta: metadata={ "name": "reservationCode", "type": "Attribute", - } + }, ) @@ -355,7 +323,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -372,20 +340,20 @@ class Meta: default=None, metadata={ "type": "Attribute", - } + }, ) aggregation: Optional[str] = field( default=None, metadata={ "type": "Element", - } + }, ) relevant_for_ranking: Optional[bool] = field( default=None, metadata={ "name": "relevantForRanking", "type": "Element", - } + }, ) @@ -403,7 +371,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -420,13 +388,13 @@ class Meta: default=None, metadata={ "type": "Attribute", - } + }, ) team: Optional[str] = field( default=None, metadata={ "type": "Attribute", - } + }, ) @@ -444,7 +412,7 @@ class Meta: metadata={ "type": "Element", "min_occurs": 1, - } + }, ) @@ -458,21 +426,21 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) regular: Optional[bool] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) reason: Optional[str] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -491,7 +459,7 @@ class Meta: default_factory=list, metadata={ "type": "Element", - } + }, ) @@ -509,13 +477,13 @@ class Meta: default=None, metadata={ "type": "Element", - } + }, ) score: Optional[Score] = field( default=None, metadata={ "type": "Element", - } + }, ) @@ -532,7 +500,7 @@ class Meta: default_factory=list, metadata={ "type": "Element", - } + }, ) @@ -542,6 +510,7 @@ class WelcomeMessage(RoomOrchestrationMessage): Welcome message is sent to the client when the client joins the room. In this message the server tells the client which team it is. """ + team: TeamEnum @@ -551,6 +520,7 @@ class Result(ObservableRoomMessage): Result of a game. This will the server _send after a game is finished. """ + definition: Definition scores: Scores winner: Winner @@ -566,7 +536,7 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -580,7 +550,7 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -594,21 +564,21 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) r: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) s: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -622,14 +592,14 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) aggregation: Optional[str] = field( default=None, metadata={ "type": "Element", "required": True, - } + }, ) relevant_for_ranking: Optional[bool] = field( default=None, @@ -637,7 +607,7 @@ class Meta: "name": "relevantForRanking", "type": "Element", "required": True, - } + }, ) @@ -651,14 +621,14 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) team: Optional[str] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -672,21 +642,21 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) r: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) s: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -700,7 +670,7 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -714,7 +684,7 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -751,7 +721,7 @@ class Meta: "namespace": "", }, ), - } + }, ) @@ -770,14 +740,14 @@ class Meta: metadata={ "name": "class", "type": "Attribute", - } + }, ) actions: Optional[Actions] = field( default=None, metadata={ "type": "Element", - } + }, ) @@ -786,9 +756,11 @@ class Error: """ This sends the server when the client sent a erroneous message. """ + message: str originalMessage: OriginalMessage + @dataclass class Entry: class Meta: @@ -799,14 +771,14 @@ class Meta: metadata={ "type": "Element", "required": True, - } + }, ) score: Optional[Score] = field( default=None, metadata={ "type": "Element", "required": True, - } + }, ) @@ -820,14 +792,14 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) passenger: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) @@ -894,7 +866,7 @@ class Meta: "namespace": "", }, ), - } + }, ) @@ -908,35 +880,35 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) direction: Optional[str] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) speed: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) coal: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) passengers: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) free_turns: Optional[int] = field( default=None, @@ -944,21 +916,21 @@ class Meta: "name": "freeTurns", "type": "Attribute", "required": True, - } + }, ) points: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) position: Optional[Position] = field( default=None, metadata={ "type": "Element", "required": True, - } + }, ) @@ -972,7 +944,7 @@ class Meta: metadata={ "type": "Element", "required": True, - } + }, ) @@ -986,14 +958,14 @@ class Meta: metadata={ "type": "Attribute", "required": True, - } + }, ) center: Optional[Center] = field( default=None, metadata={ "type": "Element", "required": True, - } + }, ) field_array: List[FieldArray] = field( default_factory=list, @@ -1001,7 +973,7 @@ class Meta: "name": "field-array", "type": "Element", "min_occurs": 1, - } + }, ) @@ -1016,14 +988,14 @@ class Meta: "name": "nextDirection", "type": "Attribute", "required": True, - } + }, ) segment: List[Segment] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, - } + }, ) @@ -1038,7 +1010,7 @@ class Meta: "name": "class", "type": "Attribute", "required": True, - } + }, ) start_team: Optional[str] = field( default=None, @@ -1046,14 +1018,14 @@ class Meta: "name": "startTeam", "type": "Attribute", "required": True, - } + }, ) turn: Optional[int] = field( default=None, metadata={ "type": "Attribute", "required": True, - } + }, ) current_team: Optional[str] = field( default=None, @@ -1061,28 +1033,28 @@ class Meta: "name": "currentTeam", "type": "Attribute", "required": True, - } + }, ) board: Optional[Board] = field( default=None, metadata={ "type": "Element", "required": True, - } + }, ) ship: List[Ship] = field( default_factory=list, metadata={ "type": "Element", "min_occurs": 1, - } + }, ) last_move: Optional[LastMove] = field( default=None, metadata={ "name": "lastMove", "type": "Element", - } + }, ) @@ -1097,59 +1069,57 @@ class Meta: "name": "class", "type": "Attribute", "required": True, - } - ) - class_binding: Optional[object] = field( - default=None + }, ) + class_binding: Optional[object] = field(default=None) definition: Optional[Definition] = field( default=None, metadata={ "type": "Element", - } + }, ) scores: Optional[Scores] = field( default=None, metadata={ "type": "Element", - } + }, ) winner: Optional[Winner] = field( default=None, metadata={ "type": "Element", - } + }, ) state: Optional[State] = field( default=None, metadata={ "type": "Element", - } + }, ) actions: Optional[Actions] = field( default=None, metadata={ "type": "Element", - } + }, ) original_message: Optional[OriginalMessage] = field( default=None, metadata={ "name": "originalMessage", "type": "Element", - } + }, ) color: Optional[str] = field( default=None, metadata={ "type": "Attribute", - } + }, ) message: Optional[str] = field( default=None, metadata={ "type": "Attribute", - } + }, ) @@ -1164,34 +1134,14 @@ class Meta: "name": "roomId", "type": "Attribute", "required": True, - } + }, ) data: Optional[Data] = field( default=None, metadata={ "type": "Element", "required": True, - } - ) - - -@dataclass -class Prepared(RoomOrchestrationMessage): - class Meta: - name = "prepared" - - room_id: Optional[str] = field( - default=None, - metadata={ - "name": "roomId", - "type": "Attribute", - } - ) - reservation: List[str] = field( - default_factory=list, - metadata={ - "type": "Element", - } + }, ) @@ -1205,7 +1155,7 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) @@ -1219,27 +1169,13 @@ class Meta: metadata={ "name": "roomId", "type": "Attribute", - } + }, ) reservation: List[str] = field( default_factory=list, metadata={ "type": "Element", - } - ) - - -@dataclass -class Observed(RoomOrchestrationMessage): - class Meta: - name = "observed" - - room_id: Optional[str] = field( - default=None, - metadata={ - "name": "roomId", - "type": "Attribute", - } + }, ) @@ -1258,73 +1194,72 @@ class Meta: default=None, metadata={ "type": "Element", - } + }, ) joined_game_room: Optional[JoinedGameRoom] = field( default=None, metadata={ "name": "joinedGameRoom", "type": "Element", - } + }, ) prepare: Optional[Prepare] = field( default=None, metadata={ "type": "Element", - } + }, ) observe: Optional[Observe] = field( default=None, metadata={ "type": "Element", - } + }, ) pause: Optional[Pause] = field( default=None, metadata={ "type": "Element", - } + }, ) step: Optional[Step] = field( default=None, metadata={ "type": "Element", - } + }, ) cancel: Optional[Cancel] = field( default=None, metadata={ "type": "Element", - } + }, ) join: Optional[Join] = field( default=None, metadata={ "type": "Element", - } + }, ) joined: Optional[Joined] = field( default=None, metadata={ "type": "Element", - } + }, ) room: List[Room] = field( default_factory=list, metadata={ "type": "Element", - } - + }, ) prepared: Optional[Prepared] = field( default=None, metadata={ "type": "Element", - } + }, ) observed: Optional[Observed] = field( default=None, metadata={ "type": "Element", - } + }, ) diff --git a/python/socha/utils/package_builder.py b/python/socha/utils/package_builder.py index e930cce..2ae409b 100644 --- a/python/socha/utils/package_builder.py +++ b/python/socha/utils/package_builder.py @@ -3,6 +3,8 @@ import logging import os import shutil + +# trunk-ignore(bandit/B404) import subprocess import sys import time @@ -14,63 +16,79 @@ class SochaPackageBuilder: def __init__(self, package_name): self.package_name = package_name - self.dependencies_dir = 'dependencies' - self.packages_dir = 'packages' - self.cache_dir = '.pip_cache' + self.dependencies_dir = "dependencies" + self.packages_dir = "packages" + self.cache_dir = ".pip_cache" self.start_time = time.time_ns() self.build_dir = self._create_build_directory() def _download_dependencies(self): current_dir = os.getcwd() - req_file = os.path.join(current_dir, 'requirements.txt') + req_file = os.path.join(current_dir, "requirements.txt") try: - with open(req_file, 'r') as f: + with open(req_file, "r") as f: requirements = f.read().splitlines() except Exception as e: logging.error(f"Error reading requirements file: {str(e)}") logging.info( - f"Please create a 'requirements.txt' in the same folder as your logic.") + "Please create a 'requirements.txt' in the same folder as your logic." + ) sys.exit(1) - logging.info(f'Downloading the following packages: {requirements}') + logging.info(f"Downloading the following packages: {requirements}") # Download all dependencies to the dependencies directory try: + # trunk-ignore(bandit/B603) subprocess.check_call( - [sys.executable, '-m', 'pip', 'download', '--only-binary=:all:', '-d', - f'{self.build_dir}/{self.package_name}/{self.dependencies_dir}'] + requirements) + [ + sys.executable, + "-m", + "pip", + "download", + "--only-binary=:all:", + "-d", + f"{self.build_dir}/{self.package_name}/{self.dependencies_dir}", + ] + + requirements + ) except subprocess.CalledProcessError as e: logging.error(f"Error downloading dependencies: {str(e)}") sys.exit(1) @staticmethod def _create_build_directory(): - current_time = time.strftime('%Y-%m-%d_%H-%M-%S', time.localtime()) - build_dir = os.path.join('socha_builds', current_time) + current_time = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) + build_dir = os.path.join("socha_builds", current_time) os.makedirs(build_dir, exist_ok=True) return build_dir def _create_directory_structure(self): try: - if not os.path.exists(f'{self.build_dir}/{self.package_name}'): - logging.info(f'Creating directory {self.package_name}') + if not os.path.exists(f"{self.build_dir}/{self.package_name}"): + logging.info(f"Creating directory {self.package_name}") os.mkdir(f"{self.build_dir}/{self.package_name}") - logging.info(f'Creating directory {self.dependencies_dir}') - if not os.path.exists(f'{self.build_dir}/{self.package_name}/{self.dependencies_dir}'): + logging.info(f"Creating directory {self.dependencies_dir}") + if not os.path.exists( + f"{self.build_dir}/{self.package_name}/{self.dependencies_dir}" + ): os.mkdir( - f'{self.build_dir}/{self.package_name}/{self.dependencies_dir}') + f"{self.build_dir}/{self.package_name}/{self.dependencies_dir}" + ) - logging.info(f'Creating directory {self.packages_dir}') - if not os.path.exists(f'{self.build_dir}/{self.package_name}/{self.packages_dir}'): - os.mkdir( - f'{self.build_dir}/{self.package_name}/{self.packages_dir}') + logging.info(f"Creating directory {self.packages_dir}") + if not os.path.exists( + f"{self.build_dir}/{self.package_name}/{self.packages_dir}" + ): + os.mkdir(f"{self.build_dir}/{self.package_name}/{self.packages_dir}") - logging.info(f'Creating directory {self.cache_dir}') - if not os.path.exists(f'{self.build_dir}/{self.package_name}/{self.cache_dir}'): - os.mkdir( - f'{self.build_dir}/{self.package_name}/{self.cache_dir}') + logging.info(f"Creating directory {self.cache_dir}") + if not os.path.exists( + f"{self.build_dir}/{self.package_name}/{self.cache_dir}" + ): + os.mkdir(f"{self.build_dir}/{self.package_name}/{self.cache_dir}") except OSError as e: logging.error(f"Error creating directory: {e}") @@ -92,13 +110,20 @@ def _get_modules(): main_modules |= set( obj.__name__ for obj in gc.get_objects() - if isinstance(obj, types.ModuleType) and obj.__name__.startswith(main_module.__name__) + if isinstance(obj, types.ModuleType) + and obj.__name__.startswith(main_module.__name__) ) main_modules = { - name for name in main_modules - if hasattr(sys.modules[name], "__file__") and sys.modules[name].__file__ is not None - and (os.path.abspath(os.path.dirname(sys.modules[name].__file__)) == main_dir - or os.path.abspath(os.path.dirname(sys.modules[name].__file__)).startswith(main_dir + os.path.sep)) + name + for name in main_modules + if hasattr(sys.modules[name], "__file__") + and sys.modules[name].__file__ is not None + and ( + os.path.abspath(os.path.dirname(sys.modules[name].__file__)) == main_dir + or os.path.abspath( + os.path.dirname(sys.modules[name].__file__) + ).startswith(main_dir + os.path.sep) + ) } module_paths = set() @@ -115,71 +140,81 @@ def _copy_modules(self): Recursively searches for the given python file in the current working directory and its subdirectories, and copies all python files with their directory structure to the target_folder. """ - logging.info(f'Copying python files to {self.package_name}') + logging.info(f"Copying python files to {self.package_name}") source_folder = os.getcwd() main_modules = self._get_modules() for root, dirs, files in os.walk(source_folder): if "socha_builds" in dirs: dirs.remove("socha_builds") for file in files: - if file.endswith('.py'): + if file.endswith(".py"): source_file_path = os.path.join(root, file) - target_file_path = os.path.join(self.build_dir, self.package_name, - os.path.relpath(source_file_path, source_folder)) + target_file_path = os.path.join( + self.build_dir, + self.package_name, + os.path.relpath(source_file_path, source_folder), + ) if file in sys.argv[0]: - os.makedirs(os.path.dirname( - target_file_path), exist_ok=True) + os.makedirs(os.path.dirname(target_file_path), exist_ok=True) shutil.copy2(source_file_path, target_file_path) logging.info( - f'Copying {source_file_path} to {target_file_path}') + f"Copying {source_file_path} to {target_file_path}" + ) if source_file_path in main_modules: - os.makedirs(os.path.dirname( - target_file_path), exist_ok=True) + os.makedirs(os.path.dirname(target_file_path), exist_ok=True) shutil.copy2(source_file_path, target_file_path) logging.info( - f'Copying {source_file_path} to {target_file_path}') + f"Copying {source_file_path} to {target_file_path}" + ) def _create_shell_script(self): - logging.info(f'Creating shell script {self.package_name}/start.sh') - with open(f'{self.build_dir}/{self.package_name}/start.sh', 'w', newline='\n') as f: - f.write('#!/bin/sh\n') - f.write('\n') - f.write('# Exit immediately if any command fails\n') - f.write('set -e\n') - f.write('\n') - f.write( - f'# Sets the environment variable, which specifies the location for pip to store its cache files\n') + logging.info(f"Creating shell script {self.package_name}/start.sh") + with open( + f"{self.build_dir}/{self.package_name}/start.sh", "w", newline="\n" + ) as f: + f.write("#!/bin/sh\n") + f.write("\n") + f.write("# Exit immediately if any command fails\n") + f.write("set -e\n") + f.write("\n") f.write( - f'export XDG_CACHE_HOME=./{self.package_name}/.pip_cache\n') - f.write('\n') - f.write( - f'# Sets the environment variable, which adds the directory to the list of paths that Python searches ' - f'for modules and packages when they are imported.\n') + "# Sets the environment variable, which specifies the location for pip to store its cache files\n" + ) + f.write(f"export XDG_CACHE_HOME=./{self.package_name}/.pip_cache\n") + f.write("\n") f.write( - f'export PYTHONPATH=./{self.package_name}/packages:$PYTHONPATH\n') - f.write('\n') - f.write(f'# Install the package socha and dependencies\n') + "# Sets the environment variable, which adds the directory to the list of paths that Python searches " + "for modules and packages when they are imported.\n" + ) + f.write(f"export PYTHONPATH=./{self.package_name}/packages:$PYTHONPATH\n") + f.write("\n") + f.write("# Install the package socha and dependencies\n") f.write( - f'pip install --no-index --find-links=./{self.package_name}/{self.dependencies_dir}/ --target=./' - f'{self.package_name}/{self.packages_dir}/ --cache-dir=./{self.package_name}/{self.cache_dir} ') + f"pip install --no-index --find-links=./{self.package_name}/{self.dependencies_dir}/ --target=./" + f"{self.package_name}/{self.packages_dir}/ --cache-dir=./{self.package_name}/{self.cache_dir} " + ) # Add all downloaded packages to the pip install command - for package in os.listdir(f'{self.build_dir}/{self.package_name}/{self.dependencies_dir}'): - f.write( - f'./{self.package_name}/{self.dependencies_dir}/{package} ') + for package in os.listdir( + f"{self.build_dir}/{self.package_name}/{self.dependencies_dir}" + ): + f.write(f"./{self.package_name}/{self.dependencies_dir}/{package} ") - f.write('\n\n') + f.write("\n\n") f.write( - f'# Run the {os.path.basename(sys.argv[0])} script with start arguments\n') + f"# Run the {os.path.basename(sys.argv[0])} script with start arguments\n" + ) f.write( - f'python3 ./{self.package_name}/{os.path.basename(sys.argv[0])} "$@"\n') + f'python3 ./{self.package_name}/{os.path.basename(sys.argv[0])} "$@"\n' + ) def _zipdir(self): - logging.info(f'Zipping directory {self.package_name}') + logging.info(f"Zipping directory {self.package_name}") try: zipf = zipfile.ZipFile( - f'{self.build_dir}/{self.package_name}.zip', 'w', zipfile.ZIP_DEFLATED) - for root, dirs, files in os.walk(f'{self.build_dir}/{self.package_name}'): + f"{self.build_dir}/{self.package_name}.zip", "w", zipfile.ZIP_DEFLATED + ) + for root, dirs, files in os.walk(f"{self.build_dir}/{self.package_name}"): for file in files: file_path = os.path.join(root, file) arc_name = os.path.relpath(file_path, self.build_dir) @@ -189,13 +224,13 @@ def _zipdir(self): arc_name = os.path.relpath(dir_path, self.build_dir) zipf.write(dir_path, arcname=arc_name) zipf.close() - logging.info(f'{self.package_name}.zip successfully created!') + logging.info(f"{self.package_name}.zip successfully created!") except Exception as e: - logging.error(f'Error creating {self.package_name}.zip: {str(e)}') + logging.error(f"Error creating {self.package_name}.zip: {str(e)}") sys.exit(1) def build_package(self): - logging.info('Building package...') + logging.info("Building package...") # Create the directory structure self._create_directory_structure() @@ -213,4 +248,4 @@ def build_package(self): self._zipdir() # Log a success message - logging.info(f'{self.package_name} package successfully built!') + logging.info(f"{self.package_name} package successfully built!") diff --git a/src/plugin/actions/accelerate.rs b/src/plugin/actions/accelerate.rs index 167df81..5e4f199 100644 --- a/src/plugin/actions/accelerate.rs +++ b/src/plugin/actions/accelerate.rs @@ -3,9 +3,9 @@ use pyo3::exceptions::PyBaseException; use pyo3::prelude::*; use crate::plugin::constants::PluginConstants; -use crate::plugin::{ errors::acceleration_errors::AccelerationProblem, game_state::GameState }; use crate::plugin::field::FieldType; use crate::plugin::ship::Ship; +use crate::plugin::{errors::acceleration_errors::AccelerationProblem, game_state::GameState}; /// `Accelerate` is representing a ship's ability to change its speed and acceleration. /// It contains methods for initiating and managing the acceleration process. @@ -38,32 +38,48 @@ impl Accelerate { Self { acc } } pub fn perform(&self, state: &GameState) -> Result { - debug!("perform() called with acc: {} and game state: {:?}", self.acc, state); - let mut ship: Ship = state.current_ship.clone(); + debug!( + "perform() called with acc: {} and game state: {:?}", + self.acc, state + ); + let mut ship: Ship = state.current_ship; let mut speed = ship.speed; speed += self.acc; match () { _ if self.acc == 0 => { debug!("Zero acceleration is not allowed"); - return Err(PyBaseException::new_err(AccelerationProblem::ZeroAcc.message())); + Err(PyBaseException::new_err( + AccelerationProblem::ZeroAcc.message(), + )) } _ if speed > PluginConstants::MAX_SPEED => { debug!("Acceleration would exceed max speed but was {}", speed); - return Err(PyBaseException::new_err(AccelerationProblem::AboveMaxSpeed.message())); + Err(PyBaseException::new_err( + AccelerationProblem::AboveMaxSpeed.message(), + )) } _ if speed < PluginConstants::MIN_SPEED => { debug!("Acceleration would go below min speed but was {}", speed); - return Err(PyBaseException::new_err(AccelerationProblem::BelowMinSpeed.message())); + Err(PyBaseException::new_err( + AccelerationProblem::BelowMinSpeed.message(), + )) } _ if state.board.get(&ship.position).unwrap().field_type == FieldType::Sandbank => { - debug!("Cannot accelerate on sandbank. Ship position: {}", ship.position); - return Err(PyBaseException::new_err(AccelerationProblem::OnSandbank.message())); + debug!( + "Cannot accelerate on sandbank. Ship position: {}", + ship.position + ); + Err(PyBaseException::new_err( + AccelerationProblem::OnSandbank.message(), + )) } _ => { let new_ship: Ship = self.accelerate(&mut ship); if new_ship.coal < 0 { debug!("Insufficient coal for acceleration was {}", new_ship.coal); - Err(PyBaseException::new_err(AccelerationProblem::InsufficientCoal.message())) + Err(PyBaseException::new_err( + AccelerationProblem::InsufficientCoal.message(), + )) } else { debug!("Ship accelerated successfully"); Ok(ship) @@ -79,7 +95,7 @@ impl Accelerate { ship.free_acc = (-used_coal).max(0); ship.accelerate_by(self.acc); debug!("Acceleration completed and ship status: {:?}", ship); - return ship.clone(); + *ship } fn accelerate_unchecked(&self, ship: &mut Ship) -> Ship { @@ -88,12 +104,18 @@ impl Accelerate { 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() + debug!( + "Unchecked acceleration completed and ship status: {:?}", + ship + ); + *ship } fn __repr__(&self) -> PyResult { - debug!("__repr__ method called for Accelerate with acc: {}", self.acc); + debug!( + "__repr__ method called for Accelerate with acc: {}", + self.acc + ); Ok(format!("Accelerate({})", self.acc)) } } @@ -104,11 +126,11 @@ mod tests { use crate::plugin::board::Board; use crate::plugin::constants::PluginConstants; - use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; + use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; use crate::plugin::field::Field; use crate::plugin::game_state::GameState; use crate::plugin::segment::Segment; - use crate::plugin::ship::{ Ship, TeamEnum }; + use crate::plugin::ship::{Ship, TeamEnum}; use super::*; @@ -136,7 +158,7 @@ mod tests { None, None, None, - None + None, ); let team_two: Ship = Ship::new( CubeCoordinates::new(-1, 1), @@ -147,9 +169,9 @@ mod tests { None, None, None, - None + None, ); - let game_state: GameState = GameState::new(board, 0, team_one.clone(), team_two, None); + let game_state: GameState = GameState::new(board, 0, team_one, team_two, None); (accelerate, game_state) } @@ -161,7 +183,10 @@ mod tests { prepare_freethreaded_python(); Python::with_gil(|py| { - assert_eq!(result_error.value(py).to_string(), AccelerationProblem::ZeroAcc.message()); + assert_eq!( + result_error.value(py).to_string(), + AccelerationProblem::ZeroAcc.message() + ); }); } @@ -188,7 +213,10 @@ mod tests { prepare_freethreaded_python(); Python::with_gil(|py| { - assert_eq!(result.value(py).to_string(), AccelerationProblem::BelowMinSpeed.message()); + assert_eq!( + result.value(py).to_string(), + AccelerationProblem::BelowMinSpeed.message() + ); }); } @@ -200,7 +228,7 @@ mod tests { mute_state.current_ship.coal = 0; mute_state.other_ship.coal = 0; - let result = accelerate.perform(&mute_state).unwrap_err(); + let result = accelerate.perform(mute_state).unwrap_err(); prepare_freethreaded_python(); Python::with_gil(|py| { @@ -232,7 +260,7 @@ mod tests { None, None, None, - None + None, ); assert_eq!(ship.speed, PluginConstants::MIN_SPEED); diff --git a/src/plugin/actions/advance.rs b/src/plugin/actions/advance.rs index 2e2689e..491a764 100644 --- a/src/plugin/actions/advance.rs +++ b/src/plugin/actions/advance.rs @@ -3,9 +3,7 @@ use pyo3::exceptions::PyBaseException; use pyo3::prelude::*; use crate::plugin::{ - constants::PluginConstants, - errors::advance_errors::AdvanceProblem, - game_state::GameState, + constants::PluginConstants, errors::advance_errors::AdvanceProblem, game_state::GameState, }; use crate::plugin::ship::Ship; @@ -28,12 +26,12 @@ impl Advance { pub fn perform(&self, state: &GameState) -> Result { debug!("Performing advance with distance: {}", self.distance); - let mut ship = state.current_ship.clone(); - let valid_distance = self.validate_distance(&state, &ship)?; + let mut ship = state.current_ship; + let valid_distance = self.validate_distance(state, &ship)?; let advance_info = state.calculate_advance_info( &ship.position, &ship.resolve_direction(!valid_distance), - ship.movement + ship.movement, ); let advance_possible = (advance_info.distance() as i32) >= self.distance.abs(); @@ -48,21 +46,22 @@ impl Advance { } fn validate_distance(&self, state: &GameState, ship: &Ship) -> Result { - if - (self.distance < PluginConstants::MIN_SPEED && - !state.board.is_sandbank(&ship.position)) || - self.distance > PluginConstants::MAX_SPEED + if (self.distance < PluginConstants::MIN_SPEED && !state.board.is_sandbank(&ship.position)) + || self.distance > PluginConstants::MAX_SPEED { debug!("Invalid distance: {}", self.distance); - return Err(PyBaseException::new_err(AdvanceProblem::InvalidDistance.message())); + return Err(PyBaseException::new_err( + AdvanceProblem::InvalidDistance.message(), + )); } if self.distance > ship.movement { debug!( "Movement points missing: {} needed, {} available", - self.distance, - ship.movement + self.distance, ship.movement ); - return Err(PyBaseException::new_err(AdvanceProblem::MovementPointsMissing.message())); + return Err(PyBaseException::new_err( + AdvanceProblem::MovementPointsMissing.message(), + )); } Ok(true) } @@ -77,11 +76,11 @@ mod tests { use pyo3::prepare_freethreaded_python; use crate::plugin::board::Board; - use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; - use crate::plugin::field::{ Field, FieldType }; + use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; + use crate::plugin::field::{Field, FieldType}; use crate::plugin::game_state::GameState; use crate::plugin::segment::Segment; - use crate::plugin::ship::{ Ship, TeamEnum }; + use crate::plugin::ship::{Ship, TeamEnum}; use super::*; @@ -107,7 +106,7 @@ mod tests { None, None, None, - None + None, ); team_one.speed = 5; team_one.movement = 5; @@ -120,17 +119,11 @@ mod tests { None, None, None, - None + None, ); team_two.speed = 5; team_two.movement = 5; - let game_state: GameState = GameState::new( - board, - 0, - team_one.clone(), - team_two.clone(), - None - ); + let game_state: GameState = GameState::new(board, 0, *team_one, *team_two, None); game_state } @@ -159,7 +152,10 @@ mod tests { prepare_freethreaded_python(); Python::with_gil(|py| { - assert_eq!(error.value(py).to_string(), AdvanceProblem::InvalidDistance.message()); + assert_eq!( + error.value(py).to_string(), + AdvanceProblem::InvalidDistance.message() + ); }); } diff --git a/src/plugin/actions/push.rs b/src/plugin/actions/push.rs index faba639..1d87c48 100644 --- a/src/plugin/actions/push.rs +++ b/src/plugin/actions/push.rs @@ -2,7 +2,7 @@ use log::debug; use pyo3::exceptions::PyBaseException; use pyo3::prelude::*; -use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; +use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; use crate::plugin::errors::push_error::PushProblem; use crate::plugin::field::FieldType; use crate::plugin::game_state::GameState; @@ -25,12 +25,14 @@ impl Push { pub fn perform(&self, state: &GameState) -> Result<(Ship, Ship), PyErr> { debug!("Performing push with direction: {}", self.direction); - let mut current_ship: Ship = state.current_ship.clone(); - let mut other_ship: Ship = state.other_ship.clone(); + let mut current_ship: Ship = state.current_ship; + let mut other_ship: Ship = state.other_ship; if current_ship.movement == 0 { debug!("Movement points missing: {}", current_ship.movement); - return Err(PyBaseException::new_err(PushProblem::MovementPointsMissing.message())); + return Err(PyBaseException::new_err( + PushProblem::MovementPointsMissing.message(), + )); } current_ship.movement -= 1; @@ -41,36 +43,44 @@ impl Push { Some(value) => value, None => { debug!("Invalid field push: {}", push_to); - return Err(PyBaseException::new_err(PushProblem::InvalidFieldPush.message())); + return Err(PyBaseException::new_err( + PushProblem::InvalidFieldPush.message(), + )); } }; if !shift_to_field.is_empty() { debug!("Blocked field push to: {}", push_to); - return Err(PyBaseException::new_err(PushProblem::BlockedFieldPush.message())); + return Err(PyBaseException::new_err( + PushProblem::BlockedFieldPush.message(), + )); } if push_from != other_ship.position { debug!( "Same field push from: {} but other ship is at: {}", - push_from, - other_ship.position + push_from, other_ship.position ); - return Err(PyBaseException::new_err(PushProblem::SameFieldPush.message())); + return Err(PyBaseException::new_err( + PushProblem::SameFieldPush.message(), + )); } if state.board.get(&push_from).unwrap().field_type == FieldType::Sandbank { debug!("Sandbank push from: {}", push_from); - return Err(PyBaseException::new_err(PushProblem::SandbankPush.message())); + return Err(PyBaseException::new_err( + PushProblem::SandbankPush.message(), + )); } if self.direction == current_ship.direction.opposite() { debug!( "Backward pushing restricted: {} and current ship direction: {}", - self.direction, - current_ship.direction + self.direction, current_ship.direction ); - return Err(PyBaseException::new_err(PushProblem::BackwardPushingRestricted.message())); + return Err(PyBaseException::new_err( + PushProblem::BackwardPushingRestricted.message(), + )); } if shift_to_field.field_type == FieldType::Sandbank { @@ -96,11 +106,11 @@ mod tests { use pyo3::prepare_freethreaded_python; use crate::plugin::board::Board; - use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; + use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; use crate::plugin::field::Field; use crate::plugin::game_state::GameState; use crate::plugin::segment::Segment; - use crate::plugin::ship::{ Ship, TeamEnum }; + use crate::plugin::ship::{Ship, TeamEnum}; use super::*; @@ -108,7 +118,7 @@ mod tests { c1: CubeCoordinates, c2: CubeCoordinates, fields: Vec>, - dir: CubeDirection + dir: CubeDirection, ) -> (GameState, Push) { let segment: Vec = vec![Segment { direction: CubeDirection::Right, @@ -116,30 +126,12 @@ mod tests { fields, }]; let board: Board = Board::new(segment, CubeDirection::Right); - let mut team_one: Ship = Ship::new( - c1, - TeamEnum::One, - None, - None, - None, - None, - None, - None, - None - ); + let mut team_one: Ship = + Ship::new(c1, TeamEnum::One, None, None, None, None, None, None, None); team_one.speed = 5; team_one.movement = 5; - let mut team_two: Ship = Ship::new( - c2, - TeamEnum::Two, - None, - None, - None, - None, - None, - None, - None - ); + let mut team_two: Ship = + Ship::new(c2, TeamEnum::Two, None, None, None, None, None, None, None); team_two.speed = 5; team_two.movement = 5; let state: GameState = GameState::new(board, 0, team_one, team_two, None); @@ -153,7 +145,7 @@ mod tests { CubeCoordinates::new(0, 0), CubeCoordinates::new(0, 0), vec![vec![Field::new(FieldType::Water, None); 4]; 5], - CubeDirection::Right + CubeDirection::Right, ); let result: Result<(Ship, Ship), PyErr> = push.perform(&state); @@ -172,39 +164,39 @@ mod tests { Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Island, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ]; let (state, push) = setup( CubeCoordinates::new(0, 0), CubeCoordinates::new(0, 0), fields, - CubeDirection::Right + CubeDirection::Right, ); let result: Result<(Ship, Ship), PyErr> = push.perform(&state); @@ -225,7 +217,7 @@ mod tests { CubeCoordinates::new(0, 0), CubeCoordinates::new(1, 0), vec![vec![Field::new(FieldType::Water, None); 4]; 5], - CubeDirection::Right + CubeDirection::Right, ); let result: Result<(Ship, Ship), PyErr> = push.perform(&state); @@ -246,7 +238,7 @@ mod tests { CubeCoordinates::new(0, 0), CubeCoordinates::new(0, 0), vec![vec![Field::new(FieldType::Water, None); 4]; 5], - CubeDirection::Left + CubeDirection::Left, ); let result: Result<(Ship, Ship), PyErr> = push.perform(&state); diff --git a/src/plugin/actions/turn.rs b/src/plugin/actions/turn.rs index 171392c..24b9582 100644 --- a/src/plugin/actions/turn.rs +++ b/src/plugin/actions/turn.rs @@ -1,12 +1,9 @@ use log::debug; -use pyo3::{ exceptions::PyBaseException, prelude::* }; +use pyo3::{exceptions::PyBaseException, prelude::*}; use crate::plugin::{ - coordinate::CubeDirection, - errors::turn_error::TurnProblem, - field::FieldType, - game_state::GameState, - ship::Ship, + coordinate::CubeDirection, errors::turn_error::TurnProblem, field::FieldType, + game_state::GameState, ship::Ship, }; #[pyclass] @@ -26,9 +23,9 @@ impl Turn { pub fn perform(&self, state: &GameState) -> Result { debug!("Performing turn with direction: {}", self.direction); - let mut current_ship: Ship = state.current_ship.clone(); + let mut current_ship: Ship = state.current_ship; - let turn_count: i32 = current_ship.direction.turn_count_to(self.direction.clone()); + let turn_count: i32 = current_ship.direction.turn_count_to(self.direction); let abs_turn_count: i32 = turn_count.abs(); let used_coal: i32 = abs_turn_count - current_ship.free_turns; @@ -36,28 +33,36 @@ impl Turn { current_ship.free_turns = std::cmp::max(current_ship.free_turns - abs_turn_count, 0); if state.board.get(¤t_ship.position).unwrap().field_type == FieldType::Sandbank { - debug!("Rotation on sandbank not allowed. Position: {}", current_ship.position); - return Err( - PyBaseException::new_err(TurnProblem::RotationOnSandbankNotAllowed.message()) + debug!( + "Rotation on sandbank not allowed. Position: {}", + current_ship.position ); + return Err(PyBaseException::new_err( + TurnProblem::RotationOnSandbankNotAllowed.message(), + )); } if current_ship.coal < used_coal { debug!("Not enough coal for rotation. Coal: {}", current_ship.coal); - return Err(PyBaseException::new_err(TurnProblem::NotEnoughCoalForRotation.message())); + return Err(PyBaseException::new_err( + TurnProblem::NotEnoughCoalForRotation.message(), + )); } if used_coal > 0 { current_ship.coal -= used_coal; } - current_ship.direction = self.direction.clone(); + current_ship.direction = self.direction; debug!("Turn completed and ship status: {:?}", current_ship); Ok(current_ship) } pub fn coal_cost(&self, ship: &Ship) -> i32 { - self.direction.turn_count_to(self.direction.clone()).abs().saturating_sub(ship.free_turns) + self.direction + .turn_count_to(self.direction) + .abs() + .saturating_sub(ship.free_turns) } fn __repr__(&self) -> PyResult { @@ -70,11 +75,11 @@ mod tests { use pyo3::prepare_freethreaded_python; use crate::plugin::board::Board; - use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; - use crate::plugin::field::{ Field, FieldType }; + use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; + use crate::plugin::field::{Field, FieldType}; use crate::plugin::game_state::GameState; use crate::plugin::segment::Segment; - use crate::plugin::ship::{ Ship, TeamEnum }; + use crate::plugin::ship::{Ship, TeamEnum}; use super::*; @@ -101,7 +106,7 @@ mod tests { None, None, None, - None + None, ); team_one.speed = 5; team_one.movement = 5; @@ -115,18 +120,12 @@ mod tests { None, None, None, - None + None, ); team_two.speed = 5; team_two.movement = 5; team_two.coal = coal; - let game_state: GameState = GameState::new( - board, - 0, - team_one.clone(), - team_two.clone(), - None - ); + let game_state: GameState = GameState::new(board, 0, *team_one, *team_two, None); game_state } diff --git a/src/plugin/board.rs b/src/plugin/board.rs index 73c0306..2b5c1fb 100644 --- a/src/plugin/board.rs +++ b/src/plugin/board.rs @@ -1,10 +1,10 @@ use std::cmp::Ordering; -use std::collections::{ HashSet, BinaryHeap }; +use std::collections::{BinaryHeap, HashSet}; use pyo3::prelude::*; -use crate::plugin::coordinate::{ CartesianCoordinate, CubeCoordinates, CubeDirection }; -use crate::plugin::field::{ Field, FieldType }; +use crate::plugin::coordinate::{CartesianCoordinate, CubeCoordinates, CubeDirection}; +use crate::plugin::field::{Field, FieldType}; use crate::plugin::game_state::GameState; use crate::plugin::segment::Segment; use crate::plugin::ship::Ship; @@ -38,7 +38,7 @@ impl Board { self.segments .iter() .enumerate() - .find(|(_, segment)| { segment.contains(coords.clone()) }) + .find(|(_, segment)| segment.contains(coords)) .map(|(i, s)| (i, s.clone())) } @@ -54,16 +54,18 @@ impl Board { pub fn does_field_have_stream(&self, coords: &CubeCoordinates) -> bool { self.segment_with_index_at(*coords) .map(|(i, s)| { - let next_dir: CubeCoordinates = self.segments + let next_dir: CubeCoordinates = self + .segments .get(i + 1) .map(|s| s.direction.vector()) - .unwrap_or(self.next_direction.vector()); + .unwrap_or_else(|| self.next_direction.vector()); [ s.center - s.direction.vector(), s.center, s.center + next_dir, s.center + next_dir * 2, - ].contains(&coords) + ] + .contains(coords) }) .unwrap_or(false) } @@ -71,16 +73,16 @@ impl Board { pub fn get_field_in_direction( &self, direction: &CubeDirection, - coords: &CubeCoordinates + coords: &CubeCoordinates, ) -> Option { - self.get(&(coords.clone() + direction.vector())) + self.get(&(*coords + direction.vector())) } pub fn set_field_in_direction( &mut self, direction: &CubeDirection, coords: &CubeCoordinates, - field: Field + field: Field, ) { for segment in &mut self.segments { if segment.contains(*coords) { @@ -94,19 +96,17 @@ impl Board { &self, segment_index: usize, x_index: usize, - y_index: usize + y_index: usize, ) -> CubeCoordinates { - let coord: CubeCoordinates = CartesianCoordinate::new( - x_index as i32, - y_index as i32 - ).to_cube(); + let coord: CubeCoordinates = + CartesianCoordinate::new(x_index as i32, y_index as i32).to_cube(); self.segments[segment_index].local_to_global(coord) } pub fn segment_distance( &self, coordinate1: &CubeCoordinates, - coordinate2: &CubeCoordinates + coordinate2: &CubeCoordinates, ) -> i32 { let segment_index1 = self.segment_index(coordinate1).unwrap(); let segment_index2 = self.segment_index(coordinate2).unwrap(); @@ -114,7 +114,9 @@ impl Board { } pub fn segment_index(&self, coordinate: &CubeCoordinates) -> Option { - self.segments.iter().position(|segment| segment.contains(coordinate.clone())) + self.segments + .iter() + .position(|segment| segment.contains(*coordinate)) } pub fn find_segment(&self, coordinate: &CubeCoordinates) -> Option { @@ -123,22 +125,24 @@ impl Board { } pub fn neighboring_fields(&self, coords: &CubeCoordinates) -> Vec> { - CubeDirection::VALUES.iter() - .map(|direction| self.get_field_in_direction(&direction, coords)) + CubeDirection::VALUES + .iter() + .map(|direction| self.get_field_in_direction(direction, coords)) .collect() } pub fn neighboring_coordinates( &self, - coords: &CubeCoordinates + coords: &CubeCoordinates, ) -> Vec> { - CubeDirection::VALUES.iter() + CubeDirection::VALUES + .iter() .zip( - CubeDirection::VALUES.iter().map(|direction| - self.get_field_in_direction(&direction, coords) - ) + CubeDirection::VALUES + .iter() + .map(|direction| self.get_field_in_direction(direction, coords)), ) - .map(|(direction, field)| field.map(|_| coords.clone() + direction.vector())) + .map(|(direction, field)| field.map(|_| *coords + direction.vector())) .collect() } @@ -162,9 +166,9 @@ impl Board { let mut ship = new_state.current_ship; if self.effective_speed(&ship) < 2 { if let Some(mut field) = new_state.board.pickup_passenger_at_position(&ship.position) { - field.passenger.as_mut().map(|passenger| { + if let Some(passenger) = field.passenger.as_mut() { passenger.passenger -= 1; - }); + } ship.passengers += 1; } } @@ -172,7 +176,8 @@ impl Board { } fn pickup_passenger_at_position(&self, pos: &CubeCoordinates) -> Option { - CubeDirection::VALUES.iter() + CubeDirection::VALUES + .iter() .filter_map(|direction| { let field = self.get_field_in_direction(direction, pos)?; if field.passenger.as_ref()?.passenger > 0 { @@ -220,40 +225,44 @@ impl Board { pub fn find_nearest_field_types( &self, start_coordinates: &CubeCoordinates, - field_type: FieldType + field_type: FieldType, ) -> HashSet { - let max_fields: usize = ((self.segments.len() as i32) * - PluginConstants::SEGMENT_FIELDS_HEIGHT * - PluginConstants::SEGMENT_FIELDS_WIDTH) as usize; + let max_fields: usize = ((self.segments.len() as i32) + * PluginConstants::SEGMENT_FIELDS_HEIGHT + * PluginConstants::SEGMENT_FIELDS_WIDTH) as usize; let mut nearest_coordinates: HashSet = HashSet::with_capacity(max_fields); let mut visited: HashSet = HashSet::with_capacity(max_fields); let mut queue: BinaryHeap = BinaryHeap::new(); queue.push(QueueItem { - coordinates: start_coordinates.clone(), + coordinates: *start_coordinates, distance: 0, }); - while let Some(QueueItem { coordinates: current_coords, distance }) = queue.pop() { + while let Some(QueueItem { + coordinates: current_coords, + distance, + }) = queue.pop() + { if self.found_fields(start_coordinates, nearest_coordinates.clone(), distance) { break; } if let Some(field) = self.get(¤t_coords) { if field.field_type == field_type { - nearest_coordinates.insert(current_coords.clone()); + nearest_coordinates.insert(current_coords); } } self.neighboring_coordinates(¤t_coords) .into_iter() - .filter_map(|coord| coord) - .filter(|coord| visited.insert(coord.clone())) - .for_each(|coord| + .flatten() + .filter(|coord| visited.insert(*coord)) + .for_each(|coord| { queue.push(QueueItem { coordinates: coord, distance: distance + 1, }) - ); + }); } nearest_coordinates @@ -263,17 +272,23 @@ impl Board { &self, start_coordinates: &CubeCoordinates, nearest_coordinates: HashSet, - distance: i32 + distance: i32, ) -> bool { - !nearest_coordinates.is_empty() && - distance > - start_coordinates.distance_to( - nearest_coordinates.iter().next().unwrap_or(start_coordinates) + !nearest_coordinates.is_empty() + && distance + > start_coordinates.distance_to( + nearest_coordinates + .iter() + .next() + .unwrap_or(start_coordinates), ) } fn __repr__(&self) -> PyResult { - Ok(format!("Board(segments={:?}, next_direction={:?})", self.segments, self.next_direction)) + Ok(format!( + "Board(segments={:?}, next_direction={:?})", + self.segments, self.next_direction + )) } } @@ -323,40 +338,40 @@ mod tests { Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], }]; let mut board: Board = Board::new(segment, CubeDirection::DownRight); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(0, 0)), true); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(0, 1)), true); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(-1, 1)), false); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(1, 1)), false); + assert!(board.does_field_have_stream(&CubeCoordinates::new(0, 0))); + assert!(board.does_field_have_stream(&CubeCoordinates::new(0, 1))); + assert!(!board.does_field_have_stream(&CubeCoordinates::new(-1, 1))); + assert!(!board.does_field_have_stream(&CubeCoordinates::new(1, 1))); segment = vec![Segment { direction: CubeDirection::DownRight, @@ -366,41 +381,41 @@ mod tests { Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], }]; board = Board::new(segment, CubeDirection::DownRight); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(0, 0)), true); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(0, 1)), true); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(-1, 1)), false); - assert_eq!(board.does_field_have_stream(&CubeCoordinates::new(1, 1)), false); + assert!(board.does_field_have_stream(&CubeCoordinates::new(0, 0))); + assert!(board.does_field_have_stream(&CubeCoordinates::new(0, 1))); + assert!(!board.does_field_have_stream(&CubeCoordinates::new(-1, 1))); + assert!(!board.does_field_have_stream(&CubeCoordinates::new(1, 1))); } #[test] @@ -439,29 +454,29 @@ mod tests { Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), Field::new(FieldType::Sandbank, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], }, Segment { @@ -472,13 +487,13 @@ mod tests { Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new( @@ -486,26 +501,26 @@ mod tests { Some(Passenger { direction: CubeDirection::DownRight, passenger: 1, - }) + }), ), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], - } + }, ]; let mut board: Board = Board::new(segment, CubeDirection::DownRight); @@ -526,8 +541,8 @@ mod tests { CubeCoordinates::new(0, -1), CubeCoordinates::new(1, -1) ] - .into_iter() - .collect() + .into_iter() + .collect() ); assert_eq!( board.find_nearest_field_types(&CubeCoordinates::new(2, 0), FieldType::Sandbank), diff --git a/src/plugin/coordinate.rs b/src/plugin/coordinate.rs index 447a317..f7a5a64 100644 --- a/src/plugin/coordinate.rs +++ b/src/plugin/coordinate.rs @@ -1,7 +1,7 @@ -use std::{ fmt, ops::Sub }; -use std::ops::{ Add, AddAssign, Mul, SubAssign, Neg, DivAssign, Div, MulAssign }; +use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, SubAssign}; +use std::{fmt, ops::Sub}; -use pyo3::{ pyclass, pymethods }; +use pyo3::{pyclass, pymethods}; #[pyclass] #[derive(PartialEq, Eq, PartialOrd, Clone, Copy, Debug, Hash)] @@ -247,11 +247,11 @@ impl CubeDirection { } pub fn angle(&self) -> i32 { - (self.clone() as i32) * 60 + (*self as i32) * 60 } pub fn with_neighbors(&self) -> [CubeDirection; 3] { - [self.rotated_by(-1), self.clone(), self.rotated_by(1)] + [self.rotated_by(-1), *self, self.rotated_by(1)] } pub fn opposite(&self) -> CubeDirection { @@ -266,16 +266,16 @@ impl CubeDirection { } pub fn turn_count_to(&self, target: CubeDirection) -> i32 { - let diff = ((target as i32) - (self.clone() as i32)).rem_euclid(6); + let diff = ((target as i32) - (*self as i32)).rem_euclid(6); if diff > 3 { - diff - (6 as i32) + diff - 6_i32 } else { diff } } pub fn rotated_by(&self, turns: i32) -> CubeDirection { - Self::VALUES[((self.clone() as i32) + (turns as i32)).rem_euclid(6) as usize] + Self::VALUES[((*self as i32) + turns).rem_euclid(6) as usize] } pub fn ordinal(&self) -> i32 { @@ -290,33 +290,40 @@ impl CubeDirection { } 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", - }) + format!( + "CubeDirection::{}", + match self { + CubeDirection::Right => "Right", + CubeDirection::DownRight => "DownRight", + CubeDirection::DownLeft => "DownLeft", + CubeDirection::Left => "Left", + CubeDirection::UpLeft => "UpLeft", + CubeDirection::UpRight => "UpRight", + } + ) } } impl fmt::Display for CubeDirection { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", match self { - CubeDirection::Right => "Right", - CubeDirection::DownRight => "DownRight", - CubeDirection::DownLeft => "DownLeft", - CubeDirection::Left => "Left", - CubeDirection::UpLeft => "UpLeft", - CubeDirection::UpRight => "UpRight", - }) + write!( + f, + "{}", + match self { + CubeDirection::Right => "Right", + CubeDirection::DownRight => "DownRight", + CubeDirection::DownLeft => "DownLeft", + CubeDirection::Left => "Left", + CubeDirection::UpLeft => "UpLeft", + CubeDirection::UpRight => "UpRight", + } + ) } } #[cfg(test)] mod tests { - use crate::plugin::coordinate::{ CubeDirection, CubeCoordinates }; + use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; #[test] fn test_cube_direction_angle() { @@ -330,36 +337,54 @@ mod tests { #[test] fn test_cube_direction_with_neighbors() { - assert_eq!(CubeDirection::Right.with_neighbors(), [ - CubeDirection::UpRight, - CubeDirection::Right, - CubeDirection::DownRight, - ]); - assert_eq!(CubeDirection::DownRight.with_neighbors(), [ - CubeDirection::Right, - CubeDirection::DownRight, - CubeDirection::DownLeft, - ]); - assert_eq!(CubeDirection::DownLeft.with_neighbors(), [ - CubeDirection::DownRight, - CubeDirection::DownLeft, - CubeDirection::Left, - ]); - assert_eq!(CubeDirection::Left.with_neighbors(), [ - CubeDirection::DownLeft, - CubeDirection::Left, - CubeDirection::UpLeft, - ]); - assert_eq!(CubeDirection::UpLeft.with_neighbors(), [ - CubeDirection::Left, - CubeDirection::UpLeft, - CubeDirection::UpRight, - ]); - assert_eq!(CubeDirection::UpRight.with_neighbors(), [ - CubeDirection::UpLeft, - CubeDirection::UpRight, - CubeDirection::Right, - ]); + assert_eq!( + CubeDirection::Right.with_neighbors(), + [ + CubeDirection::UpRight, + CubeDirection::Right, + CubeDirection::DownRight, + ] + ); + assert_eq!( + CubeDirection::DownRight.with_neighbors(), + [ + CubeDirection::Right, + CubeDirection::DownRight, + CubeDirection::DownLeft, + ] + ); + assert_eq!( + CubeDirection::DownLeft.with_neighbors(), + [ + CubeDirection::DownRight, + CubeDirection::DownLeft, + CubeDirection::Left, + ] + ); + assert_eq!( + CubeDirection::Left.with_neighbors(), + [ + CubeDirection::DownLeft, + CubeDirection::Left, + CubeDirection::UpLeft, + ] + ); + assert_eq!( + CubeDirection::UpLeft.with_neighbors(), + [ + CubeDirection::Left, + CubeDirection::UpLeft, + CubeDirection::UpRight, + ] + ); + assert_eq!( + CubeDirection::UpRight.with_neighbors(), + [ + CubeDirection::UpLeft, + CubeDirection::UpRight, + CubeDirection::Right, + ] + ); } #[test] @@ -375,11 +400,23 @@ mod tests { #[test] fn test_cube_direction_turn_count_to() { assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::Right), 0); - assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::DownRight), 1); - assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::DownLeft), 2); + assert_eq!( + CubeDirection::Right.turn_count_to(CubeDirection::DownRight), + 1 + ); + assert_eq!( + CubeDirection::Right.turn_count_to(CubeDirection::DownLeft), + 2 + ); assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::Left), 3); - assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::UpLeft), -2); - assert_eq!(CubeDirection::Right.turn_count_to(CubeDirection::UpRight), -1); + assert_eq!( + CubeDirection::Right.turn_count_to(CubeDirection::UpLeft), + -2 + ); + assert_eq!( + CubeDirection::Right.turn_count_to(CubeDirection::UpRight), + -1 + ); } #[test] diff --git a/src/plugin/field.rs b/src/plugin/field.rs index 483cb45..3c2259f 100644 --- a/src/plugin/field.rs +++ b/src/plugin/field.rs @@ -59,9 +59,9 @@ impl Field { } pub fn is_empty(&self) -> bool { - self.field_type == FieldType::Sandbank || - self.field_type == FieldType::Water || - self.field_type == FieldType::Goal + self.field_type == FieldType::Sandbank + || self.field_type == FieldType::Water + || self.field_type == FieldType::Goal } pub fn is_field_type(&self, field_type: FieldType) -> bool { @@ -90,31 +90,31 @@ mod tests { #[test] fn test_is_empty() { let field1 = Field::new(FieldType::Sandbank, None); - assert_eq!(field1.is_empty(), true); + assert!(field1.is_empty()); let field2 = Field::new(FieldType::Water, None); - assert_eq!(field2.is_empty(), true); + assert!(field2.is_empty()); let field3 = Field::new(FieldType::Goal, None); - assert_eq!(field3.is_empty(), true); + assert!(field3.is_empty()); let field4 = Field::new(FieldType::Island, None); - assert_eq!(field4.is_empty(), false); + assert!(!field4.is_empty()); let field5 = Field::new( FieldType::Passenger, Some(Passenger { direction: CubeDirection::DownRight, passenger: 1, - }) + }), ); - assert_eq!(field5.is_empty(), false); + assert!(!field5.is_empty()); } #[test] fn test_is_field_type() { let field = Field::new(FieldType::Water, None); - assert_eq!(field.is_field_type(FieldType::Water), true); - assert_eq!(field.is_field_type(FieldType::Island), false); + assert!(field.is_field_type(FieldType::Water)); + assert!(!field.is_field_type(FieldType::Island)); } } diff --git a/src/plugin/game_state.rs b/src/plugin/game_state.rs index 544657a..2269555 100644 --- a/src/plugin/game_state.rs +++ b/src/plugin/game_state.rs @@ -6,20 +6,20 @@ use pyo3::exceptions::PyBaseException; use pyo3::prelude::*; use crate::plugin::actions::accelerate::Accelerate; -use crate::plugin::actions::Action; use crate::plugin::actions::advance::Advance; use crate::plugin::actions::push::Push; use crate::plugin::actions::turn::Turn; +use crate::plugin::actions::Action; use crate::plugin::board::Board; use crate::plugin::constants::PluginConstants; -use crate::plugin::coordinate::{ CubeCoordinates, CubeDirection }; +use crate::plugin::coordinate::{CubeCoordinates, CubeDirection}; +use crate::plugin::errors::advance_errors::AdvanceProblem; use crate::plugin::errors::movement_error::MoveMistake; use crate::plugin::field::FieldType; use crate::plugin::r#move::Move; use crate::plugin::ship::Ship; -use crate::plugin::errors::advance_errors::AdvanceProblem; -use super::field::{ Field, Passenger }; +use super::field::{Field, Passenger}; use super::ship::TeamEnum; #[pyclass] @@ -52,7 +52,11 @@ impl AdvanceInfo { pub fn advances(&self, distance: Option) -> Vec { let distance = distance.unwrap_or(self.costs.len()); - (1..=distance).map(|it| Advance { distance: it as i32 }).collect() + (1..=distance) + .map(|it| Advance { + distance: it as i32, + }) + .collect() } pub fn distance(&self) -> usize { @@ -60,7 +64,10 @@ impl AdvanceInfo { } pub fn __repr__(&self) -> PyResult { - Ok(format!("AdvanceInfo(costs={:?}, problem={:?})", self.costs, self.problem)) + Ok(format!( + "AdvanceInfo(costs={:?}, problem={:?})", + self.costs, self.problem + )) } } @@ -154,7 +161,7 @@ impl GameState { turn: i32, current_ship: Ship, other_ship: Ship, - last_move: Option + last_move: Option, ) -> GameState { GameState { board, @@ -174,62 +181,55 @@ impl GameState { let other_points = calculate_points(self.other_ship); match (current_points.cmp(&other_points), &self.current_ship.team) { - (Ordering::Greater, _) => self.current_ship.clone(), - (Ordering::Less, _) => self.other_ship.clone(), - (_, TeamEnum::One) => self.current_ship.clone(), - _ => self.other_ship.clone(), + (Ordering::Greater, _) => self.current_ship, + (Ordering::Less, _) => self.other_ship, + (_, TeamEnum::One) => self.current_ship, + _ => self.other_ship, } } pub fn is_current_ship_on_current(&self) -> bool { - self.board.does_field_have_stream(&self.current_ship.position) + self.board + .does_field_have_stream(&self.current_ship.position) } pub fn perform_action(&self, action: Action) -> Result { let mut new_state = self.clone(); match action { - Action::Accelerate(accelerate) => { - match accelerate.perform(&new_state) { - Ok(updated_ship) => { - new_state.current_ship = updated_ship; - } - Err(e) => { - return Err(e); - } + Action::Accelerate(accelerate) => match accelerate.perform(&new_state) { + Ok(updated_ship) => { + new_state.current_ship = updated_ship; } - } - Action::Advance(advance) => { - match advance.perform(&new_state) { - Ok(updated_ship) => { - new_state.current_ship = updated_ship; - } - Err(e) => { - return Err(e); - } + Err(e) => { + return Err(e); } - } - Action::Turn(turn) => { - match turn.perform(&new_state) { - Ok(updated_ship) => { - new_state.current_ship = updated_ship; - } - Err(e) => { - return Err(e); - } + }, + Action::Advance(advance) => match advance.perform(&new_state) { + Ok(updated_ship) => { + new_state.current_ship = updated_ship; } - } - Action::Push(push) => { - match push.perform(&new_state) { - Ok((updated_current_ship, updated_other_ship)) => { - new_state.current_ship = updated_current_ship; - new_state.other_ship = updated_other_ship; - } - Err(e) => { - return Err(e); - } + Err(e) => { + return Err(e); } - } + }, + Action::Turn(turn) => match turn.perform(&new_state) { + Ok(updated_ship) => { + new_state.current_ship = updated_ship; + } + Err(e) => { + return Err(e); + } + }, + Action::Push(push) => match push.perform(&new_state) { + Ok((updated_current_ship, updated_other_ship)) => { + new_state.current_ship = updated_current_ship; + new_state.other_ship = updated_other_ship; + } + Err(e) => { + return Err(e); + } + }, } Ok(new_state) @@ -238,16 +238,21 @@ impl GameState { fn move_pre_check(&self, action: Action, action_idx: usize, ship: Ship) -> Result<(), PyErr> { match action { Action::Push(_) if !self.must_push() => { - return Err(PyBaseException::new_err(MoveMistake::PushActionRequired.message())); + return Err(PyBaseException::new_err( + MoveMistake::PushActionRequired.message(), + )); } Action::Accelerate(_) if action_idx != 0 => { - return Err(PyBaseException::new_err(MoveMistake::FirstActionAccelerate.message())); + return Err(PyBaseException::new_err( + MoveMistake::FirstActionAccelerate.message(), + )); } - Action::Advance(ad) if - self.board + Action::Advance(ad) + if self + .board .get(&(ship.position + ship.direction.vector() * ad.distance)) - .map_or(false, |f| f.field_type == FieldType::Sandbank) - => { + .map_or(false, |f| f.field_type == FieldType::Sandbank) => + { return Err(PyBaseException::new_err(MoveMistake::SandBankEnd.message())); } _ => {} @@ -258,15 +263,11 @@ impl GameState { fn move_after_check(&self, ship: Ship) -> Result<(), PyErr> { if ship.movement != 0 { - return Err( - PyBaseException::new_err( - if ship.movement > 0 { - MoveMistake::MovementPointsLeft.message() - } else { - MoveMistake::MovementPointsMissing.message() - } - ) - ); + return Err(PyBaseException::new_err(if ship.movement > 0 { + MoveMistake::MovementPointsLeft.message() + } else { + MoveMistake::MovementPointsMissing.message() + })); } Ok(()) @@ -284,9 +285,7 @@ impl GameState { debug!("Actions: {:?}", move_.actions); for (i, action) in move_.actions.iter().enumerate() { - new_state.move_pre_check(*action, i, self.current_ship).map_err(|e| { - return e; - })?; + new_state.move_pre_check(*action, i, self.current_ship)?; match new_state.perform_action(*action) { Ok(state) => { new_state = state; @@ -297,9 +296,7 @@ impl GameState { } } - new_state.move_after_check(new_state.current_ship).map_err(|e| { - return e; - })?; + new_state.move_after_check(new_state.current_ship)?; new_state.pick_up_passenger_current_ship(); new_state.current_ship.points = new_state @@ -324,6 +321,7 @@ impl GameState { Ok(new_state) } + #[allow(clippy::nonminimal_bool)] pub fn advance_turn(&mut self) { self.current_ship.free_acc = 1; self.current_ship.free_turns = 1; @@ -331,9 +329,8 @@ impl GameState { self.turn += 1; - if - (self.turn % 2 == 0 && self.determine_ahead_team() != self.current_ship) || - self.turn % 2 != 0 + if (self.turn % 2 == 0 && self.determine_ahead_team() != self.current_ship) + || self.turn % 2 != 0 { swap(&mut self.current_ship, &mut self.other_ship); } @@ -360,7 +357,7 @@ impl GameState { self.board.set_field_in_direction( &d, &coord, - Field::new(field.field_type, Some(updated_passenger)) + Field::new(field.field_type, Some(updated_passenger)), ); return true; } @@ -371,45 +368,45 @@ impl GameState { } 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"); - } + if self.effective_speed(self.current_ship) < 2 + && 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"); } } pub fn pick_up_passenger_other_ship(&mut self) { - 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"); - } + if self.effective_speed(self.other_ship) < 2 + && 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"); } } pub fn ship_advance_points(&self, ship: Ship) -> Option { let (i, segment) = self.board.segment_with_index_at(ship.position)?; Some( - (i as i32) * PluginConstants::POINTS_PER_SEGMENT + - segment.global_to_local(ship.position).array_x() + - 1 + (i as i32) * PluginConstants::POINTS_PER_SEGMENT + + segment.global_to_local(ship.position).array_x() + + 1, ) } pub fn ship_points(&self, ship: Ship) -> Option { Some( - self.ship_advance_points(ship.clone())? + - (ship.passengers as i32) * PluginConstants::POINTS_PER_PASSENGER + self.ship_advance_points(ship)? + + ship.passengers * PluginConstants::POINTS_PER_PASSENGER, ) } pub fn must_push(&self) -> bool { - &self.current_ship.position == &self.other_ship.position + self.current_ship.position == self.other_ship.position } pub fn check_ship_advance_limit(&self, ship: &Ship) -> AdvanceInfo { @@ -420,7 +417,7 @@ impl GameState { &self, start: &CubeCoordinates, direction: &CubeDirection, - max_movement_points: i32 + max_movement_points: i32, ) -> AdvanceInfo { let max_movement = max_movement_points.clamp(0, PluginConstants::MAX_SPEED); let mut current_position = *start; @@ -443,9 +440,8 @@ impl GameState { } } - if - self.current_ship.position == current_position || - self.other_ship.position == current_position + if self.current_ship.position == current_position + || self.other_ship.position == current_position { if total_cost < max_movement { costs.push(total_cost); @@ -454,21 +450,33 @@ impl GameState { problem: AdvanceProblem::ShipAlreadyInTarget, }; } - return AdvanceInfo { costs, problem: AdvanceProblem::InsufficientPush }; + return AdvanceInfo { + costs, + problem: AdvanceProblem::InsufficientPush, + }; } if let FieldType::Sandbank = field.field_type { - return AdvanceInfo { costs, problem: AdvanceProblem::MoveEndOnSandbank }; + return AdvanceInfo { + costs, + problem: AdvanceProblem::MoveEndOnSandbank, + }; } costs.push(total_cost); } _ => { - return AdvanceInfo { costs, problem: AdvanceProblem::FieldIsBlocked }; + return AdvanceInfo { + costs, + problem: AdvanceProblem::FieldIsBlocked, + }; } } } - return AdvanceInfo { costs, problem: AdvanceProblem::MovementPointsMissing }; + AdvanceInfo { + costs, + problem: AdvanceProblem::MovementPointsMissing, + } } fn merge_consecutive_advances(&self, actions: Vec) -> Vec { @@ -484,7 +492,9 @@ impl GameState { total_distance += a.distance; } } - merged_actions.push(Action::Advance(Advance { distance: total_distance })); + merged_actions.push(Action::Advance(Advance { + distance: total_distance, + })); } _ => merged_actions.push(action), } @@ -494,41 +504,37 @@ impl GameState { } pub fn possible_moves(&self, depth: Option) -> Vec { - self.possible_action_comb(&self, vec![], 0, depth.unwrap_or(PluginConstants::MAX_DEPTH)) + self.possible_action_comb(self, vec![], 0, depth.unwrap_or(PluginConstants::MAX_DEPTH)) .into_iter() .map(|actions| Move { actions }) .collect() } + #[allow(clippy::only_used_in_recursion)] pub fn possible_action_comb( &self, current_state: &GameState, current_actions: Vec, depth: usize, - max_depth: usize + max_depth: usize, ) -> Vec> { if depth > max_depth || (!current_state.can_move() && !current_state.must_push()) { return current_state .move_after_check(current_state.current_ship) - .map_or(vec![], |_| + .map_or(vec![], |_| { vec![current_state.merge_consecutive_advances(current_actions)] - ); + }); } current_state .possible_actions(depth, None) .iter() .filter_map(|&action| { - current_state - .perform_action(action) - .ok() - .and_then(|new_state| { - let mut new_actions = current_actions.clone(); - new_actions.push(action); - Some( - self.possible_action_comb(&new_state, new_actions, depth + 1, max_depth) - ) - }) + current_state.perform_action(action).ok().map(|new_state| { + let mut new_actions = current_actions.clone(); + new_actions.push(action); + self.possible_action_comb(&new_state, new_actions, depth + 1, max_depth) + }) }) .flatten() .collect() @@ -561,21 +567,22 @@ impl GameState { } pub fn possible_pushes(&self) -> Vec { - if - self.board.get_field_in_direction( - &self.current_ship.direction, - &self.current_ship.position - ) == Some(Field::new(FieldType::Sandbank, None)) || - !self.must_push() || - self.current_ship.movement < 1 + if self + .board + .get_field_in_direction(&self.current_ship.direction, &self.current_ship.position) + == Some(Field::new(FieldType::Sandbank, None)) + || !self.must_push() + || self.current_ship.movement < 1 { return Vec::new(); } - CubeDirection::VALUES.into_iter() + CubeDirection::VALUES + .into_iter() .filter(|&dir| { - dir != self.current_ship.direction.opposite() && - self.board + dir != self.current_ship.direction.opposite() + && self + .board .get_field_in_direction(&dir, &self.current_ship.position) .map_or(false, |f| f.is_empty()) }) @@ -584,10 +591,9 @@ impl GameState { } pub fn possible_turns(&self, max_coal: Option) -> Vec { - if - self.board.get(&self.current_ship.position) == - Some(Field::new(FieldType::Sandbank, None)) || - self.must_push() + if self.board.get(&self.current_ship.position) + == Some(Field::new(FieldType::Sandbank, None)) + || self.must_push() { return Vec::new(); } @@ -599,7 +605,7 @@ impl GameState { .flat_map(|i| { vec![ Turn::new(self.current_ship.direction.rotated_by(i as i32)), - Turn::new(self.current_ship.direction.rotated_by(-(i as i32))) + Turn::new(self.current_ship.direction.rotated_by(-(i as i32))), ] }) .take(5) @@ -611,7 +617,8 @@ impl GameState { return Vec::new(); } - self.check_ship_advance_limit(&self.current_ship).advances(None) + self.check_ship_advance_limit(&self.current_ship) + .advances(None) } #[allow(unused_variables)] @@ -628,10 +635,16 @@ impl GameState { if rank == 0 { actions.extend( - self.possible_accelerations(Some(max_coal)).into_iter().map(Action::Accelerate) + self.possible_accelerations(Some(max_coal)) + .into_iter() + .map(Action::Accelerate), ); } - actions.extend(self.possible_turns(Some(max_coal)).into_iter().map(Action::Turn)); + actions.extend( + self.possible_turns(Some(max_coal)) + .into_iter() + .map(Action::Turn), + ); actions.extend(self.possible_advances().into_iter().map(Action::Advance)); if rank != 0 { actions.extend(self.possible_pushes().into_iter().map(Action::Push)); @@ -643,13 +656,13 @@ impl GameState { 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) + (acc.acc.unsigned_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) + (turn_count.unsigned_abs() as usize) - (self.current_ship.free_turns as usize) } - Action::Push(_) | Action::Advance(_) => { 0 } + Action::Push(_) | Action::Advance(_) => 0, } } @@ -665,18 +678,18 @@ impl GameState { pub fn is_over(&self) -> bool { // Bedingung 1: ein Dampfer mit 2 Passagieren erreicht ein Zielfeld mit Geschwindigkeit 1 - let condition1 = - self.turn % 2 == 0 && - (self.is_winner(&self.current_ship) || self.is_winner(&self.other_ship)); + let condition1 = self.turn % 2 == 0 + && (self.is_winner(&self.current_ship) || self.is_winner(&self.other_ship)); // Bedingung 2: ein Spieler macht einen ungültigen Zug. // Das wird durch eine InvalidMoveException während des Spiels behandelt. // Bedingung 3: am Ende einer Runde liegt ein Dampfer mehr als 3 Spielsegmente zurück - let condition3 = - self.board - .segment_distance(&self.current_ship.position, &self.other_ship.position) - .abs() > 3; + let condition3 = self + .board + .segment_distance(&self.current_ship.position, &self.other_ship.position) + .abs() + > 3; // Bedingung 4: das Rundenlimit von 30 Runden ist erreicht let condition4 = self.turn / 2 >= PluginConstants::ROUND_LIMIT; @@ -688,13 +701,19 @@ impl GameState { } pub fn is_winner(&self, ship: &Ship) -> bool { - ship.passengers > 1 && - self.board.effective_speed(ship) < 2 && - self.board + ship.passengers > 1 + && self.board.effective_speed(ship) < 2 + && self + .board .get(&ship.position) .unwrap_or_else(|| { - panic!("[is_winner] Field at position {} does not exist", ship.position) - }).field_type == FieldType::Goal + panic!( + "[is_winner] Field at position {} does not exist", + ship.position + ) + }) + .field_type + == FieldType::Goal } pub fn get_points_for_team(&self, ship: &Ship) -> TeamPoints { @@ -707,22 +726,24 @@ impl GameState { } fn __repr__(&self) -> PyResult { - Ok( - format!( - "GameState(board={:?} segments, turn={}, team_one={:?}, team_two={:?}, last_move={:?})", - self.board.segments.len(), - self.turn, - self.current_ship, - self.other_ship, - self.last_move - ) - ) + Ok(format!( + "GameState(board={:?} segments, turn={}, team_one={:?}, team_two={:?}, last_move={:?})", + self.board.segments.len(), + self.turn, + self.current_ship, + self.other_ship, + self.last_move + )) } } #[cfg(test)] mod tests { - use crate::plugin::{ segment::Segment, field::{ Field, Passenger }, ship::TeamEnum }; + use crate::plugin::{ + field::{Field, Passenger}, + segment::Segment, + ship::TeamEnum, + }; use super::*; @@ -730,10 +751,7 @@ mod tests { Segment { direction, center, - fields: vec![ - vec![Field::new(FieldType::Water, None); 4]; - 5 - ], + fields: vec![vec![Field::new(FieldType::Water, None); 4]; 5], } } @@ -742,19 +760,29 @@ mod tests { } fn create_game_state(segment: Vec, team_one: Ship, team_two: Ship) -> GameState { - GameState::new(Board::new(segment, CubeDirection::Right), 0, team_one, team_two, None) + 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 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))) + Field::new( + FieldType::Passenger, + Some(Passenger::new(CubeDirection::UpLeft, 1)), + ), ); let mut game_state = create_game_state(segment, team_one, team_two); @@ -770,7 +798,10 @@ mod tests { #[test] fn find_possible_moves_returns_correct_count() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let 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); let game_state = create_game_state(segment, team_one, team_two); @@ -781,7 +812,10 @@ mod tests { #[test] fn test_check_advance_limit() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -799,7 +833,10 @@ mod tests { #[test] fn test_check_advance_limit_to_upperleft_end() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, -1), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -818,7 +855,10 @@ mod tests { #[test] fn test_get_accelerations() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -834,7 +874,10 @@ mod tests { #[test] fn test_get_turns() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -850,7 +893,10 @@ mod tests { #[test] fn test_get_advances() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -866,7 +912,10 @@ mod tests { #[test] fn test_get_pushes() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -882,7 +931,10 @@ mod tests { #[test] fn test_only_pushes_if_must_push() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let segment = vec![create_water_segment( + CubeCoordinates::new(0, 0), + CubeDirection::Right, + )]; let mut team_one = create_ship(CubeCoordinates::new(0, 0), TeamEnum::One); team_one.speed = 5; team_one.movement = 5; @@ -907,32 +959,32 @@ mod tests { Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], }, Segment { @@ -943,37 +995,37 @@ mod tests { Field::new(FieldType::Water, None), Field::new( FieldType::Passenger, - Some(Passenger::new(CubeDirection::DownLeft, 1)) + Some(Passenger::new(CubeDirection::DownLeft, 1)), ), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Island, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) + Field::new(FieldType::Water, None), ], vec![ Field::new(FieldType::Island, None), Field::new(FieldType::Water, None), Field::new(FieldType::Water, None), - Field::new(FieldType::Water, None) - ] + Field::new(FieldType::Water, None), + ], ], - } + }, ]; let board: Board = Board::new(segment, CubeDirection::Right); let team_one: &mut Ship = &mut Ship::new( @@ -985,7 +1037,7 @@ mod tests { Some(0), Some(0), Some(0), - Some(1) + Some(1), ); let team_two: &mut Ship = &mut Ship::new( CubeCoordinates::new(-2, 1), @@ -996,22 +1048,20 @@ mod tests { Some(0), Some(0), Some(0), - Some(1) - ); - let game_state: GameState = GameState::new( - board, - 0, - team_one.clone(), - team_two.clone(), - None + Some(1), ); + let game_state: GameState = GameState::new(board, 0, *team_one, *team_two, None); - let move_: Move = Move::new( - vec![Action::Accelerate(Accelerate::new(1)), Action::Advance(Advance::new(2))] - ); + let move_: Move = Move::new(vec![ + Action::Accelerate(Accelerate::new(1)), + Action::Advance(Advance::new(2)), + ]); assert_eq!(game_state.current_ship.team, TeamEnum::One); - assert_eq!(game_state.current_ship.position, CubeCoordinates::new(-1, -1)); + assert_eq!( + game_state.current_ship.position, + CubeCoordinates::new(-1, -1) + ); let new_state: GameState = game_state.perform_move(move_).unwrap(); assert_eq!(new_state.other_ship.team, TeamEnum::One); @@ -1020,20 +1070,30 @@ mod tests { assert_eq!(new_state.current_ship.team, TeamEnum::Two); assert_eq!(new_state.current_ship.position, CubeCoordinates::new(-2, 1)); - let second_move_: Move = Move::new( - vec![Action::Accelerate(Accelerate::new(1)), Action::Advance(Advance::new(2))] - ); + let second_move_: Move = Move::new(vec![ + Action::Accelerate(Accelerate::new(1)), + Action::Advance(Advance::new(2)), + ]); let second_new_state: GameState = new_state.perform_move(second_move_).unwrap(); assert_eq!(second_new_state.current_ship.team, TeamEnum::One); - assert_eq!(second_new_state.current_ship.position, CubeCoordinates::new(1, -1)); + assert_eq!( + second_new_state.current_ship.position, + CubeCoordinates::new(1, -1) + ); assert_eq!(second_new_state.other_ship.team, TeamEnum::Two); - assert_eq!(second_new_state.other_ship.position, CubeCoordinates::new(0, 1)); + assert_eq!( + second_new_state.other_ship.position, + CubeCoordinates::new(0, 1) + ); } #[test] fn test_advance_turn() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let 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); let mut game_state = create_game_state(segment, team_one, team_two); @@ -1054,7 +1114,10 @@ mod tests { #[test] fn test_team_ahead() { - let segment = vec![create_water_segment(CubeCoordinates::new(0, 0), CubeDirection::Right)]; + let 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); let game_state = create_game_state(segment, team_one, team_two); @@ -1067,9 +1130,10 @@ mod tests { assert_eq!(new_state.determine_ahead_team().team, TeamEnum::One); - let second_move: Move = Move::new( - vec![Action::Accelerate(Accelerate::new(1)), Action::Advance(Advance::new(2))] - ); + let second_move: Move = Move::new(vec![ + Action::Accelerate(Accelerate::new(1)), + Action::Advance(Advance::new(2)), + ]); let second_new_state: GameState = new_state.perform_move(second_move).unwrap(); diff --git a/src/plugin/ship.rs b/src/plugin/ship.rs index 9142835..625863f 100644 --- a/src/plugin/ship.rs +++ b/src/plugin/ship.rs @@ -1,6 +1,10 @@ use pyo3::prelude::*; -use super::{ constants::PluginConstants, coordinate::{ CubeCoordinates, CubeDirection }, game_state::AdvanceInfo }; +use super::{ + constants::PluginConstants, + coordinate::{CubeCoordinates, CubeDirection}, + game_state::AdvanceInfo, +}; #[derive(PartialEq, Eq, PartialOrd, Clone, Debug, Hash, Copy)] #[pyclass] @@ -46,6 +50,7 @@ pub struct Ship { #[pymethods] impl Ship { #[new] + #[allow(clippy::too_many_arguments)] pub fn new( position: CubeCoordinates, team: TeamEnum, @@ -55,7 +60,7 @@ impl Ship { passengers: Option, points: Option, free_turns: Option, - movement: Option + movement: Option, ) -> Self { Ship { team, @@ -67,7 +72,7 @@ impl Ship { free_turns: free_turns.unwrap_or(PluginConstants::FREE_TURNS), points: points.unwrap_or(0), free_acc: PluginConstants::FREE_ACC, - movement: movement.unwrap_or(speed.unwrap_or(PluginConstants::MIN_SPEED)), + movement: movement.unwrap_or_else(|| speed.unwrap_or(PluginConstants::MIN_SPEED)), } } @@ -90,7 +95,11 @@ impl Ship { } pub fn resolve_direction(&self, reverse: bool) -> CubeDirection { - if reverse { self.direction.opposite() } else { self.direction } + if reverse { + self.direction.opposite() + } else { + self.direction + } } pub fn update_position(&mut self, distance: i32, advance_info: AdvanceInfo) { @@ -135,14 +144,14 @@ mod tests { free_acc: 0, movement: 0, }; - assert_eq!(ship.can_turn(), false); + assert!(!ship.can_turn()); ship.free_turns = 1; - assert_eq!(ship.can_turn(), true); + assert!(ship.can_turn()); ship.free_turns = 0; ship.coal = 1; - assert_eq!(ship.can_turn(), true); + assert!(ship.can_turn()); } #[test]