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)")