main.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. import heapq
  2. import sys
  3. import math
  4. import time
  5. debug = True
  6. t0 = time.time()
  7. def log(*msg):
  8. if debug:
  9. print("{} - ".format(str(time.time() - t0)[1:5]), *msg, file=sys.stderr, flush=True)
  10. def time_to(total, step):
  11. """ number of steps to reach total """
  12. return total // step + (1 if total % step > 0 else 0)
  13. class BaseClass:
  14. def __repr__(self):
  15. return f"<{self.__class__.__name__}: {self.__dict__}>"
  16. class Queue(BaseClass):
  17. def __init__(self):
  18. self.items = []
  19. def __bool__(self):
  20. return bool(self.items)
  21. def __repr__(self):
  22. return str(self.items)
  23. def values(self):
  24. return (v for _, v in self.items)
  25. def put(self, priority, item):
  26. while priority in [p for p, _ in self.items]:
  27. priority += 1
  28. heapq.heappush(self.items, (priority, item))
  29. def get(self):
  30. return heapq.heappop(self.items)[1]
  31. def get_items(self):
  32. return heapq.heappop(self.items)
  33. class Player(BaseClass):
  34. def __init__(self, id_):
  35. self.id = id_
  36. ME = Player(int(input())) # Input gives the player id: 0 plays first
  37. OPPONENT = Player(1 - ME.id)
  38. PLAYERS_INDEX = {p.id: p for p in [ME, OPPONENT]}
  39. PLAYERS_ORDER = sorted([ME, OPPONENT], key=lambda p: p.id)
  40. class Unit(BaseClass):
  41. TYPE_CULTIST = 0
  42. TYPE_CULT_LEADER = 1
  43. OWNER_0 = 0
  44. OWNER_1 = 1
  45. NO_OWNER = 2
  46. SHOOTING_RANGE = 6
  47. SHOOTING_MAX_DAMAGE = 7
  48. def __init__(self, id_):
  49. self.id = id_
  50. self.hp = 10
  51. self.x = None
  52. self.y = None
  53. self.owner = None
  54. @property
  55. def pos(self):
  56. return self.x, self.y
  57. @property
  58. def owned(self):
  59. return self.owner == ME.id
  60. @property
  61. def opponent(self):
  62. return self.owner == OPPONENT.id
  63. @property
  64. def neutral(self):
  65. return self.owner == self.NO_OWNER
  66. class CultLeader(Unit):
  67. pass
  68. class Action(BaseClass):
  69. pass
  70. class ActionWait(Action):
  71. def __repr__(self):
  72. return f"<ActionWait>"
  73. def exec(self):
  74. print("WAIT")
  75. class ActionMove(Action):
  76. def __init__(self, unit, pos):
  77. self.unit = unit
  78. self.pos = pos
  79. def __repr__(self):
  80. return f"<ActionMove: {self.unit.id} to {self.pos}>"
  81. def exec(self):
  82. print(f"{self.unit.id} MOVE {self.pos[0]} {self.pos[1]}")
  83. class ActionShoot(Action):
  84. def __init__(self, unit, target):
  85. self.unit = unit
  86. self.target = target
  87. def __repr__(self):
  88. return f"<ActionShoot: {self.unit.id} to {self.target.id}>"
  89. def exec(self):
  90. print(f"{self.unit.id} SHOOT {self.target.id}")
  91. class ActionConvert(Action):
  92. def __init__(self, unit, target):
  93. self.unit = unit
  94. self.target = target
  95. def __repr__(self):
  96. return f"<ActionConvert: {self.unit.id} to {self.target.id}>"
  97. def exec(self):
  98. print(f"{self.unit.id} CONVERT {self.target.id}")
  99. class Grid(BaseClass):
  100. def __init__(self, width, height):
  101. self.width = width
  102. self.height = height
  103. self.obstacles = []
  104. self.index = {}
  105. self.units = {}
  106. self.round = 0
  107. def prepare_round(self):
  108. self.units = {}
  109. def update_unit(self, id_, type_, hp, x, y, owner):
  110. self.units[id_] = Unit(id_) if type_ != Unit.TYPE_CULT_LEADER else CultLeader(id_)
  111. unit = self.units[id_]
  112. unit.hp = hp
  113. unit.x = x
  114. unit.y = y
  115. unit.owner = owner
  116. def update_index(self):
  117. self.index = {}
  118. for unit in self.units.values():
  119. self.index[(unit.x, unit.y)] = unit
  120. def own_cult_leader(self):
  121. return next(u for u in self.units.values() if type(u) is CultLeader and u.owned)
  122. def allied_cultists(self):
  123. return [u for u in self.units.values() if type(u) is not CultLeader and u.owned]
  124. def opponent_units(self):
  125. return [u for u in self.units.values() if u.opponent]
  126. def opponent_cult_leader(self):
  127. return [u for u in self.units.values() if type(u) is CultLeader and not u.owned]
  128. def opponent_cultists(self):
  129. return [u for u in self.units.values() if type(u) is not CultLeader and u.opponent]
  130. def neutrals(self):
  131. return [u for u in self.units.values() if u.neutral]
  132. def list_actions(self):
  133. actions = Queue()
  134. k_convert_neutrals = 10
  135. k_convert_danger = 30
  136. k_shoot_opponent_cultist = 20
  137. k_shoot_opponent_cult_leader = 10
  138. k_limit = 200
  139. cult_leader = self.own_cult_leader()
  140. for n in self.neutrals():
  141. action = ActionConvert(cult_leader, n)
  142. distance = self.manhattan(cult_leader.pos, n.pos)
  143. danger = 0
  144. for u in self.opponent_cultists():
  145. fire_dist = self.fire_dist(cult_leader.pos, u.pos)
  146. if fire_dist < u.SHOOTING_RANGE:
  147. danger += (u.SHOOTING_MAX_DAMAGE - fire_dist)
  148. priority = k_convert_neutrals * distance + k_convert_danger * danger
  149. if priority > k_limit:
  150. continue
  151. actions.put(priority, action)
  152. for a in self.allied_cultists():
  153. for u in self.opponent_cultists():
  154. fire_dist = self.fire_dist(a.pos, u.pos)
  155. if fire_dist < u.SHOOTING_RANGE:
  156. action = ActionShoot(a, u)
  157. priority = (k_shoot_opponent_cult_leader if type(
  158. u) is CultLeader else k_shoot_opponent_cultist) * fire_dist
  159. if priority > k_limit:
  160. continue
  161. actions.put(priority, action)
  162. return actions
  163. def in_grid(self, pos):
  164. return 0 <= pos[0] < self.width and 0 <= pos[1] < self.height
  165. def can_see_trough(self, pos):
  166. return self.in_grid(pos) and pos not in self.obstacles + list(self.index.values())
  167. def can_move_on(self, pos):
  168. return self.in_grid(pos) and pos not in self.obstacles + list(self.index.values())
  169. @staticmethod
  170. def manhattan(from_, to_):
  171. xa, ya = from_
  172. xb, yb = to_
  173. return abs(xa - xb) + abs(ya - yb)
  174. @classmethod
  175. def line(cls, from_, to_):
  176. """ Implementation of bresenham's algorithm """
  177. xa, ya = from_
  178. xb, yb = to_
  179. if (xa, ya) == (xb, yb):
  180. return [(xa, ya)]
  181. # diagonal symmetry
  182. vertically_oriented = (abs(yb - ya) > abs(xb - xa))
  183. if vertically_oriented:
  184. ya, xa, yb, xb = xa, ya, xb, yb
  185. # horizontal symmetry
  186. reversed_sym = (xa > xb)
  187. if reversed_sym:
  188. xb, yb, xa, ya = xa, ya, xb, yb
  189. # angle
  190. dx, dy = xb - xa, yb - ya
  191. alpha = (abs(dy) / dx)
  192. offset = 0.0
  193. step = 1 if dy > 0 else -1
  194. result = []
  195. y_ = ya
  196. for x_ in range(xa, xb + 1):
  197. result.append((y_, x_) if vertically_oriented else (x_, y_))
  198. offset += alpha
  199. if offset > 0.5:
  200. y_ += step
  201. offset -= 1.0
  202. if reversed_sym:
  203. result.reverse()
  204. return result
  205. def fire_line(self, from_, to_):
  206. line = self.line(from_, to_)
  207. return line if all(self.can_see_trough(c) for c in line) else []
  208. def fire_dist(self, from_, to_):
  209. return len(self.fire_line(from_, to_))
  210. # Create grid
  211. GRID = Grid(*[int(i) for i in input().split()])
  212. GRID.obstacles = [(i, j) for i in range(GRID.height) for j, val in enumerate(input()) if val == 'x']
  213. while 1:
  214. # TODO: prendre en compte le terrain dans la ligne de visée et les déplacements
  215. GRID.prepare_round()
  216. for _ in range(int(input())):
  217. GRID.update_unit(*[int(j) for j in input().split()])
  218. GRID.update_index()
  219. actions = GRID.list_actions()
  220. for action in actions.items:
  221. log(f"* {action}")
  222. try:
  223. action = actions.get()
  224. except IndexError:
  225. log("no action...")
  226. action = ActionWait()
  227. log(f"exec : {action}")
  228. action.exec()
  229. GRID.round += 1