From 15d141b53e1d57d65889814b3c58eb13cdb5f47d Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Sat, 14 Feb 2026 15:51:17 -0500 Subject: [PATCH] Add season system with tests --- src/mudlib/seasons.py | 68 +++++++++++++++++++++++++++++++++ tests/test_seasons.py | 87 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/mudlib/seasons.py create mode 100644 tests/test_seasons.py diff --git a/src/mudlib/seasons.py b/src/mudlib/seasons.py new file mode 100644 index 0000000..3a10386 --- /dev/null +++ b/src/mudlib/seasons.py @@ -0,0 +1,68 @@ +"""Season system derived from game day count.""" + +from __future__ import annotations + +SEASONS: list[str] = ["spring", "summer", "autumn", "winter"] +DAYS_PER_SEASON: int = 7 # 28-day year + + +def get_season(game_day: int, days_per_season: int = DAYS_PER_SEASON) -> str: + """Return the current season based on game day count. + + Args: + game_day: Current game day (0-based) + days_per_season: Number of days per season (default 7) + + Returns: + Current season: "spring", "summer", "autumn", or "winter" + """ + if game_day < 0: + game_day = 0 + + days_per_year = days_per_season * 4 + day_of_year = game_day % days_per_year + season_index = day_of_year // days_per_season + + return SEASONS[season_index] + + +def get_day_of_year(game_day: int, days_per_season: int = DAYS_PER_SEASON) -> int: + """Return the day within the current year (0 to days_per_season*4 - 1). + + Args: + game_day: Current game day (0-based) + days_per_season: Number of days per season (default 7) + + Returns: + Day of year (0-based) + """ + if game_day < 0: + game_day = 0 + + days_per_year = days_per_season * 4 + return game_day % days_per_year + + +def get_season_description(season: str, terrain: str) -> str: + """Return a seasonal description variant for a terrain type. + + Args: + season: Current season + terrain: Terrain type + + Returns: + Seasonal description for this terrain, or empty string if no variation + """ + # Only grass and forest have seasonal variation + descriptions = { + ("spring", "grass"): "fresh green grass springs up everywhere", + ("summer", "grass"): "golden grass waves in the breeze", + ("autumn", "grass"): "the grass turns brown and brittle", + ("winter", "grass"): "frost clings to brittle brown grass", + ("spring", "forest"): "the trees burst with pale blossoms", + ("summer", "forest"): "a thick green canopy spreads overhead", + ("autumn", "forest"): "the trees blaze with amber and crimson", + ("winter", "forest"): "bare branches reach toward the sky", + } + + return descriptions.get((season, terrain), "") diff --git a/tests/test_seasons.py b/tests/test_seasons.py new file mode 100644 index 0000000..d6db1ad --- /dev/null +++ b/tests/test_seasons.py @@ -0,0 +1,87 @@ +"""Tests for the season system.""" + +from mudlib import seasons + + +def test_get_season_basic(): + assert seasons.get_season(0) == "spring" + assert seasons.get_season(6) == "spring" + assert seasons.get_season(7) == "summer" + assert seasons.get_season(13) == "summer" + assert seasons.get_season(14) == "autumn" + assert seasons.get_season(20) == "autumn" + assert seasons.get_season(21) == "winter" + assert seasons.get_season(27) == "winter" + + +def test_get_season_wraps(): + assert seasons.get_season(28) == "spring" + assert seasons.get_season(35) == "summer" + assert seasons.get_season(56) == "spring" # 2 years = 56 days + + +def test_get_season_negative_day(): + assert seasons.get_season(-1) == "spring" + assert seasons.get_season(-100) == "spring" + + +def test_get_season_custom_days_per_season(): + # 10 days per season = 40-day year + assert seasons.get_season(0, days_per_season=10) == "spring" + assert seasons.get_season(9, days_per_season=10) == "spring" + assert seasons.get_season(10, days_per_season=10) == "summer" + assert seasons.get_season(19, days_per_season=10) == "summer" + assert seasons.get_season(20, days_per_season=10) == "autumn" + assert seasons.get_season(30, days_per_season=10) == "winter" + assert seasons.get_season(40, days_per_season=10) == "spring" + + +def test_get_day_of_year(): + assert seasons.get_day_of_year(0) == 0 + assert seasons.get_day_of_year(27) == 27 + assert seasons.get_day_of_year(28) == 0 # new year + assert seasons.get_day_of_year(29) == 1 + assert seasons.get_day_of_year(56) == 0 # 2 years + + +def test_get_day_of_year_custom_days_per_season(): + # 10 days per season = 40-day year + assert seasons.get_day_of_year(0, days_per_season=10) == 0 + assert seasons.get_day_of_year(39, days_per_season=10) == 39 + assert seasons.get_day_of_year(40, days_per_season=10) == 0 + assert seasons.get_day_of_year(80, days_per_season=10) == 0 + + +def test_get_season_description_grass(): + assert "green" in seasons.get_season_description("spring", "grass") + assert "golden" in seasons.get_season_description("summer", "grass") + assert "brown" in seasons.get_season_description("autumn", "grass") + assert "frost" in seasons.get_season_description("winter", "grass") + + +def test_get_season_description_forest(): + assert "blossom" in seasons.get_season_description("spring", "forest") + assert "canopy" in seasons.get_season_description("summer", "forest") + assert "amber" in seasons.get_season_description("autumn", "forest") + assert "bare" in seasons.get_season_description("winter", "forest") + + +def test_get_season_description_minimal_variation(): + # sand, mountain, water have minimal/no seasonal variation + assert seasons.get_season_description("spring", "sand") == "" + assert seasons.get_season_description("summer", "mountain") == "" + assert seasons.get_season_description("winter", "water") == "" + + +def test_get_season_description_unknown_terrain(): + # unknown terrain returns empty string + assert seasons.get_season_description("spring", "lava") == "" + assert seasons.get_season_description("summer", "unknown") == "" + + +def test_seasons_constant(): + assert seasons.SEASONS == ["spring", "summer", "autumn", "winter"] + + +def test_days_per_season_constant(): + assert seasons.DAYS_PER_SEASON == 7