Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
	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)
Exemplo n.º 3
0
	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)
Exemplo n.º 4
0
	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)
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
	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
Exemplo n.º 7
0
	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
Exemplo n.º 8
0
	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)
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
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
Exemplo n.º 12
0
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
Exemplo n.º 13
0
    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)
Exemplo n.º 15
0
    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)
Exemplo n.º 16
0
	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)
Exemplo n.º 17
0
	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)
Exemplo n.º 18
0
	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
Exemplo n.º 19
0
	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)
Exemplo n.º 20
0
	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)
Exemplo n.º 21
0
	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
Exemplo n.º 22
0
	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)
Exemplo n.º 23
0
	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)
Exemplo n.º 24
0
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
Exemplo n.º 27
0
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
Exemplo n.º 28
0
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")
Exemplo n.º 29
0
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")
Exemplo n.º 30
0
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
Exemplo n.º 31
0
    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