361 lines
11 KiB
Python
361 lines
11 KiB
Python
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[tile.coord]
|
|
|
|
|
|
class Tile:
|
|
def __init__(self, x: int, y: int):
|
|
self.y = y
|
|
self.x = x
|
|
|
|
@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
|
|
|
|
def distance(self, other: 'Entity') -> 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['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())
|
|
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 (self.mine_wait > 0 and self.shoot_wait > 0) or rand > 0.4:
|
|
target = Tile(random.randint(0, 23), random.randint(0, 21))
|
|
if self.game_map.barrels:
|
|
target = self.nearest_barrel()
|
|
|
|
action = Action.MOVE
|
|
elif rand > 0.2:
|
|
action = Action.FIRE
|
|
target = self.nearest_opponent()
|
|
else:
|
|
action = Action.MINE
|
|
|
|
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_right()
|
|
elif self.orientation is Orientation.TOP_RIGHT:
|
|
target = tile.right()
|
|
elif self.orientation is Orientation.TOP_LEFT:
|
|
target = tile.left()
|
|
elif self.orientation is Orientation.LEFT:
|
|
target = tile.top_left()
|
|
elif self.orientation is Orientation.BOTTOM_LEFT:
|
|
target = tile.left()
|
|
elif self.orientation is Orientation.BOTTOM_RIGHT:
|
|
target = tile.right()
|
|
return action, target
|
|
|
|
@property
|
|
def tile(self):
|
|
return Tile(self.x, self.y)
|
|
|
|
def tiles_on_path(self):
|
|
tile = self.tile
|
|
perr('tile', tile)
|
|
tiles = []
|
|
for _ in range(self.speed + 1):
|
|
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(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(game_map.map)
|
|
|
|
# Play
|
|
for ship in game_map.my_ships.values():
|
|
ship.play_turn()
|
|
|
|
|
|
def perr(*args):
|
|
print(*args, file=sys.stderr, flush=True)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|