diff --git a/README.md b/README.md index 5b89d68..3c6fd49 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ Alpha. One season, bandit raids plus two friendly events, six buildings. Construction also saws planks, which higher building levels need, and builds fittings from them: one-off upgrades a building takes once it's high enough (shelving, a well pump, weapon racks, enchanted chests…). Population eats food, - pays taxes, and grows up to the housing cap; it promotes the settlement + pays taxes, and fills the houses (empty homes draw settlers, about a + quarter of all homes a day, plus slow family growth); it promotes the settlement (hamlet, village at 40, town at 90, city at 150), which raises building level caps and gives everyone bank slots. - **Prayer:** enemies drop bones; burying them gives prayer points and diff --git a/game.py b/game.py index 6b8fd72..faff33c 100644 --- a/game.py +++ b/game.py @@ -482,7 +482,10 @@ TIERS = [ {"name": "City", "pop": 150, "cap": 99, "houses": 8}, ] TIER_SLOTS = 2 -GROWTH_PER_DAY = 0.12 +# Growth: empty homes draw settlers (a share of all the homes, every day, while +# any stand empty), and families grow a little on their own. +SETTLERS_PER_HOME = 0.25 +FAMILY_GROWTH = 0.05 RAID_GROUPS = ["the Red Hand", "the Hollow Crows", "the Blackwater gang", "the Ashen Wolves", "the Gallows Men", "the Thornback raiders"] @@ -1431,6 +1434,13 @@ def _start_next(world: dict, now: float) -> None: log(world, now, "build", f"{verb} the {BUILDINGS[key]['name'].lower()} began") +def growth_per_day(world: dict) -> float: + """New townsfolk a day while there's room: settlers for the homes, plus families.""" + pop = world["city"]["population"] + growth = SETTLERS_PER_HOME * housing(world) + FAMILY_GROWTH * pop + return growth * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1) + + def population_view(world: dict) -> dict: """Population, food and tier, with the numbers that explain them.""" city = world["city"] @@ -1439,7 +1449,7 @@ def population_view(world: dict) -> dict: if city["starving"]: growth = -pop * 0.04 elif pop < room - 0.01: - growth = max(0.5, pop * GROWTH_PER_DAY) + growth = growth_per_day(world) elif pop > room: growth = -pop * 0.05 else: @@ -1619,8 +1629,7 @@ def _city_hour(world: dict, now: float) -> None: else: room = housing(world) if pop < room: - growth = max(0.5, pop * GROWTH_PER_DAY) * city_rate(world, "growth") * (1.25 if fitted(world, "furniture") else 1) - city["population"] = min(room, pop + growth / 24) + city["population"] = min(room, pop + growth_per_day(world) / 24) elif pop > room: city["population"] = max(room, pop - pop * 0.05 / 24) city["treasury"] += pop * 0.5 / 24 diff --git a/tests/test_game.py b/tests/test_game.py index f32e628..fb20a65 100644 --- a/tests/test_game.py +++ b/tests/test_game.py @@ -302,11 +302,26 @@ class CityTest(unittest.TestCase): world = world_with() city = world["city"] city["stockpile"] = {"bread": 5} + city["population"] = 25.0 # a full town, so nobody new arrives world["next_fortune_at"] = None # no lucky wagon of bread game.tick(world, T0 + 4 * game.DAY, random.Random(1)) self.assertTrue(city["starving"]) self.assertNotIn("bread", city["stockpile"]) - self.assertLess(city["population"], 20) + self.assertLess(city["population"], 25) + + def test_empty_homes_draw_settlers(self): + world = world_with() + city = world["city"] + city["population"] = 22.0 + city["buildings"]["houses"]["level"] = 2 + city["stockpile"] = {"bread": 10_000} + self.assertAlmostEqual(game.growth_per_day(world), 0.25 * 65 + 0.05 * 22) + for _hour in range(24): + game._city_hour(world, T0) + self.assertGreater(city["population"], 38) + for _hour in range(48): + game._city_hour(world, T0) + self.assertEqual(city["population"], 65) def test_fed_city_grows_to_housing_and_pays_tax(self): world = world_with() @@ -899,8 +914,9 @@ class ConstructionTest(unittest.TestCase): def test_townsfolk_trickle_and_levels_help(self): world = self.site() + world["city"]["population"] = 25.0 # houses full, so the workforce stays put game.tick(world, T0 + 10 * game.HOUR, random.Random(1)) - self.assertAlmostEqual(world["city"]["construction"]["labour"], 10 * 20 * game.TOWNSFOLK_LABOUR, delta=5) + self.assertAlmostEqual(world["city"]["construction"]["labour"], 10 * 25 * game.TOWNSFOLK_LABOUR, delta=5) p = world["players"]["bob"] self.assertEqual(game.labour_per_action(p), 1) p["xp"]["construction"] = game.XP_TABLE[40]