def create(cls, area_builder, x, y, new_field_purpose): building_id = None if new_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: building_id = BUILDINGS.POTATO_FIELD elif new_field_purpose == BUILDING_PURPOSE.PASTURE: building_id = BUILDINGS.PASTURE elif new_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: building_id = BUILDINGS.SUGARCANE_FIELD elif new_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: building_id = BUILDINGS.TOBACCO_FIELD value = 0 personality = area_builder.owner.personality_manager.get('ModifiedFieldEvaluator') if new_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: value += personality.add_potato_field_value elif new_field_purpose == BUILDING_PURPOSE.PASTURE: value += personality.add_pasture_value elif new_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: value += personality.add_sugarcane_field_value elif new_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: value += personality.add_tobacco_field_value old_field_purpose = area_builder.plan[(x, y)][0] if old_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: value -= personality.remove_unused_potato_field_penalty elif old_field_purpose == BUILDING_PURPOSE.PASTURE: value -= personality.remove_unused_pasture_penalty elif old_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: value -= personality.remove_unused_sugarcane_field_penalty elif old_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: value -= personality.remove_unused_tobacco_field_penalty builder = BasicBuilder.create(building_id, (x, y), 0) return ModifiedFieldEvaluator(area_builder, builder, value, old_field_purpose)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.WEAVER, (x, y), orientation) distance_to_farm = None for building in area_builder.settlement.buildings_by_id.get(BUILDINGS.FARM, []): distance = builder.position.distance(building.position) if distance <= Entities.buildings[BUILDINGS.WEAVER].radius: wool_producer = False for provider in building.get_providers(): if isinstance(provider, Entities.buildings[BUILDINGS.PASTURE]): wool_producer = True break if wool_producer: distance_to_farm = distance if distance_to_farm is None or distance < distance_to_farm else distance_to_farm distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None # require weavers to have a collector building in range personality = area_builder.owner.personality_manager.get('WeaverEvaluator') distance_penalty = Entities.buildings[BUILDINGS.WEAVER].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance(distance_to_collector, [(personality.farm_distance_importance, distance_to_farm)], distance_penalty) value = float(Entities.buildings[BUILDINGS.WEAVER].radius) / distance + alignment * personality.alignment_importance return WeaverEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): coords = (x, y) rect_rect_distance_func = distances.distance_rect_rect builder = BasicBuilder.create(BUILDINGS.FISHER, coords, orientation) shallow_water_body = area_builder.session.world.shallow_water_body fisher_shallow_water_body_ids = set() for fisher_coords in builder.position.tuple_iter(): if fisher_coords in shallow_water_body: fisher_shallow_water_body_ids.add(shallow_water_body[fisher_coords]) fisher_shallow_water_body_ids = list(fisher_shallow_water_body_ids) assert fisher_shallow_water_body_ids tiles_used = 0 fish_value = 0.0 last_usable_tick = Scheduler().cur_tick - 60 * GAME_SPEED.TICKS_PER_SECOND # TODO: use a direct calculation for fish in area_builder.session.world.fish_indexer.get_buildings_in_range(coords): if shallow_water_body[fish.position.origin.to_tuple()] not in fisher_shallow_water_body_ids: continue # not in the same shallow water body as the fisher => unreachable if fish.last_usage_tick > last_usable_tick: continue # the fish deposit seems to be already in use distance = rect_rect_distance_func(builder.position, fish.position) + 1.0 if tiles_used >= cls.refill_cycle_in_tiles: fish_value += min(1.0, (3 * cls.refill_cycle_in_tiles - tiles_used) / distance) / 10.0 else: fish_value += min(1.0, (cls.refill_cycle_in_tiles - tiles_used) / distance) tiles_used += distance if tiles_used >= 3 * cls.refill_cycle_in_tiles: break if fish_value < 1.5: return None return FisherEvaluator(area_builder, builder, fish_value)
def create(cls, area_builder, x, y, new_field_purpose): building_id = { BUILDING_PURPOSE.POTATO_FIELD: BUILDINGS.POTATO_FIELD, BUILDING_PURPOSE.PASTURE: BUILDINGS.PASTURE, BUILDING_PURPOSE.SUGARCANE_FIELD: BUILDINGS.SUGARCANE_FIELD, BUILDING_PURPOSE.TOBACCO_FIELD: BUILDINGS.TOBACCO_FIELD, BUILDING_PURPOSE.HERBARY: BUILDINGS.HERBARY, }.get(new_field_purpose) personality = area_builder.owner.personality_manager.get('ModifiedFieldEvaluator') value = { BUILDING_PURPOSE.POTATO_FIELD: personality.add_potato_field_value, BUILDING_PURPOSE.PASTURE: personality.add_pasture_value, BUILDING_PURPOSE.SUGARCANE_FIELD: personality.add_sugarcane_field_value, BUILDING_PURPOSE.TOBACCO_FIELD: personality.add_tobacco_field_value, BUILDING_PURPOSE.HERBARY: personality.add_herbary_field_value, }.get(new_field_purpose, 0) old_field_purpose = area_builder.plan[(x, y)][0] value -= { BUILDING_PURPOSE.POTATO_FIELD: personality.remove_unused_potato_field_penalty, BUILDING_PURPOSE.PASTURE: personality.remove_unused_pasture_penalty, BUILDING_PURPOSE.SUGARCANE_FIELD: personality.remove_unused_sugarcane_field_penalty, BUILDING_PURPOSE.TOBACCO_FIELD: personality.remove_unused_tobacco_field_penalty, BUILDING_PURPOSE.HERBARY: personality.remove_unused_herbary_field_penalty, }.get(old_field_purpose, 0) builder = BasicBuilder.create(building_id, (x, y), 0) return ModifiedFieldEvaluator(area_builder, builder, value, old_field_purpose)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.SMELTERY, (x, y), orientation) distance_to_iron_mine = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.MINE) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) distance_to_charcoal_burner = cls._distance_to_nearest_building( area_builder, builder, BUILDINGS.CHARCOAL_BURNER ) if distance_to_collector is None and (distance_to_charcoal_burner is None or distance_to_iron_mine is None): return None personality = area_builder.owner.personality_manager.get("SmelteryEvaluator") distance_penalty = Entities.buildings[BUILDINGS.SMELTERY].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance( distance_to_iron_mine, [ (personality.collector_distance_importance, distance_to_collector), (personality.charcoal_burner_distance_importance, distance_to_charcoal_burner), ], distance_penalty, ) value = ( float(Entities.buildings[BUILDINGS.SMELTERY].radius) / distance + alignment * personality.alignment_importance ) return SmelteryEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): # TODO: create a late initialization phase for this kind of stuff if cls.__radius_offsets is None: cls.__init_outline() area_value = 0 coastline = area_builder.land_manager.coastline personality = area_builder.owner.personality_manager.get('LumberjackEvaluator') for dx, dy in cls.__radius_offsets: coords = (x + dx, y + dy) if coords in area_builder.plan and coords not in coastline: purpose = area_builder.plan[coords][0] if purpose == BUILDING_PURPOSE.NONE: area_value += personality.new_tree elif purpose == BUILDING_PURPOSE.TREE: area_value += personality.shared_tree area_value = min(area_value, personality.max_forest_value) # the lumberjack doesn't actually need all the trees if area_value < personality.min_forest_value: return None # the area is too bad for a lumberjack personality = area_builder.owner.personality_manager.get('LumberjackEvaluator') alignment = cls._get_alignment_from_outline(area_builder, cls._get_outline(x, y)) value = area_value + alignment * personality.alignment_importance builder = BasicBuilder.create(BUILDINGS.LUMBERJACK, (x, y), orientation) return LumberjackEvaluator(area_builder, builder, value)
def extend_settlement_with_storage(self, target_position): """Build a storage to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" if not self.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection(storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets(storage_class.size) coastline = self.land_manager.coastline options = [] for (x, y) in sorted(storage_spots): builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) alignment = 1 for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.plan or self.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 distance = distances.distance_rect_rect(target_position, builder.position) value = distance - alignment * 0.7 options.append((-value, builder)) return self.build_best_option(options, BUILDING_PURPOSE.STORAGE)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.DISTILLERY, (x, y), orientation) distance_to_farm = None for building in area_builder.settlement.buildings_by_id.get(BUILDINGS.FARM, []): distance = builder.position.distance(building.position) if distance <= Entities.buildings[BUILDINGS.DISTILLERY].radius: sugarcane_producer = False for provider in building.get_providers(): if isinstance(provider, Entities.buildings[BUILDINGS.SUGARCANE_FIELD]): sugarcane_producer = True break if sugarcane_producer: distance_to_farm = ( distance if distance_to_farm is None or distance < distance_to_farm else distance_to_farm ) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None # require distilleries to have a collector building in range personality = area_builder.owner.personality_manager.get("DistilleryEvaluator") distance_penalty = Entities.buildings[BUILDINGS.DISTILLERY].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance( distance_to_collector, [(personality.farm_distance_importance, distance_to_farm)], distance_penalty ) value = ( float(Entities.buildings[BUILDINGS.DISTILLERY].radius) / distance + alignment * personality.alignment_importance ) return DistilleryEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.BOAT_BUILDER, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None # require boat builders to have a collector building in range personality = area_builder.owner.personality_manager.get('BoatBuilderEvaluator') alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) value = float(Entities.buildings[BUILDINGS.BOAT_BUILDER].radius) / distance_to_collector + alignment * personality.alignment_importance return BoatBuilderEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.SIGNAL_FIRE, (x, y), orientation) sea_area = 0 for coords in builder.position.get_radius_coordinates(Entities.buildings[BUILDINGS.SIGNAL_FIRE].radius): if coords in area_builder.session.world.water: sea_area += 1 personality = area_builder.owner.personality_manager.get('SignalFireEvaluator') alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) value = sea_area + alignment * personality.alignment_importance return SignalFireEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.STONEMASON, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None distance_to_stone_pit = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.STONE_PIT) alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) personality = area_builder.owner.personality_manager.get('StonemasonEvaluator') distance_penalty = Entities.buildings[BUILDINGS.STONEMASON].radius * personality.distance_penalty distance = cls._weighted_distance(distance_to_collector, [(personality.stone_pit_distance_importance, distance_to_stone_pit)], distance_penalty) value = float(Entities.buildings[BUILDINGS.STONEMASON].radius) / distance + alignment * personality.alignment_importance return StonemasonEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.BRICKYARD, (x, y), orientation) distance_to_clay_pit = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.CLAY_PIT) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_clay_pit is None and distance_to_collector is None: return None personality = area_builder.owner.personality_manager.get('BrickyardEvaluator') distance_penalty = Entities.buildings[BUILDINGS.BRICKYARD].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance(distance_to_clay_pit, [(personality.collector_distance_importance, distance_to_collector)], distance_penalty) value = float(Entities.buildings[BUILDINGS.BRICKYARD].radius) / distance + alignment * personality.alignment_importance return BrickyardEvaluator(area_builder, builder, value)
def create(cls, production_builder, x, y, orientation): settlement_manager = production_builder.settlement_manager village_builder = settlement_manager.village_builder builder = BasicBuilder.create(BUILDINGS.FIRE_STATION, (x, y), orientation) assigned_residences = village_builder.special_building_assignments[BUILDING_PURPOSE.FIRE_STATION][(x, y)] total = len(assigned_residences) not_serviced = 0 for residence_coords in assigned_residences: if village_builder.plan[residence_coords][0] == BUILDING_PURPOSE.RESIDENCE: not_serviced += 1 if not_serviced <= 0 or not_serviced < total * settlement_manager.owner.personality_manager.get('AbstractFireStation').fraction_of_assigned_residences_built: return None return FireStationEvaluator(village_builder, builder, not_serviced)
def _improve_deposit_coverage(self): """Get closer to having a resource deposit in the settlement.""" if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES available_deposits = [] for tile in self.land_manager.resource_deposits[self._deposit_resource_id]: if tile.object.settlement is None: available_deposits.append(tile.object) if not available_deposits: return BUILD_RESULT.IMPOSSIBLE storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection( storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache, ) options = [] for coords in sorted(storage_spots): builder = BasicBuilder.create(BUILDINGS.STORAGE, coords, 0) min_distance = None for building in available_deposits: distance = building.position.distance(builder.position) if min_distance is None or min_distance > distance: min_distance = distance alignment = 0 for tile in self.production_builder.iter_neighbor_tiles(builder.position): if tile is None: continue coords = (tile.x, tile.y) if ( coords not in self.production_builder.plan or self.production_builder.plan[coords][0] != BUILDING_PURPOSE.NONE ): alignment += 1 value = min_distance - alignment * self.personality.alignment_coefficient options.append((-value, builder)) return self.production_builder.build_best_option(options, BUILDING_PURPOSE.STORAGE)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.CHARCOAL_BURNER, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None personality = area_builder.owner.personality_manager.get('CharcoalBurnerEvaluator') distance_penalty = Entities.buildings[BUILDINGS.CHARCOAL_BURNER].radius * personality.distance_penalty distance_to_iron_mine = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.MINE) distance_to_lumberjack = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.LUMBERJACK) alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance(distance_to_collector, [(personality.lumberjack_distance_importance, distance_to_lumberjack), (personality.iron_mine_distance_importance, distance_to_iron_mine)], distance_penalty) value = float(Entities.buildings[BUILDINGS.CHARCOAL_BURNER].radius) / distance + alignment * personality.alignment_importance return CharcoalBurnerEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.TOOLMAKER, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder) if distance_to_collector is None: return None distance_to_smeltery = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.SMELTERY) distance_to_charcoal_burner = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.CHARCOAL_BURNER) distance_to_lumberjack = cls._distance_to_nearest_building(area_builder, builder, BUILDINGS.LUMBERJACK) alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) personality = area_builder.owner.personality_manager.get('ToolmakerEvaluator') distance_penalty = Entities.buildings[BUILDINGS.TOOLMAKER].radius * personality.distance_penalty distance = cls._weighted_distance(distance_to_collector, [(personality.smeltery_distance_importance, distance_to_smeltery), (personality.charcoal_burner_distance_importance, distance_to_charcoal_burner), (personality.lumberjack_distance_importance, distance_to_lumberjack)], distance_penalty) value = float(Entities.buildings[BUILDINGS.TOOLMAKER].radius) / distance + alignment * personality.alignment_importance return ToolmakerEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.WEAVER, (x, y), orientation) distance_to_farm = None for building in area_builder.settlement.buildings_by_id.get( BUILDINGS.FARM, []): distance = builder.position.distance(building.position) if distance <= Entities.buildings[BUILDINGS.WEAVER].radius: wool_producer = False for provider in building.get_providers(): if isinstance(provider, Entities.buildings[BUILDINGS.PASTURE]): wool_producer = True break if wool_producer: distance_to_farm = distance if distance_to_farm is None or distance < distance_to_farm else distance_to_farm distance_to_collector = cls._distance_to_nearest_collector( area_builder, builder) if distance_to_collector is None: return None # require weavers to have a collector building in range personality = area_builder.owner.personality_manager.get( 'WeaverEvaluator') distance_penalty = Entities.buildings[ BUILDINGS.WEAVER].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance( distance_to_collector, [(personality.farm_distance_importance, distance_to_farm)], distance_penalty) value = float( Entities.buildings[BUILDINGS.WEAVER].radius ) / distance + alignment * personality.alignment_importance return WeaverEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.BRICKYARD, (x, y), orientation) distance_to_clay_pit = cls._distance_to_nearest_building( area_builder, builder, BUILDINGS.CLAY_PIT) distance_to_collector = cls._distance_to_nearest_collector( area_builder, builder) if distance_to_clay_pit is None and distance_to_collector is None: return None personality = area_builder.owner.personality_manager.get( 'BrickyardEvaluator') distance_penalty = Entities.buildings[ BUILDINGS.BRICKYARD].radius * personality.distance_penalty alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) distance = cls._weighted_distance(distance_to_clay_pit, [ (personality.collector_distance_importance, distance_to_collector) ], distance_penalty) value = float( Entities.buildings[BUILDINGS.BRICKYARD].radius ) / distance + alignment * personality.alignment_importance return BrickyardEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, new_field_purpose): building_id = None if new_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: building_id = BUILDINGS.POTATO_FIELD elif new_field_purpose == BUILDING_PURPOSE.PASTURE: building_id = BUILDINGS.PASTURE elif new_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: building_id = BUILDINGS.SUGARCANE_FIELD elif new_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: building_id = BUILDINGS.TOBACCO_FIELD value = 0 personality = area_builder.owner.personality_manager.get( 'ModifiedFieldEvaluator') if new_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: value += personality.add_potato_field_value elif new_field_purpose == BUILDING_PURPOSE.PASTURE: value += personality.add_pasture_value elif new_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: value += personality.add_sugarcane_field_value elif new_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: value += personality.add_tobacco_field_value old_field_purpose = area_builder.plan[(x, y)][0] if old_field_purpose == BUILDING_PURPOSE.POTATO_FIELD: value -= personality.remove_unused_potato_field_penalty elif old_field_purpose == BUILDING_PURPOSE.PASTURE: value -= personality.remove_unused_pasture_penalty elif old_field_purpose == BUILDING_PURPOSE.SUGARCANE_FIELD: value -= personality.remove_unused_sugarcane_field_penalty elif old_field_purpose == BUILDING_PURPOSE.TOBACCO_FIELD: value -= personality.remove_unused_tobacco_field_penalty builder = BasicBuilder.create(building_id, (x, y), 0) return ModifiedFieldEvaluator(area_builder, builder, value, old_field_purpose)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.SALT_PONDS, (x, y), orientation) alignment = cls._get_alignment(area_builder, builder.position.tuple_iter()) return SaltPondsEvaluator(area_builder, builder, alignment)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.MINE, (x, y), orientation) return IronMineEvaluator(area_builder, builder, 0)
def _build_extra_storage(self): """Build an extra storage tent to improve collector coverage.""" if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES reachable = dict.fromkeys(self.land_manager.roads) # {(x, y): [(building worldid, distance), ...], ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if purpose == BUILDING_PURPOSE.NONE: reachable[coords] = [] for key in reachable: if reachable[key] is None: reachable[key] = [] storage_radius = Entities.buildings[BUILDINGS.STORAGE].radius moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] for building in self._problematic_buildings: distance = dict.fromkeys(reachable) queue = deque() for coords in self.production_builder.iter_possible_road_coords(building.loading_area, building.position): if coords in distance: distance[coords] = 0 queue.append(coords) while queue: x, y = queue[0] queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in distance and distance[coords2] is None: distance[coords2] = distance[(x, y)] + 1 queue.append(coords2) for coords, dist in distance.iteritems(): if dist is not None: if building.loading_area.distance(coords) <= storage_radius: reachable[coords].append((building.worldid, dist)) options = [] storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection(storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) for coords, building_distances in reachable.iteritems(): if coords not in storage_spots: continue builder = BasicBuilder.create(BUILDINGS.STORAGE, coords, 0) actual_distance = {} for coords in builder.position.tuple_iter(): for building_worldid, distance in reachable[coords]: if building_worldid not in actual_distance or actual_distance[building_worldid] > distance: actual_distance[building_worldid] = distance if not actual_distance: continue usefulness = min(len(actual_distance), self.personality.max_reasonably_served_buildings) for distance in actual_distance.itervalues(): usefulness += 1.0 / (distance + self.personality.collector_extra_distance) alignment = 1 for tile in self.production_builder.iter_neighbor_tiles(builder.position): coords = (tile.x, tile.y) if coords not in self.production_builder.plan or self.production_builder.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = usefulness + alignment * self.personality.alignment_coefficient options.append((value, builder)) return self.production_builder.build_best_option(options, BUILDING_PURPOSE.STORAGE)
def _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() coastline = self.land_manager.coastline # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys(self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if coords not in coastline and purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: assert coords not in coastline if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(set) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[coords][0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection(storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets(storage_class.size) options = [] num_offsets = int(len(self._radius_offsets) * self.personality.overlap_precision) radius_offsets = self.session.random.sample(self._radius_offsets, num_offsets) for coords in sorted(storage_spots): if coords not in area_label: continue x, y = coords area_number = area_label[coords] area_coords_set = coords_set_by_area[area_number] useful_area = 0 for dx, dy in radius_offsets: coords = (x + dx, y + dy) if coords in area_coords_set: useful_area += 1 if not useful_area: continue alignment = 1 builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.production_builder.plan or self.production_builder.plan[coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option(options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size(x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.village_builder.extend_settlement_with_tent(Rect.init_from_topleft_and_size_tuples(best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def create(cls, area_builder, farm_x, farm_y, road_dx, road_dy, min_fields, field_purpose, field_spots_set, road_spots_set, positive_alignment): farm_plan = {} # place the farm area road existing_roads = 0 for other_offset in range(-3, 6): coords = None if road_dx == 0: coords = (farm_x + other_offset, farm_y + road_dy) else: coords = (farm_x + road_dx, farm_y + other_offset) assert coords in road_spots_set farm_plan[coords] = BUILDING_PURPOSE.ROAD if coords in area_builder.land_manager.roads: existing_roads += 1 # place the fields fields = 0 for (dx, dy) in cls.__field_offsets: if fields >= 8: break # unable to place more anyway coords = (farm_x + dx, farm_y + dy) if coords not in field_spots_set: continue field_fits = True for (fdx, fdy) in cls.__field_pos_offsets: coords2 = (coords[0] + fdx, coords[1] + fdy) if coords2 in farm_plan: field_fits = False break if not field_fits: continue # some part of the area is reserved for something else fields += 1 for (fdx, fdy) in cls.__field_pos_offsets: coords2 = (coords[0] + fdx, coords[1] + fdy) farm_plan[coords2] = BUILDING_PURPOSE.RESERVED farm_plan[coords] = field_purpose if fields < min_fields: return None # go for the most fields possible # add the farm itself to the plan builder = BasicBuilder.create(BUILDINGS.FARM, (farm_x, farm_y), 0) for coords in builder.position.tuple_iter(): farm_plan[coords] = BUILDING_PURPOSE.RESERVED farm_plan[(farm_x, farm_y)] = BUILDING_PURPOSE.FARM # calculate the alignment value and the rectangle that contains the whole farm alignment = 0 min_x, max_x, min_y, max_y = None, None, None, None for x, y in farm_plan: min_x = x if min_x is None or min_x > x else min_x max_x = x if max_x is None or max_x < x else max_x min_y = y if min_y is None or min_y > y else min_y max_y = y if max_y is None or max_y < y else max_y for dx, dy in cls.__moves: coords = (x + dx, y + dy) if coords not in farm_plan and coords in positive_alignment: alignment += 1 # calculate the value of the farm road end points (larger is better) personality = area_builder.owner.personality_manager.get('FarmEvaluator') immediate_connections = 0 for other_offset in [-4, 6]: if road_dx == 0: coords = (farm_x + other_offset, farm_y + road_dy) else: coords = (farm_x + road_dx, farm_y + other_offset) if coords in area_builder.land_manager.roads: immediate_connections += personality.immediate_connection_road elif coords in area_builder.plan: if area_builder.plan[coords][0] == BUILDING_PURPOSE.NONE: immediate_connections += personality.immediate_connection_free extra_space = (max_x - min_x + 1) * (max_y - min_y + 1) - 9 * (fields + 2) value = fields + existing_roads * personality.existing_road_importance + \ alignment * personality.alignment_importance - extra_space * personality.wasted_space_penalty + \ immediate_connections * personality.immediate_connection_importance return FarmEvaluator(area_builder, builder, value, farm_plan, fields, field_purpose)
def create(cls, area_builder, farm_x, farm_y, road_dx, road_dy, min_fields, field_purpose, field_spots_set, road_spots_set, positive_alignment): farm_plan = {} # place the farm area road existing_roads = 0 for other_offset in xrange(-3, 6): coords = None if road_dx == 0: coords = (farm_x + other_offset, farm_y + road_dy) else: coords = (farm_x + road_dx, farm_y + other_offset) assert coords in road_spots_set farm_plan[coords] = BUILDING_PURPOSE.ROAD if coords in area_builder.land_manager.roads: existing_roads += 1 # place the fields fields = 0 for (dx, dy) in cls.__field_offsets: if fields >= 8: break # unable to place more anyway coords = (farm_x + dx, farm_y + dy) if coords not in field_spots_set: continue field_fits = True for (fdx, fdy) in cls.__field_pos_offsets: coords2 = (coords[0] + fdx, coords[1] + fdy) if coords2 in farm_plan: field_fits = False break if not field_fits: continue # some part of the area is reserved for something else fields += 1 for (fdx, fdy) in cls.__field_pos_offsets: coords2 = (coords[0] + fdx, coords[1] + fdy) farm_plan[coords2] = BUILDING_PURPOSE.RESERVED farm_plan[coords] = field_purpose if fields < min_fields: return None # go for the most fields possible # add the farm itself to the plan builder = BasicBuilder.create(BUILDINGS.FARM, (farm_x, farm_y), 0) for coords in builder.position.tuple_iter(): farm_plan[coords] = BUILDING_PURPOSE.RESERVED farm_plan[(farm_x, farm_y)] = BUILDING_PURPOSE.FARM # calculate the alignment value and the rectangle that contains the whole farm alignment = 0 min_x, max_x, min_y, max_y = None, None, None, None for x, y in farm_plan: min_x = x if min_x is None or min_x > x else min_x max_x = x if max_x is None or max_x < x else max_x min_y = y if min_y is None or min_y > y else min_y max_y = y if max_y is None or max_y < y else max_y for dx, dy in cls.__moves: coords = (x + dx, y + dy) if coords not in farm_plan and coords in positive_alignment: alignment += 1 # calculate the value of the farm road end points (larger is better) personality = area_builder.owner.personality_manager.get('FarmEvaluator') immediate_connections = 0 for other_offset in [-4, 6]: if road_dx == 0: coords = (farm_x + other_offset, farm_y + road_dy) else: coords = (farm_x + road_dx, farm_y + other_offset) if coords in area_builder.land_manager.roads: immediate_connections += personality.immediate_connection_road elif coords in area_builder.plan: if area_builder.plan[coords][0] == BUILDING_PURPOSE.NONE: immediate_connections += personality.immediate_connection_free extra_space = (max_x - min_x + 1) * (max_y - min_y + 1) - 9 * (fields + 2) value = fields + existing_roads * personality.existing_road_importance + \ alignment * personality.alignment_importance - extra_space * personality.wasted_space_penalty + \ immediate_connections * personality.immediate_connection_importance return FarmEvaluator(area_builder, builder, value, farm_plan, fields, field_purpose)
def _enlarge_collector_area(self): if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] # valid moves for collectors collector_area = self.production_builder.get_collector_area() coastline = self.land_manager.coastline # area_label contains free tiles in the production area and all road tiles area_label = dict.fromkeys( self.land_manager.roads) # {(x, y): area_number, ...} for coords, (purpose, _) in self.production_builder.plan.iteritems(): if coords not in coastline and purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: assert coords not in coastline if coords in area_label and area_label[coords] is not None: continue queue = deque([coords]) while queue: x, y = queue[0] queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in area_label and area_label[coords2] is None: area_label[coords2] = areas queue.append(coords2) areas += 1 coords_set_by_area = defaultdict(lambda: set()) for coords, area_number in area_label.iteritems(): if coords in self.production_builder.plan and self.production_builder.plan[ coords][ 0] == BUILDING_PURPOSE.NONE and coords not in collector_area: coords_set_by_area[area_number].add(coords) storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection( storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) storage_surrounding_offsets = Rect.get_surrounding_offsets( storage_class.size) options = [] num_offsets = int( len(self._radius_offsets) * self.personality.overlap_precision) radius_offsets = self.session.random.sample(self._radius_offsets, num_offsets) for coords in sorted(storage_spots): if coords not in area_label: continue x, y = coords area_number = area_label[coords] area_coords_set = coords_set_by_area[area_number] useful_area = 0 for dx, dy in radius_offsets: coords = (x + dx, y + dy) if coords in area_coords_set: useful_area += 1 if not useful_area: continue alignment = 1 builder = BasicBuilder.create(BUILDINGS.STORAGE, (x, y), 0) for (dx, dy) in storage_surrounding_offsets: coords = (x + dx, y + dy) if coords in coastline or coords not in self.production_builder.plan or self.production_builder.plan[ coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = useful_area + alignment * self.personality.alignment_coefficient options.append((value, builder)) if options: return self.production_builder.build_best_option( options, BUILDING_PURPOSE.STORAGE) # enlarge the settlement area instead since just enlarging the collector area is impossible if self.village_builder.tent_queue: tent_size = Entities.buildings[BUILDINGS.RESIDENTIAL].size tent_radius = Entities.buildings[BUILDINGS.RESIDENTIAL].radius best_coords = None best_area = 0 for x, y in self.village_builder.tent_queue: new_area = 0 for coords in Rect.init_from_topleft_and_size( x, y, tent_size[0], tent_size[1]).get_radius_coordinates(tent_radius): if coords in area_label and coords not in self.land_manager.roads and coords not in collector_area: new_area += 1 if new_area > best_area: best_coords = (x, y) best_area = new_area if best_coords is not None: return self.village_builder.extend_settlement_with_tent( Rect.init_from_topleft_and_size_tuples( best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.STONE_PIT, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder, False) value = 1.0 / (distance_to_collector + 1) return StonePitEvaluator(area_builder, builder, value)
def create(cls, area_builder, x, y, orientation): builder = BasicBuilder.create(BUILDINGS.CLAY_PIT, (x, y), orientation) distance_to_collector = cls._distance_to_nearest_collector(area_builder, builder, False) value = 1.0 / (distance_to_collector + 1) return ClayPitEvaluator(area_builder, builder, value)
def _build_extra_storage(self): """Build an extra storage tent to improve collector coverage.""" if not self.production_builder.have_resources(BUILDINGS.STORAGE): return BUILD_RESULT.NEED_RESOURCES reachable = dict.fromkeys( self.land_manager.roads ) # {(x, y): [(building worldid, distance), ...], ...} for coords, (purpose, _) in self.production_builder.plan.items(): if purpose == BUILDING_PURPOSE.NONE: reachable[coords] = [] for key in reachable: if reachable[key] is None: reachable[key] = [] storage_radius = Entities.buildings[BUILDINGS.STORAGE].radius moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] for building in self._problematic_buildings: distance = dict.fromkeys(reachable) queue = deque() for coords in self.production_builder.iter_possible_road_coords( building.loading_area, building.position): if coords in distance: distance[coords] = 0 queue.append(coords) while queue: x, y = queue.popleft() for dx, dy in moves: coords2 = (x + dx, y + dy) if coords2 in distance and distance[coords2] is None: distance[coords2] = distance[(x, y)] + 1 queue.append(coords2) for coords, dist in distance.items(): if dist is not None: if building.loading_area.distance( coords) <= storage_radius: reachable[coords].append((building.worldid, dist)) options = [] storage_class = Entities.buildings[BUILDINGS.STORAGE] storage_spots = self.island.terrain_cache.get_buildability_intersection( storage_class.terrain_type, storage_class.size, self.settlement.buildability_cache, self.production_builder.buildability_cache) for coords, building_distances in reachable.items(): if coords not in storage_spots: continue builder = BasicBuilder.create(BUILDINGS.STORAGE, coords, 0) actual_distance = {} for coords in builder.position.tuple_iter(): for building_worldid, distance in reachable[coords]: if building_worldid not in actual_distance or actual_distance[ building_worldid] > distance: actual_distance[building_worldid] = distance if not actual_distance: continue usefulness = min(len(actual_distance), self.personality.max_reasonably_served_buildings) for distance in actual_distance.values(): usefulness += 1.0 / (distance + self.personality.collector_extra_distance) alignment = 1 for tile in self.production_builder.iter_neighbor_tiles( builder.position): coords = (tile.x, tile.y) if coords not in self.production_builder.plan or self.production_builder.plan[ coords][0] != BUILDING_PURPOSE.NONE: alignment += 1 value = usefulness + alignment * self.personality.alignment_coefficient options.append((value, builder)) return self.production_builder.build_best_option( options, BUILDING_PURPOSE.STORAGE)