def process_frame(self): self.countdown -= 1 if False and (self.countdown <= 0): quick_followup = (random.random() < 0.1) if quick_followup: self.countdown = 7 else: self.countdown = int(27* (random.random() * 15)) what_to_do = random.random() if what_to_do < 0.2: self.c.send_split() else: self.c.send_shoot() self.log_pos(self.c.player.center) self.log_mass(self.c.player.total_mass) cells = self.c.world.cells.values() own_cells = list(self.c.player.own_cells) own_total_size = sum( map(lambda cell : cell.size, own_cells) ) own_total_mass = sum( map(lambda cell : cell.mass, own_cells) ) n_own_cells = len(own_cells) n = 3 for cell in filter(lambda cell : not cell.is_food and not cell.is_virus and not cell.is_ejected_mass, cells): if hasattr(cell,'poslog') and len(cell.poslog) > n+1: cellspeed = 0 for i in range(1,n+1): cellspeed += (cell.poslog[-i] - cell.poslog[-i-1]).len() / n cellspeed = int(cellspeed*10)/10 self.data.size_vs_speed[cell.size][cellspeed] += 1 visible_width = max( map(lambda cell : cell.pos.x - cell.size, cells) ) - min( map(lambda cell : cell.pos.x + cell.size, cells) ) visible_height = max( map(lambda cell : cell.pos.y - cell.size, cells) ) - min( map(lambda cell : cell.pos.y + cell.size, cells) ) self.data.size_vs_visible_window[n_own_cells][own_total_size].append((visible_width,visible_height)) self.data.mass_vs_visible_window[n_own_cells][own_total_mass].append((visible_width,visible_height)) # log virus sizes for cell in cells: if cell.is_virus: self.data.observed_virus_sizes[cell.size] += 1 # detect re-merging cells for cell in own_cells: for cell2 in own_cells: if cell2 != cell: dist = (cell.pos - cell2.pos).len() expected_dist = cell.size + cell2.size min_dist = max(cell.size, cell2.size) if (dist < (0.9 * expected_dist + 0.1 * min_dist)): is_parent_child = (cell == cell2.parent or cell2 == cell.parent) print("cells seem to be merging! they are "+ ("" if is_parent_child else "NOT ") + "parent and child") pair_id = (min(cell.cid,cell2.cid), max(cell.cid,cell2.cid)) if pair_id not in self.data.remerging: self.data.remerging[pair_id] = ReMerging(cell.size, cell2.size, cell.spawntime, cell2.spawntime, is_parent_child, self.c.world.time) else: self.data.remerging[pair_id].end_time = self.c.world.time # find ejected mass, split cells or viruses that have come to rest for cell in cells: if hasattr(cell,"parent") and cell.parent != None and not cell.calmed_down: # we're only interested in cells with a parent set, because # this also implies that we have tracked them since their # creation. # also, we're only interested in cells that are still flying # as a result of being ejected/split. if not cell.is_food and not cell.is_ejected_mass and not cell.is_virus: expected_speed = mechanics.speed(cell.size) celltype = "split cell" elif cell.is_virus: expected_speed = 1 celltype = "virus" elif cell.is_ejected_mass: expected_speed = 1 celltype = "ejected mass" if cell.movement.len() < expected_speed * 1.1: print(celltype+" has come to rest, nframes="+str(len(cell.poslog))) cell.calmed_down = True # TODO: speed log # distance is calculated naively distance = (cell.spawnpoint - cell.pos).len() # distance2 is calculated along the cell's path (will differ if the flight was not colinear) poslog = list(cell.poslog) speeds = list(map(lambda vecs : (vecs[0]-vecs[1]).len(), zip(poslog, poslog[1:]))) distance2 = sum(speeds) distance_from_parent = (cell.parentpos_when_spawned - cell.pos).len() self.data.eject_distlogs[celltype] += [(distance, distance2, distance_from_parent, cell.parentsize_when_spawned, len(cell.poslog), speeds)] print(" flown distance = %.2f / %.2f"%(distance,distance2)) if len(cell.poslog) == 5: # calculate movement direction from the first 5 samples # first check whether they're on a straight line if geometry.is_colinear(cell.poslog) and cell.shoot_vec != None: print(celltype+" direction available!") fly_direction = cell.poslog[-1] - cell.poslog[0] fly_angle = math.atan2(fly_direction.y, fly_direction.x) shoot_angle = math.atan2(cell.shoot_vec.y, cell.shoot_vec.x) deviation = (fly_angle - shoot_angle) % (2*math.pi) if deviation > math.pi: deviation -= 2*math.pi print(" deviation = "+str(deviation*180/math.pi)) self.data.eject_deviations[celltype] += [deviation] if (celltype == 'virus'): # FIXME so ugly try: shoot_angle = math.atan2(cell.shoot_vec2.y, cell.shoot_vec2.x) deviation = (fly_angle - shoot_angle) % (2*math.pi) if deviation > math.pi: deviation -= 2*math.pi print(" deviation2= "+str(deviation*180/math.pi)) self.data.eject_deviations['virus2'] += [deviation] except AttributeError: print("virus2 not available, wtf?!") try: shoot_angle = math.atan2(cell.shoot_vec3.y, cell.shoot_vec3.x) deviation = (fly_angle - shoot_angle) % (2*math.pi) if deviation > math.pi: deviation -= 2*math.pi print(" deviation3= "+str(deviation*180/math.pi)) self.data.eject_deviations['virus3'] += [deviation] except AttributeError: print("virus3 not available") else: print(celltype+" did NOT fly in a straight line, ignoring...")
def quality(self, cell, cells, myspeed): dd_sq = max((cell.pos[0] - self.c.player.center[0]) ** 2 + (cell.pos[1] - self.c.player.center[1]) ** 2, 0.001) sigma = 500 * max(cell.mass, 1) # TODO FIXME don't try to eat running away cells if mechanics.speed(cell) - myspeed >= 0: sigma = sigma / 3 / math.exp((mechanics.speed(cell) - myspeed) / 10) dist_score = -math.exp(-dd_sq / (2 * sigma ** 2)) rivals = filter(lambda r: self.rival(r, cell), cells) hugecells = filter(self.hugecell, cells) splitkillers = filter(self.splitkiller, cells) nonsplitkillers = filter(self.nonsplitkiller, cells) rival_score = 0 for r in rivals: dd_sq = max(0.001, (r.pos[0] - cell.pos[0]) ** 2 + (r.pos[1] - cell.pos[1]) ** 2) sigma = r.size + 100 rival_score += math.exp(-dd_sq / (2 * sigma ** 2)) hugecell_score = 0 for s in hugecells: dd_sq = max(0.001, (s.pos[0] - cell.pos[0]) ** 2 + (s.pos[1] - cell.pos[1]) ** 2) sigma = s.size + 10 hugecell_score += math.exp(-dd_sq / (2 * sigma ** 2)) splitkill_score = 0 for s in splitkillers: dd_sq = max(0.001, (s.pos[0] - cell.pos[0]) ** 2 + (s.pos[1] - cell.pos[1]) ** 2) sigma = s.size + 650 + 250 splitkill_score += math.exp(-dd_sq / (2 * sigma ** 2)) nonsplitkill_score = 0 for s in nonsplitkillers: dd_sq = max(0.001, (s.pos[0] - cell.pos[0]) ** 2 + (s.pos[1] - cell.pos[1]) ** 2) sigma = (75 + s.size) + 250 nonsplitkill_score += math.exp(-dd_sq / (2 * sigma ** 2)) density_score = 0 sigma = 300 # for f in filter(lambda c : c.is_food and c!=cell, self.c.world.cells.values()): # dd_sq = (f.pos[0]-cell.pos[0])**2 + (f.pos[1]-cell.pos[1])**2 # density_score -= math.exp(-dd_sq/(2*sigma**2)) wall_score = 0 wall_dist = min( cell.pos[0] - self.c.world.top_left[1], self.c.world.bottom_right[1] - cell.pos[0], cell.pos[1] - self.c.world.top_left[0], self.c.world.bottom_right[0] - cell.pos[1], ) sigma = 100 wall_score = math.exp(-wall_dist ** 2 / (2 * sigma ** 2)) return ( 0.5 * dist_score + 0.2 * rival_score + 5.0 * hugecell_score + 5.0 * nonsplitkill_score + 15 * splitkill_score + 0.1 * density_score + 5 * wall_score )
def process_frame(self): runaway = False my_smallest = min(self.c.player.own_cells, key=lambda cell: cell.mass) my_largest = max(self.c.player.own_cells, key=lambda cell: cell.mass) cells = filter(lambda r: not r.is_food and not r.is_virus, self.c.world.cells.values()) friendly_cells = list(filter(lambda c: c.is_virus or c.name in friendly_players, self.c.world.cells.values())) if friendly_cells: dist_to_friend = min( map(lambda c: (self.c.player.center - c.pos).len() - max(my_largest.size, c.size), friendly_cells) ) else: dist_to_friend = float("inf") if dist_to_friend < 20 or my_largest.mass < 36: if self.do_approach_friends: print("not approaching friends") self.do_approach_friends = False elif dist_to_friend > 200 and my_largest.mass > 36 + 10 * 16: if not self.do_approach_friends: print("approaching friends") self.do_approach_friends = True if friendly_cells and self.do_approach_friends: friend_to_feed = max(friendly_cells, key=lambda c: c.mass) if friend_to_feed.mass < 1.25 * my_largest.mass: print("friend too small") friend_to_feed = None if friend_to_feed: self.gui.hilight_cell(friend_to_feed, (255, 255, 255), (255, 127, 127), 30) self.target_cell = friend_to_feed self.target_type = "friend" if self.do_approach_friends: for c in self.c.player.own_cells: self.gui.hilight_cell(c, (255, 255, 255), (255, 127, 127), 20) # can this cell feed that cell? # "False" means "No, definitely not" # "True" means "Maybe" def can_feed(this, that): if that.is_food or that.is_ejected_mass or that.size < 43: # too small cells cannot eat the ejected mass return False relpos = this.pos - that.pos dist = relpos.len() if dist == 0 or dist >= 700 + this.size + that.size: return False return check_cell_in_interval( this.pos, that, (this.movement_angle - 10 * math.pi / 180, this.movement_angle + 10 * math.pi / 180) ) success_rate = 0 for my_cell in self.c.player.own_cells: try: my_cell.movement_angle except AttributeError: print("cannot calculate shoot angle, too few backlog") continue # check if ejecting mass would feed a friend possibly_feedable_cells = list(filter(lambda c: can_feed(my_cell, c), self.c.world.cells.values())) possibly_feedable_cells.sort(key=lambda c: (my_cell.pos - c.pos).len()) good_intervals = [] for feedable in possibly_feedable_cells: self.gui.hilight_cell(feedable, (255, 192, 127), (127, 127, 255)) if feedable not in friendly_cells: break good_intervals += canonicalize_angle_interval(interval_occupied_by_cell(my_cell.pos, feedable)) good_intervals = merge_intervals(good_intervals) area = interval_area( intersection( good_intervals, canonicalize_angle_interval( ( my_cell.movement_angle - mechanics.eject_delta * math.pi / 180, my_cell.movement_angle + mechanics.eject_delta * math.pi / 180, ) ), ) ) success_rate += area / (2 * mechanics.eject_delta * math.pi / 180) / len(list(self.c.player.own_cells)) self.gui.draw_bar(((100, 40), (500, 24)), success_rate, thresh=0.80, color=(0, 0, 127)) if success_rate >= 0.80: self.c.send_shoot() # enemy/virus/friend-we-would-kill avoidance forbidden_intervals = [] for cell in self.c.world.cells.values(): relpos = ((cell.pos[0] - self.c.player.center[0]), (cell.pos[1] - self.c.player.center[1])) dist = math.sqrt(relpos[0] ** 2 + relpos[1] ** 2) # find out the allowed minimum distance allowed_dist = None if cell.is_virus: if cell.mass < my_largest.mass: allowed_dist = cell.size + 2 else: allowed_dist = "don't care" elif cell in friendly_cells: if 1.25 * my_largest.mass > cell.mass: # we're dangerous to our friends allowed_dist = my_largest.size + 40 elif ( cell not in self.c.player.own_cells and not cell.is_virus and not cell.is_ejected_mass and not cell.is_food ) and cell.mass + 20 > 1.25 * my_smallest.mass: # our enemy is, or will be dangerous to us if (cell.mass + 20) / 2 < 1.25 * my_smallest.mass: # they can't splitkill us (soon) allowed_dist = cell.size + 75 elif cell.mass / 15.0 < self.c.player.total_mass: # they can and they will splitkill us allowed_dist = 650 + cell.size else: # we're too small, not worth a splitkill. they have absolutely no # chance to chase us allowed_dist = cell.size + 10 else: allowed_dist = "don't care" if allowed_dist != "don't care" and dist < allowed_dist: try: angle = math.atan2(relpos[1], relpos[0]) corridor_halfwidth = math.asin(min(1, cell.size / dist)) forbidden_intervals += canonicalize_angle_interval( (angle - corridor_halfwidth, angle + corridor_halfwidth) ) runaway = True except: print("TODO FIXME: need to handle enemy cell which is in our centerpoint!") print("dist=%.2f, allowed_dist=%.2f" % (dist, allowed_dist)) # wall avoidance if self.c.player.center[0] < self.c.world.top_left[1] + (self.c.player.total_size * 2): forbidden_intervals += [(0.5 * pi, 1.5 * pi)] if self.c.player.center[0] > self.c.world.bottom_right[1] - (self.c.player.total_size * 2): forbidden_intervals += [(0, 0.5 * pi), (1.5 * pi, 2 * pi)] if self.c.player.center[1] < self.c.world.top_left[0] + (self.c.player.total_size * 2): forbidden_intervals += [(pi, 2 * pi)] if self.c.player.center[1] > self.c.world.bottom_right[0] - (self.c.player.total_size * 2): forbidden_intervals += [(0, pi)] # if there's actually an enemy to avoid: if runaway: # find the largest non-forbidden interval, and run into this direction. forbidden_intervals = merge_intervals(forbidden_intervals) allowed_intervals = invert_angle_intervals(forbidden_intervals) try: (a, b) = find_largest_angle_interval(allowed_intervals) except: print("TODO FIXME: need to handle no runaway direction being available!") (a, b) = (0, 0) runaway_angle = (a + b) / 2 runaway_x, runaway_y = ( (self.c.player.center[0] + int(100 * math.cos(runaway_angle))), (self.c.player.center[1] + int(100 * math.sin(runaway_angle))), ) self.target = (runaway_x, runaway_y) self.target_type = None self.target_cell = None self.color = (255, 0, 0) # a bit of debugging information for i in forbidden_intervals: self.gui.draw_arc(self.c.player.center, self.c.player.total_size + 10, i, (255, 0, 255)) # if however there's no enemy to avoid, try to feed a friend. or chase food or fly randomly around else: if self.target_cell != None: self.target = tuple(self.target_cell.pos) # check if target went out of sight, or became infeasible if self.target_cell not in self.c.world.cells.values() or ( not self.edible(self.target_cell) and not self.target_cell in friendly_cells ): self.target_cell = None self.target_type = None elif self.target == tuple(self.c.player.center): self.target_type = None print("Reached random destination") if not self.target_type == "friend": # i.e. None, random or food food = list(filter(self.edible, self.c.world.cells.values())) myspeed = mechanics.speed(my_largest) food = sorted(food, key=lambda c: self.quality(c, cells, myspeed)) if len(food) > 0: food_candidate = food[0] if ( self.target_type == None or self.target_type == "random" or ( self.target_type == "food" and self.quality(food_candidate, cells, myspeed) < self.quality(self.target_cell, cells, myspeed) - 1 ) ): if self.target_type == "food": print( "abandoning food of value %.3f for %.3f" % ( self.quality(self.target_cell, cells, myspeed), self.quality(food_candidate, cells, myspeed), ) ) self.target_cell = food_candidate self.target = (self.target_cell.pos[0], self.target_cell.pos[1]) self.target_type = "food" self.color = (0, 0, 255) if self.target == None: rx = self.c.player.center[0] + random.randrange(-400, 401) ry = self.c.player.center[1] + random.randrange(-400, 401) self.target = (rx, ry) self.target_type = "random" self.color = (0, 255, 0) print("Nothing to do, heading to random targetination: " + str((rx, ry))) # more debugging self.gui.draw_line(self.c.player.center, self.target, self.color) return self.target
def on_world_update_post(self): self.newFrame = True self.c.world.time = self.time self.time += 1 if self.time % 100 == 0: self.cleanup_victims() # create and purge poslog history, movement and movement_angle for cid in self.history: self.history[cid].stale = True for cid in self.c.world.cells: if cid not in self.history: self.history[cid] = CellHistory() self.history[cid].poslog.append(self.c.world.cells[cid].pos.copy()) self.c.world.cells[cid].poslog = self.history[cid].poslog self.history[cid].stale = False self.history = {k: v for k, v in self.history.items() if v.stale == False} for cid in self.c.world.cells: cell = self.c.world.cells[cid] if not hasattr(cell, "spawntime"): cell.spawntime = self.c.world.time try: oldpos = cell.poslog[-3-1] cell.movement = (cell.pos - oldpos)/3 cell.movement_angle = cell.movement.angle() except (AttributeError, IndexError): pass # create OtherPlayer entries otherplayers = {} for cell in self.c.world.cells.values(): playerid = None if not cell.is_food and not cell.is_ejected_mass and not cell.is_virus: playerid = (cell.name, cell.color) elif cell.is_virus: playerid = "virus" elif cell.is_food: playerid = "food" elif cell.is_ejected_mass: playerid = "ejected mass" else: playerid = "???" if playerid not in otherplayers: otherplayers[playerid] = OtherPlayer(playerid) cell.player = otherplayers[playerid] cell.player.cells.add(cell) # detect split cells and clean up obsolete parent references for cell in self.c.world.cells.values(): # create attribute if not already there try: cell.parent = cell.parent except: cell.parent = None cell.calmed_down = True # clean up obsolete parent references if cell.parent and cell.parent.cid not in self.c.world.cells: cell.parent = None # find split cells is_split = False if not cell.is_food and not cell.is_ejected_mass and not cell.is_virus: try: if cell.parent == None and cell.movement.len() > 2 * mechanics.speed(cell.size): print("looks like a split!"+str(cell.movement.len() / mechanics.speed(cell.size))) is_split = True except AttributeError: pass if is_split: history_len = len(cell.poslog) cell.parent = min(cell.player.cells, key=lambda c : (c.poslog[-history_len] - cell.poslog[-history_len]).len() if c != cell and len(c.poslog) >= history_len else float('inf')) try: cell.shoot_vec = cell.parent.movement.copy() except: cell.shoot_vec = None cell.calmed_down = False elif cell.is_virus: try: if cell.parent == None and cell.movement.len() > 0: print("split virus!") is_split = True except AttributeError: pass if is_split: cell.parent = min(cell.player.cells, key=lambda c : (c.pos - cell.poslog[0]).len() if c != cell else float('inf')) try: last_feed = self.victims[cell.parent.cid][-1][0] if not last_feed.is_ejected_mass: print("wtf, last virus feed was not ejected mass?!") raise KeyError else: cell.shoot_vec = cell.parent.pos - last_feed.poslog[0] cell.shoot_vec2 = last_feed.poslog[-1] - last_feed.poslog[0] try: pos_when_shot = last_feed.parent.poslog[-len(last_feed.poslog)] cell.shoot_vec3 = cell.parent.pos - pos_when_shot except: print("MOAAAHH") cell.shoot_vec3 = None except KeyError: print("wtf, no last virus feed?!") cell.shoot_vec = None cell.shoot_vec2 = None cell.shoot_vec3 = None cell.calmed_down = False elif cell.is_ejected_mass: try: if cell.parent == None and cell.movement.len() > 0: print("ejected mass!") is_split = True except AttributeError: pass if is_split: history_len = len(cell.poslog) try: cell.parent = min(filter(lambda c : not c.is_ejected_mass and not c.is_food and not c.is_virus and c.color == cell.color, self.c.world.cells.values()), key=lambda c : (c.poslog[-history_len] - cell.poslog[-history_len]).len() if len(c.poslog) >= history_len else float('inf')) try: cell.shoot_vec = cell.parent.movement.copy() except: cell.shoot_vec = None cell.calmed_down = False except ValueError: # if no possible parents are found, min will raise a ValueError. ignore that. pass if is_split: cell.spawnpoint = cell.pos.copy() cell.parentsize_when_spawned = cell.parent.size if cell.parent != None else None cell.parentpos_when_spawned = cell.parent.pos.copy() if cell.parent != None else None