unleash.py 31 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076
  1. '''
  2. @author: olivier.massot, oct. 2019
  3. '''
  4. import heapq
  5. import sys
  6. import time
  7. # TODO
  8. # * poser les mines de préférence sur des emplacements qui sont sous radar ennemi
  9. # * si une case est suspectée d'abriter un radar, regarder s'il se passe 3 tours sans que les ennemis y creuse. Si c'est le cas, baisser l'intérêt de toutes ces cases
  10. # * prevoir le mouvements suivant lors du retour au qg
  11. debug = True
  12. verbose_input = False
  13. t0 = time.time()
  14. def log(*msg):
  15. if debug:
  16. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
  17. sys.stderr.flush()
  18. def input_():
  19. s = input()
  20. if verbose_input:
  21. log("I>", s)
  22. return s
  23. # OWNER
  24. ME = 0
  25. OPPONENT = 1
  26. # ENTITIES AND ITEMS
  27. NONE = -1
  28. ROBOT = 0
  29. OPP_ROBOT = 1
  30. RADAR = 2
  31. TRAP = 3
  32. ORE = 4
  33. # PARAMS
  34. COOLDOWN = 5
  35. RADAR_SCOPE = 4
  36. MOVING = 4
  37. class Base():
  38. def __repr__(self):
  39. return f"<{self.__class__.__name__}: {self.__dict__}>"
  40. class Queue(Base):
  41. def __init__(self):
  42. self.items = []
  43. self.indices = {}
  44. def __bool__(self):
  45. return bool(self.items)
  46. def __repr__(self):
  47. return str(self.items)
  48. def values(self):
  49. return (v for _, v in self.items)
  50. def put(self, item, priority):
  51. heapq.heappush(self.items, (priority, item))
  52. def fput(self, item, priority):
  53. indice = 0
  54. if priority in self.indices:
  55. indice = self.indices[priority] + 1
  56. self.indices[priority] = indice
  57. self.put(item, (priority, indice))
  58. def get(self):
  59. return heapq.heappop(self.items)[1]
  60. @classmethod
  61. def from_iter(cls, iterable):
  62. q = cls()
  63. for item, p in iterable:
  64. q.fput(item, p)
  65. return q
  66. class Task(Base):
  67. def __init__(self, target = None):
  68. self.target = target
  69. class GoDig(Task):
  70. pass
  71. class GetOre(Task):
  72. pass
  73. class BringBackOre(Task):
  74. pass
  75. class GetRadar(Task):
  76. pass
  77. class GetTrap(Task):
  78. pass
  79. class SetRadar(Task):
  80. pass
  81. class SetTrap(Task):
  82. pass
  83. class Lure(Task):
  84. pass
  85. class Dispose(Task):
  86. pass
  87. class Sabotage(Task):
  88. pass
  89. class Action(Base):
  90. def resolve(self, command):
  91. log(f" >> {command}")
  92. print(command)
  93. class Wait(Action):
  94. def resolve(self):
  95. print("WAIT")
  96. class Move(Action):
  97. def __init__(self, x, y):
  98. self.x = int(x)
  99. self.y = int(y)
  100. @property
  101. def target(self):
  102. return self.x, self.y
  103. def resolve(self):
  104. super().resolve(f"MOVE {self.x} {self.y}")
  105. class Dig(Action):
  106. def __init__(self, x, y):
  107. self.x = int(x)
  108. self.y = int(y)
  109. @property
  110. def target(self):
  111. return self.x, self.y
  112. def resolve(self):
  113. super().resolve(f"DIG {self.x} {self.y}")
  114. class Request(Action):
  115. def __init__(self, item):
  116. self.item = item
  117. def resolve(self):
  118. super().resolve(f"REQUEST {self.item}")
  119. class RequestRadar(Request):
  120. def __init__(self):
  121. super().__init__("RADAR")
  122. def resolve(self):
  123. me.radar_cooldown = COOLDOWN
  124. super().resolve()
  125. class RequestTrap(Request):
  126. def __init__(self):
  127. super().__init__("TRAP")
  128. def resolve(self):
  129. me.trap_cooldown = COOLDOWN
  130. super().resolve()
  131. class Entity(Base):
  132. def __init__(self, id_, x, y):
  133. self.id_ = int(id_)
  134. self.x = int(x)
  135. self.y = int(y)
  136. @property
  137. def pos(self):
  138. return self.x, self.y
  139. def update(self, x, y, _):
  140. self.x = x
  141. self.y = y
  142. class Radar(Entity):
  143. def __init__(self, id_, x, y):
  144. super().__init__(id_, x, y)
  145. class Trap(Entity):
  146. def __init__(self, id_, x, y):
  147. super().__init__(id_, x, y)
  148. self.area = [self.pos] + grid.neighbors(*self.pos, diags = False)
  149. class Ore(Entity):
  150. def __init__(self, id_, x, y):
  151. super().__init__(id_, x, y)
  152. class Robot(Entity):
  153. def __init__(self, id_, x, y, item, owner):
  154. super().__init__(id_, x, y)
  155. self.item = item if item != NONE else None
  156. self.owner = owner
  157. self.has_played = False
  158. self.moved = False
  159. self.may_have_item = False
  160. self.may_have_found_ore_at = None
  161. self.is_bait = False
  162. self.last_action = None
  163. self.todo = None
  164. self.last_task = None
  165. def update(self, x, y, item):
  166. self.has_played = False
  167. self.moved = (x, y) != self.pos
  168. self.last_task = self.todo
  169. self.todo = None
  170. if not self.owned and not self.ko:
  171. # try to guess last action
  172. if self.moved:
  173. self.last_action = Move(x, y)
  174. else:
  175. if self.in_hq():
  176. self.last_action = Request("?")
  177. else:
  178. self.last_action = Dig(x, y)
  179. if isinstance(self.last_action, Request):
  180. # * if is in hq and did not move: may_have_item
  181. self.may_have_item = True
  182. log(f"% [Robot {self.id_}] May have item")
  183. elif isinstance(self.last_action, Move) and x == 0:
  184. # * If just get in HQ, last may_have_found_ore_at updated as has_ore=true
  185. if self.may_have_found_ore_at:
  186. log(f"% [Robot {self.id_}] May have found ore at: {self.may_have_found_ore_at}")
  187. grid.cells[self.may_have_found_ore_at].possibly_ore = True
  188. self.may_have_found_ore_at = None
  189. if isinstance(self.last_action, Dig):
  190. if self.may_have_item:
  191. # * if not in hq and may_have_item and dig, cell become suspect (and scope with it). may_have_item become false
  192. self.may_have_item = False
  193. may_have_digged = [self.pos] + grid.neighbors(*self.pos)
  194. may_have_digged = [c for c in may_have_digged if c[0] > 0]
  195. log(f"% [Robot {self.id_}] Suspect cells: {may_have_digged}")
  196. for n in may_have_digged:
  197. grid.cells[n].suspect = 10
  198. grid.cells[self.pos].suspect = 10
  199. else:
  200. # had no item and digged: may have found ore
  201. if self.may_have_found_ore_at:
  202. grid.cells[self.may_have_found_ore_at].seems_empty = True
  203. self.may_have_found_ore_at = self.pos
  204. for n in [self.pos] + grid.neighbors(*self.pos):
  205. if grid.cells[n].memorized_ore:
  206. grid.cells[n].memorized_ore -= 0.2
  207. grid.cells[n].suspect -= 2
  208. if self.owned and not self.ko:
  209. if isinstance(self.last_action, Dig):
  210. target = grid.cells[self.last_action.target]
  211. if item == ORE:
  212. target.had_ore = True
  213. target.memorized_ore -= 1
  214. else:
  215. target.dry = True
  216. target.memorized_ore = 0
  217. if target.new_hole:
  218. target.was_empty = True
  219. target.suspect = 0
  220. self.x = x
  221. self.y = y
  222. self.item = item if item != NONE else None
  223. if self.ko:
  224. self.todo = None
  225. @property
  226. def owned(self):
  227. return self.owner == ME
  228. @property
  229. def ko(self):
  230. return self.x < 0
  231. def in_hq(self):
  232. return self.x == 0
  233. def next_to_hq(self):
  234. return self.x <= 1
  235. @property
  236. def hold_radar(self):
  237. return self.item == RADAR
  238. @property
  239. def hold_trap(self):
  240. return self.item == TRAP
  241. @property
  242. def hold_ore(self):
  243. return self.item == ORE
  244. def go_to(self, pos, around_ok=True):
  245. if around_ok:
  246. # best position to interact with the target cell (ie: the less dangerous)
  247. pos = min([pos] + grid.neighbors(*pos), key=lambda c: (grid.cells[c].moving_cost(), grid.distance(self.pos, c)))
  248. log(f" (best position for action: {pos})")
  249. path = grid.path(self.pos, pos)
  250. if path:
  251. log(f"Path: {path}")
  252. new_pos = path[0]
  253. self.x, self.y = new_pos
  254. else:
  255. new_pos = pos
  256. log("<!!> No path found")
  257. for n in grid.scope2[new_pos]:
  258. grid.cells[n].ally_near = True
  259. self.move(*new_pos)
  260. def go_back_to_hq(self):
  261. self.current_digging_target = None
  262. c = grid.closest(self.pos, grid.hq)
  263. self.go_to(c, around_ok=False)
  264. def digging_queue(self):
  265. q = Queue()
  266. last_target = None
  267. if type(self.last_task) in (GoDig, GetOre):
  268. last_target = self.last_task.target
  269. for c, cell in grid.cells.items():
  270. if cell.dry or cell.is_hq or cell.has_trap or cell.suspect:
  271. continue
  272. if cell.under_radar and not cell.ore:
  273. continue # we know there is nothing here
  274. p = 0
  275. p += ((4 * grid.moving_distance(self.pos, c) + c[0]) // 2) # mean between dist to robot and dist to hq
  276. p -= max(cell.interest, 30)
  277. if c == last_target:
  278. p -= 3 # avoid hesitations
  279. q.put(c, p)
  280. return q
  281. def best_to_dig(self):
  282. try:
  283. return self.digging_queue().get()
  284. except IndexError:
  285. return None
  286. def radar_positioning_queue(self):
  287. q = Queue()
  288. last_target = None
  289. if type(self.last_task) is SetRadar:
  290. last_target = self.last_task.target
  291. for c, cell in grid.cells.items():
  292. if cell.is_hq or cell.under_radar or cell.has_trap or cell.suspect:
  293. continue
  294. p = 0
  295. p += ((4 * grid.moving_distance(self.pos, c) + c[0]) // 2)
  296. p -= cell.interest
  297. if x in (5, 8, 11, 14, 18, 22, 26) and 2 <= y <= 12:
  298. p -= 10
  299. if y in (3, 7, 11) and 5 <= x <= 26:
  300. p -= 10
  301. for c2 in grid.scope4[c]:
  302. other = grid.cells[c2]
  303. if not (other.dry or other.discovered or other.is_hq or other.has_trap):
  304. p -= 1
  305. if c[0] < 5:
  306. p += 20
  307. if cell.ore: # one stone two birds: we can take the ore while setting the radar
  308. p -= 2
  309. if c == last_target:
  310. p -= 3 # avoid hesitations
  311. q.put(c, p)
  312. return q
  313. def best_for_radar(self):
  314. try:
  315. return self.radar_positioning_queue().get()
  316. except IndexError:
  317. grid.no_more_radar = True
  318. return None
  319. def trap_positioning_queue(self):
  320. q = Queue()
  321. last_target = None
  322. if type(self.last_task) is SetTrap:
  323. last_target = self.last_task.target
  324. for c, cell in grid.cells.items():
  325. if cell.is_hq or cell.has_trap or cell.suspect:
  326. continue
  327. if cell.scarecrow: # this cell should already have become suspect to ennemy
  328. continue
  329. p = 0
  330. p += ((4 * grid.moving_distance(self.pos, c) + c[0]) // 2)
  331. p -= cell.interest
  332. if cell.ore == 2:
  333. p -= 30
  334. elif cell.ore or cell.possibly_ore or cell.memorized_ore:
  335. p -= 10
  336. if any(r.pos in grid.scope2[c] for r in me.robots):
  337. p += 5
  338. if c == last_target:
  339. p -= 5 # avoid hesitations
  340. q.put(c, p)
  341. return q
  342. def best_for_trap(self):
  343. try:
  344. return self.trap_positioning_queue().get()
  345. except IndexError:
  346. return None
  347. def dispose(self):
  348. # dispose of the current item in the fisrt place available
  349. q = Queue()
  350. for c, cell in grid.cells.items():
  351. if cell.is_hq or cell.under_trap or cell.ore:
  352. continue
  353. p = 0
  354. p += 3 * grid.manhattan(self.pos, c)
  355. q.put(c, p)
  356. target = q.get()
  357. log(f"Dispose of item {self.item} at {target}")
  358. self.go_dig(target)
  359. def go_set_radar(self, pos):
  360. self.go_dig(pos)
  361. def go_set_trap(self, pos):
  362. self.go_dig(pos)
  363. def go_dig(self, pos):
  364. if pos == self.pos or pos in grid.neighbors(*self.pos):
  365. if self.item == TRAP:
  366. grid.cells[pos].has_trap = True
  367. grid.cells[pos].under_trap = True
  368. self.dig(*pos)
  369. else:
  370. self.go_to(pos)
  371. def collateral_for(self, trap):
  372. area = grid.collateral_area_for(trap.pos)
  373. return [e for e in self.entities if isinstance(e, Robot) and e.pos in area]
  374. # commands
  375. def _act(self, action):
  376. self.has_played = True
  377. self.last_action = action
  378. action.resolve()
  379. def wait(self):
  380. self._act(Wait())
  381. def move(self, x, y):
  382. self._act(Move(x, y))
  383. def dig(self, x, y):
  384. self._act(Dig(x, y))
  385. if self.is_bait:
  386. grid.cells[(x, y)].scarecrow = True
  387. self.is_bait = False
  388. def request(self, item):
  389. self._act(Request(item))
  390. def request_radar(self):
  391. me.radar_cooldown = COOLDOWN
  392. self._act(RequestRadar())
  393. def request_trap(self):
  394. me.trap_cooldown = COOLDOWN
  395. self._act(RequestTrap())
  396. class Team():
  397. def __init__(self, player):
  398. self.player = player
  399. self.score = 0
  400. self.robots = []
  401. self.radar_cooldown = None
  402. self.trap_cooldown = None
  403. def update(self, entities):
  404. self.robots = sorted([e for e in entities if isinstance(e, Robot) and e.owner == self.player], key= lambda r: r.id_)
  405. def active_robots(self):
  406. return [r for r in self.robots if not r.ko]
  407. class Cell():
  408. def __init__(self, x, y):
  409. self.x = x
  410. self.y = y
  411. self.ore = 0
  412. self.hole = 0
  413. self.had_ore = False
  414. self.suspect = 0
  415. self.possibly_ore = False
  416. self.new_hole = False
  417. self.dry = False
  418. self.scarecrow = False
  419. self.seems_empty = False
  420. self.was_empty = False
  421. self.available_ore = 0
  422. self.memorized_ore = 0
  423. self.under_radar = False
  424. self.discovered = False
  425. self.ally_near = False
  426. @property
  427. def pos(self):
  428. return self.x, self.y
  429. @property
  430. def is_hq(self):
  431. return self.x == 0
  432. def print_state(self):
  433. if not self.hole and not self.under_radar:
  434. return '?'
  435. elif self.has_radar:
  436. return 'R'
  437. elif self.has_trap:
  438. return 'T'
  439. elif self.had_ore:
  440. return '1'
  441. elif self.possibly_ore:
  442. return '*'
  443. else:
  444. return '0'
  445. def update(self, ore, hole):
  446. self.new_hole = hole and not self.hole
  447. if self.under_radar and self.ore:
  448. self.memorized_ore = self.ore
  449. if self.under_radar:
  450. self.discovered = True
  451. self.ore = int(ore) if ore != '?' else None
  452. self.hole = (int(hole) == 1)
  453. if self.ore:
  454. self.had_ore = True
  455. self.interest = 0
  456. self.tokens = 0
  457. self.has_radar = False
  458. self.under_radar = False
  459. self.has_trap = False
  460. self.under_trap = False
  461. self.occupied = False
  462. self.under_suspect = False
  463. self.ennemy_near = False
  464. def moving_cost(self):
  465. if self.has_trap:
  466. return 15 + 10 * self.ennemy_near + 5 * self.ally_near
  467. if self.under_trap or self.suspect:
  468. return 10 + 10 * self.ennemy_near + 5 * self.ally_near
  469. elif self.under_suspect:
  470. return 10 + 5 * self.ennemy_near + 5 * self.ally_near
  471. return 10
  472. def may_have_ore(self):
  473. return self.ore or self.memorized_ore or self.possibly_ore
  474. class PathNode(tuple):
  475. def __new__(self, x, y, parent=None):
  476. n = tuple.__new__(self, (x, y))
  477. n.parent = parent
  478. n.cost = 0
  479. return n
  480. def __repr__(self):
  481. return f"<{self[0]}, {self[1]}, c:{self.cost}>"
  482. class Grid(Base):
  483. w = 30
  484. h = 15
  485. def __init__(self):
  486. self.cells = {(x, y): Cell(x, y) for x in range(Grid.w) for y in range(Grid.h)}
  487. self.hq = [(0, y) for y in range(Grid.h)]
  488. self.scope2 = {c: self.zone(c, 2) for c in self.cells}
  489. self.scope3 = {c: self.zone(c, 3) for c in self.cells}
  490. self.scope4 = {c: self.zone(c, 4) for c in self.cells}
  491. self.dscope4 = {c: self.dzone(c, 4) for c in self.cells}
  492. self.scope5 = {c: self.zone(c, 5) for c in self.cells}
  493. self.entities = {}
  494. self.at = {}
  495. self.no_more_radar = False
  496. @property
  497. def grid(self):
  498. return [[self.cells[(x, y)] for x in range(Grid.w)] for y in range(Grid.h)]
  499. def print_grid(self, key=None):
  500. if key is None:
  501. key = lambda x: x
  502. return "\n"+ "\n".join(["".join([f"{key(c)}|" for c in row]) for row in self.grid])
  503. def update(self, edata):
  504. new_entities = {}
  505. just_destroyed = []
  506. for e in edata:
  507. id_, type_, x, y, item = e
  508. if id_ in self.entities:
  509. e = self.entities[id_]
  510. if e.x > 0 and x < 0:
  511. just_destroyed.append(id_)
  512. e.update(x, y, item)
  513. new_entities[id_] = e
  514. else:
  515. if type_ == ROBOT:
  516. e = Robot(id_, x, y, item, ME)
  517. elif type_ == OPP_ROBOT:
  518. e = Robot(id_, x, y, item, OPPONENT)
  519. elif type_ == RADAR:
  520. e = Radar(id_, x, y)
  521. elif type_ == TRAP:
  522. e = Trap(id_, x, y)
  523. new_entities[id_] = e
  524. # exploded mines
  525. exploded = [e.pos for id_, e in self.entities.items() if type(e) is Trap and not id_ in new_entities]
  526. for id_, e in self.entities.items():
  527. if type(e) is Robot and e.owned and id_ in just_destroyed:
  528. # this robot was just destroyed
  529. if isinstance(e.last_action, Dig):
  530. exploded.append(e.last_action.target)
  531. self.entities = new_entities
  532. # indexes
  533. self.at = {c: [e for e in self.entities.values() if e.pos == c] for c in self.cells}
  534. # update cells states
  535. for e in new_entities.values():
  536. if isinstance(e, Radar):
  537. self.cells[e.pos].has_radar = True
  538. for c in self.scope4[e.pos]:
  539. self.cells[c].under_radar = True
  540. self.cells[c].possibly_ore = False
  541. if not self.cells[c].ore:
  542. self.cells[c].memorized_ore = 0
  543. if isinstance(e, Trap):
  544. self.cells[e.pos].has_trap = True
  545. for c in e.area:
  546. self.cells[c].under_trap = True
  547. if isinstance(e, Robot):
  548. if e.ko:
  549. continue
  550. self.cells[e.pos].occupied = True
  551. if e.owner == OPPONENT:
  552. for c in self.scope5[e.pos]:
  553. self.ennemy_near = True
  554. # interest of undiscovered cells
  555. for c, cell in self.cells.items():
  556. cell.available_ore = cell.ore or int(not cell.under_radar and cell.had_ore and not cell.dry)
  557. for c, cell in self.cells.items():
  558. suspection = cell.suspect * (((200 - round_) // 50) + 1)
  559. if cell.ore:
  560. cell.interest = 40 - suspection
  561. elif int(cell.memorized_ore):
  562. cell.interest = 20 - suspection
  563. else:
  564. for x, y, d in grid.dscope4[c]:
  565. other = grid.cells[(x, y)]
  566. di = (5 - d)
  567. if other.had_ore or other.possibly_ore:
  568. cell.interest += di
  569. if x == 0:
  570. cell.interest -= di # move away from HQ a little
  571. if y == 0 or y == (grid.h - 1):
  572. cell.interest -= 2 # counterbalance the advantage of border cells
  573. if other.was_empty or other.seems_empty:
  574. cell.interest -= di # this was an empty cell
  575. cell.interest //= 4
  576. if x < 8 and round_ < 20: # 8th row seems the best to start
  577. cell.interest -= abs(8 - x)
  578. if cell.available_ore:
  579. cell.interest += 10
  580. if cell.suspect:
  581. for c2 in self.neighbors(*c):
  582. grid.cells[c2].under_suspect = True
  583. for pos in exploded:
  584. log(f"Trap exploded at {pos}, cells around no longer suspects")
  585. for c in [pos] + self.neighbors(*pos):
  586. self.cells[c].suspect = 0
  587. me.update(new_entities.values())
  588. opponent.update(new_entities.values())
  589. def available_ore(self):
  590. return any(c.available_ore for c in self.cells.values())
  591. def is_radar_needed(self):
  592. if grid.no_more_radar:
  593. return False
  594. discovered = sum([cell.ore for cell in self.cells.values() if cell.discovered and cell.ore and not cell.suspect and not cell.has_trap])
  595. robots_alive = len([r for r in me.robots if not r.ko])
  596. if discovered > 4 * robots_alive:
  597. return False
  598. return True
  599. @staticmethod
  600. def manhattan(from_, to_):
  601. xa, ya = from_
  602. xb, yb = to_
  603. return abs(xa - xb) + abs(ya - yb)
  604. @staticmethod
  605. def distance(from_, to_): # alias
  606. return Grid.manhattan(from_, to_)
  607. @staticmethod
  608. def moving_distance(from_, to_):
  609. q, r = divmod(max(0, Grid.manhattan(from_, to_) - 1), MOVING)
  610. return q + int(bool(r))
  611. def neighbors(self, x, y, diags=False, inside_only=True):
  612. n = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  613. if diags:
  614. n += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  615. if inside_only:
  616. n = [(x, y) for x, y in n if 0 <= x < Grid.w and 0 <= y < Grid.h]
  617. return n
  618. @classmethod
  619. def zone(cls, center, radius):
  620. return [(x, y) for x in range(0, cls.w) for y in range(0, cls.h) if cls.manhattan(center, (x, y)) <= radius]
  621. @classmethod
  622. def dzone(cls, center, radius):
  623. z = []
  624. for y in range(0, cls.h):
  625. for x in range(0, cls.w):
  626. d = cls.manhattan(center, (x, y))
  627. if d <= radius:
  628. z.append((x, y, d))
  629. return z
  630. def collateral_area_for(self, trap_pos, current=None):
  631. area = {trap_pos}
  632. for n in self.neighbors(*trap_pos):
  633. if current and n in current:
  634. continue
  635. area.add(n)
  636. if self.cells[n].has_trap:
  637. area |= self.collateral_area_for(n, area)
  638. return area
  639. @staticmethod
  640. def closest(from_, in_):
  641. return min(in_, key=lambda x: Grid.manhattan(from_, x))
  642. def path(self, start, target):
  643. nodes = Queue()
  644. its, break_on = 0, 4000
  645. origin = PathNode(*start)
  646. nodes.put(origin, 0)
  647. neighbors = []
  648. while nodes:
  649. current = nodes.get()
  650. if current == target:
  651. path = []
  652. previous = current
  653. while previous:
  654. if previous != start:
  655. path.insert(0, previous)
  656. previous = previous.parent
  657. return path
  658. neighbors = self.scope4[current]
  659. for x, y in neighbors:
  660. its += 1
  661. if its > break_on:
  662. log("<!> pathfinding broken")
  663. return None
  664. if (x, y) == current.parent:
  665. continue
  666. cell = self.cells[(x, y)]
  667. moving_cost = cell.moving_cost()
  668. if moving_cost < 0:
  669. continue
  670. cost = current.cost + moving_cost
  671. priority = cost + 5 * Grid.manhattan((x, y), target)
  672. node = PathNode(x, y, current)
  673. node.cost = cost
  674. nodes.put(node, priority)
  675. return None
  676. Grid.w, Grid.h = [int(i) for i in input_().split()]
  677. grid = Grid()
  678. me = Team(ME)
  679. opponent = Team(OPPONENT)
  680. round_ = 0
  681. while True:
  682. round_ += 1
  683. # get scores
  684. me.score, opponent.score = [int(i) for i in input_().split()]
  685. log("# Get new data")
  686. # get cells data
  687. for y in range(Grid.h):
  688. s = input_().split()
  689. data = list(zip(s[::2], s[1::2]))
  690. for x, args in enumerate(data):
  691. grid.cells[(x, y)].update(*args)
  692. # various data
  693. n, me.radar_cooldown, me.trap_cooldown = [int(i) for i in input_().split()]
  694. # log("Cooldown: ", me.radar_cooldown, me.trap_cooldown)
  695. # entities data
  696. e_data = [(int(j) for j in input_().split()) for i in range(n)]
  697. grid.update(e_data)
  698. # log("Entities: ", grid.entities)
  699. # log(grid.print_grid(key=lambda c: "{}".format("x" if c.suspect else "_")))
  700. # log(grid.print_grid(key=lambda c: c.print_state()))
  701. ## attributing tasks
  702. # Predefined tasks
  703. log("# Attribute task")
  704. get_radar_affected = False
  705. get_trap_affected = False
  706. sabotage_at = []
  707. for r in me.active_robots():
  708. if r.hold_radar:
  709. target = r.best_for_radar()
  710. if target:
  711. r.todo = SetRadar(target)
  712. grid.cells[target].tokens += 1
  713. else:
  714. log(f" <!> No place found for the radar, dispose")
  715. r.todo = Dispose()
  716. elif r.hold_trap:
  717. target = r.best_for_trap()
  718. if target:
  719. r.todo = SetTrap(target)
  720. grid.cells[target].tokens += 1
  721. else:
  722. log(f" <!> No place found for the trap, dispose")
  723. r.todo = Dispose()
  724. elif r.hold_ore:
  725. r.todo = BringBackOre()
  726. elif me.radar_cooldown == 0 and not get_radar_affected and (r.next_to_hq() or not grid.available_ore()):
  727. if grid.is_radar_needed():
  728. r.todo = GetRadar()
  729. get_radar_affected = True
  730. else:
  731. log(" * No radar needed at this time")
  732. # elif me.trap_cooldown == 0 and r.in_hq() and round_ < 170 and not get_trap_affected:
  733. # r.todo = GetTrap()
  734. # get_trap_affected = True
  735. #
  736. # elif r.in_hq() and round_ < 150 and not isinstance(r.last_action, Wait) and random.randint(1,3) == 2:
  737. # r.todo = Lure()
  738. else:
  739. for c in [r.pos] + grid.neighbors(*r.pos):
  740. cell = grid.cells[c]
  741. if (cell.suspect and cell.may_have_ore()) or cell.has_trap and not c in sabotage_at:
  742. collateral_area = grid.collateral_area_for(c)
  743. allies_victims = [r_ for r_ in me.robots if r_.pos in collateral_area]
  744. opponent_victims = [r_ for r_ in opponent.robots if r_.pos in collateral_area]
  745. if len(opponent_victims) > len(allies_victims) or (cell.suspect and len(opponent_victims) == len(allies_victims)):
  746. r.todo = Sabotage(c)
  747. sabotage_at.append(c)
  748. log(f"Going for sabotage at {c}, area: {len(collateral_area)}, allies: {[x.id_ for x in allies_victims]}, ennemies: {[x.id_ for x in opponent_victims]}")
  749. break
  750. # To-Priorize tasks
  751. task_queue = Queue()
  752. # exploration
  753. for r in me.active_robots():
  754. for c, cell in grid.cells.items():
  755. if cell.dry or cell.is_hq or cell.has_trap:
  756. continue
  757. if cell.under_radar and not cell.ore:
  758. continue # we know there is nothing here
  759. if cell.ore or int(cell.memorized_ore):
  760. task_queue.fput((r, GetOre(c)), grid.distance(r.pos, c) - cell.interest)
  761. else:
  762. task_queue.fput((r, GoDig(c)), grid.distance(r.pos, c) - cell.interest)
  763. while any(r.todo is None for r in me.robots if not r.ko):
  764. try:
  765. r, task = task_queue.get()
  766. except IndexError:
  767. log("<!> Not enough task for everyone")
  768. break
  769. if r.todo:
  770. continue
  771. cell = grid.cells[task.target]
  772. if isinstance(task, GetOre) and cell.tokens >= cell.available_ore:
  773. continue
  774. r.todo = task
  775. cell.tokens += 1
  776. log("# Execution")
  777. for r in me.robots:
  778. log(f"** Robot {r.id_} [{r.pos}] plays (has item: {r.item})")
  779. if r.ko:
  780. log(" -- KO --")
  781. r.wait()
  782. continue
  783. log(f"> Task: {r.todo}")
  784. if type(r.todo) is GetRadar:
  785. log(f" Go get a radar at HQ")
  786. if r.in_hq():
  787. r.request_radar()
  788. else:
  789. r.go_back_to_hq()
  790. elif type(r.todo) is GetTrap:
  791. log(f" Go get a trap at HQ")
  792. if r.in_hq():
  793. r.request_trap()
  794. else:
  795. r.go_back_to_hq()
  796. elif type(r.todo) is BringBackOre:
  797. log(" Bring back ore to HQ")
  798. r.go_back_to_hq()
  799. elif type(r.todo) is Lure:
  800. log(" $ Wait to trick")
  801. r.is_bait = True
  802. r.wait()
  803. elif type(r.todo) is GetOre:
  804. log(f" Go get ore at {r.todo.target}")
  805. r.go_dig(r.todo.target)
  806. elif type(r.todo) is GoDig:
  807. log(f" Go dig at {r.todo.target}")
  808. r.go_dig(r.todo.target)
  809. elif type(r.todo) is SetRadar:
  810. log(f" Go set a radar at {r.todo.target}")
  811. r.go_set_radar(r.todo.target)
  812. elif type(r.todo) is SetTrap:
  813. log(f" Go set a trap at {r.todo.target}")
  814. r.go_set_trap(r.todo.target)
  815. elif type(r.todo) is Dispose:
  816. log(f" Dispose of item")
  817. r.dispose()
  818. elif type(r.todo) is Sabotage:
  819. log(f" Sabotage at {r.todo.target}")
  820. r.go_dig(r.todo.target)
  821. else:
  822. log(" <!> No task (or unknown)")
  823. if not r.has_played:
  824. log(" <!> Has not played")
  825. r.wait()