diff --git a/socha/api/plugin/penguins/board.py b/socha/api/plugin/penguins/board.py index d0b3858..31c42b0 100644 --- a/socha/api/plugin/penguins/board.py +++ b/socha/api/plugin/penguins/board.py @@ -1,6 +1,8 @@ import _pickle as pickle import logging import warnings +from itertools import chain, takewhile +from operator import attrgetter from typing import List, Union, Optional from socha.api.plugin.penguins.coordinate import HexCoordinate, Vector, CartesianCoordinate @@ -110,12 +112,7 @@ def get_empty_fields(self) -> List[Field]: """ :return: A list of all empty fields. """ - fields: List[Field] = [] - for row in self.board: - for field in row: - if field.is_empty(): - fields.append(field) - return fields + return list(filter(lambda field: field.is_empty(), chain(*self.board))) def is_occupied(self, coordinates: HexCoordinate) -> bool: """ @@ -179,9 +176,7 @@ def get_field_or_none(self, position: HexCoordinate) -> Union[Field, None]: :return: The field at the given position, or None if the position is not valid. """ cartesian = position.to_cartesian() - if self.is_valid(position): - return self._get_field(cartesian.x, cartesian.y) - return None + return self._get_field(cartesian.x, cartesian.y) if self.is_valid(position) else None def get_field_by_index(self, index: int) -> Field: """ @@ -227,10 +222,7 @@ def contains(self, field: Field) -> bool: :param field: The field to check for. :return: True if the board contains the field, False otherwise. """ - for row in self.board: - if field in row: - return True - return False + return any(field in row for row in self.board) def contains_all(self, fields: List[Field]) -> bool: """ @@ -242,37 +234,31 @@ def contains_all(self, fields: List[Field]) -> bool: if not fields: return False - for field in fields: - if not self.contains(field): - return False - return True + return all(self.contains(field) for field in fields) def get_moves_in_direction(self, origin: HexCoordinate, direction: Vector, team_enum: Optional[TeamEnum] = None) \ -> List[Move]: """ Gets all moves in the given direction from the given origin. - Args: origin: The origin of the move. direction: The direction of the move. team_enum: Team to make moves for. - Returns: List[Move]: List of moves that can be made in the given direction from the given index, for the given team_enum """ - if team_enum is None: - team_enum = self.get_field(origin).penguin.team_enum + team_enum = team_enum or self.get_field(origin).penguin.team_enum if not self.get_field(origin).penguin or self.get_field(origin).penguin.team_enum != team_enum: return [] - moves = [] - for i in range(1, self.width()): + def valid_destination(i): destination = origin.add_vector(direction.scalar_product(i)) - if self._is_destination_valid(destination): - moves.append(Move(team_enum=team_enum, from_value=origin, to_value=destination)) - else: - break + return self._is_destination_valid(destination) + + moves = [Move(team_enum=team_enum, from_value=origin, to_value=origin.add_vector(direction.scalar_product(i))) + for i in takewhile(valid_destination, range(1, self.width()))] + return moves def _is_destination_valid(self, field: HexCoordinate) -> bool: @@ -316,12 +302,9 @@ def get_teams_penguins(self, team: TeamEnum) -> List[Penguin]: :param team: The team_enum to search for. :return: A list of all coordinates that are occupied by a penguin of the given team_enum. """ - penguins = [] - for row in self.board: - for field in row: - if field.penguin and field.penguin.team_enum == team: - penguins.append(field.penguin) - return penguins + is_team_penguin = lambda field: field.penguin and field.penguin.team_enum == team + penguins = filter(is_team_penguin, (field for row in self.board for field in row)) + return list(map(attrgetter('penguin'), penguins)) def get_most_fish(self) -> List[Field]: """ @@ -329,13 +312,9 @@ def get_most_fish(self) -> List[Field]: :return: A list of Fields. """ - - fields = list(filter(lambda field_x: not field_x.is_occupied(), self.get_all_fields())) - fields.sort(key=lambda field_x: field_x.get_fish(), reverse=True) - for i, field in enumerate(fields): - if field.get_fish() < fields[0].get_fish(): - fields = fields[:i] - return fields + fields = [field for field in self.get_all_fields() if not field.is_occupied()] + max_fish = max(fields, key=lambda field: field.get_fish()).get_fish() + return list(filter(lambda field: field.get_fish() == max_fish, fields)) def get_board_intersection(self, other: 'Board') -> List[Field]: """ @@ -389,16 +368,13 @@ def move(self, move: Move) -> 'Board': def pretty_print(self): print() for i, row in enumerate(self.board): + row_str = "" if (i + 1) % 2 == 0: - print(" ", end="") - for field in row: - if field.is_empty(): - print("~", end=" ") - elif field.is_occupied(): - print(field.get_team().value[0], end=" ") - else: - print(field.get_fish(), end=" ") - print() + row_str += " " + row_str += " ".join(["~" if field.is_empty() else field.get_team().value[0] if field.is_occupied() else str( + field.get_fish()) + for field in row]) + print(row_str) print() def __eq__(self, other): diff --git a/socha/api/plugin/penguins/game_state.py b/socha/api/plugin/penguins/game_state.py index f255a3d..1f9edf9 100644 --- a/socha/api/plugin/penguins/game_state.py +++ b/socha/api/plugin/penguins/game_state.py @@ -1,6 +1,5 @@ import _pickle as pickle import logging -from dataclasses import dataclass from typing import List, Optional from socha.api.plugin.penguins.board import Board @@ -47,6 +46,7 @@ def __init__(self, board: Board, turn: int, first_team: Team, second_team: Team, self.first_team = first_team self.second_team = second_team self.last_move = last_move + self.possible_moves = self._get_possible_moves(self.current_team) @property def round(self): @@ -64,10 +64,6 @@ def other_team(self): def current_pieces(self): return self.current_team.get_penguins() - @property - def possible_moves(self): - return self._get_possible_moves(self.current_team) - def _get_possible_moves(self, current_team: Optional[Team]) -> List[Move]: """ Gets all possible moves for the current team. @@ -77,23 +73,22 @@ def _get_possible_moves(self, current_team: Optional[Team]) -> List[Move]: :return: A list of all possible moves from the current player's turn. """ current_team = current_team or self.current_team - moves = [] - if not current_team: - return moves + return [] if len(self.board.get_teams_penguins(current_team.name)) < 4: - for x in range(self.board.width()): - for y in range(self.board.height()): - field = self.board.get_field(CartesianCoordinate(x, y).to_hex()) - if not field.is_occupied() and field.get_fish() == 1: - moves.append( - Move(team_enum=current_team.name, from_value=None, - to_value=CartesianCoordinate(x, y).to_hex())) + moves = [(x, y) for x in range(self.board.width()) for y in range(self.board.height())] + moves = filter(lambda pos: not self.board.get_field( + CartesianCoordinate(*pos).to_hex()).is_occupied() and self.board.get_field( + CartesianCoordinate(*pos).to_hex()).get_fish() == 1, moves) + moves = map(lambda pos: Move(team_enum=current_team.name, from_value=None, + to_value=CartesianCoordinate(*pos).to_hex()), moves) + return list(moves) else: - for piece in self.board.get_teams_penguins(current_team.name): - moves.extend(self.board.possible_moves_from(piece.coordinate, current_team.name)) - return moves + pieces = self.board.get_teams_penguins(current_team.name) + moves = map(lambda piece: self.board.possible_moves_from(piece.coordinate, current_team.name), pieces) + moves = [item for sublist in moves for item in sublist] + return moves def current_team_from_turn(self, turn: int) -> Team: """