Now that we can have as many room shapes as we can imagine, let’s imagine some. The results may surprise you. They surprised me, in a good way.

At Friday Geeks Night Out last Tuesday, it was generally agreed that my original room shape, which grows randomly by accretion, would be better if it had more extrusions and incursions, making it a bit less smooth i appearance. I’ve been thinking about different shapes and have some ideas, still created randomly, including:

  1. Adjusting the weights of the four directions we might build in should let us create rooms that tend to be longer in one direction than in the other. Perhaps even rooms that look kind of diagonal would appear.

  2. Making the cell most recently used have a higher chance of being selected again might make for interesting extrusions.

  3. We can clearly build rectangular rooms quite readily, and should probably provide the capability for our right [angle] minded associates.

  4. A room built of horizontal or vertical stripes, or both, might be interesting. What if the lengths varied substantially but started short, got long, then short again?

I expect that looking at these will give us ideas about other possibilities.

Mostly Experimental

The whole point of these notions is to build rooms that look good. As such, there are probably no substantive tests to be written for them, although we may encounter surprises that call for tests, and there may be associated tests as we work out ways to create the random values that will behave as we like. Mostly, though, we’ll be coding and looking at the resulting map.

Research

A single search for “python weighted random choice” tells me about random.choices, which takes a list of items to be chosen randomly, and a list of weights. Let’s gin up an experimental builder to see what we get. The tricky bit will be to come up with a basic scheme that lets us influence the choices. Let’s try a similar scheme to the basic cave builder but with a high probability of building off the cell most recently found.

I’ll just snag the basic cave code and put it into a new class, then hammer it until it submits.

Results

I’ll shows you the code in a moment but did you ever have something work so well that you felt you could throw away all your alternative ideas? I just did.

The basic idea behind the pictures that follow is that the standard random cave-making code, which selects a random cell from which to grow, has a high probability of using the most recently used cell, basically idea #2 above.

Here are the results:

50% chance of using previous growth cell

fifty percent chance of using previous growth cell


70% chance of using previous growth cell

seventy percent chance of using previous growth cell


90% chance of using previous growth cell

ninety percent chance of using previous growth cell


Use previous growth cell if possible

use previous growth cell if possible


Frankly, I find those rooms to be simply marvelous. If you’re in the market for random cave-style rooms, it seems to me that you can get just what you want by tuning the chances of using the preceding cell for the next extension.

Sometimes you do bite the bear. Here’s the code, just a few lines long. This is just a patch: we’ll make the code more nearly right later:

class ExperimentalCellCollector:
    def __init__(self):
        self.cells:list[Cell] = []
        self.growth_candidates:list[Cell] = []

    def build(self, number_of_cells: int, origin: Cell, name =''):
        self._build_cave(number_of_cells, origin)
        return self.cells

    def _build_cave(self, number_of_cells, origin):
        new_cell: Cell = origin
        for _ in range(number_of_cells):
            self.take(new_cell)
            self.growth_candidates.append(new_cell)
            new_cell = self.find_adjacent_cell(new_cell)
            if new_cell is None:
                return

    def take(self, new_cell: Cell):
        self.cells.append(new_cell)
        new_cell.room = self

    def find_adjacent_cell(self, previous):
        # changes start here
        if random.random() < 0.5: # tune this value
            available = previous.available_neighbors
            if available:
                return choice(available)
        # changes end here
        for cell in sample(self.growth_candidates,
                           len(self.growth_candidates)):
            available = cell.available_neighbors
            if available:
                return choice(available)
            else:
                self.growth_candidates.remove(cell)
        return None

That’s all there was to it. Roll the dice, and use the previous cell if it has any usable neighbors.

My tentative but likely plan is to provide an additional (optional?) parameter to the CaveCellCollector’s build. We might also explore using keyword parameters for the build, which should allow us a chance to consolidate some code in RoomMaker, if we think it makes sense.

Summary

These results are so fine that I’m going to stop. Got a late start, wrote half a dozen lines of code, spent most of a pleasant hour trying random values and enjoying maps.

Certainly the new code should just be integrated into the existing cave code, and I do think that we should look into keyword parameters for the various caves we can build. We’ll surely add rectangles, but I’m not sure I want to do much more than that: what we have is fine. Might be time to figure out the next steps for this little exercise.

See you next time!