McRogueFace/docs/cookbook/procgen/33_advanced_caves.py
2026-01-13 19:42:37 -05:00

163 lines
5.9 KiB
Python

"""Advanced: Natural Cave System
Combines: Noise (cave formation) + Threshold (open areas) + Kernel (smoothing) + BSP (structured areas)
Creates organic cave networks with some structured rooms.
"""
import mcrfpy
from mcrfpy import automation
GRID_WIDTH, GRID_HEIGHT = 64, 48
CELL_SIZE = 16
def run_demo(runtime):
# Step 1: Generate cave base using turbulent noise
noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=42)
cave_noise = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
cave_noise.add_noise(noise, world_size=(10, 8), mode='turbulence', octaves=4)
cave_noise.normalize(0.0, 1.0)
# Step 2: Create cave mask via threshold
# Values > 0.45 become open cave, rest is rock
cave_mask = cave_noise.threshold_binary((0.4, 1.0), 1.0)
# Step 3: Apply smoothing kernel to remove isolated pixels
smooth_kernel = {
(-1, -1): 1, (0, -1): 2, (1, -1): 1,
(-1, 0): 2, (0, 0): 4, (1, 0): 2,
(-1, 1): 1, (0, 1): 2, (1, 1): 1,
}
cave_mask.kernel_transform(smooth_kernel)
cave_mask.normalize(0.0, 1.0)
# Re-threshold after smoothing
cave_mask = cave_mask.threshold_binary((0.5, 1.0), 1.0)
# Step 4: Add some structured rooms using BSP in one corner
# This represents ancient ruins within the caves
bsp = mcrfpy.BSP(pos=(GRID_WIDTH - 22, GRID_HEIGHT - 18), size=(18, 14))
bsp.split_recursive(depth=2, min_size=(6, 5), seed=42)
ruins_hmap = bsp.to_heightmap(
size=(GRID_WIDTH, GRID_HEIGHT),
select='leaves',
shrink=1,
value=1.0
)
# Step 5: Combine caves and ruins
combined = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
combined.copy_from(cave_mask)
combined.max(ruins_hmap)
# Step 6: Add connecting tunnels from ruins to main cave
# Find a cave entrance point
tunnel_points = []
for y in range(GRID_HEIGHT - 18, GRID_HEIGHT - 10):
for x in range(GRID_WIDTH - 25, GRID_WIDTH - 20):
if cave_mask.get((x, y)) > 0.5:
tunnel_points.append((x, y))
break
if tunnel_points:
break
if tunnel_points:
tx, ty = tunnel_points[0]
# Carve tunnel to ruins entrance
combined.dig_bezier(
points=((tx, ty), (tx + 3, ty), (GRID_WIDTH - 22, ty + 2), (GRID_WIDTH - 20, GRID_HEIGHT - 15)),
start_radius=1.5, end_radius=1.5,
start_height=1.0, end_height=1.0
)
# Step 7: Add large cavern (central chamber)
combined.add_hill((GRID_WIDTH // 3, GRID_HEIGHT // 2), 8, 0.6)
# Step 8: Create water pools in low noise areas
water_noise = mcrfpy.NoiseSource(dimensions=2, algorithm='perlin', seed=99)
water_map = mcrfpy.HeightMap((GRID_WIDTH, GRID_HEIGHT), fill=0.0)
water_map.add_noise(water_noise, world_size=(15, 12), mode='fbm', octaves=3)
water_map.normalize(0.0, 1.0)
# Step 9: Render
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
cave_val = combined.get((x, y))
water_val = water_map.get((x, y))
original_noise = cave_noise.get((x, y))
# Check if in ruins area
in_ruins = (x >= GRID_WIDTH - 22 and x < GRID_WIDTH - 4 and
y >= GRID_HEIGHT - 18 and y < GRID_HEIGHT - 4)
if cave_val < 0.3:
# Solid rock
base = 30 + int(original_noise * 25)
color_layer.set(((x, y)), mcrfpy.Color(base + 10, base + 5, base + 15))
elif cave_val < 0.5:
# Cave wall edge
color_layer.set(((x, y)), mcrfpy.Color(45, 40, 50))
else:
# Open cave floor
if water_val > 0.7 and not in_ruins:
# Water pool
t = (water_val - 0.7) / 0.3
color_layer.set(((x, y)), mcrfpy.Color(
int(30 + t * 20), int(50 + t * 30), int(100 + t * 50)
))
elif in_ruins and ruins_hmap.get((x, y)) > 0.5:
# Ruins floor (worked stone)
base = 85 + ((x + y) % 3) * 5
color_layer.set(((x, y)), mcrfpy.Color(base + 10, base + 5, base))
else:
# Natural cave floor
base = 55 + int(original_noise * 20)
var = ((x * 3 + y * 5) % 8)
color_layer.set(((x, y)), mcrfpy.Color(base + var, base - 5 + var, base - 8))
# Glowing fungi spots
fungi_noise = mcrfpy.NoiseSource(dimensions=2, algorithm='simplex', seed=777)
for y in range(GRID_HEIGHT):
for x in range(GRID_WIDTH):
if combined.get((x, y)) > 0.5: # Only in open areas
fungi_val = fungi_noise.get((x * 0.5, y * 0.5))
if fungi_val > 0.8:
color_layer.set(((x, y)), mcrfpy.Color(80, 180, 120))
# Border
for x in range(GRID_WIDTH):
color_layer.set(((x, 0)), mcrfpy.Color(20, 18, 25))
color_layer.set(((x, GRID_HEIGHT - 1)), mcrfpy.Color(20, 18, 25))
for y in range(GRID_HEIGHT):
color_layer.set(((0, y)), mcrfpy.Color(20, 18, 25))
color_layer.set(((GRID_WIDTH - 1, y)), mcrfpy.Color(20, 18, 25))
# Title
title = mcrfpy.Caption(text="Cave System: Noise + Threshold + Kernel + BSP Ruins", pos=(10, 10))
title.fill_color = mcrfpy.Color(255, 255, 255)
title.outline = 1
title.outline_color = mcrfpy.Color(0, 0, 0)
scene.children.append(title)
# Setup
scene = mcrfpy.Scene("caves")
grid = mcrfpy.Grid(
grid_size=(GRID_WIDTH, GRID_HEIGHT),
pos=(0, 0),
size=(GRID_WIDTH * CELL_SIZE, GRID_HEIGHT * CELL_SIZE),
layers={}
)
grid.fill_color = mcrfpy.Color(0, 0, 0)
color_layer = grid.add_layer("color", z_index=-1)
scene.children.append(grid)
scene.activate()
# Run the demo
run_demo(0)
# Take screenshot
automation.screenshot("procgen_33_advanced_caves.png")
print("Screenshot saved: procgen_33_advanced_caves.png")