Package horizons :: Package ai :: Package aiplayer :: Module buildingevaluator
[hide private]
[frames] | no frames]

Source Code for Module horizons.ai.aiplayer.buildingevaluator

  1  # ################################################### 
  2  # Copyright (C) 2008-2017 The Unknown Horizons Team 
  3  # team@unknown-horizons.org 
  4  # This file is part of Unknown Horizons. 
  5  # 
  6  # Unknown Horizons is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, 
 12  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 14  # GNU General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the 
 18  # Free Software Foundation, Inc., 
 19  # 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 20  # ################################################### 
 21   
 22  import logging 
 23   
 24  from horizons.ai.aiplayer.constants import BUILD_RESULT, BUILDING_PURPOSE 
 25  from horizons.entities import Entities 
26 27 28 -class BuildingEvaluator:
29 """Class representing a set of instructions for building a building complex along with its value.""" 30 31 log = logging.getLogger("ai.aiplayer.buildingevaluator") 32 need_collector_connection = True 33 record_plan_change = True 34 35 __slots__ = ('area_builder', 'builder', 'value') 36
37 - def __init__(self, area_builder, builder, value):
38 """ 39 @param area_builder: the relevant AreaBuilder instance 40 @param builder: Builder instance 41 @param value: the value of the evaluator (bigger is better) 42 """ 43 44 self.area_builder = area_builder 45 self.builder = builder 46 self.value = value
47 48 @classmethod
49 - def _weighted_distance(cls, main_component, other_components, none_value):
50 """ 51 Return the weights sum of the component distances with the specified weights. 52 53 @param main_component: value of the main component 54 @param other_components: list[(weight, value), ...] where weight is a float and value is either None or a float 55 @param none_value: the penalty for None in place of a component value 56 """ 57 58 others = 0.0 59 for weight, value in other_components: 60 others += weight 61 result = (1 - others) * (main_component if main_component is not None else none_value) 62 for weight, value in other_components: 63 if value is None: 64 result += weight * none_value 65 else: 66 result += weight * value 67 return result
68 69 @classmethod
70 - def _distance_to_nearest_building(cls, area_builder, builder, building_id):
71 """ 72 Return the shortest distance to a building of type building_id that is in range of the builder. 73 74 @param area_builder: AreaBuilder instance 75 @param builder: Builder instance 76 @param building_id: the building type id of the building to which the distance should be measured 77 """ 78 79 shortest_distance = None 80 for building in area_builder.settlement.buildings_by_id.get(building_id, []): 81 distance = builder.position.distance(building.position) 82 if distance <= Entities.buildings[builder.building_id].radius: 83 shortest_distance = distance if shortest_distance is None or distance < shortest_distance else shortest_distance 84 return shortest_distance
85 86 @classmethod
87 - def _distance_to_nearest_collector(cls, production_builder, builder, must_be_in_range=True):
88 """ 89 Return the shortest distance to a collector that (usually) has to be in range of the builder. 90 91 @param production_builder: ProductionBuilder instance 92 @param builder: Builder instance 93 @param must_be_in_range: whether the building has to be in range of the builder 94 """ 95 96 shortest_distance = None 97 for building in production_builder.collector_buildings: 98 distance = builder.position.distance(building.position) 99 if not must_be_in_range or distance <= Entities.buildings[builder.building_id].radius: 100 shortest_distance = distance if shortest_distance is None or distance < shortest_distance else shortest_distance 101 return shortest_distance
102 103 @classmethod
104 - def _get_outline_coords_list(cls, coords_list):
105 """Return the list of coordinates that share sides the given coordinates list.""" 106 moves = [(-1, 0), (0, -1), (0, 1), (1, 0)] 107 if not isinstance(coords_list, set): 108 coords_list = set(coords_list) 109 110 result = set() 111 for x, y in coords_list: 112 for dx, dy in moves: 113 coords = (x + dx, y + dy) 114 if coords not in coords_list: 115 result.add(coords) 116 return result
117 118 @classmethod
119 - def _get_alignment_from_outline(cls, area_builder, outline_coords_list):
120 """Return an alignment value given the list of coordinates that form the outline of a shape.""" 121 personality = area_builder.owner.personality_manager.get('BuildingEvaluator') 122 alignment = 0 123 for coords in outline_coords_list: 124 if coords in area_builder.land_manager.roads: 125 alignment += personality.alignment_road 126 elif coords in area_builder.plan: 127 purpose = area_builder.plan[coords][0] 128 if purpose != BUILDING_PURPOSE.NONE: 129 alignment += personality.alignment_production_building 130 elif coords in area_builder.settlement.ground_map: 131 object = area_builder.settlement.ground_map[coords].object 132 if object is not None and not object.buildable_upon: 133 alignment += personality.alignment_other_building 134 else: 135 alignment += personality.alignment_edge 136 return alignment
137 138 @classmethod
139 - def _get_alignment(cls, area_builder, coords_list):
140 """Return an alignment value based on the outline of the given coordinates list.""" 141 return cls._get_alignment_from_outline(area_builder, cls._get_outline_coords_list(coords_list))
142 143 @property
144 - def purpose(self):
145 """Return the BUILDING_PURPOSE constant relevant to the builder.""" 146 raise NotImplementedError('This function has to be overridden.')
147
148 - def have_resources(self):
149 """Return None if the builder is unreachable by road, False if there are not enough resources, and True otherwise.""" 150 # check without road first because the road is unlikely to be the problem and pathfinding isn't cheap 151 if not self.builder.have_resources(self.area_builder.land_manager): 152 return False 153 if not self.need_collector_connection: 154 return True # skip the road cost test for buildings that don't need one 155 road_cost = self.area_builder.get_road_connection_cost(self.builder) 156 if road_cost is None: 157 return None 158 return self.builder.have_resources(self.area_builder.land_manager, extra_resources=road_cost)
159
161 self.area_builder.register_change_list(list(self.builder.position.tuple_iter()), BUILDING_PURPOSE.RESERVED, None) 162 self.area_builder.register_change_list([self.builder.position.origin.to_tuple()], self.purpose, None)
163
164 - def execute(self):
165 """Build the specified building complex. Return (BUILD_RESULT constant, building object).""" 166 resource_check = self.have_resources() 167 if resource_check is None: 168 self.log.debug('%s, unable to reach by road', self) 169 return (BUILD_RESULT.IMPOSSIBLE, None) 170 elif not resource_check: 171 return (BUILD_RESULT.NEED_RESOURCES, None) 172 if self.need_collector_connection: 173 assert self.area_builder.build_road_connection(self.builder) 174 175 building = self.builder.execute(self.area_builder.land_manager) 176 if not building: 177 self.log.debug('%s, unknown error', self) 178 return (BUILD_RESULT.UNKNOWN_ERROR, None) 179 180 if self.record_plan_change: 181 self._register_builder_position() 182 return (BUILD_RESULT.OK, building)
183
184 - def __str__(self):
185 point = self.builder.position.origin 186 return '{0!s} at {1:d}, {2:d} with value {3:f}'. \ 187 format(self.__class__.__name__, point.x, point.y, self.value)
188 189 @classmethod
190 - def get_best_evaluator(cls, evaluators):
191 if not evaluators: 192 return None 193 194 best_index = 0 195 best_value = evaluators[0].value 196 for i in range(1, len(evaluators)): 197 if evaluators[i].value > best_value: 198 best_index = i 199 best_value = evaluators[i].value 200 return evaluators[best_index]
201