Add 2020 spring challenge (migrated from other repo)
This commit is contained in:
parent
8d2cee5250
commit
49af2f7157
1 changed files with 398 additions and 0 deletions
398
challenges/2020-spring.py
Normal file
398
challenges/2020-spring.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
import json
|
||||
import math
|
||||
import random
|
||||
|
||||
import sys
|
||||
from enum import Enum
|
||||
from typing import List, Set, Iterable, Union, Tuple, Dict
|
||||
|
||||
LOGGING = True
|
||||
ENEMY_SIGHT_DISTANCE = 3
|
||||
DISTANCE_THRESHOLD_FOR_SPEED = 3
|
||||
WALL = "#"
|
||||
PATH = " "
|
||||
UNKNOWN_PATH = "."
|
||||
|
||||
|
||||
class Type(Enum):
|
||||
ROCK = "ROCK"
|
||||
PAPER = "PAPER"
|
||||
SCISSORS = "SCISSORS"
|
||||
|
||||
def __gt__(self, other):
|
||||
wins_over = {
|
||||
Type.ROCK: Type.SCISSORS,
|
||||
Type.SCISSORS: Type.PAPER,
|
||||
Type.PAPER: Type.ROCK,
|
||||
}
|
||||
loser = wins_over[self]
|
||||
return other is loser
|
||||
|
||||
def get_winner(self):
|
||||
winner_for = {
|
||||
Type.SCISSORS: Type.ROCK,
|
||||
Type.PAPER: Type.SCISSORS,
|
||||
Type.ROCK: Type.PAPER,
|
||||
}
|
||||
return winner_for[self]
|
||||
|
||||
|
||||
def log(something, *args, **kwargs):
|
||||
if LOGGING:
|
||||
print(something, *args, file=sys.stderr, **kwargs)
|
||||
|
||||
|
||||
class Position:
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def distance_to(self, other: "Position") -> int:
|
||||
return int(math.fabs(self.x - other.x) + math.fabs(self.y - other.y))
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y))
|
||||
|
||||
def __eq__(self, other: "Position"):
|
||||
return self.x == other.x and self.y == other.y
|
||||
|
||||
def __str__(self):
|
||||
return json.dumps(self.to_dict())
|
||||
|
||||
def __repr__(self):
|
||||
return str(self)
|
||||
|
||||
def to_dict(self):
|
||||
return {"x": self.x, "y": self.y}
|
||||
|
||||
|
||||
class Pac(Position):
|
||||
def __init__(self, line: str):
|
||||
split = line.split()
|
||||
self.pac_id = int(split[0])
|
||||
self.mine = split[1] != "0"
|
||||
self.type_id = Type[split[4].upper()] # type: Type
|
||||
self.speed_turns_left = int(split[5])
|
||||
self.ability_cooldown = int(split[6])
|
||||
|
||||
x = int(split[2])
|
||||
y = int(split[3])
|
||||
super(Pac, self).__init__(x, y)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.pac_id, self.mine))
|
||||
|
||||
def __eq__(self, other: "Pac") -> bool:
|
||||
return (
|
||||
isinstance(other, Pac)
|
||||
and self.pac_id == other.pac_id
|
||||
and self.mine == other.mine
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
dct = super(Pac, self).to_dict()
|
||||
dct.update({"id": self.pac_id, "mine": self.mine})
|
||||
return dct
|
||||
|
||||
def move_to(self, unit: Position) -> str:
|
||||
return f"MOVE {self.pac_id} {unit.x} {unit.y}"
|
||||
|
||||
def speed(self) -> str:
|
||||
return f"SPEED {self.pac_id}"
|
||||
|
||||
def switch(self, type: Type, comment: str = "") -> str:
|
||||
return f"SWITCH {self.pac_id} {type.value} {comment}"
|
||||
|
||||
def target_type(self, other: "Pac") -> Union[Type, None]:
|
||||
winner_type = other.type_id.get_winner()
|
||||
if winner_type is self.type_id:
|
||||
return None
|
||||
return winner_type
|
||||
|
||||
@property
|
||||
def can_use_power(self) -> bool:
|
||||
return self.ability_cooldown <= 1
|
||||
|
||||
@property
|
||||
def is_speeding(self) -> bool:
|
||||
return self.speed_turns_left > 0
|
||||
|
||||
|
||||
class Pellet(Position):
|
||||
def __init__(self, line: str):
|
||||
x, y, value = [int(i) for i in line.split()]
|
||||
self.value = value
|
||||
super(Pellet, self).__init__(x, y)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y, self.value))
|
||||
|
||||
def __eq__(self, other: "Pellet"):
|
||||
return (
|
||||
isinstance(other, Pellet)
|
||||
and super(Pellet, self).__eq__(other)
|
||||
and self.value == other.value
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
dct = super().to_dict()
|
||||
dct.update({"value": self.value})
|
||||
return dct
|
||||
|
||||
@property
|
||||
def is_super(self) -> bool:
|
||||
return self.value > 1
|
||||
|
||||
|
||||
class Grid:
|
||||
def __init__(self, line: str, process_rows: bool = True):
|
||||
# Invariants
|
||||
# width: size of the grid
|
||||
# height: top left corner is (x=0, y=0)
|
||||
width, height = [int(i) for i in line.split()]
|
||||
self.width = width # type: int
|
||||
self.height = height # type: int
|
||||
self.map = [] # type: List[List[str]]
|
||||
if process_rows:
|
||||
for i in range(self.height):
|
||||
self.map.append(list(input().replace(PATH, UNKNOWN_PATH)))
|
||||
|
||||
# Variants
|
||||
self.pacs = set() # type: Set[Pac]
|
||||
self.pellets = set() # type: Set[Pellet]
|
||||
|
||||
self.previous_pacs = set()
|
||||
|
||||
@property
|
||||
def my_pacs(self) -> Iterable[Pac]:
|
||||
return filter(lambda pac: pac.mine, self.pacs)
|
||||
|
||||
@property
|
||||
def enemy_pacs(self) -> Iterable[Pac]:
|
||||
return filter(lambda pac: not pac.mine, self.pacs)
|
||||
|
||||
def pac_at_position(self, x, y, enemy_filter=None) -> Union[Pac, None]:
|
||||
if enemy_filter is True:
|
||||
pacs = self.enemy_pacs
|
||||
elif enemy_filter is False:
|
||||
pacs = self.my_pacs
|
||||
else:
|
||||
pacs = self.pacs
|
||||
|
||||
x = x % self.width
|
||||
y = y % self.height
|
||||
for pac in pacs:
|
||||
if pac.x == x and pac.y == y:
|
||||
return pac
|
||||
|
||||
def new_turn(self):
|
||||
self.previous_pacs = self.pacs
|
||||
self.pacs = set()
|
||||
self.pellets = set()
|
||||
|
||||
def add_pellet(self, line: str) -> Pellet:
|
||||
pellet = Pellet(line)
|
||||
self.set_map_at_coordinates(pellet, str(pellet.value))
|
||||
self.pellets.add(pellet)
|
||||
return pellet
|
||||
|
||||
def add_pac(self, line: str) -> Pac:
|
||||
pac = Pac(line)
|
||||
self.pacs.add(pac)
|
||||
return pac
|
||||
|
||||
def get_random_position(self) -> Position:
|
||||
return Position(
|
||||
random.randint(0, self.width - 1), random.randint(0, self.height - 1)
|
||||
)
|
||||
|
||||
def get_closest_unknown(self, me: Pac) -> Union[Position, None]:
|
||||
closest = None
|
||||
for y, row in enumerate(self.map):
|
||||
for x, cell in enumerate(row):
|
||||
if cell != UNKNOWN_PATH:
|
||||
continue
|
||||
pos = Position(x, y)
|
||||
if not closest or me.distance_to(pos) < me.distance_to(closest):
|
||||
closest = pos
|
||||
|
||||
return closest
|
||||
|
||||
def get_action(
|
||||
self, me: Pac, already_targeted: Set[Position]
|
||||
) -> Tuple[str, Union[Position, None]]:
|
||||
enemy = self.closest_enemy_in_sight(me)
|
||||
if me.can_use_power and enemy:
|
||||
new_type = me.target_type(enemy)
|
||||
if new_type:
|
||||
return (
|
||||
me.switch(new_type, f"switching to {new_type.value} for {enemy}"),
|
||||
None,
|
||||
)
|
||||
|
||||
if enemy and me.type_id > enemy.type_id:
|
||||
return me.move_to(enemy), enemy
|
||||
|
||||
target = self.find_target(me, already_targeted)
|
||||
if me.can_use_power:
|
||||
return me.speed(), None
|
||||
|
||||
return me.move_to(target), target
|
||||
|
||||
def find_target(self, me: Pac, already_targeted: Set[Position]) -> Position:
|
||||
rnd = self.get_random_position()
|
||||
default = self.get_closest_unknown(me)
|
||||
same_position = False
|
||||
min_dist = 2 if me.is_speeding else 1
|
||||
for pac in self.previous_pacs:
|
||||
if pac == me and pac.x == me.x and pac.y == me.y:
|
||||
same_position = True
|
||||
break
|
||||
|
||||
if same_position:
|
||||
return default or rnd
|
||||
|
||||
closest_pellet_distance = math.inf
|
||||
closest_pellet = None
|
||||
closest_super_pellet_distance = math.inf
|
||||
closest_super_pellet = None
|
||||
fallback_pellet = None
|
||||
for pellet in self.pellets:
|
||||
if pellet in already_targeted:
|
||||
continue
|
||||
distance = me.distance_to(pellet)
|
||||
if pellet.is_super:
|
||||
if distance < closest_super_pellet_distance:
|
||||
closest_super_pellet_distance = distance
|
||||
closest_super_pellet = pellet
|
||||
else:
|
||||
if min_dist <= distance < closest_pellet_distance:
|
||||
closest_pellet_distance = distance
|
||||
closest_pellet = pellet
|
||||
elif distance < closest_pellet_distance:
|
||||
fallback_pellet = pellet
|
||||
|
||||
if (
|
||||
fallback_pellet
|
||||
and closest_pellet
|
||||
and math.fabs(
|
||||
me.distance_to(fallback_pellet) - me.distance_to(closest_pellet)
|
||||
)
|
||||
> 3
|
||||
):
|
||||
closest_pellet = fallback_pellet
|
||||
|
||||
log(me.pac_id, closest_super_pellet, closest_pellet, default)
|
||||
return (
|
||||
closest_super_pellet or closest_pellet or fallback_pellet or default or rnd
|
||||
)
|
||||
|
||||
def get_map_at_coordinates(self, x: int, y: int) -> str:
|
||||
x = x % self.width
|
||||
y = y % self.height
|
||||
return self.map[y][x]
|
||||
|
||||
def set_map_at_coordinates(self, position: Position, value: str):
|
||||
x = position.x % self.width
|
||||
y = position.y % self.height
|
||||
self.map[y][x] = value
|
||||
|
||||
def closest_enemy_in_sight(self, me: Pac) -> Union[Pac, None]:
|
||||
try_right, try_left, try_up, try_down = True, True, True, True
|
||||
closest_enemy = None
|
||||
self.set_map_at_coordinates(me, PATH)
|
||||
for i in range(1, max(self.height, self.width)):
|
||||
if try_right:
|
||||
x = me.x + i
|
||||
y = me.y
|
||||
cell = self.get_map_at_coordinates(x, y)
|
||||
if cell == WALL:
|
||||
try_right = False
|
||||
elif cell == UNKNOWN_PATH:
|
||||
self.set_map_at_coordinates(Position(x, y), PATH)
|
||||
pac_at_position = self.pac_at_position(x, y, enemy_filter=True)
|
||||
if (
|
||||
pac_at_position
|
||||
and me.distance_to(pac_at_position) <= ENEMY_SIGHT_DISTANCE
|
||||
and not closest_enemy
|
||||
):
|
||||
closest_enemy = pac_at_position
|
||||
if try_left:
|
||||
x = me.x - i
|
||||
y = me.y
|
||||
cell = self.get_map_at_coordinates(x, y)
|
||||
if cell == WALL:
|
||||
try_left = False
|
||||
elif cell == UNKNOWN_PATH:
|
||||
self.set_map_at_coordinates(Position(x, y), PATH)
|
||||
pac_at_position = self.pac_at_position(x, y, enemy_filter=True)
|
||||
|
||||
if (
|
||||
pac_at_position
|
||||
and me.distance_to(pac_at_position) <= ENEMY_SIGHT_DISTANCE
|
||||
and not closest_enemy
|
||||
):
|
||||
closest_enemy = pac_at_position
|
||||
if try_up:
|
||||
x = me.x
|
||||
y = me.y - i
|
||||
cell = self.get_map_at_coordinates(x, y)
|
||||
if cell == WALL:
|
||||
try_up = False
|
||||
elif cell == UNKNOWN_PATH:
|
||||
self.set_map_at_coordinates(Position(x, y), PATH)
|
||||
pac_at_position = self.pac_at_position(x, y, enemy_filter=True)
|
||||
|
||||
if (
|
||||
pac_at_position
|
||||
and me.distance_to(pac_at_position) <= ENEMY_SIGHT_DISTANCE
|
||||
and not closest_enemy
|
||||
):
|
||||
closest_enemy = pac_at_position
|
||||
if try_down:
|
||||
x = me.x
|
||||
y = me.y + i
|
||||
cell = self.get_map_at_coordinates(x, y)
|
||||
if cell == WALL:
|
||||
try_down = False
|
||||
elif cell == UNKNOWN_PATH:
|
||||
self.set_map_at_coordinates(Position(x, y), PATH)
|
||||
pac_at_position = self.pac_at_position(x, y, enemy_filter=True)
|
||||
|
||||
if (
|
||||
pac_at_position
|
||||
and me.distance_to(pac_at_position) <= ENEMY_SIGHT_DISTANCE
|
||||
and not closest_enemy
|
||||
):
|
||||
closest_enemy = pac_at_position
|
||||
return closest_enemy
|
||||
|
||||
|
||||
def main():
|
||||
grid = Grid(input())
|
||||
|
||||
# game loop
|
||||
while True:
|
||||
grid.new_turn()
|
||||
my_score, opponent_score = [int(i) for i in input().split()] # type: int, int
|
||||
|
||||
visible_pac_count = int(input()) # type: int
|
||||
for i in range(visible_pac_count):
|
||||
grid.add_pac(input())
|
||||
|
||||
visible_pellet_count = int(input()) # type: int
|
||||
for _ in range(visible_pellet_count):
|
||||
grid.add_pellet(input())
|
||||
|
||||
output = []
|
||||
targets = set()
|
||||
for pac in grid.my_pacs:
|
||||
action, target = grid.get_action(pac, targets)
|
||||
output.append(action)
|
||||
targets.add(target)
|
||||
|
||||
print(" | ".join(output))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue