def generate(self): self.map_canvas.clear(Floor) room = Room(self.region) room.draw_to_canvas(self.map_canvas) noise = discrete_perlin_noise_factory(*self.region.size, resolution=5, octaves=4) for point in self.region.iter_points(): # TODO would greatly prefer some architecture types that just have # a 'decay' property affecting their rendering, but that would # require rendering to be per-entity, and either a method or # something that could be updated on the fly if self.map_canvas._arch_grid[point] is Wall: n = noise(*point) if n < 0.7: arch = e.Ruin(Breakable(n / 0.7)) else: arch = e.Wall self.map_canvas.set_architecture(point, arch) elif self.map_canvas._arch_grid[point] is Floor: n = noise(*point) if n < 0.5: arch = e.Rubble(Breakable(n / 0.5)) else: arch = e.Floor self.map_canvas.set_architecture(point, arch)
def _generate_river(self, noise): # TODO seriously starting to feel like i need a Feature type for these # things? like, passing `noise` around is a really weird way to go # about this. what would the state even look like though? ''' # TODO i think this needs another flooding algorithm, which probably # means it needs to be a lot simpler and faster... noise_factory = discrete_perlin_noise_factory( *self.region.size, resolution=2, octaves=1) noise = { point: abs(noise_factory(*point) - 0.5) * 2 for point in self.region.iter_points() } for point, n in noise.items(): if n < 0.2: self.map_canvas.set_architecture(point, e.Water) return ''' # Build some Blob internals representing the two halves of the river. left_side = {} right_side = {} river = {} center_factory = discrete_perlin_noise_factory( self.region.height, resolution=3) width_factory = discrete_perlin_noise_factory( self.region.height, resolution=6, octaves=2) center = random_normal_int( self.region.center().x, self.region.width / 4 / 3) for y in self.region.range_height(): center += (center_factory(y) - 0.5) * 3 width = width_factory(y) * 2 + 5 x0 = int(center - width / 2) x1 = int(x0 + width + 0.5) for x in range(x0, x1 + 1): self.map_canvas.set_architecture(Point(x, y), e.Water) left_side[y] = (Span(self.region.left, x0 - 1),) right_side[y] = (Span(x1 + 1, self.region.right),) river[y] = (Span(x0, x1),) return Blob(left_side), Blob(river), Blob(right_side)
def generate(self): from flax.noise import discrete_perlin_noise_factory noise = discrete_perlin_noise_factory(*self.region.size, resolution=4, octaves=2) for point in self.region.iter_points(): n = noise(*point) if n < 0.2: arch = Floor elif n < 0.4: arch = Dirt elif n < 0.6: arch = CutGrass elif n < 0.8: arch = Grass else: arch = Tree self.map_canvas.arch_grid[point] = arch
def generate(self): # TODO not guaranteed that all the walkable spaces are attached noise_factory = discrete_perlin_noise_factory(*self.region.size, resolution=5, octaves=1) noise = {point: noise_factory(*point) for point in self.region.iter_points()} low_points = set() for point, n in noise.items(): if n < 0.3: low_points.add(point) arch = CutGrass elif n < 0.6: arch = Grass else: arch = Tree self.map_canvas.set_architecture(point, arch) local_minima = set() for point in sorted(low_points, key=lambda pt: noise[pt]): n = noise[point] if any(noise[npt] < n for npt in point.neighbors if npt in low_points): continue local_minima.add(point) for x in self.region.range_width(): for y in (self.region.top, self.region.bottom): point = Point(x, y) n = noise[point] if n < noise.get(Point(x - 1, y), 1) and n < noise.get(Point(x + 1, y), 1): local_minima.add(point) for y in self.region.range_height(): for x in (self.region.left, self.region.right): point = Point(x, y) n = noise[point] if n < noise.get(Point(x, y - 1), 1) and n < noise.get(Point(x, y + 1), 1): local_minima.add(point) for point in local_minima: self.map_canvas.set_architecture(point, e.Dirt) # Flood the forest. floodzones = dict(zip(local_minima, range(len(local_minima)))) zone_map = {z: z for z in floodzones.values()} pending = defaultdict(set) paths = defaultdict(dict) for point, zone in floodzones.items(): for neighbor in point.neighbors: if neighbor not in noise: continue pending[neighbor].add(zone) if zone not in paths[neighbor] or noise[paths[neighbor][zone]] > noise[point]: paths[neighbor][zone] = point while pending: point = min(pending, key=noise.__getitem__) zones = pending.pop(point) zones = {zone_map[z] for z in zones} if len(zones) > 1: # Gasp! A connection! self.map_canvas.set_architecture(point, e.Dirt) for zone, pt in paths[point].items(): if zone not in zones: continue # TODO it seems this can cause infinite loops, # exacerbated by having more points while pt: self.map_canvas.set_architecture(pt, e.Dirt) # TODO ugly. should never have multiple branches # though. maybe? pt = paths[pt].get(zone) canon_zone = min(zones) zones.remove(canon_zone) for from_zone, to_zone in zone_map.items(): if from_zone in zones or to_zone in zones: zone_map[from_zone] = canon_zone # UGH need to rewrite paths in its entirety for pt, zonepoints in paths.items(): new_zonepoints = {} for orig_zone, pt2 in zonepoints.items(): new_zone = zone_map[orig_zone] if new_zone not in new_zonepoints or noise[new_zonepoints[new_zone]] > noise[pt2]: new_zonepoints[new_zone] = pt2 paths[pt] = new_zonepoints else: canon_zone, = zones floodzones[point] = canon_zone for neighbor in point.neighbors: if neighbor not in noise: continue if neighbor in floodzones: continue pending[neighbor].add(canon_zone) if canon_zone not in paths[neighbor] or noise[paths[neighbor][canon_zone]] > noise[point]: paths[neighbor][canon_zone] = point
def generate(self): # This noise is interpreted roughly as the inverse of "frequently # travelled" -- low values are walked often (and are thus short grass), # high values are left alone (and thus are trees). noise_factory = discrete_perlin_noise_factory( *self.region.size, resolution=6) noise = { point: noise_factory(*point) for point in self.region.iter_points() } local_minima = set() for point, n in noise.items(): # We want to ensure that each "walkable region" is connected. # First step is to collect all local minima -- any walkable tile is # guaranteed to be conneted to one. if all(noise[npt] >= n for npt in point.neighbors if npt in noise): local_minima.add(point) if n < 0.3: arch = CutGrass elif n < 0.6: arch = Grass else: arch = Tree self.map_canvas.set_architecture(point, arch) left_bank, river_blob, right_bank = self._generate_river(noise) # Decide where bridges should go. They can only cross where there's # walkable space on both sides, so find all such areas. # TODO maybe a nicer api for testing walkability here # TODO this doesn't detect a walkable area on one side that has no # walkable area on the other side, and tbh i'm not sure what to do in # such a case anyway. could forcibly punch a path through the trees, i # suppose? that's what i'll have to do anyway, right? # TODO this will break if i ever add a loop in the river, but tbh i # have no idea how to draw bridges in that case new_block = True start = None end = None blocks = [] for y, (span,) in river_blob.spans.items(): if self.map_canvas._arch_grid[Point(span.start - 1, y)] is not Tree and \ self.map_canvas._arch_grid[Point(span.end + 1, y)] is not Tree: if new_block: start = y end = y new_block = False else: end = y else: if not new_block: blocks.append((start, end)) new_block = True if not new_block: blocks.append((start, end)) for start, end in blocks: y = random_normal_range(start, end) span = river_blob.spans[y][0] local_minima.add(Point(span.start - 1, y)) local_minima.add(Point(span.end + 1, y)) for x in span: self.map_canvas.set_architecture(Point(x, y), e.Bridge) # Consider all local minima along the edges, as well. for x in self.region.range_width(): for y in (self.region.top, self.region.bottom): point = Point(x, y) n = noise[point] if (n < noise.get(Point(x - 1, y), 1) and n < noise.get(Point(x + 1, y), 1)): local_minima.add(point) for y in self.region.range_height(): for x in (self.region.left, self.region.right): point = Point(x, y) n = noise[point] if (n < noise.get(Point(x, y - 1), 1) and n < noise.get(Point(x, y + 1), 1)): local_minima.add(point) for point in local_minima: if point not in river_blob: self.map_canvas.set_architecture(point, e.Dirt) for blob in (left_bank, right_bank): paths = self.flood_valleys(blob, local_minima, noise) for path_point in paths: self.map_canvas.set_architecture(path_point, e.Dirt) # Whoops time for another step: generating a surrounding cave wall. for edge in Direction.orthogonal: width = self.region.edge_length(edge) wall_noise = discrete_perlin_noise_factory(width, resolution=6) for n in self.region.edge_span(edge): offset = int(wall_noise(n) * 4 + 1) for m in range(offset): point = self.region.edge_point(edge, n, m) self.map_canvas.set_architecture(point, e.CaveWall)