Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of cluster computation in CampFort task #13

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 73 additions & 69 deletions pokemongo_bot/cell_workers/camp_fort.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ def __init__(self, bot, config):
super(CampFort, self).__init__(bot, config)

def initialize(self):
self.destination = None
self.clusters = None
self.cluster = None
self.walker = None
self.stay_until = 0
self.move_until = 0
self.walker = None
self.no_log_until = 0

self.config_max_distance = self.config.get("max_distance", 2000)
Expand All @@ -47,7 +48,7 @@ def work(self):
return WorkerResult.SUCCESS

if 0 < self.stay_until < now:
self.destination = None
self.cluster = None
self.stay_until = 0
self.move_until = now + self.config_moving_time

Expand All @@ -62,67 +63,56 @@ def work(self):
formatted='No pokeballs left, refuse to sit at lure!',
)
# Move away from lures for a time
self.destination = None
self.cluster = None
self.stay_until = 0
self.move_until = now + max(self.config_moving_time, NO_BALLS_MOVING_TIME)

return WorkerResult.SUCCESS

if self.destination is None:
forts = self.get_forts()
forts_clusters = self.get_forts_clusters(forts)

if len(forts_clusters) > 0:
self.destination = forts_clusters[0]
self.walker = PolylineWalker(self.bot, self.destination[0], self.destination[1])
self.emit_event(
'new_destination',
formatted='New destination at {} meters: {} forts, {} lured'.format(
round(self.destination[4], 2), self.destination[3], self.destination[2]))
forts = self.get_forts()

if self.cluster is None:
if self.clusters is None:
self.clusters = self.get_clusters(forts.values())

available_clusters = self.get_available_clusters(forts)

if len(available_clusters) > 0:
self.cluster = available_clusters[0]
self.walker = PolylineWalker(self.bot, self.cluster["center"][0], self.cluster["center"][1])

self.no_log_until = now + LOG_TIME_INTERVAL
self.emit_event("new_destination",
formatted='New destination at {distance:.2f} meters: {size} forts, {lured} lured'.format(**self.cluster))
else:
return WorkerResult.SUCCESS

if self.stay_until >= now:
cluster = self.get_current_cluster()
self.update_cluster_distance(self.cluster)
self.update_cluster_lured(self.cluster, forts)

if self.stay_until >= now:
if self.no_log_until < now:
self.emit_event(
'staying_at_destination',
formatted='Staying at destination: {} forts, {} lured'.format(
cluster[3], cluster[2]))

self.no_log_until = now + LOG_TIME_INTERVAL
self.emit_event("staying_at_destination",
formatted='Staying at destination: {size} forts, {lured} lured'.format(**self.cluster))

if cluster[2] == 0:
if self.cluster["lured"] == 0:
self.stay_until -= NO_LURED_TIME_MALUS

self.walker.step(speed=0)
elif self.walker.step():
cluster = self.get_current_cluster()
self.emit_event(
'arrived_at_destination',
formatted='Arrived at destination: {} forts, {} lured'.format(
cluster[3], cluster[2]))
self.stay_until = now + self.config_camping_time
self.emit_event("arrived_at_destination",
formatted="Arrived at destination: {size} forts, {lured} lured".format(**self.cluster))
elif self.no_log_until < now:
cluster = self.get_current_cluster()

if cluster[2] == 0:

self.emit_event(
'reset_destination',
formatted="Lures gone! Resetting destination!")
self.destination = None
self.stay_until = 0
return WorkerResult.SUCCESS

self.emit_event(
'moving_to_destination',
formatted="Moving to destination at {} meters: {} forts, {} lured".format(
round(cluster[4], 2), cluster[3], cluster[2])
)
self.no_log_until = now + LOG_TIME_INTERVAL
if self.cluster["lured"] == 0:
self.cluster = None
self.emit_event("reset_destination",
formatted="Lures gone! Resetting destination!")
else:
self.no_log_until = now + LOG_TIME_INTERVAL
self.emit_event("moving_to_destination",
formatted="Moving to destination at {distance:.2f} meters: {size} forts, {lured} lured".format(**self.cluster))

return WorkerResult.RUNNING

Expand All @@ -135,20 +125,36 @@ def get_forts(self):
forts = [f for f in self.bot.cell["forts"] if ("latitude" in f) and ("type" in f)]
forts = [f for f in forts if self.get_distance(self.bot.start_position, f) <= radius]

return forts
return {f["id"]: f for f in forts}

def get_available_clusters(self, forts):
for cluster in self.clusters:
self.update_cluster_distance(cluster)
self.update_cluster_lured(cluster, forts)

available_clusters = [c for c in self.clusters if c["lured"] >= self.config_min_lured_forts_count]
available_clusters = [c for c in available_clusters if c["size"] >= self.config_min_forts_count]
available_clusters.sort(key=lambda c: self.get_cluster_key(c), reverse=True)

return available_clusters

def get_forts_clusters(self, forts):
def get_clusters(self, forts):
clusters = []
points = self.get_all_snap_points(forts)

for c1, c2, fort1, fort2 in points:
cluster_1 = self.get_cluster(forts, c1)
cluster_2 = self.get_cluster(forts, c2)
cluster_key_1 = self.get_cluster_key(cluster_1)
cluster_key_2 = self.get_cluster_key(cluster_2)

self.update_cluster_distance(cluster_1)
self.update_cluster_distance(cluster_2)

key_1 = self.get_cluster_key(cluster_1)
key_2 = self.get_cluster_key(cluster_2)

radius = Constants.MAX_DISTANCE_FORT_IS_REACHABLE

if cluster_key_1 >= cluster_key_2:
if key_1 >= key_2:
cluster = cluster_1

while True:
Expand All @@ -157,9 +163,9 @@ def get_forts_clusters(self, forts):
if not new_circle:
break

new_cluster = self.get_cluster(forts, new_circle)
new_cluster = self.get_cluster(cluster["forts"], new_circle)

if new_cluster[3] < cluster[3]:
if len(new_cluster["forts"]) < len(cluster["forts"]):
break

cluster = new_cluster
Expand All @@ -173,20 +179,16 @@ def get_forts_clusters(self, forts):
if not new_circle:
break

new_cluster = self.get_cluster(forts, new_circle)
new_cluster = self.get_cluster(cluster["forts"], new_circle)

if new_cluster[3] < cluster[3]:
if len(new_cluster["forts"]) < len(cluster["forts"]):
break

cluster = new_cluster
radius -= 1

clusters.append(cluster)

clusters = [c for c in clusters if c[2] >= self.config_min_lured_forts_count]
clusters = [c for c in clusters if c[3] >= self.config_min_forts_count]
clusters.sort(key=lambda c: self.get_cluster_key(c), reverse=True)

return clusters

def get_all_snap_points(self, forts):
Expand All @@ -203,8 +205,6 @@ def get_all_snap_points(self, forts):
return points

def get_enclosing_circles(self, fort1, fort2, radius):
# This is an approximation which is good enough for us
# since we are dealing with small distances
x1, y1 = coord2merc(fort1["latitude"], fort1["longitude"])
x2, y2 = coord2merc(fort2["latitude"], fort2["longitude"])
dx = x2 - x1
Expand All @@ -224,19 +224,23 @@ def get_enclosing_circles(self, fort1, fort2, radius):

def get_cluster(self, forts, circle):
forts_in_circle = [f for f in forts if self.get_distance(circle, f) <= circle[2]]
count = len(forts_in_circle)
lured = len([f for f in forts_in_circle if "active_fort_modifier" in f])
dst = great_circle(self.bot.position, circle).meters

return (circle[0], circle[1], lured, count, dst)
cluster = {"center": (circle[0], circle[1]),
"distance": 0,
"forts": forts_in_circle,
"size": len(forts_in_circle),
"lured": sum(1 for f in forts_in_circle if f.get("active_fort_modifier", None) is not None)}

return cluster

def get_cluster_key(self, cluster):
return (cluster[2], cluster[3], -cluster[4])
return (cluster["lured"], cluster["size"], -cluster["distance"])

def get_current_cluster(self):
forts = self.get_forts()
circle = (self.destination[0], self.destination[1], Constants.MAX_DISTANCE_FORT_IS_REACHABLE)
return self.get_cluster(forts, circle)
def update_cluster_distance(self, cluster):
cluster["distance"] = great_circle(self.bot.position, cluster["center"]).meters

def update_cluster_lured(self, cluster, forts):
cluster["lured"] = sum(1 for f in cluster["forts"] if forts.get(f["id"], {}).get("active_fort_modifier", None) is not None)

def get_distance(self, location, fort):
return great_circle(location, (fort["latitude"], fort["longitude"])).meters