243 lines
7.9 KiB
Python
243 lines
7.9 KiB
Python
from mudlib.world.terrain import World
|
|
|
|
|
|
def test_world_deterministic_from_seed():
|
|
"""Same seed produces identical terrain."""
|
|
world1 = World(seed=42, width=100, height=100)
|
|
world2 = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
assert world1.get_tile(x, y) == world2.get_tile(x, y)
|
|
|
|
|
|
def test_world_different_seeds_produce_different_terrain():
|
|
"""Different seeds produce different terrain."""
|
|
world1 = World(seed=42, width=100, height=100)
|
|
world2 = World(seed=99, width=100, height=100)
|
|
|
|
different_tiles = 0
|
|
for y in range(100):
|
|
for x in range(100):
|
|
if world1.get_tile(x, y) != world2.get_tile(x, y):
|
|
different_tiles += 1
|
|
|
|
# at least 10% should be different
|
|
assert different_tiles > 1000
|
|
|
|
|
|
def test_world_dimensions():
|
|
"""World has correct dimensions."""
|
|
world = World(seed=42, width=100, height=50)
|
|
|
|
# should not raise for valid coordinates
|
|
world.get_tile(0, 0)
|
|
world.get_tile(99, 49)
|
|
|
|
# coordinates wrap instead of raising
|
|
assert world.get_tile(100, 0) == world.get_tile(0, 0)
|
|
assert world.get_tile(0, 50) == world.get_tile(0, 0)
|
|
assert world.get_tile(-1, 0) == world.get_tile(99, 0)
|
|
assert world.get_tile(0, -1) == world.get_tile(0, 49)
|
|
|
|
|
|
def test_get_tile_returns_valid_terrain():
|
|
"""get_tile returns valid terrain characters."""
|
|
world = World(seed=42, width=100, height=100)
|
|
valid_terrain = {".", "^", "~", "T", ":"}
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
assert tile in valid_terrain
|
|
|
|
|
|
def test_is_passable_mountains_impassable():
|
|
"""Mountains are impassable."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
if tile == "^":
|
|
assert not world.is_passable(x, y)
|
|
|
|
|
|
def test_is_passable_water_impassable():
|
|
"""Water is impassable."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
if tile == "~":
|
|
assert not world.is_passable(x, y)
|
|
|
|
|
|
def test_is_passable_grass_passable():
|
|
"""Grass is passable."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
if tile == ".":
|
|
assert world.is_passable(x, y)
|
|
|
|
|
|
def test_is_passable_forest_passable():
|
|
"""Forest is passable."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
if tile == "T":
|
|
assert world.is_passable(x, y)
|
|
|
|
|
|
def test_is_passable_sand_passable():
|
|
"""Sand is passable."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
if tile == ":":
|
|
assert world.is_passable(x, y)
|
|
|
|
|
|
def test_get_viewport_dimensions():
|
|
"""get_viewport returns correct dimensions."""
|
|
world = World(seed=42, width=100, height=100)
|
|
viewport = world.get_viewport(50, 50, width=10, height=8)
|
|
|
|
assert len(viewport) == 8
|
|
assert len(viewport[0]) == 10
|
|
|
|
|
|
def test_get_viewport_centers_correctly():
|
|
"""get_viewport centers on given coordinates."""
|
|
world = World(seed=42, width=100, height=100)
|
|
viewport = world.get_viewport(50, 50, width=5, height=5)
|
|
|
|
# center tile should be at position (2, 2) in viewport
|
|
center_tile = viewport[2][2]
|
|
assert center_tile == world.get_tile(50, 50)
|
|
|
|
|
|
def test_get_viewport_wraps_at_edges():
|
|
"""Viewport wraps around world boundaries."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
# near top-left corner - viewport should wrap to bottom/right
|
|
viewport = world.get_viewport(0, 0, width=5, height=5)
|
|
assert len(viewport) == 5
|
|
assert len(viewport[0]) == 5
|
|
# top-left of viewport wraps to bottom-right of world
|
|
assert viewport[0][0] == world.get_tile(98, 98)
|
|
|
|
# near bottom-right corner - viewport should wrap to top/left
|
|
viewport = world.get_viewport(99, 99, width=5, height=5)
|
|
assert len(viewport) == 5
|
|
assert len(viewport[0]) == 5
|
|
# bottom-right of viewport wraps to top-left of world
|
|
assert viewport[4][4] == world.get_tile(1, 1)
|
|
|
|
|
|
def test_terrain_distribution_reasonable():
|
|
"""Terrain has reasonable distribution (not all one type)."""
|
|
world = World(seed=42, width=100, height=100)
|
|
terrain_counts = {".": 0, "^": 0, "~": 0, "T": 0, ":": 0}
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
tile = world.get_tile(x, y)
|
|
terrain_counts[tile] += 1
|
|
|
|
# should have at least 3 different terrain types
|
|
types_present = sum(1 for count in terrain_counts.values() if count > 0)
|
|
assert types_present >= 3
|
|
|
|
# no single type should dominate completely (> 95%)
|
|
total = sum(terrain_counts.values())
|
|
for terrain_type, count in terrain_counts.items():
|
|
assert count < 0.95 * total, f"{terrain_type} dominates with {count}/{total}"
|
|
|
|
|
|
def test_rivers_exist():
|
|
"""Terrain has some water tiles (rivers/lakes)."""
|
|
world = World(seed=42, width=100, height=100)
|
|
water_count = 0
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
if world.get_tile(x, y) == "~":
|
|
water_count += 1
|
|
|
|
# should have at least some water
|
|
assert water_count > 0
|
|
|
|
|
|
def test_terrain_tiles_seamlessly():
|
|
"""Terrain at opposite edges matches for seamless wrapping."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
# check that a strip along the right edge is continuous with the left edge
|
|
# by verifying the elevation-derived terrain doesn't have a hard seam.
|
|
# the noise is tileable, so adjacent tiles across the boundary should be
|
|
# from the same smooth noise field (not identical, but not a cliff).
|
|
# we verify the wrap helper itself works correctly:
|
|
for y in range(100):
|
|
assert world.get_tile(-1, y) == world.get_tile(99, y)
|
|
assert world.get_tile(100, y) == world.get_tile(0, y)
|
|
for x in range(100):
|
|
assert world.get_tile(x, -1) == world.get_tile(x, 99)
|
|
assert world.get_tile(x, 100) == world.get_tile(x, 0)
|
|
|
|
|
|
def test_large_world_generation():
|
|
"""Can generate a 1000x1000 world without errors."""
|
|
world = World(seed=42, width=1000, height=1000)
|
|
|
|
# spot check some tiles
|
|
assert world.get_tile(0, 0) in {".", "^", "~", "T", ":"}
|
|
assert world.get_tile(500, 500) in {".", "^", "~", "T", ":"}
|
|
assert world.get_tile(999, 999) in {".", "^", "~", "T", ":"}
|
|
|
|
|
|
# --- terrain caching ---
|
|
|
|
|
|
def test_world_saves_cache(tmp_path):
|
|
"""World saves terrain cache when cache_dir is provided."""
|
|
world = World(seed=42, width=100, height=100, cache_dir=tmp_path)
|
|
cache_file = tmp_path / "terrain_42_100x100.bin"
|
|
assert cache_file.exists()
|
|
assert cache_file.stat().st_size == 100 * 100 # 1 byte per tile
|
|
assert not world.cached # first run generates, doesn't load from cache
|
|
|
|
|
|
def test_world_loads_from_cache(tmp_path):
|
|
"""World loads terrain from cache on second creation."""
|
|
world1 = World(seed=42, width=100, height=100, cache_dir=tmp_path)
|
|
world2 = World(seed=42, width=100, height=100, cache_dir=tmp_path)
|
|
|
|
for y in range(100):
|
|
for x in range(100):
|
|
assert world1.get_tile(x, y) == world2.get_tile(x, y)
|
|
|
|
assert world2.cached
|
|
|
|
|
|
def test_world_cache_different_params(tmp_path):
|
|
"""Different seed/size doesn't use wrong cache."""
|
|
World(seed=42, width=100, height=100, cache_dir=tmp_path)
|
|
world2 = World(seed=99, width=100, height=100, cache_dir=tmp_path)
|
|
assert not world2.cached # different seed, must generate
|
|
|
|
|
|
def test_world_no_cache_by_default():
|
|
"""World doesn't cache when no cache_dir provided."""
|
|
world = World(seed=42, width=50, height=50)
|
|
assert not world.cached
|