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() screen_width_as_coords = (horizons.main.fife.engine_settings.getScreenWidth()/cell_dim.x, \ horizons.main.fife.engine_settings.getScreenHeight()/cell_dim.y) 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 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] - 1, cls.size[1] - 1) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1] - 1, cls.size[0] - 1) buildable = True tearset = [] return _BuildPosition(position, rotation, tearset, buildable)
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._get_rotated_coords( self._world_coord_to_minimap_coord(tup)) world_to_minimap = self._get_world_to_minimap_ratio() rect = Rect.init_from_topleft_and_size(minimap_point[0], minimap_point[1], \ int(round(1/world_to_minimap[0])), \ int(round(1/world_to_minimap[1]))) self._recalculate(rect)
def __init(self, deposit_class, mine_empty_msg_shown): self.__deposit_class = deposit_class self._mine_empty_msg_shown = mine_empty_msg_shown # setup loading area # TODO: for now we assume that a mine building is 5x5 with a 3x1 entry on 1 side # this needs to be generalised, possibly by defining the loading tiles in the db pos = self.position if self.rotation == 45: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x, pos.origin.y + 1, 0, 2) elif self.rotation == 135: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + 1, pos.origin.y + pos.height - 1, 2, 0) elif self.rotation == 225: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + pos.width - 1, pos.origin.y + 1, 0, 2) elif self.rotation == 315: self.loading_area = Rect.init_from_topleft_and_size( pos.origin.x + 1, pos.origin.y, 2, 0) else: assert False
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] - 1, cls.size[1] - 1) else: position = Rect.init_from_topleft_and_size(point.x, point.y, cls.size[1] - 1, cls.size[0] - 1) buildable = True tearset = [] try: 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) if check_settlement: cls._check_settlement(session, position, ship=ship, issuer=issuer) except _NotBuildableError: buildable = False return _BuildPosition(position, rotation, tearset, buildable)
def get_job(self): jobs = JobList(self, JobList.order_by.random) collectable_resources = self.get_needed_resources() # iterate over all possible providers and needed resources # and save possible job targets position_rect = Rect.init_from_topleft_and_size( self.position.x, self.position.y, 0, 0) reach = RadiusRect(position_rect, self.walking_range) for provider in self.home_island.get_providers_in_range(reach): if self.check_possible_job_target(provider): for res in collectable_resources: job = self.check_possible_job_target_for(provider, res) if job is not None: jobs.append(job) return self.get_best_possible_job(jobs)
def _timed_update(self): """Regular updates for domains we can't or don't want to keep track of.""" # update ship dots self.renderer.removeAll("minimap_ship") for ship in self.world.ship_map.itervalues(): coord = self._world_coord_to_minimap_coord( ship().position.to_tuple()) color = ship().owner.color.to_tuple() area_to_color = Rect.init_from_topleft_and_size( coord[0], coord[1], 2, 2) for tup in area_to_color.tuple_iter(): try: self.renderer.addPoint( "minimap_ship", self.renderernodes[self._get_rotated_coords(tup)], *color) except KeyError: # this happens in rare cases, when the ship is at the border of the map, # and since we color an area, that's bigger than a point, it can exceed the # minimap's dimensions. pass
def __init__(self, session, gui): super(IngameGui, self).__init__() self.session = session self.main_gui = gui self.widgets = {} self.tabwidgets = {} self.settlement = None self.resource_source = None self.resources_needed, self.resources_usable = {}, {} self._old_menu = None self.widgets = LazyWidgetsDict(self.styles, center_widgets=False) cityinfo = self.widgets['city_info'] cityinfo.child_finder = PychanChildFinder(cityinfo) cityinfo.position_technique = "center-10:top+5" self.logbook = LogBook() self.logbook.add_pause_request_listener( Callback(self.session.speed_pause)) self.logbook.add_unpause_request_listener( Callback(self.session.speed_unpause)) self.scenario_chooser = ScenarioChooser(self.session) # self.widgets['minimap'] is the guichan gui around the actual minimap, # which is saved in self.minimap minimap = self.widgets['minimap'] minimap.position_technique = "right-20:top+4" minimap.show() minimap_rect = Rect.init_from_topleft_and_size( minimap.position[0] + 77, 55, 120, 120) self.minimap = Minimap(minimap_rect, self.session, \ self.session.view.renderer['GenericRenderer']) minimap.mapEvents({ 'zoomIn': self.session.view.zoom_in, 'zoomOut': self.session.view.zoom_out, 'rotateRight': Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right), 'rotateLeft': Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left), 'speedUp': self.session.speed_up, 'speedDown': self.session.speed_down }) minimap_overlay = minimap.findChild(name='minimap_overlay_image') self.minimap.use_overlay_icon(minimap_overlay) self.widgets['menu_panel'].position_technique = "right+15:top+153" self.widgets['menu_panel'].show() self.widgets['menu_panel'].mapEvents({ 'destroy_tool': self.session.destroy_tool, 'build': self.show_build_menu, 'helpLink': self.main_gui.on_help, 'gameMenuButton': self.main_gui.show_pause, 'logbook': self.logbook.toggle_visibility }) self.widgets['tooltip'].hide() self.widgets['status'].child_finder = PychanChildFinder( self.widgets['status']) self.widgets['status_extra'].child_finder = PychanChildFinder( self.widgets['status_extra']) self.message_widget = MessageWidget(self.session, \ cityinfo.position[0] + cityinfo.size[0], 5) self.widgets['status_gold'].show() self.widgets['status_gold'].child_finder = PychanChildFinder( self.widgets['status_gold']) self.widgets['status_extra_gold'].child_finder = PychanChildFinder( self.widgets['status_extra_gold']) # map button names to build functions calls with the building id callbackWithArguments = pychan.tools.callbackWithArguments self.callbacks_build = {} for id, button_name, settler_level in horizons.main.db.get_building_id_buttonname_settlerlvl( ): if not settler_level in self.callbacks_build: self.callbacks_build[settler_level] = {} self.callbacks_build[settler_level][button_name] = Callback( self._build, id)
def create_random_island(id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. @param id_string: random island id string @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_dict = {} # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # place this number of shapes for i in xrange(int(float(width + height) / 2 * 1.5)): x = rand.randint(4, width - 4) y = rand.randint(4, height - 4) # place shape determined by shape_id on (x, y) if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) if rand.randint(1, 4) == 1: # use a rect if creation_method == 0: for shape_coord in Rect.init_from_topleft_and_size( x - 3, y - 3, 5, 5).tuple_iter(): map_dict[shape_coord] = 1 elif creation_method == 1: for shape_coord in Rect.init_from_topleft_and_size( x - 5, y - 5, 8, 8).tuple_iter(): map_dict[shape_coord] = 1 else: # use a circle, where radius is determined by shape_id for shape_coord in Circle(Point(x, y), shape_id).tuple_iter(): map_dict[shape_coord] = 1 # write values to db map_db = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # assign these characters, if a coastline is found in this offset offset_coastline = {'a': (0, -1), 'b': (1, 0), 'c': (0, 1), 'd': (-1, 0)} for x, y in map_dict.iterkeys(): # add a coastline tile for coastline, or default land else coastline = "" for offset_char in sorted(offset_coastline): if (x + offset_coastline[offset_char][0], y + offset_coastline[offset_char][1]) not in map_dict: coastline += offset_char if coastline: # TODO: use coastline tile depending on coastline map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, 49) else: map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, GROUND.DEFAULT_LAND) map_db("COMMIT") return map_db
def generate_map(seed=None): """Generates a whole map. @param seed: argument passed to random.seed @return filename to the sqlite db containing the new map""" rand = random.Random(seed) filename = tempfile.mkstemp()[1] shutil.copyfile(PATHS.SAVEGAME_TEMPLATE, filename) db = DbReader(filename) island_space = (35, 35) island_min_size = (25, 25) island_max_size = (28, 28) method = rand.randint(0, 1) # choose map creation method if method == 0: # generate up to 9 islands number_of_islands = 0 for i in Rect.init_from_topleft_and_size(0, 0, 2, 2): if rand.randint(0, 2) != 0: # 2/3 chance for an island here number_of_islands = number_of_islands + 1 x = int(i.x * island_space[0] * (rand.random() / 6 + 0.90)) y = int(i.y * island_space[1] * (rand.random() / 6 + 0.90)) island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 0, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0], island_max_size[0]), \ 'height': rand.randint(island_min_size[1], island_max_size[1])} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) # if there is 1 or 0 islands created, it places 1 large island in the centre if number_of_islands == 0: x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif number_of_islands == 1: db("DELETE FROM island") x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 1: # places 1 large island in the centre x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) return filename
def __init__(self, session, gui): super(IngameGui, self).__init__() self.session = session self.main_gui = gui self.widgets = {} self.tabwidgets = {} self.settlement = None self.resource_source = None self.resources_needed, self.resources_usable = {}, {} self._old_menu = None self.widgets = LazyWidgetsDict(self.styles, center_widgets=False) screenwidth = horizons.main.fife.engine_settings.getScreenWidth() cityinfo = self.widgets['city_info'] cityinfo.child_finder = PychanChildFinder(cityinfo) cityinfo.position = ( screenwidth/2 - cityinfo.size[0]/2 - 10, 5 ) self.logbook = LogBook(session) # self.widgets['minimap'] is the guichan gui around the actual minimap, # which is saved in self.minimap minimap = self.widgets['minimap'] minimap.position = (screenwidth - minimap.size[0] -20, 4) minimap.show() minimap_rect = Rect.init_from_topleft_and_size(minimap.position[0]+77, 55, 120, 120) self.minimap = Minimap(minimap_rect, self.session, \ self.session.view.renderer['GenericRenderer']) minimap.mapEvents({ 'zoomIn' : self.session.view.zoom_in, 'zoomOut' : self.session.view.zoom_out, 'rotateRight' : Callback.ChainedCallbacks(self.session.view.rotate_right, self.minimap.rotate_right), 'rotateLeft' : Callback.ChainedCallbacks(self.session.view.rotate_left, self.minimap.rotate_left), 'speedUp' : self.session.speed_up, 'speedDown' : self.session.speed_down }) minimap_overlay = minimap.findChild(name='minimap_overlay_image') self.minimap.use_overlay_icon(minimap_overlay) menupanel = self.widgets['menu_panel'] menupanel.position = (screenwidth - menupanel.size[0] +15, 149) menupanel.show() menupanel.mapEvents({ 'destroy_tool' : self.session.destroy_tool, 'build' : self.show_build_menu, 'helpLink' : self.main_gui.on_help, 'gameMenuButton' : self.main_gui.show_pause, 'logbook' : self.logbook.toggle_visibility }) self.widgets['tooltip'].hide() for w in ('status','status_extra','status_gold','status_extra_gold'): self.widgets[w].child_finder = PychanChildFinder(self.widgets[w]) self.widgets['status_gold'].show() self.message_widget = MessageWidget(self.session, \ cityinfo.position[0] + cityinfo.size[0], 5) self.resbar = ResBar(self.session, gui, self.widgets, self.resource_source) # map button names to build functions calls with the building id building_list = horizons.main.db.get_building_id_buttonname_settlerlvl() self.callbacks_build = {} for id,button_name,settler_level in building_list: if not settler_level in self.callbacks_build: self.callbacks_build[settler_level] = {} self.callbacks_build[settler_level][button_name] = Callback(self._build, id)
def create_random_island(id_string): """Creates a random island as sqlite db. It is rather primitive; it places shapes on the dict. @param id_string: random island id string @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_dict = {} # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # place this number of shapes for i in xrange(int(float(width + height) / 2 * 1.5)): x = rand.randint(8, width - 8) y = rand.randint(8, height - 8) # place shape determined by shape_id on (x, y) if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) if rand.randint(1, 4) == 1: # use a rect if creation_method == 0: for shape_coord in Rect.init_from_topleft_and_size( x - 3, y - 3, 5, 5).tuple_iter(): map_dict[shape_coord] = True elif creation_method == 1: for shape_coord in Rect.init_from_topleft_and_size( x - 5, y - 5, 8, 8).tuple_iter(): map_dict[shape_coord] = True else: # use a circle, where radius is determined by shape_id for shape_coord in Circle(Point(x, y), shape_id).tuple_iter(): map_dict[shape_coord] = True # write values to db map_db = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_dict.iterkeys(): map_db("INSERT INTO ground VALUES(?, ?, ?)", x, 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 """ 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) ]) while True: to_fill = {} for x, y in map_dict.iterkeys(): for x_offset, y_offset in neighbours: x2 = x + x_offset y2 = y + y_offset if (x2, y2) in map_dict: continue # (x2, y2) is now a point just off the island neighbours_dirs = 0 for i in range(len(neighbours)): x3 = x2 + neighbours[i][0] y3 = y2 + neighbours[i][1] if (x3, y3) not in map_dict: neighbours_dirs |= (1 << i) if neighbours_dirs in bad_configs: # part of a straight 1 tile gulf to_fill[(x2, y2)] = True 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_dict and (x4, y4) in map_dict: # part of a diagonal 1 tile gulf to_fill[(x2, y2)] = True 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_dict: continue if abs(x_offset) == 1: y2 = y + y_offset / 2 if (x2, y2) in map_dict or (x, y2) in map_dict: continue else: x2 = x + x_offset / 2 if (x2, y2) in map_dict or (x2, y) in map_dict: continue to_fill[(x2, y2)] = True if to_fill: for x, y in to_fill.iterkeys(): map_dict[(x, y)] = tile map_db("INSERT INTO ground VALUES(?, ?, ?)", x, y, tile) 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 = {} for x, y in map_dict.iterkeys(): for offset_x, offset_y in all_moves.itervalues(): coords = (x + offset_x, y + offset_y) if coords not in map_dict: result[coords] = True return result # add grass to sand tiles fill_tiny_spaces(GROUND.DEFAULT_LAND) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: 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(?, ?, ?)", x, y, tile) for coords, type in outline.iteritems(): map_dict[coords] = type # add sand to shallow water tiles fill_tiny_spaces(GROUND.SAND) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: 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(?, ?, ?)", x, y, tile) for coords, type in outline.iteritems(): map_dict[coords] = type # add shallow water to deep water tiles fill_tiny_spaces(GROUND.SHALLOW_WATER) outline = get_island_outline() for x, y in outline.iterkeys(): filled = [] for dir in sorted(all_moves): coords = (x + all_moves[dir][1], y + all_moves[dir][0]) if coords in map_dict: 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(?, ?, ?)", x, y, tile) map_db("COMMIT") return map_db
def create_random_island(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 @return: sqlite db reader containing island """ # NOTE: the tilesystem will be redone soon, so constants indicating grounds are temporary # here and will have to be changed anyways. match_obj = re.match(_random_island_id_regexp, id_string) assert match_obj creation_method, width, height, seed = [ long(i) for i in match_obj.groups() ] rand = random.Random(seed) map_set = set() # creation_method 0 - standard small island for the 3x3 grid # creation_method 1 - large island # creation_method 2 - a number of randomly sized and placed islands # place this number of shapes for i in xrange(15 + width * height / 45): # place shape determined by shape_id on (x, y) add = True rect_chance = 6 if creation_method == 0: shape_id = rand.randint(3, 5) elif creation_method == 1: shape_id = rand.randint(5, 8) elif creation_method == 2: 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) if creation_method == 0: shape = Rect.init_from_topleft_and_size(x - 3, y - 3, 5, 5) elif creation_method == 1: shape = Rect.init_from_topleft_and_size(x - 5, y - 5, 8, 8) elif creation_method == 2: shape = Rect.init_from_topleft_and_size( x - 5, y - 5, rand.randint(2, 8), rand.randint(2, 8)) 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, where 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 = DbReader(":memory:") map_db( "CREATE TABLE ground(x INTEGER NOT NULL, y INTEGER NOT NULL, ground_id INTEGER NOT NULL)" ) map_db( "CREATE TABLE island_properties(name TEXT PRIMARY KEY NOT NULL, value TEXT NOT NULL)" ) map_db("BEGIN TRANSACTION") # add grass tiles for x, y in map_set: map_db("INSERT INTO ground VALUES(?, ?, ?)", x, 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(?, ?, ?)", x, 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(?, ?, ?)", x, 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(?, ?, ?)", x, 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(?, ?, ?)", x, y, tile) map_db("COMMIT") return map_db
def generate_map(seed=None): """Generates a whole map. @param seed: argument passed to random.seed @return filename to the sqlite db containing the new map""" rand = random.Random(seed) filename = tempfile.mkstemp()[1] shutil.copyfile(PATHS.SAVEGAME_TEMPLATE, filename) db = DbReader(filename) island_space = (35, 35) island_min_size = (25, 25) island_max_size = (28, 28) method = min(2, rand.randint( 0, 9)) # choose map creation method with 80% chance for method 2 if method == 0: # generate up to 9 islands number_of_islands = 0 for i in Rect.init_from_topleft_and_size(0, 0, 2, 2): if rand.randint(0, 2) != 0: # 2/3 chance for an island here number_of_islands = number_of_islands + 1 x = int(i.x * island_space[0] * (rand.random() / 6 + 0.90)) y = int(i.y * island_space[1] * (rand.random() / 6 + 0.90)) island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 0, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0], island_max_size[0]), \ 'height': rand.randint(island_min_size[1], island_max_size[1])} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) # if there is 1 or 0 islands created, it places 1 large island in the centre if number_of_islands == 0: x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif number_of_islands == 1: db("DELETE FROM island") x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 1: # places 1 large island in the centre x = 20 y = 20 island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 1, 'seed': island_seed, \ 'width': rand.randint(island_min_size[0] * 2, island_max_size[0] * 2), \ 'height': rand.randint(island_min_size[1] * 2, island_max_size[1] * 2)} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) elif method == 2: # tries to fill at most land_coefficient * 100% of the map with land map_width = 140 map_height = 140 min_island_size = 20 max_island_size = 65 max_islands = 20 min_space = 2 land_coefficient = max(0.3, min(0.6, rand.gauss(0.45, 0.07))) islands = [] estimated_land = 0 max_land_amount = map_width * map_height * land_coefficient for i in xrange(max_islands): size_modifier = 1.1 - 0.2 * estimated_land / float(max_land_amount) width = rand.randint(min_island_size - 5, max_island_size) width = max( min_island_size, min(max_island_size, int(round(width * size_modifier)))) coef = max(0.25, min(4, rand.gauss(1, 0.2))) height = max(min_island_size, min(int(round(width * coef)), max_island_size)) size = width * height if estimated_land + size > max_land_amount: continue for j in xrange(7): # try to place the island 7 times x = rand.randint(0, map_width - width) y = rand.randint(0, map_height - height) rect = Rect.init_from_topleft_and_size(x, y, width, height) blocked = False for existing_island in islands: if rect.distance(existing_island) < min_space: blocked = True break if blocked: continue island_seed = rand.randint(-sys.maxint, sys.maxint) island_params = {'creation_method': 2, 'seed': island_seed, \ 'width': width, 'height': height} island_string = string.Template( _random_island_id_template).safe_substitute(island_params) db("INSERT INTO island (x, y, file) VALUES(?, ?, ?)", x, y, island_string) islands.append(rect) estimated_land += size break return filename