script.py 31 KB

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