File size: 5,016 Bytes
94c4245 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | #!/usr/bin/env python3
"""
Dev-time script. Extracts buildXxx() functions from playground sandbox JS files.
Writes sandbox_cache/characters_registry.json and sandbox_cache/characters.js.
Usage:
python3 scripts/extract_characters.py
Source: /Users/bolyos/Desktop/playground/<game>/sandbox/
"""
import json
import re
from pathlib import Path
PLAYGROUND_BASE = Path("/Users/bolyos/Desktop/playground")
OUT_DIR = Path(__file__).parent.parent / "sandbox_cache"
OUT_DIR.mkdir(exist_ok=True)
# Pure Three.js geometry primitives โ not characters, exclude them
GEOMETRY_PRIMITIVES = {
"buildBox", "buildCylinder", "buildSphere", "buildCone",
"buildIcosahedron", "buildOctahedron", "buildDodecahedron",
"buildTetrahedron", "buildLathe",
}
# Scan patterns in priority order
SCAN_PATTERNS = [
"*/sandbox/src/characters.js", # jungle modular structure โ try first
"*/sandbox/main.js",
"*/sandbox/bundle.js",
]
def extract_functions(source: str) -> dict[str, str]:
"""Extract top-level function buildXxx() {...} blocks from JS source."""
registry: dict[str, str] = {}
pattern = re.compile(r'^(function (build\w+)\([^)]*\)\s*\{)', re.MULTILINE)
for m in pattern.finditer(source):
fn_name = m.group(2)
if fn_name in GEOMETRY_PRIMITIVES:
continue
start = m.start()
depth = 0
for j in range(m.start(1), len(source)):
if source[j] == "{":
depth += 1
elif source[j] == "}":
depth -= 1
if depth == 0:
registry[fn_name] = source[start : j + 1]
break
return registry
def game_slug(js_path: Path) -> str:
"""Derive a clean slug from the game directory name."""
# parent chain: .../playground/<game>/sandbox/[src/]file.js
parts = js_path.parts
try:
pg_idx = next(i for i, p in enumerate(parts) if p == "playground")
game_name = parts[pg_idx + 1]
except (StopIteration, IndexError):
game_name = js_path.parent.parent.name
return re.sub(r"[^a-zA-Z0-9]+", "_", game_name).strip("_").lower()
# โโ Collect all JS files to scan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
js_files: list[Path] = []
for pattern in SCAN_PATTERNS:
for f in sorted(PLAYGROUND_BASE.glob(pattern)):
if f not in js_files:
js_files.append(f)
print(f"Files to scan: {len(js_files)}")
for f in js_files:
print(f" {f}")
# โโ Extract, resolving buildHero collisions by namespacing โโโโโโโโโโโโโโโโโโโโ
registry: dict[str, str] = {}
hero_sources: dict[str, str] = {} # fn_name โ game_slug for collision tracking
for js_file in js_files:
slug = game_slug(js_file)
try:
text = js_file.read_text(errors="ignore")
except OSError as e:
print(f" WARNING: could not read {js_file}: {e}")
continue
found = extract_functions(text)
print(f" {js_file.name} ({slug}): {len(found)} functions found")
for fn_name, fn_body in found.items():
if fn_name == "buildHero":
namespaced = f"buildHero_{slug}"
# Rename the function declaration to match the namespaced key
fn_body_renamed = fn_body.replace(
f"function buildHero(", f"function {namespaced}(", 1
)
registry[namespaced] = fn_body_renamed
print(f" โณ buildHero โ {namespaced}")
else:
if fn_name in registry:
print(f" โณ {fn_name} already seen, skipping duplicate")
else:
registry[fn_name] = fn_body
print(f"\nTotal unique build functions: {len(registry)}")
# โโ Character functions (names containing 'Character', 'Songoku', 'Hero', or Pokemon names) โโ
character_fns = sorted([
k for k in registry
if any(tag in k for tag in ["Character", "Hero", "Songoku",
"Raticate", "Persian", "Tauros", "Snorlax",
"Graveler", "Onix", "Zubat", "Golbat",
"Pidgey", "Fearow", "Beedrill", "Butterfree",
"Voltorb", "Electrode", "Jigglypuff", "Abra",
"Alakazam", "Gengar", "Doduo", "Rapidash"])
])
print(f"\nCharacter functions ({len(character_fns)}): {character_fns}")
# โโ Write outputs โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
json_path = OUT_DIR / "characters_registry.json"
json_path.write_text(json.dumps(registry, indent=2))
print(f"\nWritten: {json_path} ({json_path.stat().st_size} bytes)")
js_path = OUT_DIR / "characters.js"
js_path.write_text("\n\n".join(registry.values()) + "\n")
print(f"Written: {js_path} ({js_path.stat().st_size} bytes)")
|