import math import random import sys from enum import Enum, IntEnum from typing import List, Sequence, Optional, Dict DEFAULT_SHOOT_WAIT = 2 DEFAULT_MINE_WAIT = 4 class EntityType(Enum): BARREL = 'BARREL' SHIP = 'SHIP' MINE = 'MINE' CANNONBALL = 'CANNONBALL' class Orientation(IntEnum): RIGHT, TOP_RIGHT, TOP_LEFT, LEFT, BOTTOM_LEFT, BOTTOM_RIGHT = range(6) class Action(Enum): MINE = 'MINE' MOVE = 'MOVE' FIRE = 'FIRE' WAIT = 'WAIT' SLOWER = 'SLOWER' class GameMap: def __init__(self, barrels: List['Barrel'] = None, my_ships: Dict[int, 'Ship'] = None, opponent_ships: List['Ship'] = None, mines: List['Mine'] = None, cannonballs: List['Cannonball'] = None): if opponent_ships is None: opponent_ships = [] if cannonballs is None: cannonballs = [] if mines is None: mines = [] if my_ships is None: my_ships = {} if barrels is None: barrels = [] self.opponent_ships = opponent_ships self.cannonballs = cannonballs self.mines = mines self.my_ships = my_ships self.barrels = barrels self.map = {} def reset(self): self.opponent_ships = [] self.cannonballs = [] self.mines = [] self.barrels = [] self.map = {} def add_opponent_ship(self, opponent: 'Ship'): self.opponent_ships.append(opponent) self._add_to_map(opponent) def add_my_ship(self, ship: 'Ship'): my_ship = self.my_ships.get(ship.id, None) if my_ship: my_ship.x = ship.x my_ship.y = ship.y my_ship.orientation = ship.orientation my_ship.speed = ship.speed my_ship.stock = ship.stock else: self.my_ships[ship.id] = ship self._add_to_map(ship) def add_mine(self, mine: 'Mine'): self.mines.append(mine) self._add_to_map(mine) def add_barrel(self, barrel: 'Barrel'): self.barrels.append(barrel) self._add_to_map(barrel) def add_cannonball(self, cannonball: 'Cannonball'): self.cannonballs.append(cannonball) self._add_to_map(cannonball) def _add_to_map(self, entity: 'Entity'): self.map[entity.coord] = entity def get_at(self, tile: 'Tile'): return self.map.get(tile.coord, Tile(tile.x, tile.y)) class Tile: def __init__(self, x: int, y: int): self.y = y self.x = x def distance(self, other: 'Tile') -> float: return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5 @property def coord(self): return '{};{}'.format(self.x, self.y) def top_left(self): y = self.y - 1 x = self.x if self.y % 2 == 0: x -= 1 return Tile(x, y) def top_right(self): y = self.y - 1 x = self.x if self.y % 2 != 0: x += 1 return Tile(x, y) def left(self): return Tile(self.x - 1, self.y) def right(self): return Tile(self.x + 1, self.y) def bottom_left(self): y = self.y + 1 x = self.x if self.y % 2 == 0: x -= 1 return Tile(x, y) def bottom_right(self): y = self.y + 1 x = self.x if self.y % 2 != 0: x += 1 return Tile(x, y) def __repr__(self): return 'Tile(x={}, y={})'.format(self.x, self.y) class Entity(Tile): def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int): super().__init__(x, y) self.game_map = game_map self.id = entity_id @property def nearest_barrel(self) -> Optional['Barrel']: return self.nearest(self.game_map.barrels) @property def nearest_opponent(self) -> Optional['Ship']: return self.nearest(self.game_map.opponent_ships) @property def nearest_mine(self) -> Optional['Mine']: return self.nearest(self.game_map.mines) def nearest(self, entities: Sequence['Entity']) -> Optional['Entity']: nearest = None best_dist = math.inf for entity in entities: dist = self.distance(entity) if dist < best_dist: best_dist = dist nearest = entity return nearest def __repr__(self): return 'Entity(id={}, x={}, y={})'.format(self.id, self.x, self.y) class Barrel(Entity): def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int, quantity: int): super().__init__(game_map, entity_id, x, y) self.quantity = quantity def __repr__(self): return 'Barrel(id={}, x={}, y={}, quantity={})'.format(self.id, self.x, self.y, self.quantity) class Ship(Entity): def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int, orientation: int, speed: int, stock: int, mine: bool = True): super().__init__(game_map, entity_id, x, y) self.orientation = Orientation(orientation) self.speed = speed self.stock = stock self.mine = mine self.mine_wait = DEFAULT_MINE_WAIT self.shoot_wait = DEFAULT_SHOOT_WAIT def play_turn(self): action = None target = None rand = random.random() perr('rand', rand) perr('pos', self.tile) perr('mine_wait', self.mine_wait) perr('shoot_wait', self.shoot_wait) perr('on path', self.tiles_on_path) perr('distance oppo', self.distance(self.nearest_opponent)) for tile in self.tiles_on_path: if isinstance(tile, Mine) or isinstance(tile, Cannonball): action, target = self.avoid(tile) break if not action: if 0.2 < rand <= 0.4 and self.shoot_wait <= 0 and self.distance(self.nearest_opponent.tiles_on_path[-1]) < 10: action = Action.FIRE target = self.nearest_opponent.tiles_on_path[-1] elif rand <= 0.2 and self.mine_wait <= 0: action = Action.MINE else: target = Tile(random.randint(0, 23), random.randint(0, 21)) if self.game_map.barrels: target = self.nearest_barrel action = Action.MOVE if action is Action.MINE: self.mine_wait = DEFAULT_MINE_WAIT elif action is Action.FIRE: self.shoot_wait = DEFAULT_SHOOT_WAIT if target: print('{} {} {}'.format(action.value, target.x, target.y)) else: print(action.value) self.mine_wait -= 1 self.shoot_wait -= 1 def avoid(self, tile: Tile): action = Action.MOVE target = None if self.orientation is Orientation.RIGHT: target = tile.top_left() elif self.orientation is Orientation.TOP_RIGHT: target = tile.left() elif self.orientation is Orientation.TOP_LEFT: target = tile.bottom_left() elif self.orientation is Orientation.LEFT: target = tile.bottom_right() elif self.orientation is Orientation.BOTTOM_LEFT: target = tile.right() elif self.orientation is Orientation.BOTTOM_RIGHT: target = tile.top_right() return action, target @property def tile(self): return Tile(self.x, self.y) @property def tiles_on_path(self): tile = self.tile tiles = [tile] if self.speed == 0: return tiles for _ in range(self.speed + 2): if self.orientation is Orientation.RIGHT: tile = tile.right() elif self.orientation is Orientation.TOP_RIGHT: tile = tile.top_right() elif self.orientation is Orientation.TOP_LEFT: tile = tile.top_left() elif self.orientation is Orientation.LEFT: tile = tile.left() elif self.orientation is Orientation.BOTTOM_LEFT: tile = tile.bottom_left() elif self.orientation is Orientation.BOTTOM_RIGHT: tile = tile.bottom_right() tiles.append(self.game_map.get_at(tile)) return tiles def __repr__(self): return ('Ship(id={}, x={}, y={}, orientation={}, speed={}, stock={}, mine={})' .format(self.id, self.x, self.y, self.orientation, self.speed, self.stock, self.mine)) class Mine(Entity): def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int): super().__init__(game_map, entity_id, x, y) def __repr__(self): return 'Mine(id={}, x={}, y={})'.format(self.id, self.x, self.y) class Cannonball(Entity): def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int, shooter_id: int, turns_before_impact: int): super().__init__(game_map, entity_id, x, y) self.turns_before_impact = turns_before_impact self.shooter_id = shooter_id def __repr__(self): return 'Barrel(id={}, x={}, y={}, shooter={}, before_impact={})'.format(self.id, self.x, self.y, self.shooter_id, self.turns_before_impact) def main(): random.seed() game_map = GameMap() while True: my_ship_count = int(input()) # the number of remaining ships entity_count = int(input()) # the number of entities (e.g. ships, mines or cannonballs) game_map.reset() # Fetch info for i in range(entity_count): entity_id, entity_type, x, y, arg_1, arg_2, arg_3, arg_4 = input().split() entity_id = int(entity_id) x = int(x) y = int(y) entity_type = EntityType(entity_type) if entity_type is EntityType.BARREL: rhum_quantity = int(arg_1) game_map.add_barrel(Barrel(game_map, entity_id, x, y, rhum_quantity)) elif entity_type is EntityType.SHIP: orientation = int(arg_1) speed = int(arg_2) rhum_stock = int(arg_3) mine = int(arg_4) == 1 ship = Ship(game_map, entity_id, x, y, orientation, speed, rhum_stock, mine) if mine: game_map.add_my_ship(ship) else: game_map.add_opponent_ship(ship) elif entity_type is EntityType.MINE: game_map.add_mine(Mine(game_map, entity_id, x, y)) elif entity_type is EntityType.CANNONBALL: shooter_id = int(arg_1) turns_before_impact = int(arg_2) game_map.add_cannonball(Cannonball(game_map, entity_id, x, y, shooter_id, turns_before_impact)) perr('detected cannonball', Cannonball(game_map, entity_id, x, y, shooter_id, turns_before_impact)) perr(game_map.map) # Play for _, ship in sorted(game_map.my_ships.items()): ship.play_turn() def perr(*args): print(*args, file=sys.stderr, flush=True) if __name__ == '__main__': main()