fall2022.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import heapq
  2. import sys
  3. import time
  4. debug = True
  5. t0 = time.time()
  6. def log(*msg):
  7. if debug:
  8. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr, flush=True)
  9. class BaseClass:
  10. def __repr__(self):
  11. return f"<{self.__class__.__name__}: {self.__dict__}>"
  12. class Queue(BaseClass):
  13. def __init__(self):
  14. self.items = []
  15. def __bool__(self):
  16. return bool(self.items)
  17. def __repr__(self):
  18. return str(self.items)
  19. def values(self):
  20. return (v for _, v in self.items)
  21. def put(self, priority, item):
  22. while priority in [p for p, _ in self.items]:
  23. priority += 1
  24. heapq.heappush(self.items, (priority, item))
  25. def get(self):
  26. return heapq.heappop(self.items)[1]
  27. def get_items(self):
  28. return heapq.heappop(self.items)
  29. class Player(BaseClass):
  30. ME = 1
  31. OPPONENT = 0
  32. NONE = -1
  33. def __init__(self, id_):
  34. self.id = id_
  35. self.matter = 0
  36. class Cell(BaseClass):
  37. def __init__(self, x, y):
  38. self.x = x
  39. self.y = y
  40. self.amount = 0
  41. self.owner = Player.NONE
  42. self.units = 0
  43. self.recycler = False
  44. self.can_build = False
  45. self.can_spawn = False
  46. self.in_range_of_recycler = False
  47. @property
  48. def pos(self):
  49. return self.x, self.y
  50. @property
  51. def owned(self):
  52. return self.owner == Player.ME
  53. @property
  54. def is_opponents(self):
  55. return self.owner == Player.OPPONENT
  56. def is_movable(self):
  57. return self.amount > 0 and not self.recycler
  58. def unmovable_next_round(self):
  59. return self.amount == 1 and self.in_range_of_recycler
  60. def update(self, scrap_amount, owner, units, recycler, can_build, can_spawn, in_range_of_recycler):
  61. self.amount = scrap_amount
  62. self.owner = owner
  63. self.units = units
  64. self.recycler = recycler
  65. self.can_build = can_build
  66. self.can_spawn = can_spawn
  67. self.in_range_of_recycler = in_range_of_recycler
  68. class RobotGroup(BaseClass):
  69. COST = 10
  70. def __init__(self, x, y, owner, amount):
  71. self.x = x
  72. self.y = y
  73. self.owner = owner
  74. self.amount = amount
  75. @property
  76. def pos(self):
  77. return self.x, self.y
  78. class Recycler(BaseClass):
  79. COST = 10
  80. def __init__(self, x, y, owner, area):
  81. self.x = x
  82. self.y = y
  83. self.owner = owner
  84. self.area = area
  85. @property
  86. def total_available_amount(self):
  87. return sum(cell.amount for cell in self.area)
  88. @property
  89. def immediately_available_amount(self):
  90. return sum((cell.amount > 0) for cell in self.area)
  91. def lifetime(self):
  92. return min(cell.amount for cell in self.area)
  93. def __repr__(self):
  94. return f"<{self.__class__.__name__}: ({self.x}, {self.y}), {self.owner}, " \
  95. f"{self.immediately_available_amount}, {self.total_available_amount}, {self.lifetime()}>"
  96. class Action(BaseClass):
  97. pass
  98. class Wait(Action):
  99. @staticmethod
  100. def do():
  101. return "WAIT"
  102. class Move(Action):
  103. def __init__(self, from_, to, amount):
  104. self.from_ = from_
  105. self.to = to
  106. self.amount = amount
  107. def do(self):
  108. x0, y0 = self.from_
  109. x1, y1 = self.to
  110. return f'MOVE {self.amount} {x0} {y0} {x1} {y1}'
  111. class Build(Action):
  112. COST = 10
  113. def __init__(self, pos):
  114. self.pos = pos
  115. def do(self):
  116. x, y = self.pos
  117. return f'BUILD {x} {y}'
  118. class Spawn(Action):
  119. COST = 10
  120. def __init__(self, pos, amount):
  121. self.pos = pos
  122. self.amount = amount
  123. def do(self):
  124. x, y = self.pos
  125. return f'SPAWN {self.amount} {x} {y}'
  126. class Grid(BaseClass):
  127. def __init__(self, width, height, me, opponent):
  128. self.width = width
  129. self.height = height
  130. self.cells = {(x, y): Cell(x, y) for x in range(width) for y in range(height)}
  131. self.round = 0
  132. self.me = me
  133. self.opponent = opponent
  134. self.units = {}
  135. self.recyclers = {}
  136. @property
  137. def grid(self):
  138. return [[self.cells[(x, y)] for x in range(self.width)] for y in range(self.height)]
  139. def print(self, key=None):
  140. if key is None:
  141. key = lambda x: x
  142. log("\n" + "\n".join(["".join([f"{key(c)}|" for c in row]) for row in self.grid]))
  143. @staticmethod
  144. def manhattan(from_, to_):
  145. xa, ya = from_
  146. xb, yb = to_
  147. return abs(xa - xb) + abs(ya - yb)
  148. def neighbors(self, x, y):
  149. return [
  150. (x, y)
  151. for x, y in [(x, y - 1), (x - 1, y), (x + 1, y), (x, y + 1)]
  152. if 0 <= x < self.width and 0 <= y < self.height
  153. ]
  154. @staticmethod
  155. def create():
  156. me = Player(Player.ME)
  157. opponent = Player(Player.OPPONENT)
  158. w, h = [int(i) for i in input().split()]
  159. return Grid(w, h, me, opponent)
  160. def update(self):
  161. my_matter, opp_matter = [int(i) for i in input().split()]
  162. self.me.matter = my_matter
  163. self.opponent.matter = opp_matter
  164. for y in range(self.height):
  165. for x in range(self.width):
  166. scrap_amount, owner, units, recycler, can_build, can_spawn, in_range_of_recycler = [int(k) for k in
  167. input().split()]
  168. self.cells[(x, y)].update(scrap_amount, owner, units, recycler, can_build, can_spawn,
  169. in_range_of_recycler)
  170. # update robots
  171. self.units = {}
  172. for cell in self.cells.values():
  173. if cell.units:
  174. self.units[cell.pos] = RobotGroup(cell.x, cell.y, cell.owner, cell.units)
  175. # update recyclers
  176. self.recyclers = {}
  177. seen = set()
  178. for cell in self.cells.values():
  179. if cell.recycler:
  180. area = [self.cells[pos] for pos in self.neighbors(*cell.pos) if cell.pos not in seen]
  181. seen |= set(self.neighbors(*cell.pos))
  182. self.recyclers[cell.pos] = Recycler(cell.x, cell.y, cell.owner, area)
  183. def owned_units(self):
  184. return [r for r in self.units.values() if r.owner == Player.ME]
  185. def opponent_units(self):
  186. return [r for r in self.units.values() if r.owner == Player.OPPONENT]
  187. def owned_recyclers(self):
  188. return [r for r in self.recyclers.values() if r.owner == Player.ME]
  189. def opponent_recyclers(self):
  190. return [r for r in self.recyclers.values() if r.owner == Player.OPPONENT]
  191. def act(self):
  192. build_actions = Queue()
  193. # List available build actions
  194. places = [c for c in self.cells.values() if c.owned and c not in self.units]
  195. k0 = 100
  196. k_available_amount = -1
  197. k_already_exploited = 30
  198. for place in places:
  199. action = Build(place.pos)
  200. k = k0
  201. area = [place.pos] + self.neighbors(*place.pos)
  202. for pos in area:
  203. k += k_available_amount * self.cells[pos].amount
  204. k += k_already_exploited * self.cells[pos].in_range_of_recycler
  205. build_actions.put(k, action)
  206. # List available spawn actions
  207. places = [c for c in self.cells.values() if c.owned]
  208. k0 = 60
  209. for place in places:
  210. action = Spawn(place.pos, 1)
  211. k = k0
  212. build_actions.put(k, action)
  213. # List available move actions
  214. move_actions_per_unit = {}
  215. # targets = Queue()
  216. k0 = 100
  217. k_blitzkrieg = -2
  218. k_occupy = -5
  219. k_destroy = -2
  220. for u in self.owned_units():
  221. move_actions_per_unit[u.pos] = Queue()
  222. nearest_enemy_cell = min(
  223. (c for c in self.cells.values() if c.is_opponents),
  224. key=lambda c: self.manhattan(c.pos, u.pos)
  225. )
  226. nearest_enemy_cell_dist = self.manhattan(nearest_enemy_cell.pos, u.pos)
  227. for pos in self.neighbors(*u.pos):
  228. place = self.cells[pos]
  229. action = Move(u.pos, place.pos, u.amount)
  230. if not place.owned and place.is_movable():
  231. k = k0
  232. k += k_blitzkrieg * (nearest_enemy_cell_dist - self.manhattan(place.pos, nearest_enemy_cell.pos))
  233. if place.is_opponents:
  234. k += k_occupy
  235. if place.pos in self.units:
  236. k += k_destroy * self.units[place.pos].amount
  237. move_actions_per_unit[u.pos].put(k, action)
  238. actions = []
  239. expanse = 0
  240. while build_actions and expanse <= self.me.matter:
  241. action = build_actions.get()
  242. actions.append(action)
  243. expanse += action.COST
  244. for move_actions in move_actions_per_unit.values():
  245. if not move_actions:
  246. continue
  247. action = move_actions.get()
  248. actions.append(action)
  249. print(";".join([a.do() for a in actions]))
  250. grid = Grid.create()
  251. while True:
  252. grid.update()
  253. # grid.print(lambda x: f"{x.amount:2d}")
  254. grid.act()