script.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. '''
  2. >> https://www.codingame.com/ide/173171838252e7c6fd6f3ff9cb8169431a08eec1
  3. @author: olivier.massot, may 2019
  4. '''
  5. import heapq
  6. import sys
  7. import time
  8. debug = True
  9. t0 = time.time()
  10. def log(*msg):
  11. if debug:
  12. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr)
  13. # OWNER
  14. ME = 0
  15. OPPONENT = 1
  16. # BUILDING TYPE
  17. HQ = 0
  18. class Base():
  19. def __repr__(self):
  20. return f"<{self.__class__.__name__}: {self.__dict__}>"
  21. class Queue(Base):
  22. def __init__(self):
  23. self.items = []
  24. def __bool__(self):
  25. return bool(self.items)
  26. def __repr__(self):
  27. return str(self.items)
  28. def put(self, item, priority):
  29. heapq.heappush(self.items, (priority, item))
  30. def fput(self, item, priority):
  31. while priority in [p for p, _ in self.items]:
  32. priority += 1
  33. self.put(item, priority)
  34. def get(self):
  35. return heapq.heappop(self.items)[1]
  36. class InterestQueue(Queue):
  37. def __add__(self, other):
  38. self.items += other.items
  39. return self
  40. def put(self, item):
  41. heapq.heappush(self.items, item)
  42. def get(self):
  43. return heapq.heappop(self.items)
  44. class Action(Base):
  45. def __init__(self, target, *args, **kwargs):
  46. self.interest = 0
  47. self.target = target
  48. self.x, self.y = target.pos
  49. self.eval(target, *args, **kwargs)
  50. def __lt__(self, other):
  51. return self.interest < other.interest
  52. def eval(self):
  53. raise NotImplementedError
  54. class Move(Action):
  55. def eval(self, target, unit):
  56. # the lower the better
  57. self.interest = 0
  58. if target.owned and target.active:
  59. self.interest += 15 # already owned and active
  60. elif target.opponents and target.active:
  61. self.interest -= 15 # owned by opponents and active
  62. # non-passable cells around
  63. self.interest += 3 * len([n for n in target.neighbors if not grid[n].movable])
  64. # an ennemy here
  65. if target.unit and target.unit.opponents:
  66. self.interest -= 20
  67. # an ennemy nearby
  68. if any((grid[n].unit and grid[n].unit.opponents) for n in target.neighbors):
  69. self.interest += 15
  70. # priorize adjacent cells
  71. self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
  72. # include 'heatmap'
  73. self.interest += 3 * target.heat
  74. class Train(Move):
  75. def eval(self, target):
  76. # the lower the better
  77. self.interest = 0
  78. if target.owned and target.active:
  79. self.interest += 15 # already owned and active
  80. elif target.opponents and target.active:
  81. self.interest -= 15 # owned by opponents and active
  82. # non-passable cells around
  83. self.interest += 3 * len([n for n in target.neighbors if not grid[n].movable])
  84. # an ennemy nearby
  85. if any((grid[n].unit and grid[n].unit.opponents) for n in target.neighbors):
  86. self.interest += 15
  87. # priorize adjacent cells
  88. self.interest -= (2 * len([n for n in target.neighbors if grid[n].owned]))
  89. # include 'heatmap'
  90. self.interest += 3 * target.heat
  91. class BaseLoc(Base):
  92. def __init__(self, x, y):
  93. self.x = x
  94. self.y = y
  95. @property
  96. def pos(self):
  97. return self.x, self.y
  98. class Mine(BaseLoc):
  99. def __init__(self, x, y):
  100. super().__init__(x, y)
  101. class BaseOwnedLoc(BaseLoc):
  102. def __init__(self, x, y, owner):
  103. super().__init__(x, y)
  104. self.owner = owner
  105. @property
  106. def owned(self):
  107. return self.owner == ME
  108. @property
  109. def opponents(self):
  110. return self.owner == OPPONENT
  111. class Building(BaseOwnedLoc):
  112. HQ = 0
  113. def __init__(self, owner, type_, x, y):
  114. super().__init__(x, y, owner)
  115. self.type_ = type_
  116. @property
  117. def hq(self):
  118. return self.type_ == Building.HQ
  119. class Unit(BaseOwnedLoc):
  120. def __init__(self, owner, id_, level, x, y):
  121. super().__init__(x, y, owner)
  122. self.id_ = id_
  123. self.level = level
  124. class Player(Base):
  125. def __init__(self, id_):
  126. self.id_ = id_
  127. self.gold = 0
  128. self.income = 0
  129. self.units = []
  130. self.buildings = []
  131. self.hq = None
  132. def update(self, gold, income, units, buildings):
  133. self.gold = gold
  134. self.income = income
  135. self.units = [u for u in units if u.owner == self.id_]
  136. self.buildings = [b for b in buildings if b.owner == self.id_]
  137. self.hq = next((b for b in self.buildings if b.type_ == HQ))
  138. class Cell(Base):
  139. def __init__(self, x, y):
  140. self.x = x
  141. self.y = y
  142. self._content = "#"
  143. self.neighbors = []
  144. self.unit = None
  145. self.building = None
  146. self.heat = 0
  147. @property
  148. def pos(self):
  149. return self.x, self.y
  150. @property
  151. def raw_val(self):
  152. return self._content
  153. def update(self, content, unit = None, building = None):
  154. self._content = content
  155. self.unit = unit
  156. self.building = building
  157. self.heat = 0
  158. @property
  159. def movable(self):
  160. return self._content != "#"
  161. @property
  162. def owned(self):
  163. return self._content.lower() == "o"
  164. @property
  165. def opponents(self):
  166. return self._content.lower() == "x"
  167. @property
  168. def headquarter(self):
  169. return self.pos in Grid.hqs
  170. @property
  171. def occupied(self):
  172. return self.unit or self.building
  173. @property
  174. def active(self):
  175. return self._content.isupper()
  176. class Grid(Base):
  177. dim = 12
  178. hqs = [(0,0), (11,11)]
  179. def __init__(self, mines = []):
  180. self.cells = {(x, y): Cell(x, y) for x in range(Grid.dim) for y in range(Grid.dim)}
  181. for pos, cell in self.cells.items():
  182. cell.neighbors = [p for p in self.neighbors(*pos) if p in self.cells]
  183. self.units = {}
  184. self.buildings = {}
  185. self.mines = {(m.x, m.y): m for m in mines}
  186. def print_grid(self):
  187. return "\n".join(["".join([c for c in row]) for row in self.grid])
  188. @property
  189. def pos(self):
  190. return self.x, self.y
  191. @property
  192. def grid(self):
  193. return [[self.cells[(x, y)].raw_val for x in range(Grid.dim)] for y in range(Grid.dim)]
  194. def __getitem__(self, key):
  195. return self.cells[key]
  196. def update(self, grid, buildings, units):
  197. self.buildings = {(b.x, b.y): b for b in buildings}
  198. self.units = {(u.x, u.y): u for u in units}
  199. for y, row in enumerate(grid):
  200. for x, c in enumerate(row):
  201. self.cells[(x, y)].update(c,
  202. self.units.get((x, y), None),
  203. self.buildings.get((x, y), None))
  204. self.update_heat_map()
  205. @staticmethod
  206. def manhattan(from_, to_):
  207. xa, ya = from_
  208. xb, yb = to_
  209. return abs(xa - xb) + abs(ya - yb)
  210. def neighbors(self, x, y, diags=False):
  211. neighs = [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  212. if diags:
  213. neighs += [(x - 1, y - 1), (x + 1, y - 1), (x - 1, y + 1), (x + 1, y + 1)]
  214. return [(x, y) for x, y in neighs if 0 <= x < Grid.dim and 0 <= y < Grid.dim]
  215. def get_hq(self, player):
  216. return next((b for b in self.buildings if b.owner == player and b.hq))
  217. def update_heat_map(self):
  218. frontier = []
  219. for p, cell in self.cells.items():
  220. if not p in frontier and cell.owned and cell.active \
  221. and any(self.cells[c].movable and (not self.cells[c].owned or not self.cells[c].active) for c in cell.neighbors):
  222. cell.heat = 1
  223. frontier.append(p)
  224. buffer = frontier
  225. next_buffer = []
  226. while buffer:
  227. for p in buffer:
  228. for n in self.cells[p].neighbors:
  229. if self.cells[n].owned and self.cells[n].active and not self.cells[n].heat:
  230. self.cells[n].heat = self.cells[p].heat + 1
  231. next_buffer.append(n)
  232. buffer = list(next_buffer)
  233. next_buffer = []
  234. def training_places(self):
  235. owned, neighbors = {p for p, c in self.cells.items() if c.owned}, set()
  236. for p in owned:
  237. neighbors |= set(self.neighbors(*p))
  238. return (self.cells[p] for p in (owned | neighbors) if not self.cells[p].occupied)
  239. def get_next_training(self):
  240. q = InterestQueue()
  241. for cell in self.training_places():
  242. q.put(Train(cell))
  243. if not q:
  244. return None
  245. return q.get()
  246. def moving_zone(self, unit):
  247. return (self.cells[p] for p in self.cells[unit.pos].neighbors
  248. if self.cells[p].movable and
  249. (not self.cells[p].building or self.cells[p].building.opponents) and
  250. (not self.cells[p].unit or self.cells[p].unit.opponents))
  251. def get_next_move(self, unit):
  252. q = InterestQueue()
  253. for cell in self.moving_zone(unit):
  254. q.put(Move(cell, unit))
  255. if not q:
  256. return None
  257. return q.get()
  258. # ******** MAIN *************
  259. test = False
  260. if test:
  261. mines_input = []
  262. else:
  263. mines_input = [input() for _ in range(int(input()))]
  264. log(mines_input)
  265. mines = [Mine(*[int(j) for j in item.split()]) for item in mines_input]
  266. log(f"* mines: {mines}")
  267. grid = Grid()
  268. player = Player(ME)
  269. opponent = Player(OPPONENT)
  270. def cmd_train(target, level=1):
  271. return f"TRAIN {level} {target.x} {target.y}"
  272. def cmd_move(unit, target):
  273. return f"MOVE {unit.id_} {target.x} {target.y}"
  274. def cmd_wait():
  275. return "WAIT"
  276. def get_input():
  277. if test:
  278. gold, income = 20, 1
  279. opponent_gold, opponent_income = 20, 1
  280. new_grid_input = ['O..#########', '...###...###', '...###....##', '#..##.....##', '...#......##', '#.........##', '##.........#', '##......#...', '##.....##..#', '##....###...', '###...###...', '#########..X']
  281. buildings_input = ['0 0 0 0', '1 0 11 11']
  282. units_input = []
  283. else:
  284. gold, income = int(input()), int(input())
  285. opponent_gold, opponent_income = int(input()), int(input())
  286. new_grid_input = [input() for _ in range(12)]
  287. buildings_input = [input() for _ in range(int(input()))]
  288. units_input = [input() for _ in range(int(input()))]
  289. # log(gold, income, opponent_gold, opponent_income)
  290. # log(new_grid_input)
  291. # log(buildings_input)
  292. # log(units_input)
  293. return gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input
  294. while True:
  295. # <--- get and parse input
  296. gold, income, opponent_gold, opponent_income, new_grid_input, buildings_input, units_input = get_input()
  297. new_grid = [list(row) for row in new_grid_input]
  298. buildings = [Building(*[int(j) for j in item.split()]) for item in buildings_input]
  299. log(f"* buildings: {buildings}")
  300. units = [Unit(*[int(j) for j in item.split()]) for item in units_input]
  301. log(f"* units: {units}")
  302. # --->
  303. # <--- update
  304. grid.update(new_grid, buildings, units)
  305. # log(f"grid:\n{grid.print_grid()}")
  306. player.update(gold, income, units, buildings)
  307. log(f"player: {player}")
  308. opponent.update(opponent_gold, opponent_income, units, buildings)
  309. log(f"opponent: {opponent}")
  310. # --->
  311. commands = []
  312. # start
  313. if not player.units or (player.gold > 20 and player.income > (5 * len(player.units))):
  314. target = grid.get_next_training()
  315. if target:
  316. commands.append(cmd_train(target))
  317. for unit in player.units:
  318. target = grid.get_next_move(unit)
  319. if target:
  320. commands.append(cmd_move(unit, target))
  321. if not commands:
  322. log("nothing to do: wait")
  323. commands = ["WAIT"]
  324. log(commands)
  325. print(";".join(commands))