diff --git a/challenge_coders_of_the_caribbean.py b/challenge_coders_of_the_caribbean.py new file mode 100644 index 0000000..44e463f --- /dev/null +++ b/challenge_coders_of_the_caribbean.py @@ -0,0 +1,234 @@ +import math +import random +from enum import Enum +from typing import List, Union, Sequence, TypeVar, Optional, Dict + +import sys + +BARREL = 'BARREL' +SHIP = 'SHIP' +MINE = 'MINE' +CANNONBALL = 'CANNONBALL' + +DEFAULT_SHOOT_WAIT = 2 +DEFAULT_MINE_WAIT = 4 + + +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 + + def reset(self): + self.opponent_ships = [] + self.cannonballs = [] + self.mines = [] + self.barrels = [] + + +class Tile: + def __init__(self, x: int, y: int): + self.y = y + self.x = x + + +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 + + def distance(self, other: 'EntityType') -> float: + return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5 + + def nearest_barrel(self) -> Optional['Barrel']: + return self.nearest(self.game_map.barrels) + + def nearest_opponent(self) -> Optional['Ship']: + return self.nearest(self.game_map.opponent_ships) + + def nearest_mine(self) -> Optional['Mine']: + return self.nearest(self.game_map.mines) + + def nearest(self, entities: Sequence['EntityType']) -> Optional['EntityType']: + 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 + + +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 + + +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 + self.speed = speed + self.stock = stock + self.mine = mine + self.mine_wait = DEFAULT_MINE_WAIT + self.shoot_wait = DEFAULT_SHOOT_WAIT + self.action = None # type: Optional[Action] + self.target = None # type: Optional[TargetType] + + def play_turn(self): + if self.action is Action.MINE: + self.mine_wait = DEFAULT_MINE_WAIT + elif self.action is Action.FIRE: + self.shoot_wait = DEFAULT_SHOOT_WAIT + + if self.target: + print('{} {} {}'.format(self.action.value, self.target.x, self.target.y)) + else: + print(self.action.value) + self.action = None + self.target = None + self.mine_wait -= 1 + self.shoot_wait -= 1 + + def drop_mine(self): + self.action = Action.MINE + + def wait(self): + self.action = Action.WAIT + + def slower(self): + self.action = Action.SLOWER + + def shoot(self, target: 'EntityType'): + self.action = Action.FIRE + self.target = target + + def move(self, target: 'TargetType'): + self.action = Action.MOVE + self.target = target + + +class Mine(Entity): + def __init__(self, game_map: GameMap, entity_id: int, x: int, y: int): + super().__init__(game_map, entity_id, x, 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 + + +EntityType = TypeVar('EntityType', Entity, Barrel, Ship, Mine, Cannonball) +TargetType = Union[EntityType, Tile] + + +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) + + if entity_type == BARREL: + rhum_quantity = int(arg_1) + game_map.barrels.append(Barrel(game_map, entity_id, x, y, rhum_quantity)) + + elif entity_type == 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: + my_ship = game_map.my_ships.get(entity_id, None) + if my_ship: + my_ship.orientation = orientation + my_ship.speed = speed + my_ship.stock = rhum_stock + else: + game_map.my_ships[entity_id] = ship + else: + game_map.opponent_ships.append(ship) + + elif entity_type == MINE: + game_map.mines.append(Mine(game_map, entity_id, x, y)) + + elif entity_type == CANNONBALL: + shooter_id = int(arg_1) + turns_before_impact = int(arg_2) + game_map.cannonballs.append(Cannonball(game_map, entity_id, x, y, shooter_id, turns_before_impact)) + + # Play + for ship in game_map.my_ships.values(): + rand = random.random() + perr('rand', rand) + perr('mine_wait', ship.mine_wait) + perr('shoot_wait', ship.shoot_wait) + + if (ship.mine_wait > 0 and ship.shoot_wait > 0) or rand > 0.4: + target = Tile(random.randint(0, 23), random.randint(0, 21)) + if game_map.barrels: + target = ship.nearest_barrel() + + ship.move(target) + + elif rand > 0.2: + opponent = ship.nearest_opponent() + ship.shoot(opponent) + else: + ship.drop_mine() + + ship.play_turn() + + +def perr(*args): + print(*args, file=sys.stderr, flush=True) + + +if __name__ == '__main__': + main()