/src/Players/PredictingPlayer.py

https://github.com/submitteddenied/planetwars · Python · 251 lines · 190 code · 33 blank · 28 comment · 67 complexity · 4b1c09d47533f5ad28d7ac0517d91319 MD5 · raw file

  1. '''
  2. Predicting Player Class
  3. @author: Michael Jensen
  4. '''
  5. from BasePlayer import BasePlayer
  6. from collections import namedtuple, deque
  7. class PredictingPlayer(BasePlayer):
  8. '''
  9. Predicting player keeps track of what it thinks the enemy would do...
  10. '''
  11. Model = namedtuple('Model', ['planets', 'fleets'])
  12. Plan = namedtuple('Plan', ['turn', 'plan_id', 'source', 'dest', 'num_ships'])
  13. SCOUT_FLEET_SIZE = 1
  14. MIN_SHIPS = 25
  15. def __init__(self, id=None, scout_enabled=True):
  16. super(PredictingPlayer, self).__init__(id)
  17. self.model = self.Model({}, {})
  18. self.scout = None
  19. self.attacks = {}
  20. self.plans = deque()
  21. self._plan_id = 0
  22. self.scout_enabled = scout_enabled
  23. def __str__(self):
  24. return "<PredictingPlayer id=%s scouting=%s>" % (self.id, self.scout_enabled)
  25. def plan_id(self):
  26. self._plan_id += 1
  27. return self._plan_id
  28. def committed_ships(self, pw, planet):
  29. result = 0
  30. for plan in self.plans:
  31. if plan.source == planet.ID():
  32. result += plan.num_ships
  33. for fleet in self.model.fleets.values():
  34. if fleet.Owner() != pw.PlayerID():
  35. result += fleet.NumShips()
  36. return result
  37. def available_ships(self, pw, planet):
  38. result = pw.GetPlanet(planet.ID()).NumShips() - self.committed_ships(pw, planet)
  39. if result < 0:
  40. return 0
  41. else:
  42. return result
  43. def invalidate_plans(self, pw, planfilter):
  44. '''
  45. Invalidates all the plans that match planfilter(plan)
  46. '''
  47. self.plans = deque(filter(lambda i: not planfilter(i), self.plans))
  48. def update_model(self, pw):
  49. for planet in pw.Planets():
  50. if planet.IsInVision():
  51. if self.model.planets.has_key(planet.ID()) and planet.Owner() != pw.PlayerID():
  52. #if there are more planets there than I expected,
  53. #invalidate any plans that had this planet as a target
  54. model_planet = self.model.planets[planet.ID()]
  55. growth = 0 if model_planet.Owner() == pw.NEUTRAL_PLAYER else model_planet.GrowthRate()
  56. if planet.NumShips() > model_planet.NumShips() + growth:
  57. self.invalidate_plans(pw, lambda p: p.dest == planet.ID())
  58. self.model.planets[planet.ID()] = planet
  59. else:
  60. if self.model.planets.has_key(planet.ID()):
  61. #update the planet in the model
  62. model_planet = self.model.planets[planet.ID()]
  63. if model_planet.Owner() != '0':
  64. model_planet.AddShips(model_planet.GrowthRate())
  65. else:
  66. self.model.planets[planet.ID()] = planet
  67. #update the fleets that are out of range
  68. fleets_in_view = pw.EnemyFleets()
  69. for fleet in self.model.fleets.values():
  70. if not fleet in fleets_in_view:
  71. fleet.Tick()
  72. if fleet.TurnsRemaining() == 0:
  73. dest = self.model.planets[fleet.DestinationPlanet().ID()]
  74. remaining = dest.NumShips() - fleet.NumShips()
  75. if(remaining < 0):
  76. dest.NumShips(-remaining)
  77. dest.Owner(fleet.Owner())
  78. verb = "captured"
  79. else:
  80. dest.RemoveShips(fleet.NumShips())
  81. verb = "arrived at"
  82. pw.log("I think fleet %s just %s planet %s" % (fleet.ID(), verb, fleet.DestinationPlanet()))
  83. del self.model.fleets[fleet.ID()]
  84. for fleet in pw.EnemyFleets():
  85. #fleet is guaranteed to be in vision
  86. if not self.model.fleets.has_key(fleet.ID()):
  87. #this is a newly spotted fleet! May need to invalidate plans...
  88. self.invalidate_plans(pw, lambda p: p.source == fleet.DestinationPlanet().ID())
  89. self.model.fleets[fleet.ID()] = fleet
  90. for target, fleet in self.attacks.items():
  91. if pw.GetFleet(fleet) is None:
  92. del self.attacks[target]
  93. def best_source(self, target, pw, desired_ships=1):
  94. #find the best source that has at least desired_ships + 1 on it
  95. #let's just assume best = closest
  96. closest = None
  97. closest_dist = 999999
  98. for planet in pw.MyPlanets():
  99. if planet == target:
  100. continue
  101. dist = planet.DistanceTo(target)
  102. if dist < closest_dist and self.available_ships(pw, planet) > desired_ships:
  103. closest = planet
  104. closest_dist = dist
  105. return closest
  106. def get_scout_target(self, pw):
  107. target = None
  108. if len(pw.EnemyPlanets()) == 0:
  109. return pw.MyPlanets()[0]
  110. for planet in pw.EnemyPlanets():
  111. if not target:
  112. target = planet
  113. else:
  114. if target.VisionAge() < planet.VisionAge():
  115. target = planet
  116. return target
  117. def get_attack_target(self, pw):
  118. target = None
  119. target_strength = 99999
  120. for planet in pw.NotMyPlanets():
  121. if self.attacks.has_key(planet.ID()):
  122. continue
  123. strength = 0
  124. best_source = self.best_source(planet, pw)
  125. if best_source is None:
  126. continue
  127. if planet.Owner() != pw.NEUTRAL_PLAYER:
  128. strength = best_source.DistanceTo(planet) * planet.GrowthRate()
  129. strength += planet.NumShips()
  130. if strength < target_strength:
  131. target = planet
  132. target_strength = strength
  133. if strength == target_strength:
  134. other_source = self.best_source(target, pw)
  135. if other_source is None or best_source is None:
  136. continue
  137. if other_source.DistanceTo(target) < best_source.DistanceTo(planet):
  138. target = planet
  139. target_strength = strength
  140. return (target, target_strength)
  141. def send_scout(self, pw):
  142. target = self.get_scout_target(pw)
  143. if target:
  144. source = self.best_source(target, pw, self.SCOUT_FLEET_SIZE)
  145. if source:
  146. self.scout = pw.IssueOrder(source, target, self.SCOUT_FLEET_SIZE)
  147. def update_scout(self, pw):
  148. scout = pw.GetFleet(self.scout)
  149. if len(pw.MyPlanets()) == 0:
  150. self.scout = pw.IssueOrder(scout, scout.DestinationPlanet(), scout.NumShips())
  151. return
  152. if scout.DestinationPlanet().Owner() == pw.PlayerID():
  153. return
  154. if scout.TurnsRemaining() == 1:
  155. target = self.get_scout_target(pw)
  156. self.scout = pw.IssueOrder(scout, target, self.SCOUT_FLEET_SIZE)
  157. def make_attack_plan(self, target, pw):
  158. sources = pw.MyPlanets()
  159. sources.sort(key=lambda p: p.DistanceTo(target))
  160. required = target.NumShips()
  161. if required == 0:
  162. required = 1
  163. plan = []
  164. for planet in sources:
  165. if planet.NumShips() < self.MIN_SHIPS:
  166. continue
  167. attack_size = self.available_ships(pw, planet) / 2
  168. if attack_size <= 0:
  169. continue
  170. if required < attack_size:
  171. attack_size = required + 1
  172. required -= attack_size
  173. plan.append((planet, attack_size))
  174. if required <= 0:
  175. break
  176. if required > 0:
  177. return
  178. plan.reverse()
  179. #send the first one straight away, then schedule the rest
  180. first_step = pw.CurrentTick()
  181. max_dist = plan[0][0].DistanceTo(target)
  182. plan_id = self.plan_id()
  183. for step in plan:
  184. planet = step[0]
  185. wait = max_dist - planet.DistanceTo(target)
  186. self.plans.append(self.Plan(first_step + wait, plan_id, planet.ID(), target.ID(), step[1]))
  187. #we've modified self.plans, so we have to resort it
  188. self.plans = deque(sorted(self.plans, key=lambda i: i.turn))
  189. def send_attack(self, pw):
  190. target, target_strength = self.get_attack_target(pw)
  191. if target:
  192. source = self.best_source(target, pw, target_strength + 1)
  193. if source:
  194. #self.attacks[target.ID()] = pw.IssueOrder(source, target, target_strength + 1)
  195. self.plans.append(self.Plan(pw.CurrentTick(), self.plan_id(), source.ID(), target.ID(), target_strength + 1))
  196. else:
  197. #can't make the attack with one, try with > 1
  198. self.make_attack_plan(target, pw)
  199. def launch_planned_attacks(self, pw):
  200. while len(self.plans) > 0 and self.plans[0].turn <= pw.CurrentTick():
  201. #issue this planned order
  202. plan = self.plans.popleft()
  203. if pw.GetPlanet(plan.source).NumShips() >= plan.num_ships:
  204. self.attacks[plan.dest] = pw.IssueOrder(plan.source, plan.dest, plan.num_ships)
  205. def DoTurn(self, pw):
  206. #update my model
  207. self.update_model(pw)
  208. #do some scouting
  209. if self.scout_enabled:
  210. if self.scout == None or pw.GetFleet(self.scout) == None:
  211. self.send_scout(pw)
  212. else:
  213. self.update_scout(pw)
  214. #launch an attack
  215. self.send_attack(pw)
  216. #process any plans
  217. self.launch_planned_attacks(pw)