LDtk import support
This commit is contained in:
parent
322beeaf78
commit
de7778b147
24 changed files with 26203 additions and 0 deletions
183
tests/unit/ldtk_apply_test.py
Normal file
183
tests/unit/ldtk_apply_test.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
"""Unit tests for LDtk auto-rule apply (resolve + write to TileLayer)."""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_apply_basic():
|
||||
"""Test applying rules to a TileLayer."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
ts = proj.tileset("Test_Tileset")
|
||||
texture = ts.to_texture()
|
||||
|
||||
# Create DiscreteMap
|
||||
dm = mcrfpy.DiscreteMap((5, 5), fill=0)
|
||||
for y in range(5):
|
||||
for x in range(5):
|
||||
if x == 0 or x == 4 or y == 0 or y == 4:
|
||||
dm.set(x, y, 1)
|
||||
elif x == 2 and y == 2:
|
||||
dm.set(x, y, 3)
|
||||
else:
|
||||
dm.set(x, y, 2)
|
||||
|
||||
# Create TileLayer and apply
|
||||
layer = mcrfpy.TileLayer(name="terrain", texture=texture, grid_size=(5, 5))
|
||||
rs.apply(dm, layer, seed=0)
|
||||
|
||||
# Verify some tiles were written
|
||||
wall_tile = layer.at(0, 0)
|
||||
assert wall_tile == 0, f"Expected wall tile 0 at (0,0), got {wall_tile}"
|
||||
|
||||
floor_tile = layer.at(1, 1)
|
||||
assert floor_tile >= 0, f"Expected floor tile at (1,1), got {floor_tile}"
|
||||
|
||||
# Empty cells (water, value=3) should still be -1 (no rule matches water)
|
||||
water_tile = layer.at(2, 2)
|
||||
assert water_tile == -1, f"Expected -1 at water (2,2), got {water_tile}"
|
||||
|
||||
print(f" applied: wall={wall_tile}, floor={floor_tile}, water={water_tile}")
|
||||
|
||||
def test_apply_preserves_unmatched():
|
||||
"""Test that unmatched cells retain their original value."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
ts = proj.tileset("Test_Tileset")
|
||||
texture = ts.to_texture()
|
||||
|
||||
# Pre-fill layer with a sentinel value
|
||||
layer = mcrfpy.TileLayer(name="test", texture=texture, grid_size=(3, 3))
|
||||
layer.fill(99)
|
||||
|
||||
# Create empty map - no rules will match
|
||||
dm = mcrfpy.DiscreteMap((3, 3), fill=0)
|
||||
rs.apply(dm, layer, seed=0)
|
||||
|
||||
# All cells should still be 99 (no rules matched)
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
val = layer.at(x, y)
|
||||
assert val == 99, f"Expected 99 at ({x},{y}), got {val}"
|
||||
print(" unmatched cells preserved: OK")
|
||||
|
||||
def test_apply_type_errors():
|
||||
"""Test that apply raises TypeError for wrong argument types."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
# Wrong first argument type
|
||||
try:
|
||||
rs.apply("not_a_dmap", None, seed=0)
|
||||
assert False, "Should have raised TypeError"
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# Wrong second argument type
|
||||
dm = mcrfpy.DiscreteMap((3, 3))
|
||||
try:
|
||||
rs.apply(dm, "not_a_layer", seed=0)
|
||||
assert False, "Should have raised TypeError"
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
print(" type errors raised correctly: OK")
|
||||
|
||||
def test_apply_clipping():
|
||||
"""Test that apply clips to the smaller of map/layer dimensions."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
ts = proj.tileset("Test_Tileset")
|
||||
texture = ts.to_texture()
|
||||
|
||||
# DiscreteMap larger than TileLayer
|
||||
dm = mcrfpy.DiscreteMap((10, 10), fill=1)
|
||||
layer = mcrfpy.TileLayer(name="small", texture=texture, grid_size=(3, 3))
|
||||
layer.fill(-1)
|
||||
|
||||
rs.apply(dm, layer, seed=0)
|
||||
|
||||
# Layer should have tiles written only within its bounds
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
val = layer.at(x, y)
|
||||
assert val >= 0, f"Expected tile at ({x},{y}), got {val}"
|
||||
print(" clipping (large map, small layer): OK")
|
||||
|
||||
# DiscreteMap smaller than TileLayer
|
||||
dm2 = mcrfpy.DiscreteMap((2, 2), fill=1)
|
||||
layer2 = mcrfpy.TileLayer(name="big", texture=texture, grid_size=(5, 5))
|
||||
layer2.fill(88)
|
||||
|
||||
rs.apply(dm2, layer2, seed=0)
|
||||
|
||||
# Only (0,0)-(1,1) should be overwritten
|
||||
for y in range(2):
|
||||
for x in range(2):
|
||||
val = layer2.at(x, y)
|
||||
assert val >= 0, f"Expected tile at ({x},{y}), got {val}"
|
||||
|
||||
# (3,3) should still be the fill value
|
||||
assert layer2.at(3, 3) == 88, f"Expected 88 at (3,3), got {layer2.at(3, 3)}"
|
||||
print(" clipping (small map, large layer): OK")
|
||||
|
||||
def test_resolve_type_error():
|
||||
"""Test that resolve raises TypeError for wrong argument."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
try:
|
||||
rs.resolve("not_a_dmap", seed=0)
|
||||
assert False, "Should have raised TypeError"
|
||||
except TypeError:
|
||||
pass
|
||||
print(" resolve TypeError: OK")
|
||||
|
||||
def test_precomputed_tiles():
|
||||
"""Test loading pre-computed auto-layer tiles from a level."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
ts = proj.tileset("Test_Tileset")
|
||||
texture = ts.to_texture()
|
||||
|
||||
level = proj.level("Level_0")
|
||||
layer_info = level["layers"][0]
|
||||
|
||||
# Create TileLayer and write pre-computed tiles
|
||||
layer = mcrfpy.TileLayer(name="precomp", texture=texture, grid_size=(5, 5))
|
||||
layer.fill(-1)
|
||||
|
||||
for tile in layer_info["auto_tiles"]:
|
||||
x, y = tile["x"], tile["y"]
|
||||
if 0 <= x < 5 and 0 <= y < 5:
|
||||
layer.set((x, y), tile["tile_id"])
|
||||
|
||||
# Verify some tiles were written
|
||||
assert layer.at(0, 0) == 0, f"Expected tile 0 at (0,0), got {layer.at(0, 0)}"
|
||||
print(f" precomputed tiles loaded: first = {layer.at(0, 0)}")
|
||||
|
||||
# Run tests
|
||||
tests = [
|
||||
test_apply_basic,
|
||||
test_apply_preserves_unmatched,
|
||||
test_apply_type_errors,
|
||||
test_apply_clipping,
|
||||
test_resolve_type_error,
|
||||
test_precomputed_tiles,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
print("=== LDtk Apply Tests ===")
|
||||
for test in tests:
|
||||
name = test.__name__
|
||||
try:
|
||||
print(f"[TEST] {name}...")
|
||||
test()
|
||||
passed += 1
|
||||
print(f" PASS")
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
print(f" FAIL: {e}")
|
||||
|
||||
print(f"\n=== Results: {passed} passed, {failed} failed ===")
|
||||
if failed > 0:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
222
tests/unit/ldtk_parse_test.py
Normal file
222
tests/unit/ldtk_parse_test.py
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
"""Unit tests for LDtk project parsing."""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import os
|
||||
|
||||
def test_load_project():
|
||||
"""Test basic project loading."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
assert proj is not None, "Failed to create LdtkProject"
|
||||
print(f" repr: {repr(proj)}")
|
||||
return proj
|
||||
|
||||
def test_version(proj):
|
||||
"""Test version property."""
|
||||
assert proj.version == "1.5.3", f"Expected version '1.5.3', got '{proj.version}'"
|
||||
print(f" version: {proj.version}")
|
||||
|
||||
def test_tileset_names(proj):
|
||||
"""Test tileset enumeration."""
|
||||
names = proj.tileset_names
|
||||
assert isinstance(names, list), f"Expected list, got {type(names)}"
|
||||
assert len(names) == 1, f"Expected 1 tileset, got {len(names)}"
|
||||
assert names[0] == "Test_Tileset", f"Expected 'Test_Tileset', got '{names[0]}'"
|
||||
print(f" tileset_names: {names}")
|
||||
|
||||
def test_ruleset_names(proj):
|
||||
"""Test ruleset enumeration."""
|
||||
names = proj.ruleset_names
|
||||
assert isinstance(names, list), f"Expected list, got {type(names)}"
|
||||
assert len(names) == 1, f"Expected 1 ruleset, got {len(names)}"
|
||||
assert names[0] == "Terrain", f"Expected 'Terrain', got '{names[0]}'"
|
||||
print(f" ruleset_names: {names}")
|
||||
|
||||
def test_level_names(proj):
|
||||
"""Test level enumeration."""
|
||||
names = proj.level_names
|
||||
assert isinstance(names, list), f"Expected list, got {type(names)}"
|
||||
assert len(names) == 1, f"Expected 1 level, got {len(names)}"
|
||||
assert names[0] == "Level_0", f"Expected 'Level_0', got '{names[0]}'"
|
||||
print(f" level_names: {names}")
|
||||
|
||||
def test_enums(proj):
|
||||
"""Test enum access."""
|
||||
enums = proj.enums
|
||||
assert isinstance(enums, list), f"Expected list, got {type(enums)}"
|
||||
assert len(enums) == 1, f"Expected 1 enum, got {len(enums)}"
|
||||
assert enums[0]["identifier"] == "TileType"
|
||||
print(f" enums: {len(enums)} enum(s), first = {enums[0]['identifier']}")
|
||||
|
||||
def test_tileset_access(proj):
|
||||
"""Test tileset retrieval."""
|
||||
ts = proj.tileset("Test_Tileset")
|
||||
assert ts is not None, "Failed to get tileset"
|
||||
print(f" tileset: {repr(ts)}")
|
||||
assert ts.name == "Test_Tileset", f"Expected 'Test_Tileset', got '{ts.name}'"
|
||||
assert ts.tile_width == 16, f"Expected tile_width 16, got {ts.tile_width}"
|
||||
assert ts.tile_height == 16, f"Expected tile_height 16, got {ts.tile_height}"
|
||||
assert ts.columns == 4, f"Expected columns 4, got {ts.columns}"
|
||||
assert ts.tile_count == 16, f"Expected tile_count 16, got {ts.tile_count}"
|
||||
|
||||
def test_tileset_not_found(proj):
|
||||
"""Test KeyError for missing tileset."""
|
||||
try:
|
||||
proj.tileset("Nonexistent")
|
||||
assert False, "Should have raised KeyError"
|
||||
except KeyError:
|
||||
pass
|
||||
print(" KeyError raised for missing tileset: OK")
|
||||
|
||||
def test_ruleset_access(proj):
|
||||
"""Test ruleset retrieval."""
|
||||
rs = proj.ruleset("Terrain")
|
||||
assert rs is not None, "Failed to get ruleset"
|
||||
print(f" ruleset: {repr(rs)}")
|
||||
assert rs.name == "Terrain", f"Expected 'Terrain', got '{rs.name}'"
|
||||
assert rs.grid_size == 16, f"Expected grid_size 16, got {rs.grid_size}"
|
||||
assert rs.value_count == 3, f"Expected 3 values, got {rs.value_count}"
|
||||
assert rs.group_count == 2, f"Expected 2 groups, got {rs.group_count}"
|
||||
assert rs.rule_count == 3, f"Expected 3 rules, got {rs.rule_count}"
|
||||
|
||||
def test_ruleset_values(proj):
|
||||
"""Test IntGrid value definitions."""
|
||||
rs = proj.ruleset("Terrain")
|
||||
values = rs.values
|
||||
assert len(values) == 3, f"Expected 3 values, got {len(values)}"
|
||||
assert values[0]["value"] == 1
|
||||
assert values[0]["name"] == "wall"
|
||||
assert values[1]["value"] == 2
|
||||
assert values[1]["name"] == "floor"
|
||||
assert values[2]["value"] == 3
|
||||
assert values[2]["name"] == "water"
|
||||
print(f" values: {values}")
|
||||
|
||||
def test_terrain_enum(proj):
|
||||
"""Test terrain_enum() generation."""
|
||||
rs = proj.ruleset("Terrain")
|
||||
Terrain = rs.terrain_enum()
|
||||
assert Terrain is not None, "Failed to create terrain enum"
|
||||
assert Terrain.NONE == 0, f"Expected NONE=0, got {Terrain.NONE}"
|
||||
assert Terrain.WALL == 1, f"Expected WALL=1, got {Terrain.WALL}"
|
||||
assert Terrain.FLOOR == 2, f"Expected FLOOR=2, got {Terrain.FLOOR}"
|
||||
assert Terrain.WATER == 3, f"Expected WATER=3, got {Terrain.WATER}"
|
||||
print(f" terrain enum: {list(Terrain)}")
|
||||
|
||||
def test_level_access(proj):
|
||||
"""Test level data retrieval."""
|
||||
level = proj.level("Level_0")
|
||||
assert isinstance(level, dict), f"Expected dict, got {type(level)}"
|
||||
assert level["name"] == "Level_0"
|
||||
assert level["width_px"] == 80
|
||||
assert level["height_px"] == 80
|
||||
assert level["world_x"] == 0
|
||||
assert level["world_y"] == 0
|
||||
print(f" level: {level['name']} ({level['width_px']}x{level['height_px']}px)")
|
||||
|
||||
def test_level_layers(proj):
|
||||
"""Test level layer data."""
|
||||
level = proj.level("Level_0")
|
||||
layers = level["layers"]
|
||||
assert len(layers) == 1, f"Expected 1 layer, got {len(layers)}"
|
||||
|
||||
layer = layers[0]
|
||||
assert layer["name"] == "Terrain"
|
||||
assert layer["type"] == "IntGrid"
|
||||
assert layer["width"] == 5
|
||||
assert layer["height"] == 5
|
||||
print(f" layer: {layer['name']} ({layer['type']}) {layer['width']}x{layer['height']}")
|
||||
|
||||
def test_level_intgrid(proj):
|
||||
"""Test IntGrid CSV data."""
|
||||
level = proj.level("Level_0")
|
||||
layer = level["layers"][0]
|
||||
intgrid = layer["intgrid"]
|
||||
assert len(intgrid) == 25, f"Expected 25 cells, got {len(intgrid)}"
|
||||
# Check corners are walls (1)
|
||||
assert intgrid[0] == 1, f"Expected wall at (0,0), got {intgrid[0]}"
|
||||
assert intgrid[4] == 1, f"Expected wall at (4,0), got {intgrid[4]}"
|
||||
# Check center is water (3)
|
||||
assert intgrid[12] == 3, f"Expected water at (2,2), got {intgrid[12]}"
|
||||
# Check floor tiles (2)
|
||||
assert intgrid[6] == 2, f"Expected floor at (1,1), got {intgrid[6]}"
|
||||
print(f" intgrid: {intgrid[:5]}... ({len(intgrid)} cells)")
|
||||
|
||||
def test_level_auto_tiles(proj):
|
||||
"""Test pre-computed auto-layer tiles."""
|
||||
level = proj.level("Level_0")
|
||||
layer = level["layers"][0]
|
||||
auto_tiles = layer["auto_tiles"]
|
||||
assert len(auto_tiles) > 0, f"Expected auto tiles, got {len(auto_tiles)}"
|
||||
# Check first tile structure
|
||||
t = auto_tiles[0]
|
||||
assert "tile_id" in t, f"Missing tile_id in auto tile: {t}"
|
||||
assert "x" in t, f"Missing x in auto tile: {t}"
|
||||
assert "y" in t, f"Missing y in auto tile: {t}"
|
||||
assert "flip" in t, f"Missing flip in auto tile: {t}"
|
||||
print(f" auto_tiles: {len(auto_tiles)} tiles, first = {auto_tiles[0]}")
|
||||
|
||||
def test_level_not_found(proj):
|
||||
"""Test KeyError for missing level."""
|
||||
try:
|
||||
proj.level("Nonexistent")
|
||||
assert False, "Should have raised KeyError"
|
||||
except KeyError:
|
||||
pass
|
||||
print(" KeyError raised for missing level: OK")
|
||||
|
||||
def test_load_nonexistent():
|
||||
"""Test IOError for missing file."""
|
||||
try:
|
||||
mcrfpy.LdtkProject("nonexistent.ldtk")
|
||||
assert False, "Should have raised IOError"
|
||||
except IOError:
|
||||
pass
|
||||
print(" IOError raised for missing file: OK")
|
||||
|
||||
# Run all tests
|
||||
tests = [
|
||||
("load_project", None),
|
||||
("version", None),
|
||||
("tileset_names", None),
|
||||
("ruleset_names", None),
|
||||
("level_names", None),
|
||||
("enums", None),
|
||||
("tileset_access", None),
|
||||
("tileset_not_found", None),
|
||||
("ruleset_access", None),
|
||||
("ruleset_values", None),
|
||||
("terrain_enum", None),
|
||||
("level_access", None),
|
||||
("level_layers", None),
|
||||
("level_intgrid", None),
|
||||
("level_auto_tiles", None),
|
||||
("level_not_found", None),
|
||||
("load_nonexistent", None),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
proj = None
|
||||
|
||||
# First test returns the project
|
||||
print("=== LDtk Parse Tests ===")
|
||||
for name, func in tests:
|
||||
try:
|
||||
test_fn = globals()[f"test_{name}"]
|
||||
print(f"[TEST] {name}...")
|
||||
if name == "load_project":
|
||||
proj = test_fn()
|
||||
elif name in ("load_nonexistent",):
|
||||
test_fn()
|
||||
else:
|
||||
test_fn(proj)
|
||||
passed += 1
|
||||
print(f" PASS")
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
print(f" FAIL: {e}")
|
||||
|
||||
print(f"\n=== Results: {passed} passed, {failed} failed ===")
|
||||
if failed > 0:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
146
tests/unit/ldtk_resolve_test.py
Normal file
146
tests/unit/ldtk_resolve_test.py
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
"""Unit tests for LDtk auto-rule resolution."""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_basic_resolve():
|
||||
"""Test resolving a simple IntGrid against auto-rules."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
# Create a DiscreteMap matching the test fixture
|
||||
dm = mcrfpy.DiscreteMap((5, 5), fill=0)
|
||||
# Fill with the same pattern as test_project.ldtk Level_0:
|
||||
# 1 1 1 1 1
|
||||
# 1 2 2 2 1
|
||||
# 1 2 3 2 1
|
||||
# 1 2 2 2 1
|
||||
# 1 1 1 1 1
|
||||
for y in range(5):
|
||||
for x in range(5):
|
||||
if x == 0 or x == 4 or y == 0 or y == 4:
|
||||
dm.set(x, y, 1) # wall
|
||||
elif x == 2 and y == 2:
|
||||
dm.set(x, y, 3) # water
|
||||
else:
|
||||
dm.set(x, y, 2) # floor
|
||||
|
||||
tiles = rs.resolve(dm, seed=0)
|
||||
assert isinstance(tiles, list), f"Expected list, got {type(tiles)}"
|
||||
assert len(tiles) == 25, f"Expected 25 tiles, got {len(tiles)}"
|
||||
print(f" resolved: {tiles}")
|
||||
|
||||
# Wall cells (value=1) should have tile_id 0 (from rule 51 matching pattern center=1)
|
||||
assert tiles[0] >= 0, f"Expected wall tile at (0,0), got {tiles[0]}"
|
||||
# Floor cells (value=2) should match floor rule (rule 61, tile_id 2 or 3)
|
||||
assert tiles[6] >= 0, f"Expected floor tile at (1,1), got {tiles[6]}"
|
||||
print(" wall and floor cells matched rules: OK")
|
||||
|
||||
def test_resolve_with_seed():
|
||||
"""Test that different seeds produce deterministic but different results for multi-tile rules."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
dm = mcrfpy.DiscreteMap((5, 5), fill=2) # All floor
|
||||
|
||||
tiles_a = rs.resolve(dm, seed=0)
|
||||
tiles_b = rs.resolve(dm, seed=0)
|
||||
tiles_c = rs.resolve(dm, seed=42)
|
||||
|
||||
# Same seed = same result
|
||||
assert tiles_a == tiles_b, "Same seed should produce same result"
|
||||
print(" deterministic with same seed: OK")
|
||||
|
||||
# Different seed may produce different tile picks (floor rule has 2 alternatives)
|
||||
# Not guaranteed to differ for all cells, but we test determinism
|
||||
tiles_d = rs.resolve(dm, seed=42)
|
||||
assert tiles_c == tiles_d, "Same seed should produce same result"
|
||||
print(" deterministic with different seed: OK")
|
||||
|
||||
def test_resolve_empty():
|
||||
"""Test resolving an all-empty grid (value 0 = empty)."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
dm = mcrfpy.DiscreteMap((3, 3), fill=0)
|
||||
tiles = rs.resolve(dm, seed=0)
|
||||
assert len(tiles) == 9, f"Expected 9 tiles, got {len(tiles)}"
|
||||
# All empty - no rules should match (rules match value 1 or 2)
|
||||
for i, t in enumerate(tiles):
|
||||
assert t == -1, f"Expected -1 at index {i}, got {t}"
|
||||
print(" empty grid: all tiles -1: OK")
|
||||
|
||||
def test_pattern_negation():
|
||||
"""Test that negative pattern values work (must NOT match)."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
# Rule 52 has pattern: [0, -1, 0, 0, 1, 0, 0, 0, 0]
|
||||
# Center must be 1 (wall), top neighbor must NOT be 1
|
||||
# Create a 3x3 grid with wall center and non-wall top
|
||||
dm = mcrfpy.DiscreteMap((3, 3), fill=0)
|
||||
dm.set(1, 1, 1) # center = wall
|
||||
dm.set(1, 0, 2) # top = floor (not wall)
|
||||
|
||||
tiles = rs.resolve(dm, seed=0)
|
||||
# The center cell should match rule 52 (wall with non-wall top)
|
||||
# Rule 52 gives tile_id 1 (from tileRectsIds [16,0] = column 1, row 0 = tile 1)
|
||||
center = tiles[4] # (1,1) = index 4 in 3x3
|
||||
print(f" negation pattern: center tile = {center}")
|
||||
# It should match either rule 51 (generic wall) or rule 52 (wall with non-wall top)
|
||||
assert center >= 0, f"Expected match at center, got {center}"
|
||||
print(" pattern negation test: OK")
|
||||
|
||||
def test_resolve_dimensions():
|
||||
"""Test resolve works with different grid dimensions."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
for w, h in [(1, 1), (3, 3), (10, 10), (1, 20), (20, 1)]:
|
||||
dm = mcrfpy.DiscreteMap((w, h), fill=1)
|
||||
tiles = rs.resolve(dm, seed=0)
|
||||
assert len(tiles) == w * h, f"Expected {w*h} tiles for {w}x{h}, got {len(tiles)}"
|
||||
print(" various dimensions: OK")
|
||||
|
||||
def test_break_on_match():
|
||||
"""Test that breakOnMatch prevents later rules from overwriting."""
|
||||
proj = mcrfpy.LdtkProject("../tests/fixtures/test_project.ldtk")
|
||||
rs = proj.ruleset("Terrain")
|
||||
|
||||
# Create a grid where rule 51 (generic wall) should match
|
||||
# Rule 51 has breakOnMatch=true, so rule 52 should not override it
|
||||
dm = mcrfpy.DiscreteMap((3, 3), fill=1) # All walls
|
||||
|
||||
tiles = rs.resolve(dm, seed=0)
|
||||
# All cells should be tile 0 (from rule 51)
|
||||
center = tiles[4]
|
||||
assert center == 0, f"Expected tile 0 from rule 51, got {center}"
|
||||
print(f" break on match: center = {center}: OK")
|
||||
|
||||
# Run tests
|
||||
tests = [
|
||||
test_basic_resolve,
|
||||
test_resolve_with_seed,
|
||||
test_resolve_empty,
|
||||
test_pattern_negation,
|
||||
test_resolve_dimensions,
|
||||
test_break_on_match,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
print("=== LDtk Resolve Tests ===")
|
||||
for test in tests:
|
||||
name = test.__name__
|
||||
try:
|
||||
print(f"[TEST] {name}...")
|
||||
test()
|
||||
passed += 1
|
||||
print(f" PASS")
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
print(f" FAIL: {e}")
|
||||
|
||||
print(f"\n=== Results: {passed} passed, {failed} failed ===")
|
||||
if failed > 0:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue