Tiled XML/JSON import support

This commit is contained in:
John McCardle 2026-02-06 21:43:03 -05:00
commit b093e087e1
18 changed files with 3040 additions and 0 deletions

View file

@ -0,0 +1,167 @@
# tiled_analysis.py - Wang set adjacency analysis utility
# Prints adjacency graph, terrain chains, and valid/invalid pair counts
# for exploring tileset Wang transition rules.
#
# Usage:
# cd build && ./mcrogueface --headless --exec ../tests/demo/screens/tiled_analysis.py
import mcrfpy
import sys
from collections import defaultdict
# -- Configuration --------------------------------------------------------
PUNY_BASE = "/home/john/Development/7DRL2026_Liber_Noster_jmccardle/assets_sources/PUNY_WORLD_v1/PUNY_WORLD_v1"
TSX_PATH = PUNY_BASE + "/Tiled/punyworld-overworld-tiles.tsx"
WANG_SET_NAME = "overworld"
def analyze_wang_set(tileset, wang_set_name):
"""Analyze a Wang set and print adjacency information."""
ws = tileset.wang_set(wang_set_name)
T = ws.terrain_enum()
print("=" * 60)
print(f"Wang Set Analysis: {ws.name}")
print(f" Type: {ws.type}")
print(f" Colors: {ws.color_count}")
print("=" * 60)
# List all terrains
terrains = [t for t in T if t != T.NONE]
print(f"\nTerrains ({len(terrains)}):")
for t in terrains:
print(f" {t.value:3d}: {t.name}")
# Test all pairs for valid Wang transitions using 2x2 grids
adjacency = defaultdict(set) # terrain -> set of valid neighbors
valid_pairs = []
invalid_pairs = []
for a in terrains:
for b in terrains:
if b.value <= a.value:
continue
# Create a 2x2 map: columns of A and B
dm = mcrfpy.DiscreteMap((2, 2))
dm.set(0, 0, a)
dm.set(1, 0, b)
dm.set(0, 1, a)
dm.set(1, 1, b)
results = ws.resolve(dm)
has_invalid = any(r == -1 for r in results)
if not has_invalid:
valid_pairs.append((a, b))
adjacency[a.name].add(b.name)
adjacency[b.name].add(a.name)
else:
invalid_pairs.append((a, b))
# Print adjacency graph
print(f"\nAdjacency Graph ({len(valid_pairs)} valid pairs):")
print("-" * 40)
for t in terrains:
neighbors = sorted(adjacency.get(t.name, set()))
if neighbors:
print(f" {t.name}")
for n in neighbors:
print(f" <-> {n}")
else:
print(f" {t.name} (ISOLATED - no valid neighbors)")
# Find terrain chains (connected components)
print(f"\nTerrain Chains (connected components):")
print("-" * 40)
visited = set()
chains = []
def bfs(start):
chain = []
queue = [start]
while queue:
node = queue.pop(0)
if node in visited:
continue
visited.add(node)
chain.append(node)
for neighbor in sorted(adjacency.get(node, set())):
if neighbor not in visited:
queue.append(neighbor)
return chain
for t in terrains:
if t.name not in visited:
chain = bfs(t.name)
if chain:
chains.append(chain)
for i, chain in enumerate(chains):
print(f"\n Chain {i+1}: {len(chain)} terrains")
for name in chain:
neighbors = sorted(adjacency.get(name, set()))
connections = ", ".join(neighbors) if neighbors else "(none)"
print(f" {name} -> [{connections}]")
# Find linear paths within chains
print(f"\nLinear Paths (degree-1 endpoints to degree-1 endpoints):")
print("-" * 40)
for chain in chains:
# Find nodes with degree 1 (endpoints) or degree > 2 (hubs)
endpoints = [n for n in chain if len(adjacency.get(n, set())) == 1]
hubs = [n for n in chain if len(adjacency.get(n, set())) > 2]
if endpoints:
print(f" Endpoints: {', '.join(endpoints)}")
if hubs:
print(f" Hubs: {', '.join(hubs)} (branch points)")
# Trace from each endpoint
for ep in endpoints:
path = [ep]
current = ep
prev = None
while True:
neighbors = adjacency.get(current, set()) - {prev} if prev else adjacency.get(current, set())
if len(neighbors) == 0:
break
if len(neighbors) > 1:
path.append(f"({current} branches)")
break
nxt = list(neighbors)[0]
path.append(nxt)
prev = current
current = nxt
if len(adjacency.get(current, set())) != 2:
break # reached endpoint or hub
print(f" Path: {' -> '.join(path)}")
# Summary statistics
total_possible = len(terrains) * (len(terrains) - 1) // 2
print(f"\nSummary:")
print(f" Total terrain types: {len(terrains)}")
print(f" Valid transitions: {len(valid_pairs)} / {total_possible} "
f"({100*len(valid_pairs)/total_possible:.1f}%)")
print(f" Invalid transitions: {len(invalid_pairs)}")
print(f" Connected components: {len(chains)}")
# Print invalid pairs for reference
if invalid_pairs:
print(f"\nInvalid Pairs ({len(invalid_pairs)}):")
for a, b in invalid_pairs:
print(f" {a.name} X {b.name}")
return valid_pairs, invalid_pairs, chains
def main():
print("Loading tileset...")
tileset = mcrfpy.TileSetFile(TSX_PATH)
print(f" {tileset.name}: {tileset.tile_count} tiles "
f"({tileset.columns} cols, {tileset.tile_width}x{tileset.tile_height}px)")
analyze_wang_set(tileset, WANG_SET_NAME)
print("\nDone!")
if __name__ == "__main__":
main()
sys.exit(0)