200 lines
6 KiB
Python
200 lines
6 KiB
Python
|
|
"""Interactive Procedural Generation Demo Launcher
|
||
|
|
|
||
|
|
Run with: ./mcrogueface ../tests/procgen_interactive/main.py
|
||
|
|
"""
|
||
|
|
|
||
|
|
import mcrfpy
|
||
|
|
import sys
|
||
|
|
|
||
|
|
# Demo classes
|
||
|
|
from .demos.cave_demo import CaveDemo
|
||
|
|
from .demos.dungeon_demo import DungeonDemo
|
||
|
|
from .demos.terrain_demo import TerrainDemo
|
||
|
|
from .demos.town_demo import TownDemo
|
||
|
|
|
||
|
|
|
||
|
|
class DemoLauncher:
|
||
|
|
"""Main menu for selecting demos."""
|
||
|
|
|
||
|
|
DEMOS = [
|
||
|
|
("Cave (Cellular Automata)", CaveDemo,
|
||
|
|
"Cellular automata cave generation with noise, smoothing, and region detection"),
|
||
|
|
("Dungeon (BSP)", DungeonDemo,
|
||
|
|
"Binary Space Partitioning with room extraction and corridor connections"),
|
||
|
|
("Terrain (Multi-layer)", TerrainDemo,
|
||
|
|
"FBM noise elevation with water level, mountains, erosion, and biomes"),
|
||
|
|
("Town (Voronoi)", TownDemo,
|
||
|
|
"Voronoi districts with Bezier roads and building placement"),
|
||
|
|
]
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
"""Build the menu scene."""
|
||
|
|
self.scene = mcrfpy.Scene("procgen_menu")
|
||
|
|
self.current_demo = None
|
||
|
|
self._build_menu()
|
||
|
|
|
||
|
|
def _build_menu(self):
|
||
|
|
"""Create the menu UI."""
|
||
|
|
ui = self.scene.children
|
||
|
|
|
||
|
|
# Background
|
||
|
|
bg = mcrfpy.Frame(
|
||
|
|
pos=(0, 0),
|
||
|
|
size=(1024, 768),
|
||
|
|
fill_color=mcrfpy.Color(25, 28, 35)
|
||
|
|
)
|
||
|
|
ui.append(bg)
|
||
|
|
|
||
|
|
# Title
|
||
|
|
title = mcrfpy.Caption(
|
||
|
|
text="Interactive Procedural Generation",
|
||
|
|
pos=(512, 60),
|
||
|
|
font_size=32,
|
||
|
|
fill_color=mcrfpy.Color(220, 220, 230)
|
||
|
|
)
|
||
|
|
ui.append(title)
|
||
|
|
|
||
|
|
subtitle = mcrfpy.Caption(
|
||
|
|
text="Educational demos for exploring generation techniques",
|
||
|
|
pos=(512, 100),
|
||
|
|
font_size=16,
|
||
|
|
fill_color=mcrfpy.Color(150, 150, 160)
|
||
|
|
)
|
||
|
|
ui.append(subtitle)
|
||
|
|
|
||
|
|
# Demo buttons
|
||
|
|
button_y = 180
|
||
|
|
button_width = 400
|
||
|
|
button_height = 80
|
||
|
|
|
||
|
|
for i, (name, demo_class, description) in enumerate(self.DEMOS):
|
||
|
|
# Button frame
|
||
|
|
btn = mcrfpy.Frame(
|
||
|
|
pos=(312, button_y),
|
||
|
|
size=(button_width, button_height),
|
||
|
|
fill_color=mcrfpy.Color(45, 48, 55),
|
||
|
|
outline=2,
|
||
|
|
outline_color=mcrfpy.Color(80, 85, 100)
|
||
|
|
)
|
||
|
|
|
||
|
|
# Demo name
|
||
|
|
name_caption = mcrfpy.Caption(
|
||
|
|
text=name,
|
||
|
|
pos=(20, 15),
|
||
|
|
font_size=20,
|
||
|
|
fill_color=mcrfpy.Color(200, 200, 210)
|
||
|
|
)
|
||
|
|
btn.children.append(name_caption)
|
||
|
|
|
||
|
|
# Description (wrap manually for now)
|
||
|
|
desc_text = description[:55] + "..." if len(description) > 55 else description
|
||
|
|
desc_caption = mcrfpy.Caption(
|
||
|
|
text=desc_text,
|
||
|
|
pos=(20, 45),
|
||
|
|
font_size=12,
|
||
|
|
fill_color=mcrfpy.Color(120, 120, 130)
|
||
|
|
)
|
||
|
|
btn.children.append(desc_caption)
|
||
|
|
|
||
|
|
# Click handler
|
||
|
|
demo_idx = i
|
||
|
|
btn.on_click = lambda p, b, a, idx=demo_idx: self._on_demo_click(idx, b, a)
|
||
|
|
btn.on_enter = lambda p, btn=btn: self._on_btn_enter(btn)
|
||
|
|
btn.on_exit = lambda p, btn=btn: self._on_btn_exit(btn)
|
||
|
|
|
||
|
|
ui.append(btn)
|
||
|
|
button_y += button_height + 20
|
||
|
|
|
||
|
|
# Instructions
|
||
|
|
instructions = [
|
||
|
|
"Click a demo to start exploring procedural generation",
|
||
|
|
"Each demo shows step-by-step visualization of the algorithm",
|
||
|
|
"",
|
||
|
|
"Controls (in demos):",
|
||
|
|
" Left/Right arrows: Navigate steps",
|
||
|
|
" Middle-drag: Pan viewport",
|
||
|
|
" Scroll wheel: Zoom in/out",
|
||
|
|
" Number keys: Toggle layer visibility",
|
||
|
|
" R: Reset view",
|
||
|
|
" Escape: Return to this menu",
|
||
|
|
]
|
||
|
|
|
||
|
|
instr_y = 580
|
||
|
|
for line in instructions:
|
||
|
|
cap = mcrfpy.Caption(
|
||
|
|
text=line,
|
||
|
|
pos=(312, instr_y),
|
||
|
|
font_size=12,
|
||
|
|
fill_color=mcrfpy.Color(100, 100, 110)
|
||
|
|
)
|
||
|
|
ui.append(cap)
|
||
|
|
instr_y += 18
|
||
|
|
|
||
|
|
# Keyboard handler
|
||
|
|
self.scene.on_key = self._on_key
|
||
|
|
|
||
|
|
def _on_btn_enter(self, btn):
|
||
|
|
"""Handle button hover enter."""
|
||
|
|
btn.fill_color = mcrfpy.Color(55, 60, 70)
|
||
|
|
btn.outline_color = mcrfpy.Color(100, 120, 180)
|
||
|
|
|
||
|
|
def _on_btn_exit(self, btn):
|
||
|
|
"""Handle button hover exit."""
|
||
|
|
btn.fill_color = mcrfpy.Color(45, 48, 55)
|
||
|
|
btn.outline_color = mcrfpy.Color(80, 85, 100)
|
||
|
|
|
||
|
|
def _on_demo_click(self, idx, button, action):
|
||
|
|
"""Handle demo button click."""
|
||
|
|
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.RELEASED:
|
||
|
|
self._launch_demo(idx)
|
||
|
|
|
||
|
|
def _launch_demo(self, idx):
|
||
|
|
"""Launch a demo by index."""
|
||
|
|
_, demo_class, _ = self.DEMOS[idx]
|
||
|
|
self.current_demo = demo_class()
|
||
|
|
self.current_demo.activate()
|
||
|
|
|
||
|
|
def _on_key(self, key, action):
|
||
|
|
"""Handle keyboard input."""
|
||
|
|
# Only process on key press
|
||
|
|
if action != mcrfpy.InputState.PRESSED:
|
||
|
|
return
|
||
|
|
|
||
|
|
# Convert key to string for easier comparison
|
||
|
|
key_str = str(key) if not isinstance(key, str) else key
|
||
|
|
|
||
|
|
# Number keys to launch demos directly
|
||
|
|
if key_str.startswith("Key.NUM") or (len(key_str) == 1 and key_str.isdigit()):
|
||
|
|
try:
|
||
|
|
num = int(key_str[-1])
|
||
|
|
if 1 <= num <= len(self.DEMOS):
|
||
|
|
self._launch_demo(num - 1)
|
||
|
|
except (ValueError, IndexError):
|
||
|
|
pass
|
||
|
|
elif key == mcrfpy.Key.ESCAPE:
|
||
|
|
sys.exit(0)
|
||
|
|
|
||
|
|
def show(self):
|
||
|
|
"""Show the menu."""
|
||
|
|
mcrfpy.current_scene = self.scene
|
||
|
|
|
||
|
|
|
||
|
|
# Global launcher instance
|
||
|
|
_launcher = None
|
||
|
|
|
||
|
|
|
||
|
|
def show_menu():
|
||
|
|
"""Show the demo menu (called from demos to return)."""
|
||
|
|
global _launcher
|
||
|
|
if _launcher is None:
|
||
|
|
_launcher = DemoLauncher()
|
||
|
|
_launcher.show()
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
"""Entry point for the demo system."""
|
||
|
|
show_menu()
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|