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

Source Code for Module horizons.ai.aiplayer.building.fisher

  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 copy 
 23  import heapq 
 24  import math 
 25   
 26  from horizons.ai.aiplayer.basicbuilder import BasicBuilder 
 27  from horizons.ai.aiplayer.building import AbstractBuilding 
 28  from horizons.ai.aiplayer.buildingevaluator import BuildingEvaluator 
 29  from horizons.ai.aiplayer.constants import BUILDING_PURPOSE 
 30  from horizons.constants import BUILDINGS, COLLECTORS, GAME_SPEED, RES 
 31  from horizons.scheduler import Scheduler 
 32  from horizons.util.shapes import distances 
33 34 35 -class AbstractFisher(AbstractBuilding):
36 - def get_production_level(self, building, resource_id):
38
39 - def get_expected_cost(self, resource_id, production_needed, settlement_manager):
40 evaluator = BuildingEvaluator.get_best_evaluator(self.get_evaluators(settlement_manager, resource_id)) 41 if evaluator is None: 42 return None 43 44 current_expected_production_level = evaluator.get_expected_production_level(resource_id) 45 extra_buildings_needed = math.ceil(max(0.0, production_needed / current_expected_production_level)) 46 return extra_buildings_needed * self.get_expected_building_cost()
47
48 - def iter_potential_locations(self, settlement_manager):
49 options = list(super().iter_potential_locations(settlement_manager)) 50 personality = settlement_manager.owner.personality_manager.get('AbstractFisher') 51 return settlement_manager.session.random.sample(options, min(len(options), personality.max_options))
52 53 @property
54 - def evaluator_class(self):
55 return FisherEvaluator
56 57 @classmethod
58 - def register_buildings(cls):
60
61 62 -class FisherEvaluator(BuildingEvaluator):
63 refill_cycle_in_tiles = 12 # TODO: replace this with a direct calculation 64 65 __slots__ = ('__production_level', ) 66
67 - def __init__(self, area_builder, builder, value):
68 super().__init__(area_builder, builder, value) 69 self.__production_level = None
70
71 - def get_expected_production_level(self, resource_id):
72 assert resource_id == RES.FOOD 73 if self.__production_level is None: 74 fishers_coords = [fisher.position.origin.to_tuple() for fisher in self.area_builder.owner.fishers] 75 self.__production_level = FisherSimulator.extra_productivity(self.area_builder.session, 76 fishers_coords, self.builder.position.origin.to_tuple()) 77 return self.__production_level
78 79 @classmethod
80 - def create(cls, area_builder, x, y, orientation):
81 coords = (x, y) 82 rect_rect_distance_func = distances.distance_rect_rect 83 builder = BasicBuilder.create(BUILDINGS.FISHER, coords, orientation) 84 85 shallow_water_body = area_builder.session.world.shallow_water_body 86 fisher_shallow_water_body_ids = set() 87 for fisher_coords in builder.position.tuple_iter(): 88 if fisher_coords in shallow_water_body: 89 fisher_shallow_water_body_ids.add(shallow_water_body[fisher_coords]) 90 fisher_shallow_water_body_ids = list(fisher_shallow_water_body_ids) 91 assert fisher_shallow_water_body_ids 92 93 tiles_used = 0 94 fish_value = 0.0 95 last_usable_tick = Scheduler().cur_tick - 60 * GAME_SPEED.TICKS_PER_SECOND # TODO: use a direct calculation 96 for fish in area_builder.session.world.fish_indexer.get_buildings_in_range(coords): 97 if shallow_water_body[fish.position.origin.to_tuple()] not in fisher_shallow_water_body_ids: 98 continue # not in the same shallow water body as the fisher => unreachable 99 if fish.last_usage_tick > last_usable_tick: 100 continue # the fish deposit seems to be already in use 101 102 distance = rect_rect_distance_func(builder.position, fish.position) + 1.0 103 if tiles_used >= cls.refill_cycle_in_tiles: 104 fish_value += min(1.0, (3 * cls.refill_cycle_in_tiles - tiles_used) / distance) / 10.0 105 else: 106 fish_value += min(1.0, (cls.refill_cycle_in_tiles - tiles_used) / distance) 107 108 tiles_used += distance 109 if tiles_used >= 3 * cls.refill_cycle_in_tiles: 110 break 111 112 if fish_value < 1.5: 113 return None 114 return FisherEvaluator(area_builder, builder, fish_value)
115 116 @property
117 - def purpose(self):
119
120 121 -class FisherSimulator:
122 # TODO: get these values the right way 123 move_time = 12 # in ticks 124 fish_respawn_time = 480 # 30 seconds in ticks 125 simulation_time = 4800 # 5 minutes in ticks 126 127 @classmethod
128 - def extra_productivity(cls, session, fishers, coords):
129 fish_indexer = session.world.fish_indexer 130 old_productivity = cls.simulate(fish_indexer, fishers) 131 new_list = copy.copy(fishers) 132 new_list.append(coords) 133 return cls.simulate(fish_indexer, new_list) - old_productivity
134 135 @classmethod
136 - def simulate(cls, fish_indexer, fishers):
137 if not fishers: 138 return 0 139 140 fish_map = {} # (x, y); tick_available 141 heap = [] # (tick, fisher_coords) 142 for fisher_coords in fishers: 143 for fish in fish_indexer.get_buildings_in_range(fisher_coords): 144 fish_map[fish.position.origin.to_tuple()] = 0 145 heap.append((0, fisher_coords)) 146 heapq.heapify(heap) 147 148 fish_caught = 0 149 while True: 150 (tick, fisher_coords) = heapq.heappop(heap) 151 if tick > cls.simulation_time: 152 break # simulate for 1 minute 153 154 found_fish = False 155 for fish in fish_indexer.get_buildings_in_range(fisher_coords): 156 fish_coords = fish.position.origin.to_tuple() 157 if fish_map[fish_coords] > tick: 158 continue 159 distance = math.sqrt((fish_coords[0] - fisher_coords[0]) ** 2 + (fish_coords[1] - fisher_coords[1]) ** 2) 160 move_time = cls.move_time * int(round(distance)) 161 fish_map[fish_coords] = tick + move_time + COLLECTORS.DEFAULT_WORK_DURATION + cls.fish_respawn_time 162 if tick + 2 * move_time + COLLECTORS.DEFAULT_WORK_DURATION <= cls.simulation_time: 163 fish_caught += 1 164 next_time = tick + 2 * move_time + COLLECTORS.DEFAULT_WORK_DURATION + COLLECTORS.DEFAULT_WAIT_TICKS 165 heapq.heappush(heap, (next_time, fisher_coords)) 166 found_fish = True 167 break 168 169 if not found_fish: 170 heapq.heappush(heap, (tick + COLLECTORS.DEFAULT_WAIT_TICKS, fisher_coords)) 171 return float(fish_caught) / cls.simulation_time
172 173 174 AbstractFisher.register_buildings() 175