s2020.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074
  1. """
  2. Created on 7 mai 2020
  3. @author: olinox
  4. """
  5. import sys
  6. import time
  7. import heapq
  8. # TODO: faire un simple pathfinding en cas d'echec au calcul de route
  9. # TODO: prévoir un plan fallback quand pas de chemin
  10. debug = False
  11. t0 = time.time()
  12. LOG_INPUT = (0 and __file__[-8:] != 's2020.py')
  13. HIJACK_INPUT = (1 and __file__[-8:] == 's2020.py')
  14. _force_input = []
  15. if HIJACK_INPUT:
  16. _force_input = [
  17. ['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'],
  18. ['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']
  19. ]
  20. INPUTS = []
  21. def l_input(*args):
  22. if HIJACK_INPUT:
  23. if not _force_input:
  24. print('no more hijack')
  25. sys.exit()
  26. inp = _force_input[0].pop(0)
  27. if len(_force_input[0]) == 0:
  28. del _force_input[0]
  29. else:
  30. inp = input()
  31. INPUTS.append(inp)
  32. return inp
  33. def log(*msg):
  34. if debug:
  35. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
  36. sys.stderr.flush()
  37. # OWNER
  38. ME = 0
  39. OPPONENT = 1
  40. # Base classes
  41. class Base():
  42. def __repr__(self):
  43. return f"<{self.__class__.__name__}: {self.__dict__}>"
  44. class Queue(Base):
  45. def __init__(self, inv=False):
  46. self.items = []
  47. def __bool__(self):
  48. return bool(self.items)
  49. def __repr__(self):
  50. return str(self.items)
  51. def values(self):
  52. return (v for _, v in self.items)
  53. def put(self, item, priority):
  54. heapq.heappush(self.items, (priority, item))
  55. def fput(self, item, priority):
  56. while priority in [p for p, _ in self.items]:
  57. priority += 1
  58. self.put(item, priority)
  59. def get(self):
  60. return heapq.heappop(self.items)[1]
  61. def get_items(self):
  62. return heapq.heappop(self.items)
  63. class Node(Base):
  64. def __init__(self, pos, path=None):
  65. self.pos = pos
  66. self.path = path or []
  67. class PathNode(tuple):
  68. def __new__(self, x, y, parent=None):
  69. n = tuple.__new__(self, (x, y))
  70. n.parent = parent
  71. n.cost = 0
  72. return n
  73. def __repr__(self):
  74. return f"<{self[0]}, {self[1]}, c:{self.cost}>"
  75. class RiskNode(tuple):
  76. def __new__(self, x, y, parent=None):
  77. n = tuple.__new__(self, (x, y))
  78. n.parent = parent
  79. n.proba = 0
  80. return n
  81. def __repr__(self):
  82. return f"<{self[0]}, {self[1]}, p:{self.proba}>"
  83. class RouteNode():
  84. def __init__(self, segment, parent=None):
  85. self.segment = segment
  86. self.parent = parent
  87. self.value = 0
  88. self.cost = 0
  89. self.visited = []
  90. def __repr__(self):
  91. return f"<{self.segment}, c:{self.cost}, v:{self.value}>"
  92. # Script classes
  93. class Player():
  94. def __init__(self, id_):
  95. self.id_ = id_
  96. self.score = 0
  97. self.pacs = []
  98. UNKNOWN_CELL = "?"
  99. WALL_CELL = "#"
  100. FLOOR_CELL = " "
  101. class Cell(Base):
  102. def __init__(self, x, y, type_=UNKNOWN_CELL):
  103. self.x = x
  104. self.y = y
  105. self.type_ = type_
  106. self._value = 1 # on part du principe que toutes les cases contiennent au moins une pastille au départ
  107. self.status = UNOCCUPIED
  108. self.neighbors = [] # only movable
  109. self.seen = False
  110. @property
  111. def pos(self):
  112. return (self.x, self.y)
  113. @property
  114. def unknown(self):
  115. return self.type_ == UNKNOWN_CELL or (self.is_floor and not self.seen)
  116. @property
  117. def is_floor(self):
  118. return self.type_ == FLOOR_CELL
  119. @property
  120. def movable(self):
  121. return self.is_floor
  122. @property
  123. def value(self):
  124. return self._value
  125. @value.setter
  126. def value(self, value):
  127. self._value = value
  128. self.seen = True
  129. def print_(self):
  130. if self.unknown:
  131. return "?"
  132. elif self.movable:
  133. return {0: " ", 1: "*", 10: "$"}[self.value]
  134. else:
  135. return WALL_CELL
  136. class Segment(Base):
  137. REPR = "?"
  138. uid = 0
  139. def __init__(self, cells):
  140. self.id_ = Segment.new_uid()
  141. self._cells = []
  142. self.cells = cells
  143. # clockwise
  144. self.neighbors = []
  145. self.status = UNOCCUPIED
  146. @property
  147. def cells(self):
  148. return self._cells
  149. @cells.setter
  150. def cells(self, cells):
  151. self._cells = cells
  152. self.coords = [c.pos for c in cells]
  153. self.length = len(cells)
  154. self.update()
  155. def update_status(self):
  156. self.status = max(c.status for c in self.cells)
  157. def update(self):
  158. self.value = sum(c.value for c in self.cells if not c.status == TARGETTED)
  159. self.update_status()
  160. def print_(self):
  161. return self.REPR
  162. @classmethod
  163. def new_uid(cls):
  164. cls.uid += 1
  165. return cls.uid
  166. def __repr__(self):
  167. return f"<{self.__class__.__name__} {self.id_}: {'-'.join([str(c.pos) for c in self.cells])}>"
  168. def go_trough(self, from_):
  169. ifrom = self.coords.index(from_)
  170. if ifrom == 0:
  171. return list(self.coords)
  172. elif ifrom == (len(self.coords) - 1):
  173. return self.coords[::-1]
  174. else:
  175. raise ValueError(f"{from_} not in segment")
  176. class StartingPos(Segment):
  177. REPR = "O"
  178. def __init__(self, cell, pac_id):
  179. super().__init__([cell])
  180. self.pac_id = pac_id
  181. def cell(self):
  182. return self.cells[0]
  183. class Intersection(Segment):
  184. REPR = "+"
  185. def __init__(self, cell):
  186. super().__init__([cell])
  187. def cell(self):
  188. return self.cells[0]
  189. class Corridor(Segment):
  190. REPR = "-"
  191. class LoopCorridor(Corridor):
  192. REPR = "~"
  193. class Deadend(Segment):
  194. REPR = "."
  195. class Grid(Base):
  196. MAX_ITS = 360
  197. def __init__(self, width, height):
  198. self.width = width
  199. self.height = height
  200. self.cells = {}
  201. self.pacs = {}
  202. self.pellets = {}
  203. self.risk = {}
  204. self.segments = []
  205. self.owned_pacs = []
  206. self.opponent_pacs = []
  207. self.segment_index = {}
  208. self.pacs_index = {}
  209. self.path_cache = {}
  210. self.consumed_its = 0
  211. @property
  212. def grid(self):
  213. return [[self.cells[(x, y)].print_() for x in range(self.width)] for y in range(self.height)]
  214. def print_(self):
  215. return "\n" + "\n".join(["".join([c for c in row]) for row in self.grid])
  216. def __getitem__(self, key):
  217. return self.cells[key]
  218. @classmethod
  219. def from_matrix(cls, matrix):
  220. width = len(matrix[0])
  221. height = len(matrix)
  222. cells = {(x, y): Cell(x, y, v) for y, r in enumerate(matrix) for x, v in enumerate(r)}
  223. grid = Grid(width, height)
  224. grid.cells = cells
  225. for pos, cell in grid.cells.items():
  226. cell.neighbors = []
  227. for n in grid.neighbors(*pos):
  228. try:
  229. if grid.cells[n].movable:
  230. cell.neighbors.append(n)
  231. except KeyError:
  232. log(f'/!\ {n} not in cells')
  233. return grid
  234. def segmentize(self):
  235. self.segments = []
  236. self.segment_index = {}
  237. segments = []
  238. # intersections and starting points
  239. pivots = []
  240. pivots_coords = set()
  241. for pos, cell in self.cells.items():
  242. if pos in self.pacs_index:
  243. segment = StartingPos(cell, self.pacs_index[pos].id)
  244. segments.append(segment)
  245. self.segment_index[pos] = segment
  246. pivots.append(segment)
  247. pivots_coords.add(pos)
  248. elif cell.movable and len(cell.neighbors) >= 3:
  249. segment = Intersection(cell)
  250. segments.append(segment)
  251. self.segment_index[pos] = segment
  252. pivots.append(segment)
  253. pivots_coords.add(pos)
  254. # corridors and deadends
  255. for pivot in pivots:
  256. pivot_cell = pivot.cell()
  257. for n in pivot_cell.neighbors:
  258. if n in self.segment_index:
  259. continue
  260. nc = self.cells[n]
  261. buffer = []
  262. while nc:
  263. buffer.append(nc)
  264. if nc.pos in pivots_coords:
  265. # corridor ou startingpos
  266. segment = Corridor(buffer[:-1])
  267. break
  268. elif len(nc.neighbors) == 1:
  269. segment = Deadend(list(buffer))
  270. break
  271. elif len(nc.neighbors) == 2:
  272. nextnc = next((self.cells[p] for p in nc.neighbors if
  273. self.cells[p] not in buffer and p != pivot_cell.pos), None)
  274. if nextnc is None:
  275. # c'est une boucle, on vient de revenir au point de depart
  276. segment = LoopCorridor(list(buffer))
  277. break
  278. nc = nextnc
  279. else:
  280. log('what happened??')
  281. for c in segment.cells:
  282. self.segment_index[c.pos] = segment
  283. segments.append(segment)
  284. # connect
  285. for pivot in pivots:
  286. pivot_cell = pivot.cell()
  287. for n in pivot_cell.neighbors:
  288. pivot.neighbors.append(n)
  289. neighbor_seg = self.segment_index[n]
  290. if type(neighbor_seg) not in (Intersection, StartingPos):
  291. neighbor_seg.neighbors.append(pivot_cell.pos)
  292. self.segments = segments
  293. for s in self.segments:
  294. s.update()
  295. def segment_neighbors(self, seg):
  296. return [self.segment_index[n] for n in seg.neighbors]
  297. def print_segments(self):
  298. rows = []
  299. for y in range(self.height):
  300. row = []
  301. for x in range(self.width):
  302. try:
  303. row.append(self.segment_index[(x, y)].print_())
  304. except KeyError:
  305. row.append("#")
  306. rows.append(row)
  307. return "\n".join(["".join([c for c in row]) for row in rows])
  308. def update(self, visible_pacs, visible_pellets):
  309. self.consumed_its = 0
  310. for c in self.cells.values():
  311. c.status = UNOCCUPIED
  312. # update pacs
  313. self.pacs_index = {}
  314. me.pacs = []
  315. for a in visible_pacs:
  316. pac_id, is_mine, x, y, type_id, speed_turns_left, ability_cooldown = a
  317. if is_mine:
  318. id_ = pac_id
  319. else:
  320. id_ = -1 * (pac_id + 1)
  321. if not id_ in grid.pacs:
  322. pac = Pac(id_, is_mine)
  323. self.pacs[id_] = pac
  324. else:
  325. pac = self.pacs[id_]
  326. pac.update(x, y, type_id, speed_turns_left, ability_cooldown)
  327. self.cells[(x, y)].status = ALLY_HERE if is_mine else ENNEMY_HERE
  328. self.pacs_index[(x, y)] = pac
  329. if is_mine:
  330. me.pacs.append(pac)
  331. else:
  332. if not pac in opponent.pacs:
  333. opponent.pacs.append(pac)
  334. # update threats
  335. for pac in me.pacs:
  336. for ennemy in opponent.pacs:
  337. dist = 2 if not pac.is_accelerated else 3
  338. scope = self.scope(ennemy.pos, dist)
  339. if pac.pos in scope and pac.lose_against(ennemy):
  340. pac.threaten_by = ennemy.id
  341. # special: if a 10-cell still exist, we'll know
  342. for c in self.cells.values():
  343. if c.value:
  344. c.value = 1
  345. # update pellets
  346. self.pellets = {}
  347. for a in visible_pellets:
  348. x, y, v = a
  349. self.pellets[(x, y)] = Pellet(x, y, v)
  350. if v == 10:
  351. # always visible
  352. self.cells[(x, y)].value = v
  353. # update cells values
  354. in_sight = set()
  355. for pac in me.pacs:
  356. sight = set(self.line_of_sight(pac.pos))
  357. in_sight |= sight
  358. for pos in in_sight:
  359. val = self.pellets[pos].value if pos in self.pellets else 0
  360. self.cells[pos].value = val
  361. @staticmethod
  362. def manhattan(from_, to_):
  363. xa, ya = from_
  364. xb, yb = to_
  365. return abs(xa - xb) + abs(ya - yb)
  366. def neighbors(self, x, y, diags=False):
  367. neighs = []
  368. for nx, ny in [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]:
  369. if not 0 <= ny < self.height:
  370. continue
  371. if nx == -1:
  372. nx = self.width - 1
  373. if nx == self.width:
  374. nx = 0
  375. neighs.append((nx, ny))
  376. return neighs
  377. @staticmethod
  378. def closest(from_, in_):
  379. return min(in_, key=lambda x: Grid.manhattan(from_, x))
  380. def line_of_sight(self, from_):
  381. x0, y0 = from_
  382. sight = [(x0, y0)]
  383. x = x0 + 1
  384. while 1:
  385. if x == self.width:
  386. x = 0
  387. if x == x0:
  388. break # tour complet
  389. if not self.cells[(x, y0)].is_floor:
  390. break
  391. sight.append((x, y0))
  392. x += 1
  393. x = x0 - 1
  394. while 1:
  395. if x == -1:
  396. x = self.width - 1
  397. if x == x0:
  398. break # tour complet
  399. if not self.cells[(x, y0)].is_floor:
  400. break
  401. sight.append((x, y0))
  402. x -= 1
  403. y = y0 + 1
  404. while self.cells[(x0, y)].is_floor:
  405. sight.append((x0, y))
  406. y += 1
  407. y = y0 - 1
  408. while self.cells[(x0, y)].is_floor:
  409. sight.append((x0, y))
  410. y -= 1
  411. return sight
  412. def scope(self, start, dist=1):
  413. """ zone de mouvement d'un pac """
  414. scope = {start}
  415. for _ in range(dist):
  416. new_scope = set()
  417. for p in scope:
  418. new_scope |= {n for n in self.neighbors(*p) if self.cells[n].is_floor}
  419. scope |= new_scope
  420. return list(scope)
  421. def path(self, start, target):
  422. nodes = Queue()
  423. its, break_on = 0, 2000
  424. origin = PathNode(*start)
  425. nodes.put(origin, 0)
  426. neighbors = []
  427. while nodes:
  428. current = nodes.get()
  429. if current == target:
  430. path = []
  431. previous = current
  432. while previous:
  433. if previous != start:
  434. path.insert(0, previous)
  435. previous = previous.parent
  436. return path
  437. neighbors = self.neighbors(*current)
  438. for x, y in neighbors:
  439. its += 1
  440. if its > break_on:
  441. log("<!> pathfinding broken")
  442. return None
  443. if (x, y) == current.parent:
  444. continue
  445. cell = self.cells[(x, y)]
  446. if not cell.movable:
  447. continue
  448. moving_cost = 1
  449. if cell.unit and cell.unit.owned:
  450. moving_cost += cell.unit.level
  451. if cell.under_tower:
  452. moving_cost += 2
  453. cost = current.cost + moving_cost
  454. priority = cost + 10 * Grid.manhattan((x, y), target)
  455. node = PathNode(x, y, current)
  456. node.cost = cost
  457. nodes.put(node, priority)
  458. return None
  459. def eval_risk(self):
  460. # eval the nest opponent moves
  461. self.risk = {}
  462. for pac in opponent.pacs:
  463. current = RiskNode(pac.x, pac.y)
  464. current.proba = 100
  465. nodes = {0: [current]}
  466. round_ = 1
  467. while round_ < 8:
  468. nodes[round_] = []
  469. for parent in nodes[round_ - 1]:
  470. neighbors = self.neighbors(*parent)
  471. candidates_cells = [self.cells[n] for n in neighbors if self.cells[n].movable]
  472. coming_from = tuple(parent.parent) if parent.parent else None
  473. # 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
  474. total_val = sum(
  475. [(4 * cell.value if (cell.x, cell.y) != coming_from else 0) + 1 for cell in candidates_cells])
  476. for cell in candidates_cells:
  477. node = RiskNode(cell.x, cell.y)
  478. node.parent = parent
  479. proba = (100 * ((4 * cell.value if (cell.x, cell.y) != coming_from else 0) + 1)) / total_val
  480. node.proba = round((proba * parent.proba) / 100)
  481. nodes[round_].append(node)
  482. round_ += 1
  483. for round_, round_nodes in nodes.items():
  484. if not round_ in self.risk:
  485. self.risk[round_] = {}
  486. for node in round_nodes:
  487. x, y = node
  488. if not (x, y) in self.risk[round_]:
  489. self.risk[round_][(x, y)] = node.proba
  490. else:
  491. if node.proba > self.risk[round_][(x, y)]:
  492. self.risk[round_][(x, y)] = node.proba
  493. def print_risk(self, round_):
  494. if not round_ in self.risk:
  495. return "no data to show"
  496. grid = []
  497. for y in range(self.height):
  498. row = []
  499. for x in range(self.width):
  500. if (x, y) in self.risk[round_]:
  501. c = str(self.risk[round_][(x, y)])
  502. if len(c) == 1:
  503. c = f".{c}."
  504. elif len(c) == 2:
  505. c = f".{c}"
  506. elif not self.cells[(x, y)].movable:
  507. c = "###"
  508. else:
  509. c = "..."
  510. row.append(c)
  511. grid.append(row)
  512. res = "\n".join(["".join([c for c in row]) for row in grid])
  513. return f"ROUND {round_}\n" + res
  514. def route_finding(self, start):
  515. if self.consumed_its > self.MAX_ITS:
  516. log('max its already consumed')
  517. return None
  518. nodes = Queue(inv=True)
  519. targeted_cost = 18
  520. reserve = Queue()
  521. its, break_on = 0, 80
  522. broken = False
  523. starting_seg = self.segment_index[start]
  524. origin = RouteNode(starting_seg)
  525. origin.visited = [starting_seg.id_]
  526. origin.value = 0
  527. origin.cost = 1
  528. nodes.put(origin, 0)
  529. current = None
  530. while 1:
  531. if broken:
  532. break
  533. if not nodes:
  534. break
  535. priority, current = nodes.get_items()
  536. if current.cost >= targeted_cost:
  537. reserve.fput(current, priority)
  538. continue
  539. neighbors = self.segment_neighbors(current.segment)
  540. for seg in neighbors:
  541. its += 1
  542. if its >= break_on or (its + self.consumed_its) > self.MAX_ITS:
  543. broken = True
  544. break
  545. if seg.status > ALLY_HERE and not seg is starting_seg:
  546. continue
  547. if seg.status == ALLY_HERE and not current.cost:
  548. continue
  549. value = seg.value if seg.id_ not in current.visited else 0
  550. cost = seg.length
  551. half_turn = (current.parent is not None and current.parent.segment.id_ == seg.id_ and type(seg) is not LoopCorridor)
  552. if half_turn:
  553. cost *= 2
  554. node = RouteNode(seg, current)
  555. node.value = current.value + value
  556. node.cost = current.cost + cost
  557. node.visited = list(current.visited) + [seg.id_]
  558. priority = 100 * (node.cost - node.value)
  559. nodes.fput(node, priority)
  560. self.consumed_its += its
  561. if broken:
  562. # loop was interrupted
  563. if reserve:
  564. # some routes were put in the reserve
  565. log('> routefinding interrupted')
  566. target = reserve.get()
  567. elif nodes:
  568. # no routes has been fully explored
  569. log('> routefinding interrupted without reserve')
  570. target = nodes.get()
  571. while target.value == 0:
  572. if not nodes:
  573. log('not a single valued node to target... :(')
  574. return None
  575. target = nodes.get()
  576. else:
  577. # something went wrong
  578. log('<!> routefinding broken')
  579. return []
  580. else:
  581. if reserve:
  582. # all paths were explored
  583. target = reserve.get()
  584. else:
  585. # something went wrong
  586. log('<!> routefinding broken')
  587. return []
  588. route = []
  589. previous = target
  590. while previous:
  591. if previous.segment.id_ != starting_seg.id_:
  592. route.insert(0, previous)
  593. previous = previous.parent
  594. return [node.segment for node in route]
  595. def route_to_path(self, route, from_):
  596. path = []
  597. cur_pos = from_
  598. for seg in route:
  599. part = []
  600. # is it an half-turn?
  601. if cur_pos in seg.coords:
  602. # on parcourt le dernier segment a l'envers avant de poursuivre
  603. part = seg.go_trough(cur_pos)[1:]
  604. else:
  605. for n in self.neighbors(*cur_pos):
  606. try:
  607. part = seg.go_trough(n)
  608. break
  609. except ValueError:
  610. pass
  611. if not part:
  612. log("error: error while rebuilding the path")
  613. return path
  614. path += part
  615. cur_pos = path[-1]
  616. if not route:
  617. break
  618. return path
  619. def cache_path(self, pac_id, path):
  620. self.path_cache[pac_id] = (path, sum(self.cells[c].value for c in path))
  621. def has_cached_path(self, pac_id):
  622. return pac_id in self.path_cache
  623. def get_cached_path(self, pac_id):
  624. """ return the cached path if its total value did not change """
  625. if not pac_id in self.path_cache:
  626. return None
  627. path, val = self.path_cache[pac_id]
  628. if not path:
  629. return None
  630. cur_val = sum(self.cells[c].value for c in path)
  631. if val != cur_val:
  632. return None
  633. if any(other.pos in path for other in me.pacs if other.id != pac_id):
  634. return None
  635. return path
  636. def record_move(self, pac, new_pos):
  637. del self.pacs_index[pac.pos]
  638. self.pacs_index[new_pos] = pac
  639. self.cells[pac.pos].status = UNOCCUPIED
  640. self.cells[new_pos].status = ALLY_HERE
  641. self.segment_index[pac.pos].status = UNOCCUPIED
  642. self.segment_index[new_pos].status = ALLY_HERE
  643. pac.pos = new_pos
  644. SPEED_DURATION = 5
  645. ABILITY_COOLDOWN = 10
  646. ROCK = "ROCK"
  647. PAPER = "PAPER"
  648. SCISSORS = "SCISSORS"
  649. _COMBOS = ((ROCK, SCISSORS),
  650. (SCISSORS, PAPER),
  651. (PAPER, ROCK))
  652. WIN_AGAINST = {t1: t2 for t1, t2 in _COMBOS}
  653. LOSE_AGAINST = {t2: t1 for t1, t2 in _COMBOS}
  654. # Types d'occupation des cases
  655. UNOCCUPIED = 0
  656. TARGETTED = 5
  657. ALLY_HERE = 10
  658. ENNEMY_HERE = 20
  659. PREDATOR_HERE = 21
  660. HARMLESS_HERE = 22
  661. PREY_HERE = 23
  662. class Pac(Base):
  663. def __init__(self, pac_id, mine):
  664. self.id = pac_id
  665. self.owner = ME if mine else OPPONENT
  666. self.type = None
  667. self.speed_cooldown = 0
  668. self.speed_turns_left = 0
  669. self.ability_cooldown = 0
  670. self.threaten_by = None
  671. self.action_cmd = ""
  672. def update(self, x, y, type_id, speed_turns_left, ability_cooldown):
  673. self.x = x
  674. self.y = y
  675. self.type = type_id
  676. self.speed_turns_left = speed_turns_left
  677. self.ability_cooldown = ability_cooldown
  678. self.action_cmd = ""
  679. self.threaten_by = None
  680. @property
  681. def pos(self):
  682. return (self.x, self.y)
  683. @pos.setter
  684. def pos(self, pos):
  685. self.x, self.y = pos
  686. @property
  687. def owned(self):
  688. return self.owner == ME
  689. def can_accelerate(self):
  690. return self.ability_cooldown == 0
  691. def can_switch(self):
  692. return self.ability_cooldown == 0
  693. @property
  694. def is_accelerated(self):
  695. return self.speed_turns_left > 0
  696. def win_against(self, other_pac):
  697. if other_pac.owned:
  698. raise Exception("Why would you fight against your friend?")
  699. return WIN_AGAINST[self.type] == other_pac.type
  700. def lose_against(self, other_pac):
  701. if other_pac.owned:
  702. raise Exception("Why would tou fight against your friend?")
  703. return LOSE_AGAINST[self.type] == other_pac.type
  704. def move(self, x, y):
  705. self.action_cmd = f"MOVE {self.id} {x} {y}"
  706. def speed_up(self):
  707. if self.speed_cooldown > 0:
  708. raise Exception("Speed cooldown still run")
  709. self.ability_cooldown = ABILITY_COOLDOWN
  710. self.speed_turns_left = SPEED_DURATION
  711. self.action_cmd = f"SPEED {self.id}"
  712. def switch(self, new_type):
  713. if new_type == self.type:
  714. raise Exception('has already the type {new_type}')
  715. self.type = new_type
  716. self.ability_cooldown = ABILITY_COOLDOWN
  717. self.action_cmd = f"SWITCH {self.id} {new_type}"
  718. def switch_to_beat_up(self, other_pac):
  719. new_type = LOSE_AGAINST[other_pac.type]
  720. self.switch(new_type)
  721. def wait(self):
  722. self.action_cmd = f"MOVE {self.id} {self.x} {self.y}"
  723. class Pellet(Base):
  724. def __init__(self, x, y, value=1):
  725. self.x = x
  726. self.y = y
  727. self.value = value
  728. # Main loop
  729. ROUND = 0
  730. _, height = [int(i) for i in l_input().split()]
  731. grid = Grid.from_matrix([list(l_input()) for _ in range(height)])
  732. me = Player(ME)
  733. opponent = Player(OPPONENT)
  734. while 1:
  735. cmds = []
  736. log(f"--- ROUND {ROUND} ---")
  737. me.score, opponent.score = [int(i) for i in l_input().split()]
  738. visible_pacs = []
  739. visible_pac_count = int(l_input()) # all your pacs and enemy pacs in sight
  740. for _ in range(visible_pac_count):
  741. pac_id, is_mine, x, y, type_id, speed_turns_left, ability_cooldown = l_input().split()
  742. visible_pacs.append(
  743. (int(pac_id), is_mine != "0", int(x), int(y), type_id, int(speed_turns_left), int(ability_cooldown)))
  744. visible_pellets = []
  745. visible_pellet_count = int(l_input()) # all pellets in sight
  746. for _ in range(visible_pellet_count):
  747. x, y, v = [int(j) for j in l_input().split()]
  748. visible_pellets.append((x, y, v))
  749. log('> input fetched')
  750. grid.update(visible_pacs, visible_pellets)
  751. log('> grid updated')
  752. log(grid.print_())
  753. if LOG_INPUT:
  754. log(INPUTS)
  755. for pac in me.pacs:
  756. grid.segmentize()
  757. grid.print_segments()
  758. log(f'# Pac {pac.id}')
  759. cached_path = grid.get_cached_path(pac.id)
  760. if pac.threaten_by:
  761. log('<!> Threaten by: ', pac.threaten_by)
  762. if cached_path and len(cached_path) > 3 and pac.can_accelerate() and not pac.threaten_by:
  763. pac.speed_up()
  764. continue
  765. if pac.threaten_by and pac.can_switch():
  766. switch_to = next(
  767. type_ for type_, against in WIN_AGAINST.items() if against == grid.pacs[pac.threaten_by].type)
  768. log(f'Switch to resist! >> {switch_to}')
  769. pac.switch(switch_to)
  770. continue
  771. if cached_path is None:
  772. log('routefinding')
  773. route = grid.route_finding(pac.pos)
  774. if route:
  775. path = grid.route_to_path(route, pac.pos)
  776. grid.cache_path(pac.id, path)
  777. log('> done')
  778. else:
  779. path = []
  780. else:
  781. path = cached_path
  782. log('use cached path')
  783. for pos in path[:2]:
  784. grid.cells[pos].status = TARGETTED
  785. # log(f"path {path[:10]}...")
  786. if path:
  787. if pac.is_accelerated and len(path) > 1:
  788. next_ = path.pop(1)
  789. else:
  790. next_ = path.pop(0)
  791. else:
  792. log('error: no path given')
  793. next_ = next((n for n in grid.neighbors(pac.x, pac.y) if grid.cells[n].movable), None)
  794. if next_:
  795. pac.move(*next_)
  796. grid.record_move(pac, next_)
  797. continue
  798. log(f"no action given, wait")
  799. pac.wait()
  800. print("|".join([pac.action_cmd for pac in me.pacs]))
  801. INPUTS = []
  802. ROUND += 1