| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074 |
- """
- Created on 7 mai 2020
- @author: olinox
- """
- import sys
- import time
- import heapq
- # TODO: faire un simple pathfinding en cas d'echec au calcul de route
- # TODO: prévoir un plan fallback quand pas de chemin
- debug = False
- t0 = time.time()
- LOG_INPUT = (0 and __file__[-8:] != 's2020.py')
- HIJACK_INPUT = (1 and __file__[-8:] == 's2020.py')
- _force_input = []
- if HIJACK_INPUT:
- _force_input = [
- ['33 16', '#################################', ' # # # # # # # # # ', '### # # # # # ##### # # # # # ###', '# # # # # # # # #', '# # # ### ### # # # ### ### # # #', '# # # # # # # #', '# # ### # # # ##### # # # ### # #', '# # # # # # # #', '##### # # # # # # # # # # # #####', '# # # # # # # # #', '# # # # ### ### # ### ### # # # #', '# # # # # # # #', '### # ### # # # # # # # ### # ###', '### # # # # ###', '### # # # ##### # ##### # # # ###', '#################################', '0 0', '6', '0 1 2 9 ROCK 0 0', '1 1 13 11 PAPER 0 0', '1 0 19 11 PAPER 0 0', '2 1 26 13 SCISSORS 0 0', '3 1 29 12 ROCK 0 0', '4 1 21 8 PAPER 0 0', '32', '1 9 1', '3 9 1', '4 9 1', '5 9 1', '14 11 1', '15 11 1', '16 11 1', '17 11 1', '18 11 1', '13 12 1', '13 13 1', '25 13 1', '24 13 1', '23 13 1', '27 13 1', '29 11 1', '29 10 1', '29 9 1', '29 13 1', '29 14 1', '21 7 1', '21 6 1', '21 5 1', '21 9 1', '21 10 1', '21 11 1', '21 12 1', '21 13 1', '5 1 10', '27 1 10', '12 13 10', '20 13 10'],
- ['137 100', '7', '0 1 9 5 SCISSORS 0 0', '1 1 5 7 ROCK 0 0', '2 1 25 6 SCISSORS 0 0', '3 1 23 5 SCISSORS 0 0', '3 0 6 5 ROCK 0 2', '4 1 13 5 PAPER 0 0', '4 0 8 5 PAPER 0 2', '10', '9 9 1', '4 7 1', '3 7 1', '2 7 1', '1 7 1', '5 14 1', '23 2 1', '23 1 1', '13 2 1', '13 1 1']
- ]
- INPUTS = []
- def l_input(*args):
- if HIJACK_INPUT:
- if not _force_input:
- print('no more hijack')
- sys.exit()
- inp = _force_input[0].pop(0)
- if len(_force_input[0]) == 0:
- del _force_input[0]
- else:
- inp = input()
- INPUTS.append(inp)
- return inp
- def log(*msg):
- if debug:
- print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
- sys.stderr.flush()
- # OWNER
- ME = 0
- OPPONENT = 1
- # Base classes
- class Base():
- def __repr__(self):
- return f"<{self.__class__.__name__}: {self.__dict__}>"
- class Queue(Base):
- def __init__(self, inv=False):
- self.items = []
- def __bool__(self):
- return bool(self.items)
- def __repr__(self):
- return str(self.items)
- def values(self):
- return (v for _, v in self.items)
- def put(self, item, priority):
- heapq.heappush(self.items, (priority, item))
- def fput(self, item, priority):
- while priority in [p for p, _ in self.items]:
- priority += 1
- self.put(item, priority)
- def get(self):
- return heapq.heappop(self.items)[1]
- def get_items(self):
- return heapq.heappop(self.items)
- class Node(Base):
- def __init__(self, pos, path=None):
- self.pos = pos
- self.path = path or []
- class PathNode(tuple):
- def __new__(self, x, y, parent=None):
- n = tuple.__new__(self, (x, y))
- n.parent = parent
- n.cost = 0
- return n
- def __repr__(self):
- return f"<{self[0]}, {self[1]}, c:{self.cost}>"
- class RiskNode(tuple):
- def __new__(self, x, y, parent=None):
- n = tuple.__new__(self, (x, y))
- n.parent = parent
- n.proba = 0
- return n
- def __repr__(self):
- return f"<{self[0]}, {self[1]}, p:{self.proba}>"
- class RouteNode():
- def __init__(self, segment, parent=None):
- self.segment = segment
- self.parent = parent
- self.value = 0
- self.cost = 0
- self.visited = []
- def __repr__(self):
- return f"<{self.segment}, c:{self.cost}, v:{self.value}>"
- # Script classes
- class Player():
- def __init__(self, id_):
- self.id_ = id_
- self.score = 0
- self.pacs = []
- UNKNOWN_CELL = "?"
- WALL_CELL = "#"
- FLOOR_CELL = " "
- class Cell(Base):
- def __init__(self, x, y, type_=UNKNOWN_CELL):
- self.x = x
- self.y = y
- self.type_ = type_
- self._value = 1 # on part du principe que toutes les cases contiennent au moins une pastille au départ
- self.status = UNOCCUPIED
- self.neighbors = [] # only movable
- self.seen = False
- @property
- def pos(self):
- return (self.x, self.y)
- @property
- def unknown(self):
- return self.type_ == UNKNOWN_CELL or (self.is_floor and not self.seen)
- @property
- def is_floor(self):
- return self.type_ == FLOOR_CELL
- @property
- def movable(self):
- return self.is_floor
- @property
- def value(self):
- return self._value
- @value.setter
- def value(self, value):
- self._value = value
- self.seen = True
- def print_(self):
- if self.unknown:
- return "?"
- elif self.movable:
- return {0: " ", 1: "*", 10: "$"}[self.value]
- else:
- return WALL_CELL
- class Segment(Base):
- REPR = "?"
- uid = 0
- def __init__(self, cells):
- self.id_ = Segment.new_uid()
- self._cells = []
- self.cells = cells
- # clockwise
- self.neighbors = []
- self.status = UNOCCUPIED
- @property
- def cells(self):
- return self._cells
- @cells.setter
- def cells(self, cells):
- self._cells = cells
- self.coords = [c.pos for c in cells]
- self.length = len(cells)
- self.update()
- def update_status(self):
- self.status = max(c.status for c in self.cells)
- def update(self):
- self.value = sum(c.value for c in self.cells if not c.status == TARGETTED)
- self.update_status()
- def print_(self):
- return self.REPR
- @classmethod
- def new_uid(cls):
- cls.uid += 1
- return cls.uid
- def __repr__(self):
- return f"<{self.__class__.__name__} {self.id_}: {'-'.join([str(c.pos) for c in self.cells])}>"
- def go_trough(self, from_):
- ifrom = self.coords.index(from_)
- if ifrom == 0:
- return list(self.coords)
- elif ifrom == (len(self.coords) - 1):
- return self.coords[::-1]
- else:
- raise ValueError(f"{from_} not in segment")
- class StartingPos(Segment):
- REPR = "O"
- def __init__(self, cell, pac_id):
- super().__init__([cell])
- self.pac_id = pac_id
- def cell(self):
- return self.cells[0]
- class Intersection(Segment):
- REPR = "+"
- def __init__(self, cell):
- super().__init__([cell])
- def cell(self):
- return self.cells[0]
- class Corridor(Segment):
- REPR = "-"
- class LoopCorridor(Corridor):
- REPR = "~"
- class Deadend(Segment):
- REPR = "."
- class Grid(Base):
- MAX_ITS = 360
- def __init__(self, width, height):
- self.width = width
- self.height = height
- self.cells = {}
- self.pacs = {}
- self.pellets = {}
- self.risk = {}
- self.segments = []
- self.owned_pacs = []
- self.opponent_pacs = []
- self.segment_index = {}
- self.pacs_index = {}
- self.path_cache = {}
- self.consumed_its = 0
- @property
- def grid(self):
- return [[self.cells[(x, y)].print_() for x in range(self.width)] for y in range(self.height)]
- def print_(self):
- return "\n" + "\n".join(["".join([c for c in row]) for row in self.grid])
- def __getitem__(self, key):
- return self.cells[key]
- @classmethod
- def from_matrix(cls, matrix):
- width = len(matrix[0])
- height = len(matrix)
- cells = {(x, y): Cell(x, y, v) for y, r in enumerate(matrix) for x, v in enumerate(r)}
- grid = Grid(width, height)
- grid.cells = cells
- for pos, cell in grid.cells.items():
- cell.neighbors = []
- for n in grid.neighbors(*pos):
- try:
- if grid.cells[n].movable:
- cell.neighbors.append(n)
- except KeyError:
- log(f'/!\ {n} not in cells')
- return grid
- def segmentize(self):
- self.segments = []
- self.segment_index = {}
- segments = []
- # intersections and starting points
- pivots = []
- pivots_coords = set()
- for pos, cell in self.cells.items():
- if pos in self.pacs_index:
- segment = StartingPos(cell, self.pacs_index[pos].id)
- segments.append(segment)
- self.segment_index[pos] = segment
- pivots.append(segment)
- pivots_coords.add(pos)
- elif cell.movable and len(cell.neighbors) >= 3:
- segment = Intersection(cell)
- segments.append(segment)
- self.segment_index[pos] = segment
- pivots.append(segment)
- pivots_coords.add(pos)
- # corridors and deadends
- for pivot in pivots:
- pivot_cell = pivot.cell()
- for n in pivot_cell.neighbors:
- if n in self.segment_index:
- continue
- nc = self.cells[n]
- buffer = []
- while nc:
- buffer.append(nc)
- if nc.pos in pivots_coords:
- # corridor ou startingpos
- segment = Corridor(buffer[:-1])
- break
- elif len(nc.neighbors) == 1:
- segment = Deadend(list(buffer))
- break
- elif len(nc.neighbors) == 2:
- nextnc = next((self.cells[p] for p in nc.neighbors if
- self.cells[p] not in buffer and p != pivot_cell.pos), None)
- if nextnc is None:
- # c'est une boucle, on vient de revenir au point de depart
- segment = LoopCorridor(list(buffer))
- break
- nc = nextnc
- else:
- log('what happened??')
- for c in segment.cells:
- self.segment_index[c.pos] = segment
- segments.append(segment)
- # connect
- for pivot in pivots:
- pivot_cell = pivot.cell()
- for n in pivot_cell.neighbors:
- pivot.neighbors.append(n)
- neighbor_seg = self.segment_index[n]
- if type(neighbor_seg) not in (Intersection, StartingPos):
- neighbor_seg.neighbors.append(pivot_cell.pos)
- self.segments = segments
- for s in self.segments:
- s.update()
- def segment_neighbors(self, seg):
- return [self.segment_index[n] for n in seg.neighbors]
- def print_segments(self):
- rows = []
- for y in range(self.height):
- row = []
- for x in range(self.width):
- try:
- row.append(self.segment_index[(x, y)].print_())
- except KeyError:
- row.append("#")
- rows.append(row)
- return "\n".join(["".join([c for c in row]) for row in rows])
- def update(self, visible_pacs, visible_pellets):
- self.consumed_its = 0
- for c in self.cells.values():
- c.status = UNOCCUPIED
- # update pacs
- self.pacs_index = {}
- me.pacs = []
- for a in visible_pacs:
- pac_id, is_mine, x, y, type_id, speed_turns_left, ability_cooldown = a
- if is_mine:
- id_ = pac_id
- else:
- id_ = -1 * (pac_id + 1)
- if not id_ in grid.pacs:
- pac = Pac(id_, is_mine)
- self.pacs[id_] = pac
- else:
- pac = self.pacs[id_]
- pac.update(x, y, type_id, speed_turns_left, ability_cooldown)
- self.cells[(x, y)].status = ALLY_HERE if is_mine else ENNEMY_HERE
- self.pacs_index[(x, y)] = pac
- if is_mine:
- me.pacs.append(pac)
- else:
- if not pac in opponent.pacs:
- opponent.pacs.append(pac)
- # update threats
- for pac in me.pacs:
- for ennemy in opponent.pacs:
- dist = 2 if not pac.is_accelerated else 3
- scope = self.scope(ennemy.pos, dist)
- if pac.pos in scope and pac.lose_against(ennemy):
- pac.threaten_by = ennemy.id
- # special: if a 10-cell still exist, we'll know
- for c in self.cells.values():
- if c.value:
- c.value = 1
- # update pellets
- self.pellets = {}
- for a in visible_pellets:
- x, y, v = a
- self.pellets[(x, y)] = Pellet(x, y, v)
- if v == 10:
- # always visible
- self.cells[(x, y)].value = v
- # update cells values
- in_sight = set()
- for pac in me.pacs:
- sight = set(self.line_of_sight(pac.pos))
- in_sight |= sight
- for pos in in_sight:
- val = self.pellets[pos].value if pos in self.pellets else 0
- self.cells[pos].value = val
- @staticmethod
- def manhattan(from_, to_):
- xa, ya = from_
- xb, yb = to_
- return abs(xa - xb) + abs(ya - yb)
- def neighbors(self, x, y, diags=False):
- neighs = []
- for nx, ny in [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]:
- if not 0 <= ny < self.height:
- continue
- if nx == -1:
- nx = self.width - 1
- if nx == self.width:
- nx = 0
- neighs.append((nx, ny))
- return neighs
- @staticmethod
- def closest(from_, in_):
- return min(in_, key=lambda x: Grid.manhattan(from_, x))
- def line_of_sight(self, from_):
- x0, y0 = from_
- sight = [(x0, y0)]
- x = x0 + 1
- while 1:
- if x == self.width:
- x = 0
- if x == x0:
- break # tour complet
- if not self.cells[(x, y0)].is_floor:
- break
- sight.append((x, y0))
- x += 1
- x = x0 - 1
- while 1:
- if x == -1:
- x = self.width - 1
- if x == x0:
- break # tour complet
- if not self.cells[(x, y0)].is_floor:
- break
- sight.append((x, y0))
- x -= 1
- y = y0 + 1
- while self.cells[(x0, y)].is_floor:
- sight.append((x0, y))
- y += 1
- y = y0 - 1
- while self.cells[(x0, y)].is_floor:
- sight.append((x0, y))
- y -= 1
- return sight
- def scope(self, start, dist=1):
- """ zone de mouvement d'un pac """
- scope = {start}
- for _ in range(dist):
- new_scope = set()
- for p in scope:
- new_scope |= {n for n in self.neighbors(*p) if self.cells[n].is_floor}
- scope |= new_scope
- return list(scope)
- def path(self, start, target):
- nodes = Queue()
- its, break_on = 0, 2000
- origin = PathNode(*start)
- nodes.put(origin, 0)
- neighbors = []
- while nodes:
- current = nodes.get()
- if current == target:
- path = []
- previous = current
- while previous:
- if previous != start:
- path.insert(0, previous)
- previous = previous.parent
- return path
- neighbors = self.neighbors(*current)
- for x, y in neighbors:
- its += 1
- if its > break_on:
- log("<!> pathfinding broken")
- return None
- if (x, y) == current.parent:
- continue
- cell = self.cells[(x, y)]
- if not cell.movable:
- continue
- moving_cost = 1
- if cell.unit and cell.unit.owned:
- moving_cost += cell.unit.level
- if cell.under_tower:
- moving_cost += 2
- cost = current.cost + moving_cost
- priority = cost + 10 * Grid.manhattan((x, y), target)
- node = PathNode(x, y, current)
- node.cost = cost
- nodes.put(node, priority)
- return None
- def eval_risk(self):
- # eval the nest opponent moves
- self.risk = {}
- for pac in opponent.pacs:
- current = RiskNode(pac.x, pac.y)
- current.proba = 100
- nodes = {0: [current]}
- round_ = 1
- while round_ < 8:
- nodes[round_] = []
- for parent in nodes[round_ - 1]:
- neighbors = self.neighbors(*parent)
- candidates_cells = [self.cells[n] for n in neighbors if self.cells[n].movable]
- coming_from = tuple(parent.parent) if parent.parent else None
- # le x4/+1 est pour laisser une proba aux cases sans pellet, ça donne du 80% pour la case à 1 et 20% pour celle à 0
- total_val = sum(
- [(4 * cell.value if (cell.x, cell.y) != coming_from else 0) + 1 for cell in candidates_cells])
- for cell in candidates_cells:
- node = RiskNode(cell.x, cell.y)
- node.parent = parent
- proba = (100 * ((4 * cell.value if (cell.x, cell.y) != coming_from else 0) + 1)) / total_val
- node.proba = round((proba * parent.proba) / 100)
- nodes[round_].append(node)
- round_ += 1
- for round_, round_nodes in nodes.items():
- if not round_ in self.risk:
- self.risk[round_] = {}
- for node in round_nodes:
- x, y = node
- if not (x, y) in self.risk[round_]:
- self.risk[round_][(x, y)] = node.proba
- else:
- if node.proba > self.risk[round_][(x, y)]:
- self.risk[round_][(x, y)] = node.proba
- def print_risk(self, round_):
- if not round_ in self.risk:
- return "no data to show"
- grid = []
- for y in range(self.height):
- row = []
- for x in range(self.width):
- if (x, y) in self.risk[round_]:
- c = str(self.risk[round_][(x, y)])
- if len(c) == 1:
- c = f".{c}."
- elif len(c) == 2:
- c = f".{c}"
- elif not self.cells[(x, y)].movable:
- c = "###"
- else:
- c = "..."
- row.append(c)
- grid.append(row)
- res = "\n".join(["".join([c for c in row]) for row in grid])
- return f"ROUND {round_}\n" + res
- def route_finding(self, start):
- if self.consumed_its > self.MAX_ITS:
- log('max its already consumed')
- return None
- nodes = Queue(inv=True)
- targeted_cost = 18
- reserve = Queue()
- its, break_on = 0, 80
- broken = False
- starting_seg = self.segment_index[start]
- origin = RouteNode(starting_seg)
- origin.visited = [starting_seg.id_]
- origin.value = 0
- origin.cost = 1
- nodes.put(origin, 0)
- current = None
- while 1:
- if broken:
- break
- if not nodes:
- break
- priority, current = nodes.get_items()
- if current.cost >= targeted_cost:
- reserve.fput(current, priority)
- continue
- neighbors = self.segment_neighbors(current.segment)
- for seg in neighbors:
- its += 1
- if its >= break_on or (its + self.consumed_its) > self.MAX_ITS:
- broken = True
- break
- if seg.status > ALLY_HERE and not seg is starting_seg:
- continue
- if seg.status == ALLY_HERE and not current.cost:
- continue
- value = seg.value if seg.id_ not in current.visited else 0
- cost = seg.length
- half_turn = (current.parent is not None and current.parent.segment.id_ == seg.id_ and type(seg) is not LoopCorridor)
- if half_turn:
- cost *= 2
- node = RouteNode(seg, current)
- node.value = current.value + value
- node.cost = current.cost + cost
- node.visited = list(current.visited) + [seg.id_]
- priority = 100 * (node.cost - node.value)
- nodes.fput(node, priority)
- self.consumed_its += its
- if broken:
- # loop was interrupted
- if reserve:
- # some routes were put in the reserve
- log('> routefinding interrupted')
- target = reserve.get()
- elif nodes:
- # no routes has been fully explored
- log('> routefinding interrupted without reserve')
- target = nodes.get()
- while target.value == 0:
- if not nodes:
- log('not a single valued node to target... :(')
- return None
- target = nodes.get()
- else:
- # something went wrong
- log('<!> routefinding broken')
- return []
- else:
- if reserve:
- # all paths were explored
- target = reserve.get()
- else:
- # something went wrong
- log('<!> routefinding broken')
- return []
- route = []
- previous = target
- while previous:
- if previous.segment.id_ != starting_seg.id_:
- route.insert(0, previous)
- previous = previous.parent
- return [node.segment for node in route]
- def route_to_path(self, route, from_):
- path = []
- cur_pos = from_
- for seg in route:
- part = []
- # is it an half-turn?
- if cur_pos in seg.coords:
- # on parcourt le dernier segment a l'envers avant de poursuivre
- part = seg.go_trough(cur_pos)[1:]
- else:
- for n in self.neighbors(*cur_pos):
- try:
- part = seg.go_trough(n)
- break
- except ValueError:
- pass
- if not part:
- log("error: error while rebuilding the path")
- return path
- path += part
- cur_pos = path[-1]
- if not route:
- break
- return path
- def cache_path(self, pac_id, path):
- self.path_cache[pac_id] = (path, sum(self.cells[c].value for c in path))
- def has_cached_path(self, pac_id):
- return pac_id in self.path_cache
- def get_cached_path(self, pac_id):
- """ return the cached path if its total value did not change """
- if not pac_id in self.path_cache:
- return None
- path, val = self.path_cache[pac_id]
- if not path:
- return None
- cur_val = sum(self.cells[c].value for c in path)
- if val != cur_val:
- return None
- if any(other.pos in path for other in me.pacs if other.id != pac_id):
- return None
- return path
- def record_move(self, pac, new_pos):
- del self.pacs_index[pac.pos]
- self.pacs_index[new_pos] = pac
- self.cells[pac.pos].status = UNOCCUPIED
- self.cells[new_pos].status = ALLY_HERE
- self.segment_index[pac.pos].status = UNOCCUPIED
- self.segment_index[new_pos].status = ALLY_HERE
- pac.pos = new_pos
- SPEED_DURATION = 5
- ABILITY_COOLDOWN = 10
- ROCK = "ROCK"
- PAPER = "PAPER"
- SCISSORS = "SCISSORS"
- _COMBOS = ((ROCK, SCISSORS),
- (SCISSORS, PAPER),
- (PAPER, ROCK))
- WIN_AGAINST = {t1: t2 for t1, t2 in _COMBOS}
- LOSE_AGAINST = {t2: t1 for t1, t2 in _COMBOS}
- # Types d'occupation des cases
- UNOCCUPIED = 0
- TARGETTED = 5
- ALLY_HERE = 10
- ENNEMY_HERE = 20
- PREDATOR_HERE = 21
- HARMLESS_HERE = 22
- PREY_HERE = 23
- class Pac(Base):
- def __init__(self, pac_id, mine):
- self.id = pac_id
- self.owner = ME if mine else OPPONENT
- self.type = None
- self.speed_cooldown = 0
- self.speed_turns_left = 0
- self.ability_cooldown = 0
- self.threaten_by = None
- self.action_cmd = ""
- def update(self, x, y, type_id, speed_turns_left, ability_cooldown):
- self.x = x
- self.y = y
- self.type = type_id
- self.speed_turns_left = speed_turns_left
- self.ability_cooldown = ability_cooldown
- self.action_cmd = ""
- self.threaten_by = None
- @property
- def pos(self):
- return (self.x, self.y)
- @pos.setter
- def pos(self, pos):
- self.x, self.y = pos
- @property
- def owned(self):
- return self.owner == ME
- def can_accelerate(self):
- return self.ability_cooldown == 0
- def can_switch(self):
- return self.ability_cooldown == 0
- @property
- def is_accelerated(self):
- return self.speed_turns_left > 0
- def win_against(self, other_pac):
- if other_pac.owned:
- raise Exception("Why would you fight against your friend?")
- return WIN_AGAINST[self.type] == other_pac.type
- def lose_against(self, other_pac):
- if other_pac.owned:
- raise Exception("Why would tou fight against your friend?")
- return LOSE_AGAINST[self.type] == other_pac.type
- def move(self, x, y):
- self.action_cmd = f"MOVE {self.id} {x} {y}"
- def speed_up(self):
- if self.speed_cooldown > 0:
- raise Exception("Speed cooldown still run")
- self.ability_cooldown = ABILITY_COOLDOWN
- self.speed_turns_left = SPEED_DURATION
- self.action_cmd = f"SPEED {self.id}"
- def switch(self, new_type):
- if new_type == self.type:
- raise Exception('has already the type {new_type}')
- self.type = new_type
- self.ability_cooldown = ABILITY_COOLDOWN
- self.action_cmd = f"SWITCH {self.id} {new_type}"
- def switch_to_beat_up(self, other_pac):
- new_type = LOSE_AGAINST[other_pac.type]
- self.switch(new_type)
- def wait(self):
- self.action_cmd = f"MOVE {self.id} {self.x} {self.y}"
- class Pellet(Base):
- def __init__(self, x, y, value=1):
- self.x = x
- self.y = y
- self.value = value
- # Main loop
- ROUND = 0
- _, height = [int(i) for i in l_input().split()]
- grid = Grid.from_matrix([list(l_input()) for _ in range(height)])
- me = Player(ME)
- opponent = Player(OPPONENT)
- while 1:
- cmds = []
- log(f"--- ROUND {ROUND} ---")
- me.score, opponent.score = [int(i) for i in l_input().split()]
- visible_pacs = []
- visible_pac_count = int(l_input()) # all your pacs and enemy pacs in sight
- for _ in range(visible_pac_count):
- pac_id, is_mine, x, y, type_id, speed_turns_left, ability_cooldown = l_input().split()
- visible_pacs.append(
- (int(pac_id), is_mine != "0", int(x), int(y), type_id, int(speed_turns_left), int(ability_cooldown)))
- visible_pellets = []
- visible_pellet_count = int(l_input()) # all pellets in sight
- for _ in range(visible_pellet_count):
- x, y, v = [int(j) for j in l_input().split()]
- visible_pellets.append((x, y, v))
- log('> input fetched')
- grid.update(visible_pacs, visible_pellets)
- log('> grid updated')
- log(grid.print_())
- if LOG_INPUT:
- log(INPUTS)
- for pac in me.pacs:
- grid.segmentize()
- grid.print_segments()
- log(f'# Pac {pac.id}')
- cached_path = grid.get_cached_path(pac.id)
- if pac.threaten_by:
- log('<!> Threaten by: ', pac.threaten_by)
- if cached_path and len(cached_path) > 3 and pac.can_accelerate() and not pac.threaten_by:
- pac.speed_up()
- continue
- if pac.threaten_by and pac.can_switch():
- switch_to = next(
- type_ for type_, against in WIN_AGAINST.items() if against == grid.pacs[pac.threaten_by].type)
- log(f'Switch to resist! >> {switch_to}')
- pac.switch(switch_to)
- continue
- if cached_path is None:
- log('routefinding')
- route = grid.route_finding(pac.pos)
- if route:
- path = grid.route_to_path(route, pac.pos)
- grid.cache_path(pac.id, path)
- log('> done')
- else:
- path = []
- else:
- path = cached_path
- log('use cached path')
- for pos in path[:2]:
- grid.cells[pos].status = TARGETTED
- # log(f"path {path[:10]}...")
- if path:
- if pac.is_accelerated and len(path) > 1:
- next_ = path.pop(1)
- else:
- next_ = path.pop(0)
- else:
- log('error: no path given')
- next_ = next((n for n in grid.neighbors(pac.x, pac.y) if grid.cells[n].movable), None)
- if next_:
- pac.move(*next_)
- grid.record_move(pac, next_)
- continue
- log(f"no action given, wait")
- pac.wait()
- print("|".join([pac.action_cmd for pac in me.pacs]))
- INPUTS = []
- ROUND += 1
|