1000x1000 tile world generated deterministically from a seed using layered Perlin noise. Terrain derived from elevation: mountains, forests, grasslands, sand, water, with rivers traced downhill from peaks. ANSI-colored viewport centered on player. Command system with registry/dispatch, 8-direction movement (n/s/e/w + diagonals), look/l, quit/q. Players see arrival/departure messages. Set connect_maxwait=0.5 on telnetlib3 to avoid the 4s CHARSET negotiation timeout — MUD clients reject CHARSET immediately via MTTS.
188 lines
5.5 KiB
Python
188 lines
5.5 KiB
Python
import pytest
|
|
|
|
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)
|
|
|
|
# should raise for out of bounds
|
|
with pytest.raises((IndexError, ValueError)):
|
|
world.get_tile(100, 0)
|
|
|
|
with pytest.raises((IndexError, ValueError)):
|
|
world.get_tile(0, 50)
|
|
|
|
|
|
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_handles_edges():
|
|
"""Viewport handles world boundaries correctly."""
|
|
world = World(seed=42, width=100, height=100)
|
|
|
|
# near top-left corner
|
|
viewport = world.get_viewport(2, 2, width=5, height=5)
|
|
assert len(viewport) == 5
|
|
assert len(viewport[0]) == 5
|
|
|
|
# near bottom-right corner
|
|
viewport = world.get_viewport(97, 97, width=5, height=5)
|
|
assert len(viewport) == 5
|
|
assert len(viewport[0]) == 5
|
|
|
|
|
|
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_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", ":"}
|