HeightMap: core class with scalar operations (closes #193)
Implement the foundational HeightMap class for procedural generation: - HeightMap(size, fill=0.0) constructor with libtcod backend - Immutable size property after construction - Scalar operations returning self for method chaining: - fill(value), clear() - add_constant(value), scale(factor) - clamp(min=0.0, max=1.0), normalize(min=0.0, max=1.0) Includes procedural generation spec document and unit tests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b32f5af28c
commit
c095be4b73
5 changed files with 1586 additions and 1 deletions
199
tests/unit/test_heightmap_basic.py
Normal file
199
tests/unit/test_heightmap_basic.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Unit tests for mcrfpy.HeightMap core functionality (#193)
|
||||
|
||||
Tests the HeightMap class constructor, size property, and scalar operations.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import mcrfpy
|
||||
|
||||
|
||||
def test_constructor_basic():
|
||||
"""HeightMap can be created with a size tuple"""
|
||||
hmap = mcrfpy.HeightMap((100, 50))
|
||||
assert hmap is not None
|
||||
print("PASS: test_constructor_basic")
|
||||
|
||||
|
||||
def test_constructor_with_fill():
|
||||
"""HeightMap can be created with a fill value"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
assert hmap is not None
|
||||
print("PASS: test_constructor_with_fill")
|
||||
|
||||
|
||||
def test_size_property():
|
||||
"""size property returns correct dimensions"""
|
||||
hmap = mcrfpy.HeightMap((100, 50))
|
||||
size = hmap.size
|
||||
assert size == (100, 50), f"Expected (100, 50), got {size}"
|
||||
print("PASS: test_size_property")
|
||||
|
||||
|
||||
def test_size_immutable():
|
||||
"""size property is read-only"""
|
||||
hmap = mcrfpy.HeightMap((100, 50))
|
||||
try:
|
||||
hmap.size = (200, 100)
|
||||
print("FAIL: test_size_immutable - should have raised AttributeError")
|
||||
sys.exit(1)
|
||||
except AttributeError:
|
||||
pass
|
||||
print("PASS: test_size_immutable")
|
||||
|
||||
|
||||
def test_fill_method():
|
||||
"""fill() sets all cells and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10))
|
||||
result = hmap.fill(0.5)
|
||||
assert result is hmap, "fill() should return self"
|
||||
print("PASS: test_fill_method")
|
||||
|
||||
|
||||
def test_clear_method():
|
||||
"""clear() sets all cells to 0.0 and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
result = hmap.clear()
|
||||
assert result is hmap, "clear() should return self"
|
||||
print("PASS: test_clear_method")
|
||||
|
||||
|
||||
def test_add_constant_method():
|
||||
"""add_constant() adds to all cells and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10))
|
||||
result = hmap.add_constant(0.25)
|
||||
assert result is hmap, "add_constant() should return self"
|
||||
print("PASS: test_add_constant_method")
|
||||
|
||||
|
||||
def test_scale_method():
|
||||
"""scale() multiplies all cells and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
result = hmap.scale(2.0)
|
||||
assert result is hmap, "scale() should return self"
|
||||
print("PASS: test_scale_method")
|
||||
|
||||
|
||||
def test_clamp_method():
|
||||
"""clamp() clamps values and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
result = hmap.clamp(0.0, 1.0)
|
||||
assert result is hmap, "clamp() should return self"
|
||||
print("PASS: test_clamp_method")
|
||||
|
||||
|
||||
def test_clamp_with_defaults():
|
||||
"""clamp() works with default parameters"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
result = hmap.clamp() # Uses defaults 0.0, 1.0
|
||||
assert result is hmap
|
||||
print("PASS: test_clamp_with_defaults")
|
||||
|
||||
|
||||
def test_normalize_method():
|
||||
"""normalize() rescales values and returns self"""
|
||||
hmap = mcrfpy.HeightMap((10, 10))
|
||||
hmap.fill(0.25).add_constant(0.1) # Some values
|
||||
result = hmap.normalize(0.0, 1.0)
|
||||
assert result is hmap, "normalize() should return self"
|
||||
print("PASS: test_normalize_method")
|
||||
|
||||
|
||||
def test_normalize_with_defaults():
|
||||
"""normalize() works with default parameters"""
|
||||
hmap = mcrfpy.HeightMap((10, 10), fill=0.5)
|
||||
result = hmap.normalize() # Uses defaults 0.0, 1.0
|
||||
assert result is hmap
|
||||
print("PASS: test_normalize_with_defaults")
|
||||
|
||||
|
||||
def test_method_chaining():
|
||||
"""Methods can be chained"""
|
||||
hmap = mcrfpy.HeightMap((10, 10))
|
||||
result = hmap.fill(0.5).scale(2.0).clamp(0.0, 1.0)
|
||||
assert result is hmap, "Chained methods should return self"
|
||||
print("PASS: test_method_chaining")
|
||||
|
||||
|
||||
def test_complex_chaining():
|
||||
"""Complex chains work correctly"""
|
||||
hmap = mcrfpy.HeightMap((100, 100))
|
||||
result = (hmap
|
||||
.fill(0.0)
|
||||
.add_constant(0.5)
|
||||
.scale(1.5)
|
||||
.clamp(0.0, 1.0)
|
||||
.normalize(0.2, 0.8))
|
||||
assert result is hmap
|
||||
print("PASS: test_complex_chaining")
|
||||
|
||||
|
||||
def test_repr():
|
||||
"""repr() returns a readable string"""
|
||||
hmap = mcrfpy.HeightMap((100, 50))
|
||||
r = repr(hmap)
|
||||
assert "HeightMap" in r
|
||||
assert "100" in r and "50" in r
|
||||
print(f"PASS: test_repr - {r}")
|
||||
|
||||
|
||||
def test_invalid_size():
|
||||
"""Negative or zero size raises ValueError"""
|
||||
try:
|
||||
mcrfpy.HeightMap((0, 10))
|
||||
print("FAIL: test_invalid_size - should have raised ValueError for width=0")
|
||||
sys.exit(1)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
mcrfpy.HeightMap((10, -5))
|
||||
print("FAIL: test_invalid_size - should have raised ValueError for height=-5")
|
||||
sys.exit(1)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
print("PASS: test_invalid_size")
|
||||
|
||||
|
||||
def test_invalid_size_type():
|
||||
"""Non-tuple size raises TypeError"""
|
||||
try:
|
||||
mcrfpy.HeightMap([100, 50]) # list instead of tuple
|
||||
print("FAIL: test_invalid_size_type - should have raised TypeError")
|
||||
sys.exit(1)
|
||||
except TypeError:
|
||||
pass
|
||||
print("PASS: test_invalid_size_type")
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all tests"""
|
||||
print("Running HeightMap basic tests...")
|
||||
print()
|
||||
|
||||
test_constructor_basic()
|
||||
test_constructor_with_fill()
|
||||
test_size_property()
|
||||
test_size_immutable()
|
||||
test_fill_method()
|
||||
test_clear_method()
|
||||
test_add_constant_method()
|
||||
test_scale_method()
|
||||
test_clamp_method()
|
||||
test_clamp_with_defaults()
|
||||
test_normalize_method()
|
||||
test_normalize_with_defaults()
|
||||
test_method_chaining()
|
||||
test_complex_chaining()
|
||||
test_repr()
|
||||
test_invalid_size()
|
||||
test_invalid_size_type()
|
||||
|
||||
print()
|
||||
print("All HeightMap basic tests PASSED!")
|
||||
|
||||
|
||||
# Run tests directly
|
||||
run_all_tests()
|
||||
sys.exit(0)
|
||||
Loading…
Add table
Add a link
Reference in a new issue