tests/demo/: - cookbook_showcase.py: Interactive demo of cookbook recipes - tutorial_showcase.py: Visual walkthrough of tutorial content - tutorial_screenshots.py: Automated screenshot generation - new_features_showcase.py: Demo of modern API features - procgen_showcase.py: Procedural generation examples - simple_showcase.py: Minimal working examples Created during docs modernization to verify cookbook examples work. 🤖 Generated with Claude Code (https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
286 lines
8.8 KiB
Python
286 lines
8.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate screenshots for procgen cookbook recipes.
|
|
|
|
Uses Frame-based visualization since Grid cell colors use ColorLayer API.
|
|
"""
|
|
import mcrfpy
|
|
from mcrfpy import automation
|
|
import sys
|
|
|
|
OUTPUT_DIR = "/opt/goblincorps/repos/mcrogueface.github.io/images/cookbook"
|
|
|
|
# Simple PRNG
|
|
_seed = 42
|
|
|
|
def random():
|
|
global _seed
|
|
_seed = (_seed * 1103515245 + 12345) & 0x7fffffff
|
|
return (_seed >> 16) / 32768.0
|
|
|
|
def seed(n):
|
|
global _seed
|
|
_seed = n
|
|
|
|
def choice(lst):
|
|
return lst[int(random() * len(lst))]
|
|
|
|
|
|
def screenshot_cellular_caves():
|
|
"""Generate cellular automata caves visualization."""
|
|
print("Generating cellular automata caves...")
|
|
|
|
scene = mcrfpy.Scene("caves")
|
|
scene.activate()
|
|
mcrfpy.step(0.1)
|
|
|
|
# Background
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(640, 500))
|
|
bg.fill_color = mcrfpy.Color(15, 15, 25)
|
|
scene.children.append(bg)
|
|
|
|
width, height = 50, 35
|
|
cell_size = 12
|
|
seed(42)
|
|
|
|
# Store cell data
|
|
cells = [[False for _ in range(width)] for _ in range(height)]
|
|
|
|
# Step 1: Random noise (45% walls)
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if x == 0 or x == width-1 or y == 0 or y == height-1:
|
|
cells[y][x] = True # Border walls
|
|
else:
|
|
cells[y][x] = random() < 0.45
|
|
|
|
# Step 2: Smooth with cellular automata (5 iterations)
|
|
for _ in range(5):
|
|
new_cells = [[cells[y][x] for x in range(width)] for y in range(height)]
|
|
for y in range(1, height - 1):
|
|
for x in range(1, width - 1):
|
|
wall_count = sum(
|
|
1 for dy in [-1, 0, 1] for dx in [-1, 0, 1]
|
|
if not (dx == 0 and dy == 0) and cells[y + dy][x + dx]
|
|
)
|
|
if wall_count >= 5:
|
|
new_cells[y][x] = True
|
|
elif wall_count <= 3:
|
|
new_cells[y][x] = False
|
|
cells = new_cells
|
|
|
|
# Find largest connected region
|
|
visited = set()
|
|
regions = []
|
|
|
|
def flood_fill(start_x, start_y):
|
|
result = []
|
|
stack = [(start_x, start_y)]
|
|
while stack:
|
|
x, y = stack.pop()
|
|
if (x, y) in visited or x < 0 or x >= width or y < 0 or y >= height:
|
|
continue
|
|
if cells[y][x]: # Wall
|
|
continue
|
|
visited.add((x, y))
|
|
result.append((x, y))
|
|
stack.extend([(x+1, y), (x-1, y), (x, y+1), (x, y-1)])
|
|
return result
|
|
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if (x, y) not in visited and not cells[y][x]:
|
|
region = flood_fill(x, y)
|
|
if region:
|
|
regions.append(region)
|
|
|
|
largest = max(regions, key=len) if regions else []
|
|
largest_set = set(largest)
|
|
|
|
# Draw cells as colored frames
|
|
for y in range(height):
|
|
for x in range(width):
|
|
px = 20 + x * cell_size
|
|
py = 20 + y * cell_size
|
|
cell = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
|
|
|
if cells[y][x]:
|
|
cell.fill_color = mcrfpy.Color(60, 40, 30) # Wall
|
|
elif (x, y) in largest_set:
|
|
cell.fill_color = mcrfpy.Color(50, 90, 100) # Main cave
|
|
else:
|
|
cell.fill_color = mcrfpy.Color(45, 35, 30) # Filled region
|
|
|
|
scene.children.append(cell)
|
|
|
|
# Title
|
|
title = mcrfpy.Caption(text="Cellular Automata Caves", pos=(20, 445))
|
|
title.fill_color = mcrfpy.Color(200, 200, 200)
|
|
title.font_size = 18
|
|
scene.children.append(title)
|
|
|
|
subtitle = mcrfpy.Caption(text="45% fill, 5 iterations, largest region preserved", pos=(20, 468))
|
|
subtitle.fill_color = mcrfpy.Color(130, 130, 140)
|
|
subtitle.font_size = 12
|
|
scene.children.append(subtitle)
|
|
|
|
mcrfpy.step(0.1)
|
|
automation.screenshot(OUTPUT_DIR + "/procgen_cellular_caves.png")
|
|
print("Saved: procgen_cellular_caves.png")
|
|
|
|
|
|
def screenshot_wfc():
|
|
"""Generate WFC pattern visualization."""
|
|
print("Generating WFC patterns...")
|
|
|
|
scene = mcrfpy.Scene("wfc")
|
|
scene.activate()
|
|
mcrfpy.step(0.1)
|
|
|
|
# Background
|
|
bg = mcrfpy.Frame(pos=(0, 0), size=(640, 500))
|
|
bg.fill_color = mcrfpy.Color(15, 20, 15)
|
|
scene.children.append(bg)
|
|
|
|
width, height = 40, 28
|
|
cell_size = 15
|
|
seed(123)
|
|
|
|
GRASS, DIRT, WATER, SAND = 0, 1, 2, 3
|
|
colors = {
|
|
GRASS: mcrfpy.Color(60, 120, 50),
|
|
DIRT: mcrfpy.Color(100, 70, 40),
|
|
WATER: mcrfpy.Color(40, 80, 140),
|
|
SAND: mcrfpy.Color(180, 160, 90)
|
|
}
|
|
|
|
rules = {
|
|
GRASS: {'N': [GRASS, DIRT, SAND], 'S': [GRASS, DIRT, SAND],
|
|
'E': [GRASS, DIRT, SAND], 'W': [GRASS, DIRT, SAND]},
|
|
DIRT: {'N': [GRASS, DIRT], 'S': [GRASS, DIRT],
|
|
'E': [GRASS, DIRT], 'W': [GRASS, DIRT]},
|
|
WATER: {'N': [WATER, SAND], 'S': [WATER, SAND],
|
|
'E': [WATER, SAND], 'W': [WATER, SAND]},
|
|
SAND: {'N': [GRASS, WATER, SAND], 'S': [GRASS, WATER, SAND],
|
|
'E': [GRASS, WATER, SAND], 'W': [GRASS, WATER, SAND]}
|
|
}
|
|
|
|
tiles = set(rules.keys())
|
|
possibilities = {(x, y): set(tiles) for y in range(height) for x in range(width)}
|
|
result = {}
|
|
|
|
# Seed water lake
|
|
for x in range(22, 32):
|
|
for y in range(8, 18):
|
|
possibilities[(x, y)] = {WATER}
|
|
result[(x, y)] = WATER
|
|
|
|
# Seed dirt path
|
|
for y in range(10, 18):
|
|
possibilities[(3, y)] = {DIRT}
|
|
result[(3, y)] = DIRT
|
|
|
|
directions = {'N': (0, -1), 'S': (0, 1), 'E': (1, 0), 'W': (-1, 0)}
|
|
|
|
def propagate(sx, sy):
|
|
stack = [(sx, sy)]
|
|
while stack:
|
|
x, y = stack.pop()
|
|
current = possibilities[(x, y)]
|
|
for dir_name, (dx, dy) in directions.items():
|
|
nx, ny = x + dx, y + dy
|
|
if not (0 <= nx < width and 0 <= ny < height):
|
|
continue
|
|
neighbor = possibilities[(nx, ny)]
|
|
if len(neighbor) == 1:
|
|
continue
|
|
allowed = set()
|
|
for tile in current:
|
|
if dir_name in rules[tile]:
|
|
allowed.update(rules[tile][dir_name])
|
|
new_opts = neighbor & allowed
|
|
if new_opts and new_opts != neighbor:
|
|
possibilities[(nx, ny)] = new_opts
|
|
stack.append((nx, ny))
|
|
|
|
# Propagate from seeds
|
|
for x in range(22, 32):
|
|
for y in range(8, 18):
|
|
propagate(x, y)
|
|
for y in range(10, 18):
|
|
propagate(3, y)
|
|
|
|
# Collapse
|
|
for _ in range(width * height):
|
|
best, best_e = None, 1000.0
|
|
for pos, opts in possibilities.items():
|
|
if len(opts) > 1:
|
|
e = len(opts) + random() * 0.1
|
|
if e < best_e:
|
|
best_e, best = e, pos
|
|
|
|
if best is None:
|
|
break
|
|
|
|
x, y = best
|
|
opts = list(possibilities[(x, y)])
|
|
if not opts:
|
|
break
|
|
|
|
weights = {GRASS: 5, DIRT: 2, WATER: 1, SAND: 2}
|
|
weighted = []
|
|
for t in opts:
|
|
weighted.extend([t] * weights.get(t, 1))
|
|
chosen = choice(weighted) if weighted else GRASS
|
|
|
|
possibilities[(x, y)] = {chosen}
|
|
result[(x, y)] = chosen
|
|
propagate(x, y)
|
|
|
|
# Fill remaining
|
|
for y in range(height):
|
|
for x in range(width):
|
|
if (x, y) not in result:
|
|
opts = list(possibilities[(x, y)])
|
|
result[(x, y)] = choice(opts) if opts else GRASS
|
|
|
|
# Draw
|
|
for y in range(height):
|
|
for x in range(width):
|
|
px = 20 + x * cell_size
|
|
py = 20 + y * cell_size
|
|
cell = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1))
|
|
cell.fill_color = colors[result[(x, y)]]
|
|
scene.children.append(cell)
|
|
|
|
# Title
|
|
title = mcrfpy.Caption(text="Wave Function Collapse", pos=(20, 445))
|
|
title.fill_color = mcrfpy.Color(200, 200, 200)
|
|
title.font_size = 18
|
|
scene.children.append(title)
|
|
|
|
subtitle = mcrfpy.Caption(text="Constraint-based terrain (seeded lake + path)", pos=(20, 468))
|
|
subtitle.fill_color = mcrfpy.Color(130, 140, 130)
|
|
subtitle.font_size = 12
|
|
scene.children.append(subtitle)
|
|
|
|
# Legend
|
|
for i, (name, tid) in enumerate([("Grass", GRASS), ("Dirt", DIRT), ("Sand", SAND), ("Water", WATER)]):
|
|
lx, ly = 480, 445 + i * 14
|
|
swatch = mcrfpy.Frame(pos=(lx, ly), size=(12, 12))
|
|
swatch.fill_color = colors[tid]
|
|
scene.children.append(swatch)
|
|
label = mcrfpy.Caption(text=name, pos=(lx + 16, ly))
|
|
label.fill_color = mcrfpy.Color(150, 150, 150)
|
|
label.font_size = 11
|
|
scene.children.append(label)
|
|
|
|
mcrfpy.step(0.1)
|
|
automation.screenshot(OUTPUT_DIR + "/procgen_wfc.png")
|
|
print("Saved: procgen_wfc.png")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
screenshot_cellular_caves()
|
|
screenshot_wfc()
|
|
print("\nDone!")
|
|
sys.exit(0)
|