Package horizons :: Package world :: Package buildability :: Module terraincache
[hide private]
[frames] | no frames]

Source Code for Module horizons.world.buildability.terraincache

  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  from horizons.util.shapes.rect import Rect 
 23   
 24   
25 -class TerrainRequirement:
26 LAND = 1 # buildings that must be entirely on flat land 27 LAND_AND_COAST = 2 # buildings that have to be partially on the coast 28 LAND_AND_COAST_NEAR_SEA = 3 # coastal buildings that have to be near the sea
29 30
31 -class TerrainBuildabilityCache:
32 """ 33 Keep track of the locations where buildings of specific types can be built. 34 35 An instance of this class is used to keep track of the buildability options given the 36 terrain. (x, y) are in instance.cache[terrain_type][(width, height)] if and only if 37 it is possible to place a building with the given terrain type restrictions and with 38 the given size at the origin (x, y) given that other conditions are met. Basically it 39 cares about the required terrain type of the building and not about the buildings on 40 the island. 41 42 Other than the terrain type dimension it acts like an immutable BinaryBuildabilityCache. 43 """ 44 45 sizes = [(1, 1), (2, 2), (2, 3), (2, 4), (3, 3), (4, 4), (6, 6)] 46 sea_radius = 3 47
48 - def __init__(self, island):
49 super().__init__() # TODO: check if this call is needed 50 self._island = island 51 self._land = None 52 self._coast = None 53 self.land_or_coast = None # set((x, y), ...) 54 self.cache = None # {terrain type: {(width, height): set((x, y), ...), ...}, ...} 55 self.create_cache()
56
57 - def _init_land_and_coast(self):
58 land = set() 59 coast = set() 60 61 for coords, tile in self._island.ground_map.items(): 62 if 'constructible' in tile.classes: 63 land.add(coords) 64 elif 'coastline' in tile.classes: 65 coast.add(coords) 66 67 self._land = land 68 self._coast = coast 69 self.land_or_coast = land.union(coast)
70
71 - def _init_rows(self):
72 # these dicts show whether there is a constructible and/or coastline tile in a row 73 # of 2 or 3 tiles starting from the coords and going right 74 # both are in the format {(x, y): (has land, has coast), ...} 75 row2 = {} 76 row3 = {} 77 78 land = self._land 79 coast = self._coast 80 land_or_coast = self.land_or_coast 81 82 for (x, y) in self.land_or_coast: 83 if (x + 1, y) not in land_or_coast: 84 continue 85 86 has_land = (x, y) in land or (x + 1, y) in land 87 has_coast = (x, y) in coast or (x + 1, y) in coast 88 row2[(x, y)] = (has_land, has_coast) 89 if (x + 2, y) not in land_or_coast: 90 continue 91 92 has_land = has_land or (x + 2, y) in land 93 has_coast = has_coast or (x + 2, y) in coast 94 row3[(x, y)] = (has_land, has_coast) 95 96 self.row2 = row2 97 self.row3 = row3
98
99 - def _init_squares(self):
100 self._init_rows() 101 row2 = self.row2 102 row3 = self.row3 103 104 sq2 = {} 105 for coords in row2: 106 coords2 = (coords[0], coords[1] + 1) 107 if coords2 in row2: 108 has_land = row2[coords][0] or row2[coords2][0] 109 has_coast = row2[coords][1] or row2[coords2][1] 110 sq2[coords] = (has_land, has_coast) 111 112 sq3 = {} 113 for coords in row3: 114 coords2 = (coords[0], coords[1] + 1) 115 coords3 = (coords[0], coords[1] + 2) 116 if coords2 in row3 and coords3 in row3: 117 has_land = row3[coords][0] or row3[coords2][0] or row3[coords3][0] 118 has_coast = row3[coords][1] or row3[coords2][1] or row3[coords3][1] 119 sq3[coords] = (has_land, has_coast) 120 121 self.sq2 = sq2 122 self.sq3 = sq3
123
124 - def create_cache(self):
125 self._init_land_and_coast() 126 127 land = {} 128 land_and_coast = {} 129 130 land[(1, 1)] = self._land 131 for size in self.sizes: 132 if size != (1, 1): 133 land[size] = set() 134 if size[0] != size[1]: 135 land[(size[1], size[0])] = set() 136 137 for size in [(2, 2), (3, 3)]: 138 land_and_coast[size] = set() 139 140 self._init_squares() 141 142 sq2 = self.sq2 143 for coords, (has_land, has_coast) in sq2.items(): 144 x, y = coords 145 if has_land and has_coast: 146 # handle 2x2 coastal buildings 147 land_and_coast[(2, 2)].add(coords) 148 elif has_land and not has_coast: 149 # handle 2x2, 2x3, and 2x4 land buildings 150 land[(2, 2)].add(coords) 151 152 if (x + 2, y) in sq2 and not sq2[(x + 2, y)][1]: 153 land[(3, 2)].add(coords) 154 land[(4, 2)].add(coords) 155 elif (x + 1, y) in sq2 and not sq2[(x + 1, y)][1]: 156 land[(3, 2)].add(coords) 157 158 if (x, y + 2) in sq2 and not sq2[(x, y + 2)][1]: 159 land[(2, 3)].add(coords) 160 land[(2, 4)].add(coords) 161 elif (x, y + 1) in sq2 and not sq2[(x, y + 1)][1]: 162 land[(2, 3)].add(coords) 163 164 sq3 = self.sq3 165 for coords, (has_land, has_coast) in sq3.items(): 166 x, y = coords 167 if has_land and has_coast: 168 # handle 3x3 coastal buildings 169 land_and_coast[(3, 3)].add(coords) 170 elif has_land and not has_coast: 171 # handle other buildings that have both sides >= 3 (3x3, 4x4, 6x6) 172 land[(3, 3)].add(coords) 173 174 if (x, y + 3) in sq3 and not sq3[(x, y + 3)][1] and (x + 3, y) in sq3 \ 175 and not sq3[(x + 3, y)][1] and (x + 3, y + 3) in sq3 and not sq3[(x + 3, y + 3)][1]: 176 land[(4, 4)].add(coords) 177 land[(6, 6)].add(coords) 178 elif (x, y + 1) in sq3 and not sq3[(x, y + 1)][1] and (x + 1, y) in sq3 \ 179 and not sq3[(x + 1, y)][1] and (x + 1, y + 1) in sq3 and not sq3[(x + 1, y + 1)][1]: 180 land[(4, 4)].add(coords) 181 182 self.cache = {} 183 self.cache[TerrainRequirement.LAND] = land 184 self.cache[TerrainRequirement.LAND_AND_COAST] = land_and_coast
185
186 - def create_sea_cache(self):
187 # currently only 3x3 buildings can require nearby sea 188 coast_set = self.cache[TerrainRequirement.LAND_AND_COAST][(3, 3)] 189 near_sea = set() 190 191 nearby_coords_list = [] 192 base_rect = Rect.init_from_topleft_and_size(0, 0, 3, 3) 193 for coords in base_rect.get_radius_coordinates(self.sea_radius): 194 nearby_coords_list.append(coords) 195 196 world = self._island.session.world 197 water_bodies = world.water_body 198 sea_number = world.sea_number 199 200 for bx, by in coast_set: 201 for dx, dy in nearby_coords_list: 202 coords = (bx + dx, by + dy) 203 if coords in water_bodies and water_bodies[coords] == sea_number: 204 near_sea.add((bx, by)) 205 break 206 207 self.cache[TerrainRequirement.LAND_AND_COAST_NEAR_SEA] = {} 208 self.cache[TerrainRequirement.LAND_AND_COAST_NEAR_SEA][(3, 3)] = near_sea
209
210 - def get_buildability_intersection(self, terrain_type, size, *other_cache_layers):
211 result = self.cache[terrain_type][size] 212 for cache_layer in other_cache_layers: 213 result = result.intersection(cache_layer.cache[size]) 214 return result
215