summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--main.py7
-rw-r--r--mechanics.py7
-rw-r--r--pathfinding.py406
3 files changed, 415 insertions, 5 deletions
diff --git a/main.py b/main.py
index b8eb746..317a089 100644
--- a/main.py
+++ b/main.py
@@ -11,7 +11,7 @@ import nogui as gui # might be overridden later.
import stats
from subscriber import EnhancingSubscriber
from interval_utils import *
-from strategy import *
+from pathfinding import PathfindingTesterStrategy
import time
class Clock:
@@ -110,7 +110,7 @@ c.player.nick=nick
gui.set_client(c)
# initialize strategy
-strategy = Strategy(c, gui)
+strategy = PathfindingTesterStrategy(c, gui)
autorespawn_counter = 60
@@ -152,12 +152,13 @@ try:
autorespawn_counter-=1
fps = clock.getfps()
- if clock.newfps:
+ if False and clock.newfps:
print("FPS: %3d" % fps)
if fps < 24:
probs.report("low fps")
if fps > 50:
probs.report("high fps")
+
except ProblemException:
print("Exiting due to a problem such as low/high fps, network lags etc")
diff --git a/mechanics.py b/mechanics.py
index d15df83..6caaf2a 100644
--- a/mechanics.py
+++ b/mechanics.py
@@ -20,6 +20,9 @@ def get_my_smallest_cell(c):
def get_my_largest_cell(c):
return sorted(c.player.own_cells, key = lambda x: x.mass)[-1]
+def random_own_cell(c):
+ return c.player.world.cells[next(iter(c.player.own_ids))]
+
def is_enemy(cell, c):
return not cell.is_virus and not cell.is_food and not cell.is_ejected_mass and (not cell.same_player(random_own_cell(c))) and cell.mass > 1.25 * get_my_smallest_cell(c).mass
@@ -27,8 +30,8 @@ def is_splitkiller(cell, c):
return not cell.is_virus and not cell.is_food and not cell.is_ejected_mass and(not cell.same_player(random_own_cell(c))) and cell.mass > 2.5 * get_my_smallest_cell(c).mass and cell.mass < 10 * get_my_smallest_cell(c).mass
def is_edible(cell, c):
- return cell.is_food or cell.is_ejected_mass or ( (not cell.same_player(c.player.own_cells[0])) and not is_enemy(cell,c) and get_my_largest_cell(c).mass > 1.25 * cell.mass )
+ return cell.is_food or cell.is_ejected_mass or ( (not cell.same_player(random_own_cell(c))) and not is_enemy(cell,c) and get_my_largest_cell(c).mass > 1.25 * cell.mass )
def is_dangerous_virus(cell, c):
- return cell.is_virus and (cell.mass < self.get_my_largest().mass)
+ return cell.is_virus and (cell.mass < get_my_largest_cell(c).mass)
diff --git a/pathfinding.py b/pathfinding.py
new file mode 100644
index 0000000..3ea93fd
--- /dev/null
+++ b/pathfinding.py
@@ -0,0 +1,406 @@
+import heapq
+import math
+from agarnet.agarnet.vec import Vec
+from mechanics import *
+
+inf = 999999
+
+"""
+pathfinding works by performing an A* search on a graph, built as follows:
+
+there is a equally spaced rectangular grid, where each node is connected
+to its 8 neighbours, with the appropriate euclidean distance.
+additionally, for each food or ejected mass blob, a node is created. they're
+additionally, for each food or ejected mass blob, a node is created. they're
+connected by straight lines with each other, if no enemy cell is in between.
+those "wormhole connections" have a cost of less than the euclidean distance.
+"""
+
+
+"""class Graph:
+ def __init__(self, center, width, height, spacing):
+ self.center = center
+ self.spacing = spacing
+ self.width = width
+ self.height = height
+
+
+ def nearest_node(self, pt):
+ rel = pt - self.center
+ rel.x = round(rel.x / spacing)
+ rel.y = round(rel.x / spacing)
+
+ nearest_blob = min(blobs, key = lambda blob : (blob.pos - pt).len())
+ dist_to_blob = (nearest_blob.pos - pt).len()
+ dist_to_grid = (spacing*rel + self.center - pt).len()
+
+ if dist_to_grid < dist_to_blob:
+ return self.get_gridnode(rel.x, rel.y)
+ else:
+ return self.get_blobnode(nearest_blob)
+"""
+
+class Graph:
+ def __init__(self, grid, blobs):
+ self.grid = grid
+ self.blobs = blobs
+
+
+class Grid:
+ def __init__(self, origin, radius, density, default=None):
+ self.radius = radius
+ self.density = density
+ self.origin = origin
+ if not hasattr(default, '__call__'):
+ self.data = [[default for x in range(int(2*radius//density+1))] for x in range(int(2*radius//density+1))]
+ else:
+ self.data = [[default() for x in range(int(2*radius//density+1))] for x in range(int(2*radius//density+1))]
+
+ def getpos(self, x, y = None):
+ if y == None:
+ x,y=x[0],x[1]
+ return ( int(x-self.origin.x+self.radius)//self.density, int(y-self.origin.y+self.radius)//self.density )
+
+ def distance(self, x, y = None):
+ if y == None:
+ x,y=x[0],x[1]
+
+ xx,yy = self.getpos(x,y)
+
+ return (Vec(x,y) - Vec(xx*self.density+self.origin.x-self.radius, yy*self.density+self.origin.y-self.radius)).len()
+
+
+ def at(self, x, y = None):
+ xx,yy = self.getpos(x,y)
+ return self.data[xx][yy]
+
+ def points_near(self, radius, x, y = None):
+ r = int(radius / self.density)
+ xx,yy = self.getpos(x,y)
+
+ result = []
+ for xxx in range(xx-r, xx+r+1):
+ for yyy in range(yy-r, yy+r+1):
+ if self.contains_raw(xxx,yyy):
+ result.append(self.data[xxx][yyy])
+ return result
+
+ def set(self, val, x, y = None):
+ xx,yy = self.getpos(x,y)
+ self.data[xx][yy] = val
+
+ def is_border(self, x, y):
+ xx,yy = self.getpos(x,y)
+ return (xx in [0,len(self.data)-1] or yy in [0, len(self.data[xx])-1])
+
+ def contains(self, x, y):
+ xx,yy = self.getpos(x,y)
+ return contains_raw(xx,yy)
+
+ def contains_raw(self, xx, yy):
+ return (0 <= xx and xx < len(self.data)) and (0 <= yy and yy < len(self.data[yy]))
+
+
+# A* code taken and adapted from https://gist.github.com/jamiees2/5531924
+
+class Node:
+ def __init__(self,value,point, is_in_wormhole_plane, graph, cell, near_wormholes = []):
+ self.value = value
+ self.point = point
+ self.parent = None
+ self.H = 0
+ self.G = 0
+ self.F = 0
+ self.graph = graph
+ self.is_in_wormhole_plane = is_in_wormhole_plane
+ self.near_wormholes = near_wormholes
+ self.is_open = False
+ self.is_closed = False
+
+ def __lt__(self, other):
+ return False
+
+ def find_near_wormholes(self, radius):
+ self.near_wormholes = list(filter(lambda blob : (self.point - blob.point).len() < radius, self.graph.blobs))
+
+ def move_cost(self,other):
+ # MUST NOT be called when other not in self.siblings()!
+
+
+ if not (self.is_in_wormhole_plane or other.is_in_wormhole_plane):
+ # assert other in siblings(self,grid). otherwise this makes no sense
+ #return 5*(distance(self, other) + (self.value + other.value)/2)
+ xd, yd = abs(self.point.x-other.point.x), abs(self.point.y-other.point.y)
+ dist=0
+ if xd == 0 or yd == 0:
+ dist = xd+yd
+ else:
+ dist = 1.41*xd
+
+ return 5*dist + (self.value + other.value)/2
+ else:
+ dist = distance(self, other)
+ return max(dist, 5*dist - 500)
+
+ def siblings(self):
+ x,y = self.graph.grid.getpos(self.point)
+ links = [self.graph.grid.data[d[0]][d[1]] for d in [(x-1, y),(x-1,y-1),(x,y - 1),(x+1,y-1),(x+1,y),(x+1,y+1),(x,y + 1),(x-1,y+1)]]
+ return [link for link in links if link.value != None]# + self.near_wormholes
+
+def distance(point,point2):
+ return math.sqrt((point.point[0] - point2.point[0])**2 + (point.point[1]-point2.point[1])**2)
+
+def aStar(start, goal):
+ openheap = []
+
+ current = start
+ current.is_open = True
+ openheap.append((0,current))
+
+ while openheap:
+ #Find the item in the open set with the lowest F = G + H score
+ current = heapq.heappop(openheap)[1]
+
+ #If it is the item we want, retrace the path and return it
+ if current == goal:
+ path = []
+ while current.parent:
+ path.append(current)
+ current = current.parent
+ path.append(current)
+ return path[::-1]
+
+ current.is_open = False
+ current.is_closed = True
+
+ for node in current.siblings():
+ if node.is_closed:
+ continue
+
+ if node.is_open:
+ #Check if we beat the G score
+ new_g = current.G + current.move_cost(node)
+ if node.G > new_g:
+ #If so, update the node to have a new parent
+ node.G = new_g
+ node.F = node.G + node.H
+ node.parent = current
+ heapq.heappush(openheap, (node.F, node))
+ else:
+ #If it isn't in the open set, calculate the G and H score for the node
+ node.G = current.G + current.move_cost(node)
+ node.H = distance(node, goal)
+ node.F = node.G + node.H
+
+ node.parent = current
+
+ node.is_open=True
+ heapq.heappush(openheap, (node.F, node))
+
+ raise ValueError('No Path Found')
+
+grid_density=int(30)
+grid_radius=int(1100/grid_density)*grid_density
+
+
+
+
+
+class PathfindingTesterStrategy:
+ def __init__(self, c, gui):
+ self.c = c
+ self.path = None
+ self.gui = gui
+
+ def grid_circle(self, graph, pos, size, val):
+ xmin,xmax = int(self.c.player.center.x-grid_radius), int(self.c.player.center.x+grid_radius+1)
+ ymin,ymax = int(self.c.player.center.y-grid_radius), int(self.c.player.center.y+grid_radius+1)
+ x1,x2 = int(max(xmin, pos.x - size - grid_density)), int(min(xmax, pos.x + size + grid_density))
+ y1,y2 = int(max(ymin, pos.y - size - grid_density)), int(min(ymax, pos.y + size + grid_density))
+ xx1,yy1 = graph.grid.getpos(x1,y1)
+ xx2,yy2 = graph.grid.getpos(x2,y2)
+ size_sq = size*size
+ for (x,xx) in zip( range(x1,x2, grid_density), range(xx1,xx2) ):
+ for (y,yy) in zip( range(y1,y2, grid_density), range(yy1,yy2) ):
+ relpos = (pos.x - x, pos.y - y)
+ dist_sq = relpos[0]**2 + relpos[1]**2
+ if dist_sq < size_sq:
+ graph.grid.data[xx][yy] += val
+
+ def grid_gaussian(self, graph, pos, size, val):
+ xmin,xmax = int(self.c.player.center.x-grid_radius), int(self.c.player.center.x+grid_radius+1)
+ ymin,ymax = int(self.c.player.center.y-grid_radius), int(self.c.player.center.y+grid_radius+1)
+ x1,x2 = int(max(xmin, pos.x - 4*size - grid_density)), int(min(xmax, pos.x + 4*size + grid_density))
+ y1,y2 = int(max(ymin, pos.y - 4*size - grid_density)), int(min(ymax, pos.y + 4*size + grid_density))
+ xx1,yy1 = graph.grid.getpos(x1,y1)
+ xx2,yy2 = graph.grid.getpos(x2,y2)
+ size_sq = size*size
+ for (x,xx) in zip( range(x1,x2, grid_density), range(xx1,xx2) ):
+ for (y,yy) in zip( range(y1,y2, grid_density), range(yy1,yy2) ):
+ relpos = (pos.x - x, pos.y - y)
+ dist_sq = relpos[0]**2 + relpos[1]**2
+ if dist_sq < size_sq * 16:
+ graph.grid.data[xx][yy] += val * math.exp(-dist_sq / size_sq / 2)
+
+ def build_graph(self):
+ graph = Graph(None, [])
+
+ graph.blobs = [ Node(0, c.pos, True, graph, c) for c in self.c.world.cells.values() if c.is_food ]
+
+
+
+ graph.grid = Grid(self.c.player.center, grid_radius, grid_density, 0)
+
+
+ tempgrid = Grid(self.c.player.center, grid_radius, grid_density, lambda : [])
+ for blob in graph.blobs:
+ for l in tempgrid.points_near(100, blob.point):
+ l.append(blob)
+ #dist = tempgrid.distance(cell.pos)
+
+
+
+
+
+ interesting_cells = list(filter(lambda c : not (c.is_food or c in self.c.player.own_cells), self.c.player.world.cells.values()))
+
+ xmin,xmax = int(self.c.player.center.x-grid_radius), int(self.c.player.center.x+grid_radius+1)
+ ymin,ymax = int(self.c.player.center.y-grid_radius), int(self.c.player.center.y+grid_radius+1)
+
+ own_speed = speed(get_my_largest_cell(self.c))
+ if own_speed == 0:
+ own_speed = 1
+ for cell in interesting_cells:
+ if is_dangerous_virus(cell, self.c):
+ self.grid_circle(graph, cell.pos, cell.size + 50, inf)
+ elif is_enemy(cell, self.c):
+ dist = (cell.pos - self.c.player.center).len()
+ dist_until_eaten = max(0, dist - cell.size)
+ eta = dist / own_speed
+
+ danger_zone = cell.size + (200 if not is_splitkiller(cell, self.c) else 800)
+
+ extrapolated_pos = cell.pos.copy()
+ movement_range = 0
+ try:
+ extrapolated_pos += cell.movement * eta
+ movement_range = cell.movement.len() * eta
+ except AttributeError:
+ pass
+
+ if dist_until_eaten < 300:
+ self.grid_circle(graph, cell.pos, cell.size + 50, inf)
+
+ self.grid_gaussian(graph, extrapolated_pos, danger_zone, 1000)
+
+
+
+ xx1,yy1 = graph.grid.getpos(xmin,ymin)
+ xx2,yy2 = graph.grid.getpos(xmax+1,ymax+1)
+ for xx in range(xx1,xx2):
+ graph.grid.data[xx][yy1+1] = None
+ graph.grid.data[xx][yy2-1] = None
+ for yy in range(yy1,yy2):
+ graph.grid.data[xx1+1][yy] = None
+ graph.grid.data[xx2-1][yy] = None
+
+ for x,xx in zip( range(xmin, xmax+1, grid_density), range(xx1,xx2) ):
+ for y,yy in zip( range(ymin, ymax+1, grid_density), range(yy1,yy2) ):
+ val = graph.grid.data[xx][yy]
+ graph.grid.data[xx][yy] = Node(val, Vec(x,y), False, graph, None, tempgrid.data[xx][yy])
+
+ for blob in graph.blobs:
+ blob.find_near_wormholes(100)
+
+ return graph
+
+ def plan_path(self):
+ graph = self.build_graph()
+ self.oldgraph=graph
+
+ path = aStar(graph.grid.at(self.c.player.center), graph.grid.at(self.gui.marker[0]))
+ return path
+
+ def path_is_valid(self, path):
+ return True # TODO FIXME
+ interesting_cells = list(filter(lambda c : not (c.is_food or c in self.c.player.own_cells), self.c.player.world.cells.values()))
+ for node in path:
+ for cell in interesting_cells:
+ relpos = (cell.pos.x - node.point[0], cell.pos.y - node.point[1])
+ dist_sq = relpos[0]**2 + relpos[1]**2
+ if dist_sq < cell.size**2 *2:
+ return False
+
+ return True
+
+ def process_frame(self):
+ for x in range(0,grid_radius, grid_density):
+ color=(192,192,192)
+ self.gui.draw_line((-8000,self.c.player.center.y + x), (8000, self.c.player.center.y + x), color)
+ self.gui.draw_line((-8000,self.c.player.center.y - x), (8000, self.c.player.center.y - x), color)
+ self.gui.draw_line((self.c.player.center.x - x,-8000), (self.c.player.center.x - x, 8000), color)
+ self.gui.draw_line((self.c.player.center.x + x,-8000), (self.c.player.center.x + x, 8000), color)
+
+ graph = None
+ try:
+ graph = self.build_graph()
+ pass
+ except:
+ pass
+
+ if graph != None:
+ x1,y1 = self.c.player.center.x - grid_radius, self.c.player.center.y - grid_radius
+ x1,y1 = int(x1),int(y1)
+ xx1,yy1 = graph.grid.getpos(x1,y1)
+ for x,xx in zip(range(x1, x1+2*grid_radius, grid_density), range(xx1, xx1+9999)):
+ for y,yy in zip(range(y1, y1+2*grid_radius, grid_density), range(yy1, yy1+9999)):
+ try:
+ v=None
+ try:
+ v = graph.grid.data[xx][yy].value
+ except:
+ pass
+ if v == None: v = 9999999999
+
+ col = float(v)/10
+
+ self.gui.draw_circle((x,y),5, mkcolor(v/2500), True)
+ except IndexError:
+ pass
+
+ else:
+ print("ach ficken")
+ pass
+
+ if self.gui.marker_updated[0]:
+ self.gui.marker_updated[0]=False
+
+ self.path = self.plan_path()
+ for node in self.path:
+ print (node.point)
+ print("="*10)
+
+
+ for (node1,node2) in zip(self.path,self.path[1:]):
+ self.gui.draw_line(node1.point, node2.point, (0,0,0))
+
+ if self.path:
+ relx, rely = self.path[0].point[0]-self.c.player.center.x, self.path[0].point[1]-self.c.player.center.y
+ if relx*relx + rely*rely < (2*grid_density)**2:
+ self.path=self.path[1:]
+
+ if self.path and not self.path_is_valid(self.path):
+ print("recalculating!")
+ self.path = self.plan_path()
+
+ if self.path:
+ return self.path[0].point
+ return self.gui.marker[0]
+
+def mkcolor(v):
+ if v<0: return (0,255,255)
+ if v>1: return (255,255,128)
+ if v < 1/4: return (0,0,int(255*v*4))
+ if v < 2/4: return (int(255*(v-1/4)*4),0,255)
+ if v < 3/4: return (255,0,int(255-255*(v-2/4)*4))
+ else: return (255,int(255*(v-3/4)*4),0)