Tileable Perlin noise: each octave wraps its integer grid coordinates with modulo at the octave's frequency, so gradients at opposite edges match and the noise field is continuous across the boundary. Coarse elevation grid interpolation wraps instead of padding boundary cells. Rivers can flow across world edges. All coordinate access (get_tile, is_passable, get_viewport) wraps via modulo. Movement, spawn search, nearby-player detection, and viewport relative positions all handle the toroidal topology.
206 lines
6.6 KiB
Python
206 lines
6.6 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", ":"}
|