script.py 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. '''
  2. >> https://www.codingame.com/ide/173171838252e7c6fd6f3ff9cb8169431a08eec1
  3. @author: olivier.massot, may 2019
  4. '''
  5. from collections import Counter
  6. import heapq
  7. import sys
  8. import time
  9. # TODO
  10. # * when building a tower, check that there is not already one, and block the cell for moving
  11. # * do not train a unit of a cell if it is in a threatened pivot zone, especially not a level 3 unit!
  12. # * take units and towers in account when computing the threat
  13. # * take units and towers in account when computing the strategig value
  14. # * review the tower placement in case of threat
  15. debug = True
  16. t0 = time.time()
  17. def log(*msg):
  18. if debug:
  19. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
  20. # OWNER
  21. ME = 0
  22. OPPONENT = 1
  23. # BUILDING TYPE
  24. HQ = 0
  25. class Base():
  26. def __repr__(self):
  27. return f"<{self.__class__.__name__}: {self.__dict__}>"
  28. class Queue(Base):
  29. def __init__(self):
  30. self.items = []
  31. def __bool__(self):
  32. return bool(self.items)
  33. def __repr__(self):
  34. return str(self.items)
  35. def put(self, item, priority):
  36. heapq.heappush(self.items, (priority, item))
  37. def fput(self, item, priority):
  38. while priority in [p for p, _ in self.items]:
  39. priority += 1
  40. self.put(item, priority)
  41. def get(self):
  42. return heapq.heappop(self.items)[1]
  43. class InterestQueue(Queue):
  44. def __add__(self, other):
  45. self.items += other.items
  46. return self
  47. def put(self, item):
  48. heapq.heappush(self.items, item)
  49. def get(self):
  50. return heapq.heappop(self.items)
  51. class BasePosition(Base):
  52. def __init__(self, cell, *args, **kwargs):
  53. self.interest = 0
  54. self.cell = cell
  55. @property
  56. def x(self):
  57. return self.cell.x
  58. @property
  59. def y(self):
  60. return self.cell.y
  61. @property
  62. def pos(self):
  63. return self.cell.pos
  64. def __lt__(self, other):
  65. return self.interest < other.interest
  66. def eval(self):
  67. raise NotImplementedError
  68. class Position(BasePosition):
  69. def __init__(self, cell):
  70. super().__init__(cell)
  71. self.possession = 0
  72. self.threat = 0
  73. self.strategic_value = 0
  74. self.pivot = 0
  75. self.union = 0
  76. self.depth = 0
  77. self.hq = 0
  78. self.tower = 0
  79. self.mine = 0
  80. self.dist_to_goal = 0
  81. self.min_level = 1
  82. def pre_eval(self):
  83. # *** Eval interest: the lower the better
  84. self.interest = 0
  85. self.min_level = 1
  86. # eval possession
  87. if self.cell.active_owned:
  88. self.possession = 1
  89. elif self.cell.active_opponent:
  90. self.possession = -1
  91. # eval threat
  92. self.threat = 0
  93. if self.cell.active_owned:
  94. self.threat = self.cell.threat
  95. # eval strategic value
  96. self.strategic_value = self.cell.strategic_value
  97. # covers (towers only)
  98. self.covers = self.strategic_value + sum([grid[n].strategic_value for n in self.cell.neighbors])
  99. # eval pivot
  100. self.pivot = sum([1 + grid[p].get_unit_level() for p in self.cell.pivot_for])
  101. # distance to the ennemy HQ
  102. self.dist_to_goal = Grid.manhattan(self.cell.pos, opponent.hq.pos)
  103. # distance to the own HQ
  104. self.dist_to_hq = Grid.manhattan(self.cell.pos, player.hq.pos)
  105. # priorize adjacent cells
  106. self.union = len([n for n in self.cell.neighbors if grid[n].active_owned])
  107. # include 'depthmap'
  108. self.depth = self.cell.depth
  109. # priorize mines or HQ
  110. if self.cell.building:
  111. self.hq = int(self.cell.building.type_ == Building.HQ)
  112. self.tower = int(self.cell.building.type_ == Building.TOWER)
  113. self.mine = int(self.cell.building.type_ == Building.MINE)
  114. # *** Min level to go there
  115. if self.cell.unit and self.cell.unit.opponents:
  116. self.min_level = min([self.cell.unit.level + 1, 3])
  117. if self.cell.under_tower:
  118. self.min_level = 3
  119. def eval(self):
  120. self.pre_eval()
  121. self.interest = 3 * self.depth + self.dist_to_goal
  122. def __repr__(self):
  123. detail = [self.possession, self.threat, self.strategic_value, self.pivot, self.dist_to_goal,
  124. self.union, self.depth, self.hq, self.tower, self.mine]
  125. return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
  126. class Defend(Position):
  127. def __init__(self, cell, emergency = False):
  128. super().__init__(cell)
  129. self.emergency = emergency
  130. def __repr__(self):
  131. detail = [self.threat, self.covers, self.pivot, self.emergency, self.dist_to_hq]
  132. return "<{} {}: {}, {} ({})>".format(self.__class__.__name__, self.pos, self.interest, self.min_level, detail)
  133. def eval(self):
  134. self.pre_eval()
  135. self.interest = 100 \
  136. - 10 * self.threat \
  137. - self.covers // 5 \
  138. - 5 * self.pivot \
  139. - 20 * self.emergency * (22 - self.dist_to_hq)
  140. class Attack(Position):
  141. def eval(self):
  142. self.pre_eval()
  143. self.interest = 15 * self.possession \
  144. - 5 * self.pivot \
  145. - 2 * self.union \
  146. + 3 * self.depth \
  147. + self.dist_to_goal \
  148. - 30 * self.tower \
  149. - 15 * self.mine \
  150. - 100 * self.hq
  151. class MinePosition(BasePosition):
  152. def __init__(self, target, type_):
  153. self.type_ = type_
  154. super().__init__(target, type_)
  155. def eval(self):
  156. # the lower the better
  157. self.interest = 0
  158. if self.type_ == Building.MINE:
  159. self.interest -= self.target.depth
  160. elif self.type_ == Building.TOWER:
  161. if self.target.pivot:
  162. self.interest -= 20
  163. class BaseLoc(Base):
  164. def __init__(self, x, y):
  165. self.x = x
  166. self.y = y
  167. @property
  168. def pos(self):
  169. return self.x, self.y
  170. class MineSite(BaseLoc):
  171. def __init__(self, x, y):
  172. super().__init__(x, y)
  173. class BaseOwnedLoc(BaseLoc):
  174. def __init__(self, x, y, owner):
  175. super().__init__(x, y)
  176. self.owner = owner
  177. @property
  178. def owned(self):
  179. return self.owner == ME
  180. @property
  181. def opponents(self):
  182. return self.owner == OPPONENT
  183. class Building(BaseOwnedLoc):
  184. HQ = 0
  185. MINE = 1
  186. TOWER = 2
  187. cost = {0: 0, 1: 20, 2: 15}
  188. maintenance = {0: 0, 1: 0, 2: 0}
  189. def __init__(self, owner, type_, x, y):
  190. super().__init__(x, y, owner)
  191. self.type_ = type_
  192. @property
  193. def hq(self):
  194. return self.type_ == Building.HQ
  195. class Unit(BaseOwnedLoc):
  196. cost = {1: 10, 2: 20, 3: 30}
  197. maintenance = {1: 1, 2: 4, 3: 20}
  198. def __init__(self, owner, id_, level, x, y):
  199. super().__init__(x, y, owner)
  200. self.id_ = id_
  201. self.level = level
  202. self.has_moved = False
  203. class Player(Base):
  204. def __init__(self, id_):
  205. self.id_ = id_
  206. self.gold = 0
  207. self.income = 0
  208. self.units = []
  209. self.buildings = []
  210. self.hq = None
  211. self.spent = 0
  212. self.new_charges = 0
  213. def update(self, gold, income, units, buildings):
  214. self.gold = gold
  215. self.income = income
  216. self.units = [u for u in units if u.owner == self.id_]
  217. self.buildings = [b for b in buildings if b.owner == self.id_]
  218. self.hq = next((b for b in self.buildings if b.type_ == HQ))
  219. self.spent = 0
  220. self.new_charges = 0
  221. @property
  222. def current_gold(self):
  223. return self.gold - self.spent
  224. @property
  225. def current_income(self):
  226. return self.income - self.new_charges
  227. def training_capacity(self):
  228. return min([(self.gold - self.spent) // Unit.cost[1], (self.income - self.new_charges) // Unit.maintenance[1]])
  229. def max_affordable(self):
  230. for lvl in range(3, 0, -1):
  231. if (self.gold - self.spent) >= Unit.cost[lvl] and (self.income - self.new_charges) >= Unit.maintenance[lvl]:
  232. return lvl
  233. return 0
  234. def can_act(self):
  235. return self.training_capacity() > 0 or self.gold >= 15 or any(not unit.has_moved for unit in self.units)
  236. class Cell(Base):
  237. def __init__(self, x, y):
  238. self.x = x
  239. self.y = y
  240. self._content = "#"
  241. self.neighbors = []
  242. self.unit = None
  243. self.building = None
  244. self.mine_site = None
  245. self.under_tower = False
  246. self.depth = 0
  247. self.pivot_for = []
  248. self.strategic_value = 0
  249. self.threat = 0
  250. # front cells
  251. self.facing = []
  252. self.support = []
  253. self.in_front_of = []
  254. @property
  255. def pos(self):
  256. return self.x, self.y
  257. @property
  258. def raw_val(self):
  259. return self._content
  260. def update(self, content, unit = None, building = None):
  261. self._content = content
  262. self.unit = unit
  263. self.building = building
  264. self.under_tower = False
  265. self.depth = 0
  266. self.pivot_for = []
  267. self.strategic_value = 0
  268. self.threat = 0
  269. self.facing = []
  270. self.support = []
  271. self.in_front_of = []
  272. @property
  273. def movable(self):
  274. return self._content != "#"
  275. @property
  276. def owned(self):
  277. return self._content.lower() == "o"
  278. @property
  279. def opponents(self):
  280. return self._content.lower() == "x"
  281. @property
  282. def owner(self):
  283. if self.owned:
  284. return ME
  285. elif self.opponents:
  286. return OPPONENT
  287. else:
  288. return None
  289. @property
  290. def headquarter(self):
  291. return self.pos in Grid.hqs
  292. @property
  293. def occupied(self):
  294. return self.unit or self.building
  295. @property
  296. def active(self):
  297. return self._content.isupper()
  298. @property
  299. def active_owned(self):
  300. return self._content == "O"
  301. @property
  302. def active_opponent(self):
  303. return self._content == "X"
  304. def owned_unit(self):
  305. if self.unit and self.unit.owned:
  306. return self.unit
  307. def owned_building(self):
  308. if self.building and self.building.owned:
  309. return self.building
  310. def take_possession(self):
  311. self._content = "O"
  312. def desactivate(self):
  313. self._content = self._content.lower()
  314. def get_unit_level(self):
  315. return self.unit.level if self.unit else 0
  316. class Node(Base):
  317. def __init__(self, pos, path=[]):
  318. self.pos = pos
  319. self.path = path
  320. class Grid(Base):
  321. dim = 12
  322. hqs = [(0,0), (11,11)]
  323. def __init__(self, mines_sites = []):
  324. self.cells = {(x, y): Cell(x, y) for x in range(Grid.dim) for y in range(Grid.dim)}
  325. for pos, cell in self.cells.items():
  326. cell.neighbors = [p for p in self.neighbors(*pos) if p in self.cells]
  327. self.units = []
  328. self.buildings = []
  329. for m in mines_sites:
  330. self.cells[(m.x, m.y)].mine_site = m
  331. self.threat_level = 0
  332. def print_grid(self):
  333. return "\n".join(["".join([c for c in row]) for row in self.grid])
  334. @property
  335. def pos(self):
  336. return self.x, self.y
  337. @property
  338. def grid(self):
  339. return [[self.cells[(x, y)].raw_val for x in range(Grid.dim)] for y in range(Grid.dim)]
  340. def __getitem__(self, key):
  341. return self.cells[key]
  342. def update(self, grid, buildings, units):
  343. buildings_ix = {(b.x, b.y): b for b in buildings}
  344. units_ix= {(u.x, u.y): u for u in units}
  345. self.buildings = list(buildings)
  346. self.units = list(units)
  347. for y, row in enumerate(grid):
  348. for x, c in enumerate(row):
  349. self.cells[(x, y)].update(c,
  350. units_ix.get((x, y), None),
  351. buildings_ix.get((x, y), None))
  352. self.update_pivots()
  353. self.update_state()
  354. def update_state(self):
  355. self.update_tower_areas()
  356. self.update_frontlines()
  357. self.update_depth_map()
  358. self.update_threats()
  359. @staticmethod
  360. def manhattan(from_, to_):
  361. xa, ya = from_
  362. xb, yb = to_
  363. return abs(xa - xb) + abs(ya - yb)
  364. def neighbors(self, x, y, diags=False):
  365. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  366. if diags:
  367. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  368. return [(x, y) for x, y in neighs if 0 <= x < Grid.dim and 0 <= y < Grid.dim]
  369. @classmethod
  370. def zone(cls, center, radius):
  371. return [(x, y) for x in range(0, cls.dim) for y in range(0, cls.dim) if cls.manhattan(center, (x, y)) <= radius]
  372. def zone_set(self, center, radius):
  373. zone = set(center)
  374. for _ in range(radius):
  375. for p in zone:
  376. zone |= self.neighbors(*p)
  377. @staticmethod
  378. def closest(from_, in_):
  379. return min(in_, key=lambda x: Grid.manhattan(from_, x))
  380. def get_hq(self, player_id):
  381. return next((b for b in self.buildings if b.owner == player_id and b.hq))
  382. def update_tower_areas(self):
  383. for b in self.buildings:
  384. if b.type_ == Building.TOWER:
  385. self.cells[b.pos].under_tower = True
  386. for n in self.cells[b.pos].neighbors:
  387. self.cells[n].under_tower = True
  388. def update_frontlines(self):
  389. # update the current frontlines
  390. self.frontline = []
  391. self.frontex = []
  392. for cell in self.cells.values():
  393. if cell.active_owned:
  394. if any(self.cells[c].movable and not self.cells[c].active_owned
  395. for c in cell.neighbors):
  396. self.frontline.append(cell)
  397. def update_depth_map(self):
  398. buffer = [c.pos for c in self.frontline]
  399. for p in buffer:
  400. self.cells[p].depth = 1
  401. next_buffer = []
  402. while buffer:
  403. for p in buffer:
  404. for n in self.cells[p].neighbors:
  405. if self.cells[n].active_owned and not self.cells[n].depth:
  406. self.cells[n].depth = self.cells[p].depth + 1
  407. next_buffer.append(n)
  408. buffer = list(next_buffer)
  409. next_buffer = []
  410. def _active_owned(self, pos, player_id):
  411. c = self.cells[pos]
  412. return c.owner == player_id and c.active
  413. def update_pivot_for(self, player_id):
  414. start = self.get_hq(player_id).pos
  415. start_node = Node(start)
  416. buffer = [start_node]
  417. nodes = {start_node}
  418. ignored = [p for p in self.cells if len([n for n in self.neighbors(*p, diags=True) if self._active_owned(n, player_id)]) == 8]
  419. while buffer:
  420. new_buffer = []
  421. for node in buffer:
  422. if node.pos in ignored:
  423. continue
  424. for n in self.neighbors(*node.pos):
  425. if not n in node.path and self._active_owned(n, player_id):
  426. new_node = Node(n, node.path + [node.pos])
  427. nodes.add(new_node)
  428. new_buffer.append(new_node)
  429. buffer = new_buffer
  430. paths_to = {}
  431. for node in nodes:
  432. if not node.pos in paths_to:
  433. paths_to[node.pos] = []
  434. paths_to[node.pos].append(node.path)
  435. pivots = {}
  436. for candidate in paths_to:
  437. if candidate == start:
  438. continue
  439. for p, paths in paths_to.items():
  440. if not paths or not paths[0] or p in ignored:
  441. continue
  442. if all(candidate in path for path in paths):
  443. if not candidate in pivots:
  444. pivots[candidate] = []
  445. pivots[candidate].append(p)
  446. for pivot, pivot_for in pivots.items():
  447. self.cells[pivot].pivot_for = pivot_for
  448. occurrences = Counter(sum(sum(paths_to.values(), []), []))
  449. while ignored:
  450. new_ignored = set()
  451. for p in ignored:
  452. occured_neighbors = [occurrences[n] for n in self.neighbors(*p) if n in occurrences]
  453. if not occured_neighbors:
  454. new_ignored.add[p]
  455. continue
  456. occurrences[p] = 2 * sum(occured_neighbors) // len(occured_neighbors)
  457. ignored = new_ignored
  458. max_occ = max(occurrences.values()) if occurrences else 1
  459. for p, occ in occurrences.items():
  460. self.cells[p].strategic_value = (100 * occ) // max_occ
  461. # if player_id == ME:
  462. # log(pivots)
  463. # log(occurrences)
  464. # log({cell.pos: cell.strategic_value for cell in self.cells.values() if cell.owned})
  465. def update_pivots(self):
  466. self.update_pivot_for(ME)
  467. self.update_pivot_for(OPPONENT)
  468. def update_threats(self):
  469. # 1 + max number of units opponents can produce in one turn
  470. self.threat_level = 1 + min([(opponent.gold + opponent.income) // Unit.cost[1], opponent.income // Unit.maintenance[1]])
  471. ennemy_frontier = [c for c in self.cells.values() if c.opponents \
  472. and any(self.cells[n].movable and not self.cells[n].opponents for n in c.neighbors)]
  473. for cell in self.cells.values():
  474. if cell.owned:
  475. threat = min([Grid.manhattan(cell.pos, o.pos) for o in ennemy_frontier])
  476. cell.threat = self.threat_level - threat
  477. self.emergency = grid[player.hq.pos].threat > 0
  478. # log({c.pos: c.threat for c in self.cells.values() if c.owned and c.threat is not None})
  479. def influence_zone(self, player_id):
  480. owned, neighbors = {p for p, c in self.cells.items() if c.owner == player_id and c.active}, set()
  481. for p in owned:
  482. neighbors |= set(self.neighbors(*p))
  483. return (self.cells[p] for p in (owned | neighbors) if self.cells[p].movable)
  484. def training_places(self):
  485. return (c for c in self.influence_zone(ME) if self.can_move(c.pos))
  486. def get_next_training(self, max_level=3):
  487. q = InterestQueue()
  488. for cell in self.training_places():
  489. q.put(Position(cell))
  490. if not q:
  491. return None
  492. action = q.get()
  493. if max_level < 3:
  494. while action.cell.under_tower:
  495. try:
  496. action = q.get()
  497. except IndexError:
  498. return None
  499. level = 1
  500. for ennemy in opponent.units:
  501. if Grid.manhattan(action.cell.pos, ennemy.pos) < 3:
  502. level = min(ennemy.level + 1, max_level)
  503. break
  504. action.level = level
  505. return action
  506. def can_move(self, pos, level=1):
  507. cell = self.cells[pos]
  508. can_move = True
  509. can_move &= cell.movable
  510. can_move &= not cell.owned_unit()
  511. can_move &= not cell.owned_building()
  512. if level != 3:
  513. can_move &= (cell.unit is None or cell.unit.level < level)
  514. can_move &= cell.owned or not cell.under_tower
  515. return can_move
  516. def moving_zone(self, unit):
  517. return (self.cells[p] for p in self.cells[unit.pos].neighbors
  518. if self.can_move(p, unit.level))
  519. def get_next_move(self, unit):
  520. q = InterestQueue()
  521. for cell in self.moving_zone(unit):
  522. o = Position(cell)
  523. o.eval()
  524. q.put(o)
  525. if not q:
  526. return None
  527. objective = q.get()
  528. return objective
  529. def building_zone(self, type_):
  530. if type_ == Building.MINE:
  531. return [cell for cell in self.cells.values() if cell.mine_site and cell.depth > 3]
  532. else:
  533. return []
  534. def get_building_site(self, type_):
  535. q = InterestQueue()
  536. for cell in self.building_zone(type_):
  537. q.put(Position(cell, type_))
  538. if not q:
  539. return None
  540. return q.get()
  541. def remove_unit_from(self, cell):
  542. opponent.units.remove(cell.unit)
  543. self.units.remove(cell.unit)
  544. cell.unit = None
  545. def cost_for_new_mine(self):
  546. return Building.cost[Building.MINE] + 4 * len([b for b in player.buildings if b.type_ == Building.MINE])
  547. def apply(self, action):
  548. if type(action) is Move:
  549. old_cell, new_cell = self.cells[action.unit.pos], action.cell
  550. if new_cell.unit:
  551. if new_cell.unit.owned:
  552. log("ERROR: impossible move")
  553. return
  554. if action.unit.level < 3 and new_cell.unit.level >= action.unit.level:
  555. log("ERROR: impossible move")
  556. return
  557. # cell is occupied by an opponent's unit with an inferior level
  558. self.remove_unit_from(new_cell)
  559. if new_cell.building and new_cell.building.type_ == Building.TOWER:
  560. if action.unit.level < 3:
  561. log("ERROR: impossible move")
  562. opponent.buildings.remove(new_cell.building)
  563. self.buildings.remove(new_cell.building)
  564. new_cell.building = None
  565. old_cell.unit = None
  566. action.unit.x, action.unit.y = new_cell.pos
  567. new_cell.unit = action.unit
  568. action.unit.has_moved = True
  569. if not new_cell.owned:
  570. if new_cell.opponents:
  571. for p in new_cell.pivot_for:
  572. self.cells[p].desactivate()
  573. if self.cells[p].unit and self.cells[p].unit.opponents:
  574. self.remove_unit_from(self.cells[p])
  575. new_cell.take_possession()
  576. self.update_state()
  577. elif type(action) is Train:
  578. new_cell = action.cell
  579. unit = Unit(ME, None, action.level, *new_cell.pos)
  580. unit.has_moved = True
  581. player.spent += Unit.cost[action.level]
  582. player.new_charges += Unit.maintenance[action.level]
  583. if new_cell.unit:
  584. if new_cell.unit.owned:
  585. log("ERROR: impossible training")
  586. return
  587. if unit.level < 3 and new_cell.unit.level >= unit.level:
  588. log("ERROR: impossible training")
  589. return
  590. # cell is occupied by an opponent's unit with an inferior level
  591. self.remove_unit_from(new_cell)
  592. if new_cell.building and new_cell.building.type_ == Building.TOWER:
  593. if unit.level < 3:
  594. log("ERROR: impossible training")
  595. opponent.buildings.remove(new_cell.building)
  596. self.buildings.remove(new_cell.building)
  597. new_cell.building = None
  598. new_cell.unit = unit
  599. if not new_cell.owned:
  600. if new_cell.opponents:
  601. for p in new_cell.pivot_for:
  602. self.cells[p].desactivate()
  603. if self.cells[p].unit and self.cells[p].unit.opponents:
  604. self.remove_unit_from(self.cells[p])
  605. new_cell.take_possession()
  606. self.update_state()
  607. elif type(action) is BuildTower:
  608. new_cell = action.cell
  609. building = Building(ME, Building.TOWER, *new_cell.pos)
  610. new_cell.building = building
  611. player.buildings.append(building)
  612. player.spent += Building.cost[Building.TOWER]
  613. if building.type_ == Building.TOWER:
  614. new_cell.under_tower = True
  615. for n in new_cell.neighbors:
  616. self.cells[n].under_tower = True
  617. self.update_state()
  618. elif type(action) is BuildMine:
  619. player.spent += self.cost_for_new_mine()
  620. new_cell = action.cell
  621. building = Building(ME, Building.MINE, *new_cell.pos)
  622. new_cell.building = building
  623. player.buildings.append(building)
  624. class Action(Base):
  625. def command(self):
  626. raise NotImplementedError()
  627. class Wait(Action):
  628. def command(self):
  629. return "WAIT"
  630. class Move(Action):
  631. def __init__(self, unit, cell):
  632. self.unit = unit
  633. self.cell = cell
  634. def __repr__(self):
  635. return f"<{self.__class__.__name__}: {self.unit.id_} {self.cell.pos}>"
  636. def command(self):
  637. return f"MOVE {self.unit.id_} {self.cell.x} {self.cell.y}"
  638. class Train(Action):
  639. def __init__(self, level, cell):
  640. self.level = level
  641. self.cell = cell
  642. def __repr__(self):
  643. return f"<{self.__class__.__name__}: {self.level} {self.cell.pos}>"
  644. def command(self):
  645. return f"TRAIN {self.level} {self.cell.x} {self.cell.y}"
  646. class Build(Action):
  647. str_types = {1: "MINE", 2: "TOWER"}
  648. def __init__(self, type_, cell):
  649. self.type_ = type_
  650. self.cell = cell
  651. def __repr__(self):
  652. return f"<{self.__class__.__name__}: {self.str_types[self.type_]} {self.cell.pos}>"
  653. def command(self):
  654. return f"BUILD {self.str_types[self.type_]} {self.cell.x} {self.cell.y}"
  655. class BuildTower(Build):
  656. def __init__(self, cell):
  657. super().__init__(Building.TOWER, cell)
  658. class BuildMine(Build):
  659. def __init__(self, cell):
  660. super().__init__(Building.MINE, cell)
  661. # ******** MAIN *************
  662. test = False
  663. if test:
  664. mines_input = []
  665. else:
  666. mines_input = [input() for _ in range(int(input()))]
  667. mines_sites = [MineSite(*[int(j) for j in item.split()]) for item in mines_input]
  668. # log(f"* mines: {mines_sites}")
  669. grid = Grid(mines_sites)
  670. player = Player(ME)
  671. opponent = Player(OPPONENT)
  672. def cmd_wait():
  673. return "WAIT"
  674. def get_input():
  675. if test:
  676. gold, income = 20, 1
  677. opponent_gold, opponent_income = 20, 1
  678. new_grid_input = ['O..#########', '...###...###', '...###....##', '#..##.....##', '...#......##', '#.........##', '##.........#', '##......#...', '##.....##..#', '##....###...', '###...###...', '#########..X']
  679. buildings_input = ['0 0 0 0', '1 0 11 11']
  680. units_input = []
  681. else:
  682. gold, income = int(input()), int(input())
  683. opponent_gold, opponent_income = int(input()), int(input())
  684. new_grid_input = [input() for _ in range(12)]
  685. buildings_input = [input() for _ in range(int(input()))]
  686. units_input = [input() for _ in range(int(input()))]
  687. # log(gold, income, opponent_gold, opponent_income)
  688. # log(new_grid_input)
  689. # log(buildings_input)
  690. # log(units_input)
  691. return gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input
  692. while True:
  693. # <--- get and parse input
  694. gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input = get_input()
  695. new_grid = [list(row) for row in new_grid_input]
  696. buildings = [Building(*[int(j) for j in item.split()]) for item in buildings_input]
  697. # log(f"* buildings: {buildings}")
  698. units = [Unit(*[int(j) for j in item.split()]) for item in units_input]
  699. # log(f"* units: {units}")
  700. # --->
  701. # <--- update
  702. player.update(gold, income, units, buildings)
  703. # log(f"player: {player}")
  704. opponent.update(opponent_gold, opponent_income, units, buildings)
  705. # log(f"opponent: {opponent}")
  706. grid.update(new_grid, buildings, units)
  707. # log(f"grid:\n{grid.print_grid()}")
  708. # --->
  709. commands = []
  710. # start
  711. log(f"Threat level: {grid.threat_level}")
  712. if grid.emergency:
  713. log("<!> HQ is threaten!")
  714. todo = []
  715. abandonned = []
  716. while player.can_act():
  717. q = InterestQueue()
  718. for cell in grid.influence_zone(ME):
  719. if cell.movable and not cell in abandonned:
  720. if cell.owned and cell.threat > 0 and cell.pos != player.hq.pos:
  721. p = Defend(cell, grid.emergency)
  722. elif not cell.owned:
  723. p = Attack(cell)
  724. else:
  725. continue
  726. p.eval()
  727. q.put(p)
  728. if not q:
  729. break
  730. objective = q.get()
  731. if type(objective) is Defend:
  732. if player.current_gold > Building.cost[Building.TOWER] and not objective.cell.mine_site:
  733. action = BuildTower(objective.cell)
  734. else:
  735. # TODO: recruit units
  736. abandonned.append(objective.cell)
  737. continue
  738. elif type(objective) is Attack:
  739. near_unit = next((grid[n].unit for n in objective.cell.neighbors if grid[n].unit \
  740. and grid[n].unit.owned \
  741. and not grid[n].unit.has_moved \
  742. and grid[n].unit.level >= objective.min_level),
  743. None)
  744. if near_unit:
  745. action = Move(near_unit, objective.cell)
  746. else:
  747. if objective.min_level > player.max_affordable():
  748. abandonned.append(objective.cell)
  749. continue
  750. action = Train(objective.min_level, objective.cell)
  751. log(f"priority: {action} -> {objective}")
  752. grid.apply(action)
  753. todo.append(action)
  754. # units which did not move
  755. for unit in player.units:
  756. if not unit.has_moved:
  757. objective = grid.get_next_move(unit)
  758. if objective:
  759. action = Move(unit, objective.cell)
  760. log(f"default: {action}")
  761. grid.apply(action)
  762. todo.append(action)
  763. else:
  764. log(f"<!> No move available for {unit}")
  765. # can build mines?
  766. if player.current_gold > grid.cost_for_new_mine():
  767. action = BuildMine(grid.get_building_site(Building.MINE))
  768. if action:
  769. log(f"default: {action}")
  770. grid.apply(action)
  771. todo.append(action)
  772. log(f"* todo: {todo}")
  773. commands = [action.command() for action in todo]
  774. if not commands:
  775. log("nothing to do: wait")
  776. commands = ["WAIT"]
  777. log(f"* commands: {commands}")
  778. print(";".join(commands))