#!/usr/bin/env python3 from __future__ import annotations from typing import Callable, Iterable, Optional, List, Set import collections import enum import json import sys # Třídy reprezentující hru class Direction(enum.Enum): UP = 0 LEFT = 1 DOWN = 2 RIGHT = 3 STAY = None def combine(self, other) -> Direction: """Rotate by another direction.""" return Direction((self.value + other.value) % 4) def invert(self) -> Direction: """Get the opposite direction.""" if self == Direction.STAY: return self return self.combine(Direction.DOWN) def __str__(self) -> str: return self.name.lower() class Team: home: Field def __init__(self, id: int, is_me: bool) -> None: self.id = id self.is_me = is_me self.protected_fields: List[Field] = [] self.occupied: List[Field] = [] self.members: List[Member] = [] def __eq__(self, other: object) -> bool: if not isinstance(other, Team): return NotImplemented return (self.id) == (other.id) def __hash__(self) -> int: return hash(self.id) class Member: def __init__(self, field: Field, team: Team, id: int) -> None: self.field = field self.team = team self.id = id self.action = Direction.STAY class Field: members: List[Member] def __init__(self, i: int, j: int, hill: bool, home_for_team: Optional[Team], occupied_by_team: Optional[Team], protected_for_team: Optional[Team],) -> None: self.i = i self.j = j self.hill = hill self.home_for_team: Optional[Team] = home_for_team self.occupied_by_team: Optional[Team] = occupied_by_team self.protected_for_team: Optional[Team] = protected_for_team def __eq__(self, other: object) -> bool: if not isinstance(other, Field): return NotImplemented return (self.i, self.j) == (other.i, other.j) def __hash__(self) -> int: return hash((self.i, self.j)) def get_neighbour_field(self, action: Direction) -> Field: """Get the next field in the given direction.""" if action == Direction.UP: neighbour_i = self.i - 1 neighbour_j = self.j elif action == Direction.DOWN: neighbour_i = self.i + 1 neighbour_j = self.j elif action == Direction.LEFT: neighbour_i = self.i neighbour_j = self.j - 1 elif action == Direction.RIGHT: neighbour_i = self.i neighbour_j = self.j + 1 else: neighbour_i = self.i neighbour_j = self.j # ensure coords are in bounds neighbour_i %= len(state.world) neighbour_j %= len(state.world[0]) return state.world[neighbour_i][neighbour_j] def is_accessible(self, team: Team) -> bool: """Jestli daný tým může vstoupit na políčko.""" if self.hill: return False if (self.protected_for_team is not None and self.protected_for_team != team): return False return True class State: """The entire game state. Also contains methods for parsing and actions submission. """ def __init__(self, state: dict) -> None: self.teams = [Team(i, i == state["team_id"]) for i in range(state["teams_count"])] self.world = self._parse_world(state["state"]["world"]) self.my_team: Team = self.teams[state["team_id"]] self.round_number: int = state["round"] self.time_to_response: Optional[int] = state["time_to_response"] def _parse_world(self, world: dict) -> List[List[Field]]: def get_team(team_id: int) -> Optional[Team]: return None if team_id is None else self.teams[team_id] fields = [] for i, row in enumerate(world): fields_row = [] for j, field in enumerate(row): parsed_field = Field( i, j, field["hill"], get_team(field["home_for_team"]), get_team(field["occupied_by_team"]), get_team(field["protected_for_team"]), ) if parsed_field.home_for_team: parsed_field.home_for_team.home = parsed_field if parsed_field.occupied_by_team: parsed_field.occupied_by_team.occupied.append(parsed_field) if parsed_field.protected_for_team: parsed_field.protected_for_team.protected_fields.append(parsed_field) members = [] for member in field["members"]: member_team = self.teams[member["team_id"]] m = Member( parsed_field, member_team, member["id"], ) members.append(m) member_team.members.append(m) parsed_field.members = members fields_row.append(parsed_field) fields.append(fields_row) return fields def build_turn(self) -> dict: """Build a dictionary with member actions.""" for team in self.teams: if not team.is_me: for member in team.members: if member.action is not Direction.STAY: raise Exception("Akce přiřazena cizímu týmu") return { "members": [ {"id": member.id, "action": str(member.action)} for member in self.my_team.members ] } state: State # Algoritmy def find_fields(predicate: Callable[[Field], bool]) -> List[Field]: """Find all fields that satisfy the given predicate.""" result = [] for row in state.world: for field in row: if predicate(field): result.append(field) return result def pathfind(member: Member, goal: Field) -> Direction: """Find the shortest path from member position to goal. Returns the first action to take. If there is no path, returns Action.STAY. """ # BFS from goal to member # this direction makes it easier to realize that no path exists explored: Set[Field] = set() queue = collections.deque([goal]) while queue: field = queue.popleft() for action in Direction: neighbour = field.get_neighbour_field(action) if (neighbour in explored or not neighbour.is_accessible(member.team)): continue if neighbour == member.field: return action.invert() queue.append(neighbour) explored.add(neighbour) return Direction.STAY # Strategie def main() -> None: global state state = State(json.loads(sys.stdin.read())) # TODO: zde doplňte svou herní strategii # příklad: všechny vojáky posílám nahoru for member in state.my_team.members: member.action = Direction.UP print(json.dumps(state.build_turn())) if __name__ == '__main__': main()