|
|
@@ -12,8 +12,15 @@ import time
|
|
|
# * Try to integer extra steps into the path algo, instead of adding independents paths
|
|
|
# * Increase the shooting rate in the second part of the game (no barrels left)
|
|
|
# * get_shooting_spots: try to intercept ennemy
|
|
|
-# * collisions: take in account the probable next area of near ennemies
|
|
|
# * make a difference between moving cost, mines, cannonballs, out of grid and other ships
|
|
|
+# * automove: priorize
|
|
|
+
|
|
|
+# Enhancements
|
|
|
+# * interception trajectories
|
|
|
+# * paths with extra-steps
|
|
|
+# * Part 1: Speed up
|
|
|
+# * Part 2: Increase shooting rate
|
|
|
+# * If an owned ship has the max stock of rum and no barrels are left: runaway!
|
|
|
|
|
|
debug = True
|
|
|
|
|
|
@@ -366,6 +373,36 @@ class Grid(Base):
|
|
|
shooting_spots.put((x, y), interest)
|
|
|
return shooting_spots.get()
|
|
|
|
|
|
+ def runaway_spot(self, ship):
|
|
|
+ runaway_spot = Queue()
|
|
|
+
|
|
|
+ for x, y in iter(self):
|
|
|
+ if ship.moving_cost(x, y) > 100:
|
|
|
+ continue
|
|
|
+
|
|
|
+ interest = 0 # the lower the better
|
|
|
+
|
|
|
+ interest += ship.moving_cost(x, y)
|
|
|
+
|
|
|
+ # avoid cells too close from borders
|
|
|
+ if not 3 < x < (self.w - 3):
|
|
|
+ interest += 70
|
|
|
+ if not 3 <= y < (self.h - 3):
|
|
|
+ interest += 70
|
|
|
+
|
|
|
+ # priorize cells in the current direction
|
|
|
+ diff = abs(Grid.diff_directions(ship.orientation, Grid.direction_to(*ship.prow, x, y)))
|
|
|
+ interest += 20 * abs(diff)
|
|
|
+
|
|
|
+ # priorize spots at distance 6 from active ship
|
|
|
+ interest += (20 * abs(6 - self.manhattan((x, y), ship.pos)))
|
|
|
+
|
|
|
+ # max distance from ennemies
|
|
|
+ interest -= (10 * min([Grid.manhattan((x, y), e.next_pos) for e in self.ennemy_ships]))
|
|
|
+
|
|
|
+ runaway_spot.put((x, y), interest)
|
|
|
+ return runaway_spot.get()
|
|
|
+
|
|
|
# geometrical algorithms
|
|
|
@staticmethod
|
|
|
def from_cubic(xu, yu, zu):
|
|
|
@@ -514,10 +551,11 @@ class Grid(Base):
|
|
|
origin = PathNode(*effective_start)
|
|
|
origin.orientation = start_d
|
|
|
|
|
|
- for _ in range(inertia):
|
|
|
- effective_start = self.next_cell(*effective_start, start_d)
|
|
|
- origin = PathNode(*effective_start, origin)
|
|
|
- origin.orientation = start_d
|
|
|
+ if inertia is not None:
|
|
|
+ for _ in range(inertia):
|
|
|
+ effective_start = self.next_cell(*effective_start, start_d)
|
|
|
+ origin = PathNode(*effective_start, origin)
|
|
|
+ origin.orientation = start_d
|
|
|
|
|
|
nodes.put(origin, 0)
|
|
|
neighbors = []
|
|
|
@@ -551,12 +589,21 @@ class Grid(Base):
|
|
|
continue
|
|
|
|
|
|
d = Grid.direction_to(*current, x, y)
|
|
|
+ if current == start and inertia == 0 and d != current.orientation:
|
|
|
+ # special: if started with speed 0, first move will require a speed_up,
|
|
|
+ # making impossible a direction change at first node
|
|
|
+ continue
|
|
|
+
|
|
|
diff = abs(Grid.diff_directions(current.orientation, d))
|
|
|
if diff > 1:
|
|
|
# change direction one degree at a time
|
|
|
continue
|
|
|
|
|
|
area = Ship.get_area(x, y, d)
|
|
|
+ area = [area[0], area[2]]
|
|
|
+ if diff:
|
|
|
+ inertial_area = Ship.get_area(x, y, current.orientation)
|
|
|
+ area += [inertial_area[0], inertial_area[2]]
|
|
|
if any((moving_costs.get(c, 1000) >= 1000 and not Grid.is_border(*c)) for c in area):
|
|
|
continue
|
|
|
|
|
|
@@ -566,10 +613,6 @@ class Grid(Base):
|
|
|
# prefer to go right at start (if not speed)
|
|
|
cost -= 10
|
|
|
|
|
|
-# if diff and current.parent and current.orientation != current.parent.orientation:
|
|
|
-# # avoid consecutives direction changes
|
|
|
-# cost += 20
|
|
|
-
|
|
|
priority = cost + 10 * Grid.manhattan((x, y), target)
|
|
|
|
|
|
node = PathNode(x, y, current)
|
|
|
@@ -685,7 +728,6 @@ class Ship(Entity):
|
|
|
|
|
|
self.goto = None
|
|
|
self.path = []
|
|
|
- self.cached_next_pos_proba = {}
|
|
|
|
|
|
self.area = Ship.get_area(self.x, self.y, self.orientation)
|
|
|
self.prow, _, self.stern = self.area
|
|
|
@@ -730,9 +772,6 @@ class Ship(Entity):
|
|
|
|
|
|
def next_pos_proba(self, in_=1):
|
|
|
|
|
|
- if in_ in self.cached_next_pos_proba:
|
|
|
- return {k: v for k, v in self.cached_next_pos_proba.items() if k <= in_}
|
|
|
-
|
|
|
# guess next positions
|
|
|
positions = {0: [Position(self.pos, self.orientation, self.speed)]}
|
|
|
for i in range(in_):
|
|
|
@@ -788,8 +827,6 @@ class Ship(Entity):
|
|
|
for c in weights:
|
|
|
proba[i][c] = 100 * weights[c] // total_weight
|
|
|
|
|
|
- self.cached_next_pos_proba = proba
|
|
|
-
|
|
|
return proba
|
|
|
|
|
|
def guess_next_positions(self, in_=1):
|
|
|
@@ -819,7 +856,7 @@ class Ship(Entity):
|
|
|
and (self._can_move[self.back_left] or grid.is_border(*self.back_left))
|
|
|
|
|
|
def can_move_fwd(self):
|
|
|
- return self._can_move[self.front]
|
|
|
+ return self._can_move[self.front] and not Grid.is_border(*self.prow)
|
|
|
|
|
|
def can_move(self):
|
|
|
return self.can_move_fwd() or self.can_turn_left() or self.can_turn_left()
|
|
|
@@ -841,9 +878,12 @@ class Ship(Entity):
|
|
|
|
|
|
def plan_next_move(self):
|
|
|
|
|
|
- if self.speed and any(self.front in s.area for s in grid.ships if s is not self):
|
|
|
- log("Blocked: speed 0")
|
|
|
- self.speed = 0
|
|
|
+ blocked = False
|
|
|
+ if self.speed:
|
|
|
+ if any(self.front in (s.area if not s.speed else [s.area[1], s.area[0], s.prow]) for s in grid.ships if s is not self):
|
|
|
+ log("Blocked: speed 0")
|
|
|
+ blocked = True
|
|
|
+ self.speed = 0
|
|
|
|
|
|
if self.path:
|
|
|
planned = self._follow_path(self.path)
|
|
|
@@ -851,7 +891,6 @@ class Ship(Entity):
|
|
|
if self.path is None:
|
|
|
if self.can_move():
|
|
|
available = {}
|
|
|
-
|
|
|
if self.can_move_fwd():
|
|
|
if self.speed:
|
|
|
available[None] = 0
|
|
|
@@ -864,14 +903,14 @@ class Ship(Entity):
|
|
|
|
|
|
for m in available:
|
|
|
new_area = self.area_after_moving(m)
|
|
|
- available[m] = 2 * self.moving_cost(*new_area[0]) + 2 * self.moving_cost(*new_area[1]) + self.moving_cost(*new_area[2])
|
|
|
+ available[m] = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*new_area[1], *self.goto))) + \
|
|
|
+ (2 if self.moving_cost(*new_area[1]) > 10 else 0)
|
|
|
|
|
|
planned = min(available.items(), key=lambda x: x[1])[0]
|
|
|
log(f"(!) broken: automove ({Ship.COMMANDS[planned]})")
|
|
|
else:
|
|
|
log(f"(!) broken: can not move")
|
|
|
- self.next_area = self.area
|
|
|
- return False
|
|
|
+ planned = None
|
|
|
elif not self.path:
|
|
|
return False
|
|
|
|
|
|
@@ -880,37 +919,58 @@ class Ship(Entity):
|
|
|
risk = Queue()
|
|
|
|
|
|
for move in available_moves:
|
|
|
+
|
|
|
new_area = self.area_after_moving(move)
|
|
|
r = 0
|
|
|
|
|
|
for i, c in enumerate(new_area):
|
|
|
mc = self.moving_cost(*c)
|
|
|
|
|
|
- if mc >= 1000 and c in grid: # special: extra-grid cells are not consider as collisions since a part of the ship can go there
|
|
|
- if grid.threat.get(c, 0) > 1 and mc < 2000:
|
|
|
- r += 10
|
|
|
+ if mc >= 1000 and (c in grid or i == 1): # special: extra-grid cells are not consider as collisions since a part of the ship can go there
|
|
|
+ countdown = grid.threat.get(c, 0)
|
|
|
+ if countdown > 1 and mc < 2000:
|
|
|
+ r += 100
|
|
|
else:
|
|
|
+ if blocked and move in (Ship.SLOW_DOWN, None, Ship.SPEED_UP):
|
|
|
+ r = 1000
|
|
|
if i == 1:
|
|
|
# the center of the ship is threaten
|
|
|
- r += 50
|
|
|
+ r += 500
|
|
|
else:
|
|
|
- r += 25
|
|
|
+ r += 250
|
|
|
+
|
|
|
+ if r:
|
|
|
+ if self.speed and move == Ship.SPEED_UP or self.speed == self.MAX_SPEED and move != Ship.SLOW_DOWN:
|
|
|
+ # danger: better slow down
|
|
|
+ r += 20
|
|
|
+
|
|
|
+ if not r:
|
|
|
+ if move == planned:
|
|
|
+ r = -1
|
|
|
+ elif self.speed <= 1 and move == Ship.SLOW_DOWN:
|
|
|
+ r = 50 # we don't want to slow down except there is a danger
|
|
|
+ else:
|
|
|
+ # distance from the prow to the current objective
|
|
|
+ r = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*new_area[1], *self.goto)))
|
|
|
+ r += (2 if self.moving_cost(*new_area[1]) > 10 else 0)
|
|
|
|
|
|
risk.fput(move, r)
|
|
|
- if r:
|
|
|
+ if r >= 100:
|
|
|
log(f"/!\ Danger: planned move <{Ship.COMMANDS[move]}> could lead to collision (risk={r}, area={new_area})")
|
|
|
- else:
|
|
|
- log(f"Safe move: {Ship.COMMANDS[move]}")
|
|
|
+ elif r < 0:
|
|
|
next_move = move
|
|
|
+ log(f"Safe move: {Ship.COMMANDS[move]}")
|
|
|
break
|
|
|
+ else:
|
|
|
+ log(f"Available move <{Ship.COMMANDS[move]}> (risk={r}, area={new_area})")
|
|
|
|
|
|
else:
|
|
|
try:
|
|
|
next_move = risk.get()
|
|
|
- log(f"* No collision-free move was found, go to the less risky: {next_move}")
|
|
|
+ log(f"* Go to the less risky: {Ship.COMMANDS[next_move]}")
|
|
|
except IndexError:
|
|
|
next_move = planned
|
|
|
- log("* No collision-free move was found, go to the initial one: {next_move}")
|
|
|
+ log(f"* No collision-free move was found, go to the initial one: {Ship.COMMANDS[next_move]}")
|
|
|
|
|
|
self.next_move = next_move
|
|
|
self.next_area = new_area
|
|
|
@@ -988,7 +1048,7 @@ class Ship(Entity):
|
|
|
def fire_at_will(self, *args, **kwargs):
|
|
|
return self._fire_at_will(*args, **kwargs)
|
|
|
|
|
|
- def _fire_at_will(self, target):
|
|
|
+ def _fire_at_will(self):
|
|
|
if not self.can_fire():
|
|
|
return False
|
|
|
|
|
|
@@ -996,54 +1056,62 @@ class Ship(Entity):
|
|
|
for ship in grid.owned_ships:
|
|
|
avoid += ship.next_area
|
|
|
|
|
|
- next_positions = target.next_pos_proba(4)
|
|
|
barrels = [b.pos for b in grid.barrels]
|
|
|
-
|
|
|
for bpos in barrels:
|
|
|
- if any(Grid.manhattan(ship.pos, bpos) <= Grid.manhattan(target.pos, bpos) for ship in grid.owned_ships):
|
|
|
+ if any(Grid.manhattan(ship.pos, bpos) <= Grid.manhattan(e.pos, bpos) for ship in grid.owned_ships for e in grid.ennemy_ships):
|
|
|
avoid.append(bpos)
|
|
|
|
|
|
- all_shots = []
|
|
|
- for t, probas in next_positions.items():
|
|
|
-
|
|
|
- # include mines and barrels
|
|
|
- mines_next = {}
|
|
|
- for c, proba in probas.items():
|
|
|
- if c in grid.next_to_mine:
|
|
|
- mpos = grid.next_to_mine[c].pos
|
|
|
- dist = Grid.manhattan(self.next_pos, mpos)
|
|
|
- if dist <= 2:
|
|
|
- continue
|
|
|
- alignment = abs(Grid.diff_directions(self.orientation, Grid.direction_to(*self.pos, *mpos)))
|
|
|
- if alignment <= 1:
|
|
|
- continue
|
|
|
- mines_next[mpos] = mines_next.get(mpos, 1) + proba
|
|
|
- probas.update(mines_next)
|
|
|
-
|
|
|
- for c in probas:
|
|
|
- if c in barrels:
|
|
|
- probas[c] *= 2
|
|
|
-
|
|
|
- shots = sorted(probas.items(), key=lambda x: x[1], reverse=True)
|
|
|
+ for m in grid.mines:
|
|
|
+ if any((Grid.manhattan(s.next_pos, m.pos) <= 2 or \
|
|
|
+ abs(Grid.diff_directions(self.orientation, Grid.direction_to(*s.next_pos, *m.pos))) <= 1)\
|
|
|
+ for s in grid.owned_ships):
|
|
|
+ avoid.append(m.pos)
|
|
|
+
|
|
|
+ all_shots = {}
|
|
|
+
|
|
|
+ for target in grid.ennemy_ships:
|
|
|
+
|
|
|
+ next_positions = target.next_pos_proba(4)
|
|
|
|
|
|
- for c, proba in shots:
|
|
|
- if c in avoid:
|
|
|
- continue
|
|
|
- if proba < 20:
|
|
|
- continue
|
|
|
- dist = Grid.manhattan(self.prow, c)
|
|
|
- if dist > self.SCOPE:
|
|
|
- continue
|
|
|
+ # include avoid, mines, barrels, and other ennemies
|
|
|
+ for t in next_positions:
|
|
|
+ probas = next_positions[t]
|
|
|
+ mines_next = {}
|
|
|
|
|
|
- # time for the cannonball to reach this pos (including fire turn)
|
|
|
- delay = 1 + (1 + round(dist / 3))
|
|
|
- if delay != t:
|
|
|
- continue
|
|
|
+ for c, proba in probas.items():
|
|
|
+ if c in grid.next_to_mine:
|
|
|
+ mpos = grid.next_to_mine[c].pos
|
|
|
+ mines_next[mpos] = mines_next.get(mpos, 1) + proba
|
|
|
+ probas.update(mines_next)
|
|
|
|
|
|
- all_shots.append((c, proba, t))
|
|
|
+ for c in probas:
|
|
|
+ if c in barrels:
|
|
|
+ probas[c] *= 2
|
|
|
+
|
|
|
+ for t, probas in next_positions.items():
|
|
|
|
|
|
+ shots = sorted(probas.items(), key=lambda x: x[1], reverse=True)
|
|
|
+ for c, proba in shots:
|
|
|
+ if c in avoid:
|
|
|
+ continue
|
|
|
+ if proba < 20:
|
|
|
+ continue
|
|
|
+ dist = Grid.manhattan(self.prow, c)
|
|
|
+ if dist > self.SCOPE:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # time for the cannonball to reach this pos (including fire turn)
|
|
|
+ delay = 1 + round(dist / 3)
|
|
|
+ if delay != t:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not c in all_shots:
|
|
|
+ all_shots[c] = (proba - t)
|
|
|
+ else:
|
|
|
+ all_shots[c] += (proba - t)
|
|
|
+
|
|
|
if all_shots:
|
|
|
- best_shot = max(all_shots, key=lambda x: x[1:])[0]
|
|
|
+ best_shot = max(all_shots.items(), key=lambda x: x[1])[0]
|
|
|
log(f"[x] precise shoot: pos={best_shot}")
|
|
|
ship.fire(*best_shot)
|
|
|
return True
|
|
|
@@ -1193,14 +1261,16 @@ while True:
|
|
|
log(f"---- ship {ship.id} ---")
|
|
|
log(f"ship: {ship}")
|
|
|
|
|
|
- log(f"target: {ship.target_ennemy}")
|
|
|
-
|
|
|
it_consumed = 0
|
|
|
|
|
|
if ship.objective:
|
|
|
ship.goto = ship.objective.target.pos
|
|
|
elif ship.target_ennemy:
|
|
|
- ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target, current=ship.goto)
|
|
|
+ if all(s.stock < ship.stock for s in grid.ships if not s is ship):
|
|
|
+ log("Best stock: runaway!")
|
|
|
+ ship.goto = grid.runaway_spot(ship)
|
|
|
+ else:
|
|
|
+ ship.goto = grid.shooting_spot(ship, ship.target_ennemy.target, current=ship.goto)
|
|
|
else:
|
|
|
log("ERROR: No target")
|
|
|
continue
|
|
|
@@ -1214,6 +1284,7 @@ while True:
|
|
|
inertia=ship.speed,
|
|
|
limit=(max_it - it_consumed))
|
|
|
it_consumed += its
|
|
|
+ log(ship.path)
|
|
|
|
|
|
if ship.objective and ship.path and ship.path[-1] == ship.goto:
|
|
|
while ship.objectives and len(ship.path) < 15:
|
|
|
@@ -1227,18 +1298,21 @@ while True:
|
|
|
new_path, its = grid.path(pos, d,
|
|
|
current_obj.target.pos,
|
|
|
moving_costs=ship._moving_costs,
|
|
|
+ inertia=None,
|
|
|
limit=(max_it - it_consumed))
|
|
|
|
|
|
it_consumed += its
|
|
|
|
|
|
if new_path and new_path[-1] == current_obj.target.pos:
|
|
|
+ log(new_path)
|
|
|
ship.path += new_path
|
|
|
else:
|
|
|
break
|
|
|
|
|
|
ship.plan_next_move()
|
|
|
|
|
|
- log(f"obj: {ship.objective}; next: {ship.objectives_next}")
|
|
|
+ log(f"obj: {ship.objective}; next: {ship.objectives_next}")
|
|
|
+ log(f"target: {ship.target_ennemy}")
|
|
|
log(f"path: {ship.path}")
|
|
|
log(f"next_move: {Ship.COMMANDS[ship.next_move]}")
|
|
|
|
|
|
@@ -1255,7 +1329,7 @@ while True:
|
|
|
continue
|
|
|
|
|
|
# no movement was required, can fire
|
|
|
- if ship.fire_at_will(ship.target_ennemy.target):
|
|
|
+ if ship.fire_at_will():
|
|
|
continue
|
|
|
|
|
|
# or mine
|
|
|
@@ -1288,4 +1362,13 @@ while True:
|
|
|
# * include distance to ennemy in the eval of the shooting spot
|
|
|
# * (disactivated because of below) avoid consecutives direction changes
|
|
|
# * path: direction change moving cost changed from 10 to 20 because of the slowing effect
|
|
|
-# * Complete the the fire_at_will method
|
|
|
+
|
|
|
+# 2019-04-18
|
|
|
+# * take in account the inertial_area in path computing when direction change
|
|
|
+# * improve the esquive algo by priorizing moves in direction of the target
|
|
|
+# * remove cache on 'next_pos_proba'
|
|
|
+# * improve the fire_at_will algo
|
|
|
+# * takes all ennemies in account to find the best shot
|
|
|
+# * add the runaway behaviour
|
|
|
+# * fix the can_go_fwd method
|
|
|
+# * better path computing for the next objectives (with inertia which can be None)
|