def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): """Check if a building is buildable here. All tiles, that the building occupies are checked. @param point: Point instance, coords @param rotation: prefered rotation of building @param check_settlement: whether to check for a settlement (for settlementless buildings) @param ship: ship instance if building from ship @return instance of _BuildPosition""" # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation == 45 or rotation == 225: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True problem = None tearset = [] try: island = cls._check_island(session, position) # TODO: if the rotation changes here for non-quadratic buildings, wrong results will be returned rotation = cls._check_rotation(session, position, rotation) tearset = cls._check_buildings(session, position, island=island) cls._check_units(session, position) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError as e: buildable = False problem = (e.errortype, _(BuildableErrorTypes.text[e.errortype])) return _BuildPosition(position, rotation, tearset, buildable, problem=problem)
def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): """Check if a building is buildable here. All tiles, that the building occupies are checked. @param point: Point instance, coords @param rotation: preferred rotation of building @param check_settlement: whether to check for a settlement (for settlementless buildings) @param ship: ship instance if building from ship @return instance of _BuildPosition""" # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation in [45, 225]: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True problem = None tearset = [] try: island = cls._check_island(session, position) # TODO: if the rotation changes here for non-quadratic buildings, wrong results will be returned rotation = cls._check_rotation(session, position, rotation) tearset = cls._check_buildings(session, position, island=island) cls._check_units(session, position) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError as e: buildable = False problem = (e.errortype, BuildableErrorTypes.text[e.errortype]) return _BuildPosition(position, rotation, tearset, buildable, problem=problem)
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target renderer for drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings) @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap NOTE: Preview generation in a different process overwrites this method. """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position self.use_overlay_icon(self.icon) # FIXME PY3 width / height of icon is sometimes zero. Why? if self.location.height == 0 or self.location.width == 0: self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128) self.session = session self.world = world if self.world: self._update_world_to_minimap_ratio() self.view = view self.rotation = 0 self.fixed_tooltip = tooltip self.click_handler = on_click if on_click is not None else self.default_on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(next(self.__class__.__minimap_id_counter)) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) self._rotation_setting = horizons.globals.fife.get_uh_setting("MinimapRotation") if self.use_rotation: SettingChanged.subscribe(self._on_setting_changed)
def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation in [45, 225]: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
def check_build(cls, session, point, rotation=45, check_settlement=True, ship=None, issuer=None): # for non-quadratic buildings, we have to switch width and height depending on the rotation if rotation == 45 or rotation == 225: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[0], cls.size[1]) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1], cls.size[0]) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None, mousearea=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target renderer for drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap NOTE: Preview generation in a different process overwrites this method. """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position if mousearea is None: mousearea = self.icon self.use_overlay_icon(mousearea) # FIXME PY3 width / height of icon is sometimes zero. Why? if self.location.height == 0 or self.location.width == 0: self.location = Rect.init_from_topleft_and_size(0, 0, 128, 128) self.session = session self.world = world self.view = view self.fixed_tooltip = tooltip self.click_handler = on_click if on_click is not None else self.default_on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(next(self.__class__.__minimap_id_counter)) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) self.transform = None
def get_loading_area(cls, building_id, rotation, pos): if building_id == BUILDINGS.MOUNTAIN or building_id == BUILDINGS.MINE: if rotation == 45: return Rect.init_from_topleft_and_size(pos.origin.x, pos.origin.y + 1, 1, 3) elif rotation == 135: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y + pos.height - 1, 3, 1) elif rotation == 225: return Rect.init_from_topleft_and_size(pos.origin.x + pos.width -1, pos.origin.y + 1, 1, 3) elif rotation == 315: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y, 3, 1) assert False else: return pos
def get_loading_area(cls, building_id, rotation, pos): if building_id in [BUILDINGS.MOUNTAIN, BUILDINGS.MINE]: if rotation == 45: return Rect.init_from_topleft_and_size(pos.origin.x, pos.origin.y + 1, 1, 3) elif rotation == 135: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y + pos.height - 1, 3, 1) elif rotation == 225: return Rect.init_from_topleft_and_size(pos.origin.x + pos.width - 1, pos.origin.y + 1, 1, 3) elif rotation == 315: return Rect.init_from_topleft_and_size(pos.origin.x + 1, pos.origin.y, 3, 1) assert False else: return pos
def extend_settlement_with_tent(self, position): """Build a tent to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" distance_rect_rect = distances.distance_rect_rect size = Entities.buildings[BUILDINGS.RESIDENTIAL].size min_distance = None best_coords = None for (x, y) in self.tent_queue: ok = True for dx in xrange(size[0]): for dy in xrange(size[1]): if (x + dx, y + dy) not in self.settlement.ground_map: ok = False break if not ok: continue distance = distance_rect_rect(Rect.init_from_topleft_and_size(x, y, size[0], size[1]), position) if min_distance is None or distance < min_distance: min_distance = distance best_coords = (x, y) if min_distance is None: return BUILD_RESULT.IMPOSSIBLE return self.build_tent(best_coords)
def extend_settlement_with_tent(self, position): """Build a tent to extend the settlement towards the given position. Return a BUILD_RESULT constant.""" distance_rect_rect = distances.distance_rect_rect size = Entities.buildings[BUILDINGS.RESIDENTIAL].size min_distance = None best_coords = None for (x, y) in self.tent_queue: ok = True for dx in xrange(size[0]): for dy in xrange(size[1]): if (x + dx, y + dy) not in self.settlement.ground_map: ok = False break if not ok: continue distance = distance_rect_rect( Rect.init_from_topleft_and_size(x, y, size[0], size[1]), position) if min_distance is None or distance < min_distance: min_distance = distance best_coords = (x, y) if min_distance is None: return BUILD_RESULT.IMPOSSIBLE return self.build_tent(best_coords)
def create_map(): """ Create a map with a square island (20x20) at position (20, 20) and return the path to the database file. """ tiles = [] for x, y in Rect.init_from_topleft_and_size(0, 0, 20, 20).tuple_iter(): if (0 < x < 20) and (0 < y < 20): ground = GROUND.DEFAULT_LAND else: # Add coastline at the borders. ground = GROUND.SHALLOW_WATER tiles.append([0, 20 + x, 20 + y] + list(ground)) fd, map_file = tempfile.mkstemp() os.close(fd) db = DbReader(map_file) with open('content/map-template.sql') as map_template: db.execute_script(map_template.read()) db('BEGIN') db.execute_many("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", tiles) db('COMMIT') db.close() return map_file
def create_map(): """ Create a map with a square island (20x20) at position (20, 20) and return the path to the database file. """ tiles = [] for x, y in Rect.init_from_topleft_and_size(0, 0, 20, 20).tuple_iter(): if (0 < x < 20) and (0 < y < 20): ground = GROUND.DEFAULT_LAND else: # Add coastline at the borders. ground = GROUND.SHALLOW_WATER tiles.append([0, 20 + x, 20 + y] + list(ground)) fd, map_file = tempfile.mkstemp() os.close(fd) db = DbReader(map_file) with open('content/map-template.sql') as map_template: db.execute_script(map_template.read()) db('BEGIN') db.execute_many("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", tiles) db('COMMIT') db.close() return map_file
def build_tent(self, coords=None): """Build the next tent (or the specified one if coords is not None).""" if not self.tent_queue: return BUILD_RESULT.IMPOSSIBLE # can_trigger_next_section is used to start building the next section when the old one is done # if a tent is built just to extend the area then that can't trigger the next section # TODO: handle extension tents nicer than just letting them die can_trigger_next_section = False if coords is None: coords = self.tent_queue[0] can_trigger_next_section = True ok = True x, y = coords owned_by_other = False size = Entities.buildings[BUILDINGS.RESIDENTIAL].size for dx in xrange(size[0]): for dy in xrange(size[1]): coords2 = (x + dx, y + dy) if coords2 not in self.settlement.ground_map: ok = False if self.island.ground_map[coords2].settlement is not None: owned_by_other = True if ok and not owned_by_other: if not self.have_resources(BUILDINGS.RESIDENTIAL): return BUILD_RESULT.NEED_RESOURCES assert BasicBuilder(BUILDINGS.RESIDENTIAL, (x, y), 0).execute(self.land_manager) if ok or owned_by_other: if self.tent_queue[0] == coords: self.tent_queue.popleft() else: for i in xrange(len(self.tent_queue)): if self.tent_queue[i] == coords: del self.tent_queue[i] break if owned_by_other: self.log.debug( '%s tent position owned by other player at (%d, %d)', self, x, y) return BUILD_RESULT.IMPOSSIBLE if not ok: # need to extends the area, it is not owned by another player self.log.debug( '%s tent position not owned by the player at (%d, %d), extending settlement area instead', self, x, y) return self.extend_settlement( Rect.init_from_topleft_and_size(x, y, size[0], size[1])) if not self.roads_built: self.build_roads() if can_trigger_next_section and self.plan[coords][1][ 0] > self.current_section: self.current_section = self.plan[coords][1][0] return BUILD_RESULT.OK
def _init_radius_offsets(cls): building_class = Entities.buildings[BUILDINGS.STORAGE] size = building_class.size assert size[0] == size[1] rect = Rect.init_from_topleft_and_size(0, 0, size[0], size[1]) cls._radius_offsets = [] for coords in rect.get_radius_coordinates(building_class.radius): cls._radius_offsets.append(coords)
def _init_radius_offsets(cls): building_class = Entities.buildings[BUILDINGS.STORAGE] size = building_class.size assert size[0] == size[1] rect = Rect.init_from_topleft_and_size(0, 0, size[0], size[1]) cls._radius_offsets = [] for coords in rect.get_radius_coordinates(building_class.radius): cls._radius_offsets.append(coords)
def get_displayed_area(self): """Returns the coords of what is displayed on the screen as Rect""" coords = self.cam.getLocation().getLayerCoordinates() cell_dim = self.cam.getCellImageDimensions() width_x = horizons.globals.fife.engine_settings.getScreenWidth() // cell_dim.x + 1 width_y = horizons.globals.fife.engine_settings.getScreenHeight() // cell_dim.y + 1 screen_width_as_coords = (width_x // self.zoom, width_y // self.zoom) return Rect.init_from_topleft_and_size(coords.x - (screen_width_as_coords[0] // 2), coords.y - (screen_width_as_coords[1] // 2), *screen_width_as_coords)
def get_displayed_area(self): """Returns the coords of what is displayed on the screen as Rect""" coords = self.cam.getLocationRef().getLayerCoordinates() cell_dim = self.cam.getCellImageDimensions() width_x = horizons.globals.fife.engine_settings.getScreenWidth() // cell_dim.x + 1 width_y = horizons.globals.fife.engine_settings.getScreenHeight() // cell_dim.y + 1 screen_width_as_coords = (width_x // self.zoom, width_y // self.zoom) return Rect.init_from_topleft_and_size(coords.x - (screen_width_as_coords[0] // 2), coords.y - (screen_width_as_coords[1] // 2), *screen_width_as_coords)
def build_tent(self, coords=None): """Build the next tent (or the specified one if coords is not None).""" if not self.tent_queue: return BUILD_RESULT.IMPOSSIBLE # can_trigger_next_section is used to start building the next section when the old one is done # if a tent is built just to extend the area then that can't trigger the next section # TODO: handle extension tents nicer than just letting them die can_trigger_next_section = False if coords is None: coords = self.tent_queue[0] can_trigger_next_section = True ok = True x, y = coords owned_by_other = False size = Entities.buildings[BUILDINGS.RESIDENTIAL].size for dx in xrange(size[0]): for dy in xrange(size[1]): coords2 = (x + dx, y + dy) if coords2 not in self.settlement.ground_map: ok = False if self.island.ground_map[coords2].settlement is not None: owned_by_other = True if ok and not owned_by_other: builder = self.make_builder(BUILDINGS.RESIDENTIAL, x, y, False) if not builder.have_resources(): return BUILD_RESULT.NEED_RESOURCES if not builder.execute(): self.log.debug('%s unable to build a tent at (%d, %d)', self, x, y) return BUILD_RESULT.UNKNOWN_ERROR if ok or owned_by_other: if self.tent_queue[0] == coords: self.tent_queue.popleft() else: for i in xrange(len(self.tent_queue)): if self.tent_queue[i] == coords: del self.tent_queue[i] break if owned_by_other: self.log.debug('%s tent position owned by other player at (%d, %d)', self, x, y) return BUILD_RESULT.IMPOSSIBLE if not ok: # need to extends the area, it is not owned by another player self.log.debug('%s tent position not owned by the player at (%d, %d), extending settlement area instead', self, x, y) return self.extend_settlement(Rect.init_from_topleft_and_size(x, y, size[0], size[1])) if not self.roads_built: self.build_roads() if can_trigger_next_section and self.plan[coords][1][0] > self.current_section: self.current_section = self.plan[coords][1][0] return BUILD_RESULT.OK
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target rendererfor drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings) @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position self.use_overlay_icon(self.icon) self.session = session self.world = world if self.world: self._update_world_to_minimap_ratio() self.view = view self.rotation = 0 self.fixed_tooltip = tooltip if on_click is not None: self.on_click = on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(self.__class__.__minimap_id_counter.next()) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) #import random #ExtScheduler().add_new_object(lambda : self.highlight( (50+random.randint(-50,50), random.randint(-50,50) + 50 )), self, 2, loops=-1) self._rotation_setting = horizons.globals.fife.get_uh_setting("MinimapRotation") if self.use_rotation: MinimapRotationSettingChanged.subscribe(self._on_rotation_setting_change)
def __init__(self, position, session, view, targetrenderer, imagemanager, renderer=None, world=None, cam_border=True, use_rotation=True, on_click=None, preview=False, tooltip=None): """ @param position: a Rect or a Pychan Icon, where we will draw to @param world: World object or fake thereof @param view: View object for cam control. Can be None to disable this @param renderer: renderer to be used if position isn't an icon @param targetrenderer: fife target rendererfor drawing on icons @param imagemanager: fife imagemanager for drawing on icons @param cam_border: boolean, whether to draw the cam border @param use_rotation: boolean, whether to use rotation (it must also be enabled in the settings) @param on_click: function taking 1 argument or None for scrolling @param preview: flag, whether to only show the map as preview @param tooltip: always show this tooltip when cursor hovers over minimap """ if isinstance(position, Rect): self.location = position self.renderer = renderer else: # assume icon self.location = Rect.init_from_topleft_and_size(0, 0, position.width, position.height) self.icon = position self.use_overlay_icon(self.icon) self.session = session self.world = world if self.world: self._update_world_to_minimap_ratio() self.view = view self.rotation = 0 self.fixed_tooltip = tooltip if on_click is not None: self.on_click = on_click self.cam_border = cam_border self.use_rotation = use_rotation self.preview = preview self.location_center = self.location.center self._id = str(self.__class__.__minimap_id_counter.next()) # internal identifier, used for allocating resources self._image_size_cache = {} # internal detail self.imagemanager = imagemanager self.minimap_image = _MinimapImage(self, targetrenderer) #import random #ExtScheduler().add_new_object(lambda : self.highlight( (50+random.randint(-50,50), random.randint(-50,50) + 50 )), self, 2, loops=-1) self._rotation_setting = horizons.globals.fife.get_uh_setting("MinimapRotation") if self.use_rotation: MinimapRotationSettingChanged.subscribe(self._on_rotation_setting_change)
def load(cls, db, worldid, session, island): self = cls.__new__(cls) self.session = session super(Settlement, self).load(db, worldid) owner = db("SELECT owner FROM settlement WHERE rowid = ?", worldid)[0][0] upgrade_permissions = {} tax_settings = {} for level, allowed, tax in db("SELECT level, upgrading_allowed, tax_setting FROM settlement_level_properties WHERE settlement = ?", worldid): upgrade_permissions[level] = allowed tax_settings[level] = tax self.__init(session, WorldObject.get_object_by_id(owner), upgrade_permissions, tax_settings) try: # normal tile loading for new savegames tile_data = db("SELECT data FROM settlement_tiles WHERE rowid = ?", worldid)[0][0] tile_data = json.loads(tile_data) for (x, y) in tile_data: # NOTE: json saves tuples as list tup = (x, y) tile = island.ground_map[tup] self.ground_map[tup] = tile tile.settlement = self except sqlite3.OperationalError: print "Updating data of outdated savegame.." # old savegame, create settlement tiles provisionally (not correct, but useable) # TODO: remove when there aren't any savegames from before december 2011 any more for b_type, x, y in db("SELECT type, x, y FROM building WHERE location = ?", worldid): cls = Entities.buildings[b_type] position = Rect.init_from_topleft_and_size(x, y, cls.size[0], cls.size[1]) for coord in position.get_radius_coordinates(cls.radius, include_self=True): tile = island.get_tile_tuple(coord) if tile is not None: if tile.settlement is None: self.ground_map[coord] = island.ground_map[coord] tile.settlement = self # load super here cause basic stuff is just set up now # load all buildings from this settlement # the buildings will expand the area of the settlement by adding everything, # that is in the radius of the building, to the settlement. from horizons.world import load_building for building_id, building_type in \ db("SELECT rowid, type FROM building WHERE location = ?", worldid): building = load_building(session, db, building_type, building_id) if building_type == BUILDINGS.WAREHOUSE: self.warehouse = building for res, amount in db("SELECT res, amount FROM settlement_produced_res WHERE settlement = ?", worldid): self.produced_res[res] = amount return self
def _update(self, tup): """Recalculate and redraw minimap for real world coord tup @param tup: (x, y)""" if self.world is None or not self.world.inited: return # don't draw while loading minimap_point = self._world_to_minimap( tup, self._get_rotation_setting() ) world_to_minimap = self._world_to_minimap_ratio # TODO: remove this remnant of the old implementation, perhaps by refactoring recalculate() minimap_point = ( minimap_point[0] + self.location.left, minimap_point[1] + self.location.top, ) rect = Rect.init_from_topleft_and_size(minimap_point[0], minimap_point[1], int(round(1/world_to_minimap[0])) + 1, int(round(1/world_to_minimap[1])) + 1) self._recalculate(rect)
def _update(self, tup): """Recalculate and redraw minimap for real world coord tup @param tup: (x, y)""" if self.world is None or not self.world.inited: return # don't draw while loading minimap_point = self._world_to_minimap( tup, self._get_rotation_setting() ) world_to_minimap = self._world_to_minimap_ratio # TODO: remove this remnant of the old implementation, perhaps by refactoring recalculate() minimap_point = ( minimap_point[0] + self.location.left, minimap_point[1] + self.location.top, ) rect = Rect.init_from_topleft_and_size(minimap_point[0], minimap_point[1], int(round(1/world_to_minimap[0])) + 1, int(round(1/world_to_minimap[1])) + 1) self._recalculate(rect)
def create_map(): """ Create a map with a square island (20x20) at position (20, 20) and return the path to the database file. """ # Create island. fd, islandfile = tempfile.mkstemp() os.close(fd) db = DbReader(islandfile) db("CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL, action_id TEXT NOT NULL, rotation INTEGER NOT NULL)") db("CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)") db("BEGIN TRANSACTION") tiles = [] for x, y in Rect.init_from_topleft_and_size(0, 0, 20, 20).tuple_iter(): if (0 < x < 20) and (0 < y < 20): ground = GROUND.DEFAULT_LAND else: # Add coastline at the borders. ground = GROUND.SHALLOW_WATER tiles.append([x, y] + list(ground)) db.execute_many("INSERT INTO ground VALUES(?, ?, ?, ?, ?)", tiles) db("COMMIT") # Create savegame with the island above. fd, savegame = tempfile.mkstemp() os.close(fd) db = DbReader(savegame) read_savegame_template(db) db("BEGIN TRANSACTION") db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", 20, 20, islandfile) db("COMMIT") return savegame
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() # 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 purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: 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) options = [] for (x, y), area_number in area_label.iteritems(): builder = self.production_builder.make_builder(BUILDINGS.STORAGE, x, y, False) if not builder: continue coords_set = set(builder.position.get_radius_coordinates(Entities.buildings[BUILDINGS.STORAGE].radius)) useful_area = len(coords_set_by_area[area_number].intersection(coords_set)) if not useful_area: continue alignment = 1 for tile in self.production_builder.iter_neighbour_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 = 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.production_builder.extend_settlement_with_tent(Rect.init_from_topleft_and_size_tuples(best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE
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 generate_random_map(seed, map_size, water_percent, max_island_size, preferred_island_size, island_size_deviation): """ Generates a random map. @param seed: random number generator seed @param map_size: maximum map side length @param water_percent: minimum percent of map covered with water @param max_island_size: maximum island side length @param preferred_island_size: mean of island side lengths @param island_size_deviation: deviation of island side lengths @return: filename of the SQLite database containing the map """ max_island_size = min(max_island_size, map_size) rand = random.Random(_simplify_seed(seed)) min_island_size = 20 # minimum chosen island side length (the real size my be smaller) min_island_separation = 3 + map_size // 100 # minimum distance between two islands max_island_side_coefficient = 4 # maximum value of island's max(side length) / min(side length) islands = [] estimated_land = 0 max_land_amount = map_size * map_size * (100 - water_percent) // 100 trial_number = 0 while trial_number < 100: trial_number += 1 width = max(min_island_size, min(max_island_size, int(round(rand.gauss(preferred_island_size, island_size_deviation))))) side_coefficient = min(1 + abs(rand.gauss(0, 0.2)), max_island_side_coefficient) side_coefficient = side_coefficient if rand.randint(0, 1) else 1.0 / side_coefficient height = max(min_island_size, min(max_island_size, int(round(width * side_coefficient)))) size = width * height if estimated_land + size > max_land_amount: continue for _ in xrange(13): x = rand.randint(0, map_size - width) y = rand.randint(0, map_size - height) rect = Rect.init_from_topleft_and_size(x, y, width, height) blocked = False for existing_island in islands: if existing_island.distance(rect) < min_island_separation: blocked = True break if not blocked: islands.append(rect) estimated_land += size trial_number = 0 break # move some of the islands to stretch the map to the right size if len(islands) > 1: min_top = min(rect.top for rect in islands) rect = rand.choice([rect for rect in islands if rect.top == min_top]) islands[islands.index(rect)] = Rect.init_from_borders(rect.left, rect.top - min_top, rect.right, rect.bottom - min_top) max_bottom = max(rect.bottom for rect in islands) rect = rand.choice([rect for rect in islands if rect.bottom == max_bottom]) shift = map_size - max_bottom - 1 islands[islands.index(rect)] = Rect.init_from_borders(rect.left, rect.top + shift, rect.right, rect.bottom + shift) min_left = min(rect.left for rect in islands) rect = rand.choice([rect for rect in islands if rect.left == min_left]) islands[islands.index(rect)] = Rect.init_from_borders(rect.left - min_left, rect.top, rect.right - min_left, rect.bottom) max_right = max(rect.right for rect in islands) rect = rand.choice([rect for rect in islands if rect.right == max_right]) shift = map_size - max_right - 1 islands[islands.index(rect)] = Rect.init_from_borders(rect.left + shift, rect.top, rect.right + shift, rect.bottom) island_strings = [] for rect in islands: # The bounds must be platform independent to make sure the same maps are generated on all platforms. island_seed = rand.randint(-2147483648, 2147483647) island_params = {'creation_method': 2, 'seed': island_seed, 'width': rect.width, 'height': rect.height, 'island_x': rect.left, 'island_y': rect.top} island_string = string.Template(_random_island_id_template).safe_substitute(island_params) island_strings.append(island_string) return island_strings
def create_random_island(map_db, island_id, id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. The coordinates of tiles will be 0 <= x < width and 0 <= y < height @param id_string: random island id string """ match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed, island_x, island_y = [long(i) for i in match_obj.groups()] assert creation_method == 2, 'The only supported island creation method is 2.' rand = random.Random(seed) map_set = set() # place this number of shapes for i in xrange(15 + width * height // 45): # place shape determined by shape_id on (x, y) add = True shape_id = rand.randint(2, 8) rect_chance = 29 if rand.randint(0, 4) == 0: rect_chance = 13 add = False shape = None if rand.randint(1, rect_chance) == 1: # use a rect if add: x = rand.randint(8, width - 7) y = rand.randint(8, height - 7) else: x = rand.randint(0, width) y = rand.randint(0, height) shape = Rect.init_from_topleft_and_size(x - 5, y - 5, rand.randint(2, 8), rand.randint(2, 8)) else: # use a circle such that the radius is determined by shape_id radius = shape_id if not add and rand.randint(0, 6) < 5: x = rand.randint(-radius * 3 // 2, width + radius * 3 // 2) y = rand.randint(-radius * 3 // 2, height + radius * 3 // 2) shape = Circle(Point(x, y), shape_id) elif width - radius - 4 >= radius + 3 and height - radius - 4 >= radius + 3: x = rand.randint(radius + 3, width - radius - 4) y = rand.randint(radius + 3, height - radius - 4) shape = Circle(Point(x, y), shape_id) if shape: for shape_coord in shape.tuple_iter(): if add: map_set.add(shape_coord) elif shape_coord in map_set: map_set.discard(shape_coord) # write values to db map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_set: map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *GROUND.DEFAULT_LAND) def fill_tiny_spaces(tile): """Fills 1 tile gulfs and straits with the specified tile @param tile: ground tile to fill with """ all_neighbours = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] neighbours = [(-1, 0), (0, -1), (0, 1), (1, 0)] corners = [(-1, -1), (-1, 1)] knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] bad_configs = set([0, 1 << 0, 1 << 1, 1 << 2, 1 << 3, (1 << 0) | (1 << 3), (1 << 1) | (1 << 2)]) edge_set = copy.copy(map_set) reduce_edge_set = True while True: to_fill = set() to_ignore = set() for x, y in edge_set: # ignore the tiles with no empty neighbours if reduce_edge_set: is_edge = False for x_offset, y_offset in all_neighbours: if (x + x_offset, y + y_offset) not in map_set: is_edge = True break if not is_edge: to_ignore.add((x, y)) continue for x_offset, y_offset in neighbours: x2 = x + x_offset y2 = y + y_offset if (x2, y2) in map_set: continue # (x2, y2) is now a point just off the island neighbours_dirs = 0 for i in xrange(len(neighbours)): x3 = x2 + neighbours[i][0] y3 = y2 + neighbours[i][1] if (x3, y3) not in map_set: neighbours_dirs |= (1 << i) if neighbours_dirs in bad_configs: # part of a straight 1 tile gulf to_fill.add((x2, y2)) else: for x_offset, y_offset in corners: x3 = x2 + x_offset y3 = y2 + y_offset x4 = x2 - x_offset y4 = y2 - y_offset if (x3, y3) in map_set and (x4, y4) in map_set: # part of a diagonal 1 tile gulf to_fill.add((x2, y2)) break # block 1 tile straits for x_offset, y_offset in knight_moves: x2 = x + x_offset y2 = y + y_offset if (x2, y2) not in map_set: continue if abs(x_offset) == 1: y2 = y + y_offset // 2 if (x2, y2) in map_set or (x, y2) in map_set: continue else: x2 = x + x_offset // 2 if (x2, y2) in map_set or (x2, y) in map_set: continue to_fill.add((x2, y2)) # block diagonal 1 tile straits for x_offset, y_offset in corners: x2 = x + x_offset y2 = y + y_offset x3 = x + 2 * x_offset y3 = y + 2 * y_offset if (x2, y2) not in map_set and (x3, y3) in map_set: to_fill.add((x2, y2)) elif (x2, y2) in map_set and (x2, y) not in map_set and (x, y2) not in map_set: to_fill.add((x2, y)) if to_fill: for x, y in to_fill: map_set.add((x, y)) map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) old_size = len(edge_set) edge_set = edge_set.difference(to_ignore).union(to_fill) reduce_edge_set = old_size - len(edge_set) > 50 else: break # possible movement directions all_moves = { 'sw' : (-1, -1), 'w' : (-1, 0), 'nw' : (-1, 1), 's' : (0, -1), 'n' : (0, 1), 'se' : (1, -1), 'e' : (1, 0), 'ne' : (1, 1) } def get_island_outline(): """ @return: the points just off the island as a dict """ result = set() for x, y in map_set: for offset_x, offset_y in all_moves.itervalues(): coords = (x + offset_x, y + offset_y) if coords not in map_set: result.add(coords) return result # add grass to sand tiles fill_tiny_spaces(GROUND.DEFAULT_LAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.SAND_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.SAND_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.SAND_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.SAND_NORTH # sandy corner elif filled == ['se']: tile = GROUND.SAND_NORTHWEST1 elif filled == ['ne']: tile = GROUND.SAND_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.SAND_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.SAND_NORTHEAST1 # grassy corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.SAND_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.SAND_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.SAND_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.SAND_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_set = map_set.union(outline) # add sand to shallow water tiles fill_tiny_spaces(GROUND.SAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.COAST_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.COAST_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.COAST_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.COAST_NORTH # mostly wet corner elif filled == ['se']: tile = GROUND.COAST_NORTHWEST1 elif filled == ['ne']: tile = GROUND.COAST_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.COAST_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.COAST_NORTHEAST1 # mostly dry corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.COAST_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.COAST_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.COAST_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.COAST_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_set = map_set.union(outline) # add shallow water to deep water tiles fill_tiny_spaces(GROUND.SHALLOW_WATER) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.DEEP_WATER_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.DEEP_WATER_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.DEEP_WATER_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.DEEP_WATER_NORTH # mostly deep corner elif filled == ['se']: tile = GROUND.DEEP_WATER_NORTHWEST1 elif filled == ['ne']: tile = GROUND.DEEP_WATER_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.DEEP_WATER_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.DEEP_WATER_NORTHEAST1 # mostly shallow corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.DEEP_WATER_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.DEEP_WATER_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.DEEP_WATER_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.DEEP_WATER_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_db("COMMIT")
def create_random_island(map_db, island_id, id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. The coordinates of tiles will be 0 <= x < width and 0 <= y < height @param id_string: random island id string """ match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed, island_x, island_y = [ long(i) for i in match_obj.groups() ] assert creation_method == 2, 'The only supported island creation method is 2.' rand = random.Random(seed) map_set = set() # place this number of shapes for i in xrange(15 + width * height // 45): # place shape determined by shape_id on (x, y) add = True shape_id = rand.randint(2, 8) rect_chance = 29 if rand.randint(0, 4) == 0: rect_chance = 13 add = False shape = None if rand.randint(1, rect_chance) == 1: # use a rect if add: x = rand.randint(8, width - 7) y = rand.randint(8, height - 7) else: x = rand.randint(0, width) y = rand.randint(0, height) shape = Rect.init_from_topleft_and_size(x - 5, y - 5, rand.randint(2, 8), rand.randint(2, 8)) else: # use a circle such that the radius is determined by shape_id radius = shape_id if not add and rand.randint(0, 6) < 5: x = rand.randint(-radius * 3 // 2, width + radius * 3 // 2) y = rand.randint(-radius * 3 // 2, height + radius * 3 // 2) shape = Circle(Point(x, y), shape_id) elif width - radius - 4 >= radius + 3 and height - radius - 4 >= radius + 3: x = rand.randint(radius + 3, width - radius - 4) y = rand.randint(radius + 3, height - radius - 4) shape = Circle(Point(x, y), shape_id) if shape: for shape_coord in shape.tuple_iter(): if add: map_set.add(shape_coord) elif shape_coord in map_set: map_set.discard(shape_coord) # write values to db map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_set: map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *GROUND.DEFAULT_LAND) def fill_tiny_spaces(tile): """Fills 1 tile gulfs and straits with the specified tile @param tile: ground tile to fill with """ all_neighbours = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)] neighbours = [(-1, 0), (0, -1), (0, 1), (1, 0)] corners = [(-1, -1), (-1, 1)] knight_moves = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] bad_configs = set([ 0, 1 << 0, 1 << 1, 1 << 2, 1 << 3, (1 << 0) | (1 << 3), (1 << 1) | (1 << 2) ]) edge_set = copy.copy(map_set) reduce_edge_set = True while True: to_fill = set() to_ignore = set() for x, y in edge_set: # ignore the tiles with no empty neighbours if reduce_edge_set: is_edge = False for x_offset, y_offset in all_neighbours: if (x + x_offset, y + y_offset) not in map_set: is_edge = True break if not is_edge: to_ignore.add((x, y)) continue for x_offset, y_offset in neighbours: x2 = x + x_offset y2 = y + y_offset if (x2, y2) in map_set: continue # (x2, y2) is now a point just off the island neighbours_dirs = 0 for i in xrange(len(neighbours)): x3 = x2 + neighbours[i][0] y3 = y2 + neighbours[i][1] if (x3, y3) not in map_set: neighbours_dirs |= (1 << i) if neighbours_dirs in bad_configs: # part of a straight 1 tile gulf to_fill.add((x2, y2)) else: for x_offset, y_offset in corners: x3 = x2 + x_offset y3 = y2 + y_offset x4 = x2 - x_offset y4 = y2 - y_offset if (x3, y3) in map_set and (x4, y4) in map_set: # part of a diagonal 1 tile gulf to_fill.add((x2, y2)) break # block 1 tile straits for x_offset, y_offset in knight_moves: x2 = x + x_offset y2 = y + y_offset if (x2, y2) not in map_set: continue if abs(x_offset) == 1: y2 = y + y_offset // 2 if (x2, y2) in map_set or (x, y2) in map_set: continue else: x2 = x + x_offset // 2 if (x2, y2) in map_set or (x2, y) in map_set: continue to_fill.add((x2, y2)) # block diagonal 1 tile straits for x_offset, y_offset in corners: x2 = x + x_offset y2 = y + y_offset x3 = x + 2 * x_offset y3 = y + 2 * y_offset if (x2, y2) not in map_set and (x3, y3) in map_set: to_fill.add((x2, y2)) elif (x2, y2) in map_set and (x2, y) not in map_set and ( x, y2) not in map_set: to_fill.add((x2, y)) if to_fill: for x, y in to_fill: map_set.add((x, y)) map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) old_size = len(edge_set) edge_set = edge_set.difference(to_ignore).union(to_fill) reduce_edge_set = old_size - len(edge_set) > 50 else: break # possible movement directions all_moves = { 'sw': (-1, -1), 'w': (-1, 0), 'nw': (-1, 1), 's': (0, -1), 'n': (0, 1), 'se': (1, -1), 'e': (1, 0), 'ne': (1, 1) } def get_island_outline(): """ @return: the points just off the island as a dict """ result = set() for x, y in map_set: for offset_x, offset_y in all_moves.itervalues(): coords = (x + offset_x, y + offset_y) if coords not in map_set: result.add(coords) return result # add grass to sand tiles fill_tiny_spaces(GROUND.DEFAULT_LAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.SAND_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.SAND_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.SAND_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.SAND_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.SAND_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.SAND_NORTH # sandy corner elif filled == ['se']: tile = GROUND.SAND_NORTHWEST1 elif filled == ['ne']: tile = GROUND.SAND_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.SAND_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.SAND_NORTHEAST1 # grassy corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.SAND_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.SAND_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.SAND_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.SAND_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_set = map_set.union(outline) # add sand to shallow water tiles fill_tiny_spaces(GROUND.SAND) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.COAST_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.COAST_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.COAST_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.COAST_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.COAST_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.COAST_NORTH # mostly wet corner elif filled == ['se']: tile = GROUND.COAST_NORTHWEST1 elif filled == ['ne']: tile = GROUND.COAST_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.COAST_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.COAST_NORTHEAST1 # mostly dry corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.COAST_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.COAST_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.COAST_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.COAST_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_set = map_set.union(outline) # add shallow water to deep water tiles fill_tiny_spaces(GROUND.SHALLOW_WATER) outline = get_island_outline() for x, y in outline: filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_set: filled.append(dir) tile = None # straight coast or 1 tile U-shaped gulfs if filled == ['s', 'se', 'sw'] or filled == ['s']: tile = GROUND.DEEP_WATER_NORTH elif filled == ['e', 'ne', 'se'] or filled == ['e']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne', 'nw'] or filled == ['n']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'sw', 'w'] or filled == ['w']: tile = GROUND.DEEP_WATER_EAST # slight turn (looks best with straight coast) elif filled == ['e', 'se'] or filled == ['e', 'ne']: tile = GROUND.DEEP_WATER_WEST elif filled == ['n', 'ne'] or filled == ['n', 'nw']: tile = GROUND.DEEP_WATER_SOUTH elif filled == ['nw', 'w'] or filled == ['sw', 'w']: tile = GROUND.DEEP_WATER_EAST elif filled == ['s', 'sw'] or filled == ['s', 'se']: tile = GROUND.DEEP_WATER_NORTH # mostly deep corner elif filled == ['se']: tile = GROUND.DEEP_WATER_NORTHWEST1 elif filled == ['ne']: tile = GROUND.DEEP_WATER_SOUTHWEST1 elif filled == ['nw']: tile = GROUND.DEEP_WATER_SOUTHEAST1 elif filled == ['sw']: tile = GROUND.DEEP_WATER_NORTHEAST1 # mostly shallow corner elif 3 <= len(filled) <= 5: coast_set = set(filled) if 'e' in coast_set and 'se' in coast_set and 's' in coast_set: tile = GROUND.DEEP_WATER_NORTHEAST3 elif 's' in coast_set and 'sw' in coast_set and 'w' in coast_set: tile = GROUND.DEEP_WATER_NORTHWEST3 elif 'w' in coast_set and 'nw' in coast_set and 'n' in coast_set: tile = GROUND.DEEP_WATER_SOUTHWEST3 elif 'n' in coast_set and 'ne' in coast_set and 'e' in coast_set: tile = GROUND.DEEP_WATER_SOUTHEAST3 assert tile map_db("INSERT INTO ground VALUES(?, ?, ?, ?, ?, ?)", island_id, island_x + x, island_y + y, *tile) map_db("COMMIT")
def generate_random_map(seed, map_size, water_percent, max_island_size, preferred_island_size, island_size_deviation): """ Generates a random map. @param seed: random number generator seed @param map_size: maximum map side length @param water_percent: minimum percent of map covered with water @param max_island_size: maximum island side length @param preferred_island_size: mean of island side lengths @param island_size_deviation: deviation of island side lengths @return: filename of the SQLite database containing the map """ max_island_size = min(max_island_size, map_size) rand = random.Random(_simplify_seed(seed)) min_island_size = 20 # minimum chosen island side length (the real size my be smaller) min_island_separation = 3 + map_size // 100 # minimum distance between two islands max_island_side_coefficient = 4 # maximum value of island's max(side length) / min(side length) islands = [] estimated_land = 0 max_land_amount = map_size * map_size * (100 - water_percent) // 100 trial_number = 0 while trial_number < 100: trial_number += 1 width = max( min_island_size, min( max_island_size, int( round( rand.gauss(preferred_island_size, island_size_deviation))))) side_coefficient = min(1 + abs(rand.gauss(0, 0.2)), max_island_side_coefficient) side_coefficient = side_coefficient if rand.randint( 0, 1) else 1.0 / side_coefficient height = max( min_island_size, min(max_island_size, int(round(width * side_coefficient)))) size = width * height if estimated_land + size > max_land_amount: continue for _ in xrange(13): x = rand.randint(0, map_size - width) y = rand.randint(0, map_size - height) rect = Rect.init_from_topleft_and_size(x, y, width, height) blocked = False for existing_island in islands: if existing_island.distance(rect) < min_island_separation: blocked = True break if not blocked: islands.append(rect) estimated_land += size trial_number = 0 break # move some of the islands to stretch the map to the right size if len(islands) > 1: min_top = min(rect.top for rect in islands) rect = rand.choice([rect for rect in islands if rect.top == min_top]) islands[islands.index(rect)] = Rect.init_from_borders( rect.left, rect.top - min_top, rect.right, rect.bottom - min_top) max_bottom = max(rect.bottom for rect in islands) rect = rand.choice( [rect for rect in islands if rect.bottom == max_bottom]) shift = map_size - max_bottom - 1 islands[islands.index(rect)] = Rect.init_from_borders( rect.left, rect.top + shift, rect.right, rect.bottom + shift) min_left = min(rect.left for rect in islands) rect = rand.choice([rect for rect in islands if rect.left == min_left]) islands[islands.index(rect)] = Rect.init_from_borders( rect.left - min_left, rect.top, rect.right - min_left, rect.bottom) max_right = max(rect.right for rect in islands) rect = rand.choice( [rect for rect in islands if rect.right == max_right]) shift = map_size - max_right - 1 islands[islands.index(rect)] = Rect.init_from_borders( rect.left + shift, rect.top, rect.right + shift, rect.bottom) island_strings = [] for rect in islands: # The bounds must be platform independent to make sure the same maps are generated on all platforms. island_seed = rand.randint(-2147483648, 2147483647) island_params = { 'creation_method': 2, 'seed': island_seed, 'width': rect.width, 'height': rect.height, 'island_x': rect.left, 'island_y': rect.top } island_string = string.Template( _random_island_id_template).safe_substitute(island_params) island_strings.append(island_string) return island_strings
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 _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() # 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 purpose == BUILDING_PURPOSE.NONE: area_label[coords] = None areas = 0 for coords in collector_area: 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) options = [] for (x, y), area_number in area_label.iteritems(): builder = self.production_builder.make_builder( BUILDINGS.STORAGE, x, y, False) if not builder: continue coords_set = set( builder.position.get_radius_coordinates( Entities.buildings[BUILDINGS.STORAGE].radius)) useful_area = len( coords_set_by_area[area_number].intersection(coords_set)) if not useful_area: continue alignment = 1 for tile in self.production_builder.iter_neighbour_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 = 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.production_builder.extend_settlement_with_tent( Rect.init_from_topleft_and_size_tuples( best_coords, tent_size)) return BUILD_RESULT.IMPOSSIBLE