McRogueFace/tests/unit/discretemap_heightmap_test.py

280 lines
8.8 KiB
Python

#!/usr/bin/env python3
"""Unit tests for DiscreteMap <-> HeightMap integration."""
import mcrfpy
import sys
from enum import IntEnum
class Terrain(IntEnum):
WATER = 0
SAND = 1
GRASS = 2
FOREST = 3
MOUNTAIN = 4
def test_from_heightmap_basic():
"""Test basic HeightMap to DiscreteMap conversion."""
# Create a simple heightmap
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
# Create a simple mapping
mapping = [
((0.0, 0.3), 0),
((0.3, 0.6), 1),
((0.6, 1.0), 2),
]
dmap = mcrfpy.DiscreteMap.from_heightmap(hmap, mapping)
# 0.5 should map to category 1
assert dmap[5, 5] == 1, f"Expected 1, got {dmap[5, 5]}"
print(" [PASS] from_heightmap basic")
def test_from_heightmap_full_range():
"""Test conversion with values spanning the full range."""
hmap = mcrfpy.HeightMap((100, 1))
# Create gradient
for x in range(100):
hmap[x, 0] = x / 100.0 # 0.0 to 0.99
mapping = [
((0.0, 0.25), Terrain.WATER),
((0.25, 0.5), Terrain.SAND),
((0.5, 0.75), Terrain.GRASS),
((0.75, 1.0), Terrain.FOREST),
]
dmap = mcrfpy.DiscreteMap.from_heightmap(hmap, mapping)
# Check values at key positions
assert dmap[10, 0] == Terrain.WATER, f"Expected WATER at 10, got {dmap[10, 0]}"
assert dmap[30, 0] == Terrain.SAND, f"Expected SAND at 30, got {dmap[30, 0]}"
assert dmap[60, 0] == Terrain.GRASS, f"Expected GRASS at 60, got {dmap[60, 0]}"
assert dmap[80, 0] == Terrain.FOREST, f"Expected FOREST at 80, got {dmap[80, 0]}"
print(" [PASS] from_heightmap full range")
def test_from_heightmap_with_enum():
"""Test from_heightmap with enum parameter."""
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
mapping = [
((0.0, 0.3), Terrain.WATER),
((0.3, 0.7), Terrain.GRASS),
((0.7, 1.0), Terrain.MOUNTAIN),
]
dmap = mcrfpy.DiscreteMap.from_heightmap(hmap, mapping, enum=Terrain)
# Value should be returned as enum member
val = dmap[5, 5]
assert val == Terrain.GRASS, f"Expected Terrain.GRASS, got {val}"
assert isinstance(val, Terrain), f"Expected Terrain type, got {type(val)}"
print(" [PASS] from_heightmap with enum")
def test_to_heightmap_basic():
"""Test basic DiscreteMap to HeightMap conversion."""
dmap = mcrfpy.DiscreteMap((10, 10), fill=100)
hmap = dmap.to_heightmap()
# Direct conversion: uint8 -> float
assert abs(hmap[5, 5] - 100.0) < 0.001, f"Expected 100.0, got {hmap[5, 5]}"
print(" [PASS] to_heightmap basic")
def test_to_heightmap_with_mapping():
"""Test to_heightmap with value mapping."""
dmap = mcrfpy.DiscreteMap((10, 10))
# Create pattern
dmap.fill(0, pos=(0, 0), size=(5, 10)) # Left half = 0
dmap.fill(1, pos=(5, 0), size=(5, 10)) # Right half = 1
# Map discrete values to heights
mapping = {
0: 0.2,
1: 0.8,
}
hmap = dmap.to_heightmap(mapping)
assert abs(hmap[2, 5] - 0.2) < 0.001, f"Expected 0.2, got {hmap[2, 5]}"
assert abs(hmap[7, 5] - 0.8) < 0.001, f"Expected 0.8, got {hmap[7, 5]}"
print(" [PASS] to_heightmap with mapping")
def test_roundtrip():
"""Test HeightMap -> DiscreteMap -> HeightMap roundtrip."""
# Create original heightmap
original = mcrfpy.HeightMap((50, 50))
for y in range(50):
for x in range(50):
original[x, y] = (x + y) / 100.0 # Gradient 0.0 to 0.98
# Convert to discrete with specific ranges
mapping = [
((0.0, 0.33), 0),
((0.33, 0.66), 1),
((0.66, 1.0), 2),
]
dmap = mcrfpy.DiscreteMap.from_heightmap(original, mapping)
# Convert back with value mapping
reverse_mapping = {
0: 0.15, # Midpoint of first range
1: 0.5, # Midpoint of second range
2: 0.85, # Midpoint of third range
}
restored = dmap.to_heightmap(reverse_mapping)
# Verify approximate restoration
assert abs(restored[0, 0] - 0.15) < 0.01, f"Expected ~0.15 at (0,0), got {restored[0, 0]}"
assert abs(restored[25, 25] - 0.5) < 0.01, f"Expected ~0.5 at (25,25), got {restored[25, 25]}"
print(" [PASS] Roundtrip conversion")
def test_query_methods():
"""Test count, count_range, min_max, histogram."""
dmap = mcrfpy.DiscreteMap((10, 10))
# Create pattern with different values
dmap.fill(0, pos=(0, 0), size=(5, 5)) # 25 cells with 0
dmap.fill(1, pos=(5, 0), size=(5, 5)) # 25 cells with 1
dmap.fill(2, pos=(0, 5), size=(5, 5)) # 25 cells with 2
dmap.fill(3, pos=(5, 5), size=(5, 5)) # 25 cells with 3
# Test count
assert dmap.count(0) == 25, f"Expected 25 zeros, got {dmap.count(0)}"
assert dmap.count(1) == 25, f"Expected 25 ones, got {dmap.count(1)}"
assert dmap.count(4) == 0, f"Expected 0 fours, got {dmap.count(4)}"
# Test count_range
assert dmap.count_range(0, 1) == 50, f"Expected 50 in range 0-1, got {dmap.count_range(0, 1)}"
assert dmap.count_range(0, 3) == 100, f"Expected 100 in range 0-3, got {dmap.count_range(0, 3)}"
# Test min_max
min_val, max_val = dmap.min_max()
assert min_val == 0, f"Expected min 0, got {min_val}"
assert max_val == 3, f"Expected max 3, got {max_val}"
# Test histogram
hist = dmap.histogram()
assert hist[0] == 25, f"Expected 25 for value 0, got {hist.get(0)}"
assert hist[1] == 25, f"Expected 25 for value 1, got {hist.get(1)}"
assert hist[2] == 25, f"Expected 25 for value 2, got {hist.get(2)}"
assert hist[3] == 25, f"Expected 25 for value 3, got {hist.get(3)}"
assert 4 not in hist, "Value 4 should not be in histogram"
print(" [PASS] Query methods")
def test_bool_int():
"""Test bool() with integer condition."""
dmap = mcrfpy.DiscreteMap((10, 10), fill=0)
dmap.fill(1, pos=(2, 2), size=(3, 3))
mask = dmap.bool(1)
# Should be 1 where original is 1, 0 elsewhere
assert mask[0, 0] == 0, f"Expected 0 outside region, got {mask[0, 0]}"
assert mask[3, 3] == 1, f"Expected 1 inside region, got {mask[3, 3]}"
assert mask.count(1) == 9, f"Expected 9 ones, got {mask.count(1)}"
print(" [PASS] bool() with int")
def test_bool_set():
"""Test bool() with set condition."""
dmap = mcrfpy.DiscreteMap((10, 10))
dmap.fill(0, pos=(0, 0), size=(5, 5))
dmap.fill(1, pos=(5, 0), size=(5, 5))
dmap.fill(2, pos=(0, 5), size=(5, 5))
dmap.fill(3, pos=(5, 5), size=(5, 5))
# Match 0 or 2
mask = dmap.bool({0, 2})
assert mask[2, 2] == 1, "Expected 1 where value is 0"
assert mask[7, 2] == 0, "Expected 0 where value is 1"
assert mask[2, 7] == 1, "Expected 1 where value is 2"
assert mask[7, 7] == 0, "Expected 0 where value is 3"
assert mask.count(1) == 50, f"Expected 50 ones, got {mask.count(1)}"
print(" [PASS] bool() with set")
def test_bool_callable():
"""Test bool() with callable condition."""
dmap = mcrfpy.DiscreteMap((10, 10), fill=0)
for y in range(10):
for x in range(10):
dmap[x, y] = x + y # Values 0-18
# Match where value > 10
mask = dmap.bool(lambda v: v > 10)
assert mask[5, 5] == 0, "Expected 0 where value is 10"
assert mask[6, 6] == 1, "Expected 1 where value is 12"
assert mask[9, 9] == 1, "Expected 1 where value is 18"
print(" [PASS] bool() with callable")
def test_mask_memoryview():
"""Test mask() returns working memoryview."""
dmap = mcrfpy.DiscreteMap((10, 10), fill=42)
mv = dmap.mask()
assert len(mv) == 100, f"Expected 100 bytes, got {len(mv)}"
assert mv[0] == 42, f"Expected 42, got {mv[0]}"
# Test writing through memoryview
mv[50] = 99
assert dmap[0, 5] == 99, f"Expected 99, got {dmap[0, 5]}"
print(" [PASS] mask() memoryview")
def test_enum_type_property():
"""Test enum_type property getter/setter."""
dmap = mcrfpy.DiscreteMap((10, 10), fill=1)
# Initially no enum
assert dmap.enum_type is None, "Expected None initially"
# Set enum type
dmap.enum_type = Terrain
assert dmap.enum_type is Terrain, "Expected Terrain enum"
# Value should now return enum member
val = dmap[5, 5]
assert val == Terrain.SAND, f"Expected Terrain.SAND, got {val}"
# Clear enum type
dmap.enum_type = None
val = dmap[5, 5]
assert isinstance(val, int), f"Expected int after clearing enum, got {type(val)}"
print(" [PASS] enum_type property")
def main():
print("Running DiscreteMap HeightMap integration tests...")
test_from_heightmap_basic()
test_from_heightmap_full_range()
test_from_heightmap_with_enum()
test_to_heightmap_basic()
test_to_heightmap_with_mapping()
test_roundtrip()
test_query_methods()
test_bool_int()
test_bool_set()
test_bool_callable()
test_mask_memoryview()
test_enum_type_property()
print("All DiscreteMap HeightMap integration tests PASSED!")
sys.exit(0)
if __name__ == "__main__":
main()