Example #1
0
def test_blob_math_contain():
    # These rectangles look like this:
    # xxxxx
    # x###x
    # x###x
    # x###x
    # xxxxx
    rect1 = Rectangle(origin=Point(0, 0), size=Size(5, 5))
    rect2 = Rectangle(origin=Point(1, 1), size=Size(3, 3))
    blob1 = Blob.from_rectangle(rect1)
    blob2 = Blob.from_rectangle(rect2)

    union_blob = blob1 + blob2
    assert union_blob.area == blob1.area
    assert union_blob.height == blob1.height

    left_blob = blob1 - blob2
    assert left_blob.area == 16
    assert left_blob.height == 5
    assert left_blob.spans == {
        0: (Span(0, 4), ),
        1: (Span(0, 0), Span(4, 4)),
        2: (Span(0, 0), Span(4, 4)),
        3: (Span(0, 0), Span(4, 4)),
        4: (Span(0, 4), ),
    }

    right_blob = blob2 - blob1
    assert right_blob.area == 0
    assert right_blob.height == 0
    assert right_blob.spans == {}
Example #2
0
def test_blob_math_overlap():
    # These rectangles look like this:
    # xxx
    # x##x
    # x##x
    #  xxx
    rect1 = Rectangle(origin=Point(0, 0), size=Size(3, 3))
    rect2 = Rectangle(origin=Point(1, 1), size=Size(3, 3))
    blob1 = Blob.from_rectangle(rect1)
    blob2 = Blob.from_rectangle(rect2)

    union_blob = blob1 + blob2
    assert union_blob.area == 14

    left_blob = blob1 - blob2
    assert left_blob.area == 5
    assert left_blob.height == 3
    assert left_blob.spans == {
        0: (Span(0, 2), ),
        1: (Span(0, 0), ),
        2: (Span(0, 0), ),
    }

    right_blob = blob2 - blob1
    assert right_blob.area == 5
    assert right_blob.height == 3
    assert right_blob.spans == {
        1: (Span(3, 3), ),
        2: (Span(3, 3), ),
        3: (Span(1, 3), ),
    }
Example #3
0
def test_blob_math_disjoint():
    # These rectangles look like this:
    # xxx
    # xxx
    # xxx   xxx
    #       xxx
    #       xxx
    rect1 = Rectangle(origin=Point(0, 0), size=Size(3, 3))
    rect2 = Rectangle(origin=Point(6, 2), size=Size(3, 3))
    blob1 = Blob.from_rectangle(rect1)
    blob2 = Blob.from_rectangle(rect2)

    union_blob = blob1 + blob2
    assert union_blob.area == blob1.area + blob2.area
    assert union_blob.area == rect1.area + rect2.area
    assert union_blob.height == 5

    left_blob = blob1 - blob2
    from pprint import pprint
    pprint(blob1.spans)
    pprint(blob2.spans)
    pprint(left_blob.spans)
    assert left_blob.area == blob1.area
    assert left_blob == blob1

    right_blob = blob2 - blob1
    from pprint import pprint
    pprint(blob1.spans)
    pprint(blob2.spans)
    pprint(right_blob.spans)
    assert right_blob.area == blob2.area
    assert right_blob == blob2
Example #4
0
File: map.py Project: jamesnvc/flax
    def __init__(self, size):
        self.rect = size.to_rect(Point.origin())

        self.entity_positions = WeakKeyDictionary()

        self.tiles = {
            point: Tile(self, point)
            for point in self.rect.iter_points()
        }
Example #5
0
    def __init__(self, size):
        self.rect = size.to_rect(Point.origin())

        # TODO i think using types instead of entities /most of the time/ is
        # more trouble than it's worth
        self._arch_grid = {point: CaveWall for point in self.rect.iter_points()}
        self._item_grid = {point: [] for point in self.rect.iter_points()}
        self._creature_grid = {point: None for point in self.rect.iter_points()}

        self.floor_spaces = set()
Example #6
0
File: map.py Project: twik/flax
    def __init__(self, size):
        self.rect = size.to_rect(Point.origin())

        self.entity_positions = WeakKeyDictionary()
        self.portal_index = {}

        self.tiles = {
            point: Tile(self, point)
            for point in self.rect.iter_points()
        }
Example #7
0
File: fractor.py Project: twik/flax
    def __init__(self, size):
        self.rect = size.to_rect(Point.origin())

        # TODO i think using types instead of entities /most of the time/ is
        # more trouble than it's worth
        self._arch_grid = {
            point: CaveWall for point in self.rect.iter_points()}
        self._item_grid = {point: [] for point in self.rect.iter_points()}
        self._creature_grid = {
            point: None for point in self.rect.iter_points()}

        self.floor_spaces = set()
Example #8
0
File: fractor.py Project: twik/flax
    def randomize(cls, region, *, minimum_size=Size(5, 5)):
        """Place a room randomly in a region, randomizing its size and position.
        """
        # TODO need to guarantee the region is big enough
        size = Size(
            random_normal_range(minimum_size.width, region.width),
            random_normal_range(minimum_size.height, region.height),
        )
        left = region.left + random.randint(0, region.width - size.width)
        top = region.top + random.randint(0, region.height - size.height)
        rect = Rectangle(Point(left, top), size)

        return cls(rect)
Example #9
0
File: fractor.py Project: twik/flax
    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)
Example #10
0
    def __init__(self, size):
        self.rect = size.to_rect(Point.origin())

        self.arch_grid = {point: CaveWall for point in self.rect.iter_points()}
        self.item_grid = {point: [] for point in self.rect.iter_points()}
        self.creature_grid = {point: None for point in self.rect.iter_points()}
Example #11
0
File: fractor.py Project: twik/flax
    def generate(self):
        self.map_canvas.clear(CaveWall)

        # First create a bunch of hallways and rooms.
        # For now, just carve a big area, run a hallway through the middle, and
        # divide either side into rooms.
        area = Room.randomize(self.region, minimum_size=self.region.size // 2)
        area.draw_to_canvas(self.map_canvas)

        center = area.rect.center()
        y0 = center.y - 2
        y1 = center.y + 2
        hallway = Rectangle(origin=Point(area.rect.left, center.y - 2), size=Size(area.rect.width, 5))
        Room(hallway).draw_to_canvas(self.map_canvas)

        top_space = area.rect.replace(bottom=hallway.top)
        bottom_space = area.rect.replace(top=hallway.bottom)

        rooms = []
        for orig_space in (top_space, bottom_space):
            space = orig_space
            # This includes walls!
            minimum_width = 7
            # Note that the rooms overlap where they touch, so we subtract one
            # from both the total width and the minimum width, in effect
            # ignoring all the walls on one side
            maximum_rooms = (space.width - 1) // (minimum_width - 1)
            # The maximum number of rooms that will fit also affects how much
            # wiggle room we're willing to have.  For example, if at most 3 rooms
            # will fit, then generating 2 rooms is also reasonable.  But if 10
            # rooms will fit, generating 2 rooms is a bit silly.  We'll arbitrarily
            # use 1/3 the maximum as the minimum.  (Plus 1, to avoid rounding down
            # to zero.)
            minimum_rooms = maximum_rooms // 6 + 1
            num_rooms = random_normal_range(minimum_rooms, maximum_rooms)

            # TODO normal distribution doesn't have good results here.  think
            # more about how people use rooms -- often many of similar size,
            # with some exceptions.  also different shapes, bathrooms or
            # closets nestled together, etc.
            while num_rooms > 1:
                # Now we want to divide a given amount of space into n chunks, where
                # the size of each chunk is normally-distributed.  I have no idea how
                # to do this in any strict mathematical sense, so instead we'll just
                # carve out one room at a time and hope for the best.
                min_width = minimum_width
                avg_width = (space.width - 1) // num_rooms + 1
                max_width = space.width - (minimum_width - 1) * (num_rooms - 1)
                room_width = random_normal_int(avg_width, min(max_width - avg_width, avg_width - min_width) // 3)

                room = space.replace(right=space.left + room_width - 1)
                rooms.append(room)
                space = space.replace(left=room.right)
                num_rooms -= 1

            rooms.append(space)

        for rect in rooms:
            Room(rect).draw_to_canvas(self.map_canvas)

        from flax.component import Lockable

        # Add some doors for funsies.
        locked_room = random.choice(rooms)
        for rect in rooms:
            x = random.randrange(rect.left + 1, rect.right - 1)
            if rect.top > hallway.top:
                side = Direction.down
            else:
                side = Direction.up
            point = rect.edge_point(side.opposite, x, 0)
            door = e.Door(Lockable(locked=rect is locked_room))
            self.map_canvas.set_architecture(point, door)

        self.hallway_area = Blob.from_rectangle(hallway)
        self.locked_area = Blob.from_rectangle(locked_room)
        self.rooms_area = reduce(operator.add, (Blob.from_rectangle(rect) for rect in rooms if rect is not locked_room))
Example #12
0
File: fractor.py Project: twik/flax
    def generate(self):
        self.map_canvas.clear(Floor)

        # So what I want here is to have a cave system with a room in the
        # middle, then decay the room.
        # Some constraints:
        # - the room must have a wall where the entrance could go, which faces
        # empty space
        # - a wall near the entrance must be destroyed
        # - the player must start in a part of the cave connected to the
        # destroyed entrance
        # - none of the decay applied to the room may block off any of its
        # interesting features

        # TODO it would be nice if i could really write all this without ever
        # having to hardcode a specific direction, so the logic could always be
        # rotated freely
        side = random.choice([Direction.left, Direction.right])

        # TODO assert region is big enough
        room_size = Size(
            random_normal_range(9, int(self.region.width * 0.4)),
            random_normal_range(9, int(self.region.height * 0.4)),
        )

        room_position = self.region.center() - room_size // 2
        room_position += Point(
            random_normal_int(0, self.region.width * 0.1),
            random_normal_int(0, self.region.height * 0.1),
        )

        room_rect = Rectangle(room_position, room_size)
        self.room_region = room_rect

        room = Room(room_rect)

        cave_area = (
            Blob.from_rectangle(self.region)
            - Blob.from_rectangle(room_rect)
        )
        self.cave_region = cave_area
        walls = [point for (point, _) in self.region.iter_border()]
        floors = []
        for point, edge in room_rect.iter_border():
            if edge is side or edge.adjacent_to(side):
                floors.append(point)
                floors.append(point + side)
        generate_caves(
            self.map_canvas, cave_area, CaveWall,
            force_walls=walls, force_floors=floors,
        )

        room.draw_to_canvas(self.map_canvas)

        # OK, now draw a gate in the middle of the side wall
        if side is Direction.left:
            x = room_rect.left
        else:
            x = room_rect.right
        mid_y = room_rect.top + room_rect.height // 2
        if room_rect.height % 2 == 1:
            min_y = mid_y - 1
            max_y = mid_y + 1
        else:
            min_y = mid_y - 2
            max_y = mid_y + 1
        for y in range(min_y, max_y + 1):
            self.map_canvas.set_architecture(Point(x, y), KadathGate)

        # Beat up the border of the room near the gate
        y = random.choice(
            tuple(range(room_rect.top, min_y))
            + tuple(range(max_y + 1, room_rect.bottom))
        )
        for dx in range(-2, 3):
            for dy in range(-2, 3):
                point = Point(x + dx, y + dy)
                # TODO i think what i may want is to have the cave be a
                # "Feature", where i can check whether it has already claimed a
                # tile, or draw it later, or whatever.
                if self.map_canvas._arch_grid[point] is not CaveWall:
                    distance = abs(dx) + abs(dy)
                    ruination = random_normal_range(0, 0.2) + distance * 0.2
                    self.map_canvas.set_architecture(
                        point, e.Rubble(Breakable(ruination)))

        # And apply some light ruination to the inside of the room
        border = list(room_rect.iter_border())
        # TODO don't do this infinitely; give up after x tries
        while True:
            point, edge = random.choice(border)
            if self.map_canvas._arch_grid[point + edge] is CaveWall:
                break
        self.map_canvas.set_architecture(point, CaveWall)
        self.map_canvas.set_architecture(point - edge, CaveWall)
        # TODO this would be neater if it were a slightly more random pattern
        for direction in (
                Direction.up, Direction.down, Direction.left, Direction.right):
            self.map_canvas.set_architecture(
                point - edge + direction, CaveWall)
Example #13
0
File: fractor.py Project: twik/flax
    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)
Example #14
0
def test_blob_create():
    rect = Rectangle(origin=Point(0, 0), size=Size(5, 5))
    blob = Blob.from_rectangle(rect)

    assert blob.area == rect.area
    assert blob.height == rect.height
Example #15
0
File: map.py Project: twik/flax
 def rows(self):
     for y in self.rect.range_height():
         yield (self.tiles[Point(x, y)] for x in self.rect.range_width())