Classes become trades; building costs draw on every trade

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-19 22:45:32 +01:00
parent f810e16eb8
commit f4b2bb1320
6 changed files with 305 additions and 137 deletions

View file

@ -8,6 +8,9 @@ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import game # noqa: E402
T0 = 1_800_000_000.0
# A test-only class with no bonuses, so tests of the rules aren't skewed by a trade.
game.CLASSES.setdefault("commoner", {"name": "Commoner", "icon": "", "fast": [], "craft": [],
"bonus": "", "masterwork": "", "signature": "", "glyph": "farmer", "tint": ""})
def world_with(*players):
@ -28,7 +31,7 @@ class XpTest(unittest.TestCase):
class ActionTest(unittest.TestCase):
def test_gathering_runs_while_away(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
self.assertIsNone(game.start_action(world, p, "mine_stone", T0))
summary = game.process_player(world, p, T0 + 200, random.Random(1))
@ -38,7 +41,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["action"]["start"], T0 + 200)
def test_make_one_then_back_to_work(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"copper_bar": 10, "logs": 10}
game.start_action(world, p, "mine_stone", T0)
@ -52,7 +55,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["bank"]["stone"], 5 + 2) # mining carried on from when the pickaxe was done
def test_make_one_while_idle_just_stops(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
game.start_action(world, p, "cut_logs", T0, once=True)
game.process_player(world, p, T0 + 500, random.Random(1))
@ -61,7 +64,7 @@ class ActionTest(unittest.TestCase):
self.assertIsNone(p["stopped"])
def test_class_speed_and_lucky_yield(self):
world = world_with(("pia", "pioneer"))
world = world_with(("pia", "miner"))
p = world["players"]["pia"]
game.start_action(world, p, "mine_stone", T0)
summary = game.process_player(world, p, T0 + 1600, random.Random(3))
@ -70,7 +73,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["bank"]["stone"], 100 + summary["lucky"])
def test_offline_cap(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
game.start_action(world, p, "cut_logs", T0)
summary = game.process_player(world, p, T0 + 3 * game.DAY, random.Random(1))
@ -78,7 +81,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["action"]["start"], T0 + 3 * game.DAY)
def test_crafting_stops_when_inputs_run_out(self):
world = world_with(("ann", "warrior"))
world = world_with(("ann", "commoner"))
p = world["players"]["ann"]
p["bank"] = {"copper_ore": 3, "logs": 10}
game.start_action(world, p, "smith_copper_bar", T0)
@ -89,16 +92,16 @@ class ActionTest(unittest.TestCase):
self.assertEqual(summary["stopped"], "Out of copper ore")
def test_level_and_city_requirements(self):
world = world_with(("ann", "warrior"))
world = world_with(("ann", "commoner"))
p = world["players"]["ann"]
self.assertEqual(game.start_action(world, p, "mine_iron", T0), "Needs level 15")
p["xp"]["smithing"] = game.XP_TABLE[30]
p["bank"] = {"iron_bar": 10}
self.assertEqual(game.start_action(world, p, "smith_iron_sword", T0), "Needs Smithy level 1")
self.assertEqual(game.start_action(world, p, "recon_roads", T0), "Only scouts can do that")
self.assertIsNone(game.can_start(world, p, game.ACTION_BY_ID["recon_roads"])) # anyone can scout
def test_full_bank_overflows_to_city(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"stone": game.BASE_STACK * game.BASE_SLOTS - 2}
game.start_action(world, p, "mine_stone", T0)
@ -110,7 +113,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["donated"], 3)
def test_full_bank_sells_what_the_city_cannot_use(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"copper_ore": game.BASE_STACK * game.BASE_SLOTS - 1}
game.start_action(world, p, "mine_copper", T0)
@ -121,7 +124,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(p["favour"], 0)
def test_only_useful_things_can_be_donated(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"copper_ore": 10, "bricks": 10, "bread": 5, "iron_helmet": 1, "steel_sword": 1}
self.assertEqual(game.donate(world, p, "copper_ore", 10, T0), "The city has no use for that")
@ -132,7 +135,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(game.city_uses("bread"), ["the granary"])
def test_a_full_stack_spills_into_another_slot(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"coal": game.BASE_STACK, "logs": 10}
self.assertEqual(game.slots_used(world, p), 2)
@ -143,7 +146,7 @@ class ActionTest(unittest.TestCase):
self.assertEqual(game.bank_room(world, p, "coal"), 500 + (game.BASE_SLOTS - 4) * game.BASE_STACK)
def test_full_bank_can_stop_instead(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["overflow"] = "stop"
p["bank"] = {f"x{i}": 1 for i in range(game.BASE_SLOTS)}
@ -153,11 +156,11 @@ class ActionTest(unittest.TestCase):
self.assertNotIn("stone", p["bank"])
def test_combat_pays_and_loses(self):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
p["bank"] = {"bread": 500}
game.start_action(world, p, "fight_wolves", T0)
summary = game.process_player(world, p, T0 + 48 * 100, random.Random(5))
summary = game.process_player(world, p, T0 + 60 * 100, random.Random(5))
self.assertEqual(summary["done"], 100)
self.assertEqual(summary["wins"] + summary["losses"], 100)
self.assertEqual(p["gold"], summary["wins"] * 2)
@ -165,7 +168,7 @@ class ActionTest(unittest.TestCase):
class GiftTest(unittest.TestCase):
def test_give_moves_items_within_bank_room(self):
world = world_with(("fred", "pioneer"), ("kay", "artisan"))
world = world_with(("fred", "miner"), ("kay", "miner"))
fred, kay = world["players"]["fred"], world["players"]["kay"]
fred["bank"] = {"copper_ore": 300}
self.assertIsNone(game.give(world, fred, kay, "copper_ore", 200, T0))
@ -184,17 +187,19 @@ class GiftTest(unittest.TestCase):
class CityTest(unittest.TestCase):
def test_fund_from_bank_and_stores_then_builds(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
city = world["city"]
p["bank"] = {"stone": 1500}
city["stockpile"] = {"bricks": 600}
city["stockpile"] = {"bricks": 600, "provisions": 90}
city["treasury"] = 300
self.assertIsNone(game.fund(world, p, "walls", "stone", "all", "bank", T0))
self.assertEqual(p["bank"], {"stone": 300}) # only what the fund needed
self.assertEqual(p["favour"], 1200)
self.assertEqual(p["bank"], {"stone": 500}) # only what the fund needed
self.assertEqual(p["favour"], 1000)
self.assertIsNone(game.fund(world, p, "walls", "bricks", 500, "city", T0))
self.assertEqual(city["stockpile"], {"bricks": 100})
self.assertEqual(city["stockpile"], {"bricks": 350, "provisions": 90})
self.assertIsNone(game.fund(world, p, "walls", "provisions", "all", "city", T0))
self.assertEqual(city["stockpile"], {"bricks": 350})
self.assertIsNone(city["construction"])
self.assertEqual(game.fund(world, p, "walls", "stone", 1, "bank", T0), "The fund doesn't need that")
self.assertIsNone(game.fund(world, p, "walls", "gold", "all", "city", T0))
@ -207,7 +212,7 @@ class CityTest(unittest.TestCase):
self.assertEqual(game.defence(world)["parts"]["walls"], 20)
def test_funded_buildings_queue_behind_the_builders(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
city = world["city"]
for key in ("walls", "houses"):
@ -222,7 +227,7 @@ class CityTest(unittest.TestCase):
self.assertEqual(city["queue"], [])
def test_raids_never_touch_funds(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
city = world["city"]
city["buildings"]["walls"]["fund"] = {"stone": 1000}
city["stockpile"] = {"stone": 1000}
@ -235,7 +240,7 @@ class CityTest(unittest.TestCase):
self.assertEqual(city["buildings"]["walls"]["fund"], {"stone": 1000})
def test_tier_caps_levels_and_promotes_for_good(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
city = world["city"]
city["buildings"]["walls"]["level"] = 1
self.assertIsNone(game.fund_target(world, "walls")) # hamlet: level 1 only
@ -252,7 +257,7 @@ class CityTest(unittest.TestCase):
self.assertEqual(city["tier"], 1)
def test_damage_turns_the_fund_towards_repairs_and_keeps_the_rest(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
city = world["city"]
city["buildings"]["walls"].update(level=1, damaged=True, fund={"stone": 2000})
@ -336,7 +341,7 @@ class CityTest(unittest.TestCase):
def test_speed_scales_time(self):
world = game.new_world(T0, speed=60)
world["players"]["fred"] = game.new_player("fred", "Fred", "warrior", T0)
world["players"]["fred"] = game.new_player("fred", "Fred", "commoner", T0)
p = world["players"]["fred"]
game.start_action(world, p, "mine_stone", T0)
game.process_player(world, p, T0 + 10, random.Random(1))
@ -361,7 +366,7 @@ class EventTest(unittest.TestCase):
self.assertGreaterEqual(view["latest"], raid["fire_at"])
def test_recon_sharpens_intel_as_the_raid_draws_near(self):
world = world_with(("sam", "scout"))
world = world_with(("sam", "ranger"))
raid = self.announce(world)
p = world["players"]["sam"]
p["xp"]["reconnaissance"] = game.XP_TABLE[20]
@ -382,14 +387,14 @@ class EventTest(unittest.TestCase):
self.assertTrue(view["camp"])
def test_scouting_stops_counting_once_the_threat_is_fully_scouted(self):
world = world_with(("sam", "scout"))
world = world_with(("sam", "commoner"))
raid = self.announce(world)
full = game.intel_costs(raid)[-1]
raid["recon"] = full - 1
world["city"]["recon_boost"] = 30
p = world["players"]["sam"]
game.start_action(world, p, "recon_roads", T0)
game.process_player(world, p, T0 + 48 * 3 + 1, random.Random(1))
game.process_player(world, p, T0 + 60 * 3 + 1, random.Random(1))
self.assertEqual(raid["recon"], full)
self.assertEqual(world["city"]["recon_boost"], 29) # leads only spent while there's something to learn
self.assertIsNotNone(p["action"]) # still trains the skill
@ -404,17 +409,17 @@ class EventTest(unittest.TestCase):
self.assertEqual(game.intel_costs(dragon), [8, 22, 45])
def test_the_spymasters_guild_boosts_and_gathers_recon(self):
world = world_with()
world = world_with(("sam", "commoner"))
raid = self.announce(world)
action = game.ACTION_BY_ID["recon_roads"]
self.assertEqual(game.recon_gain(world, action), 1)
action, p = game.ACTION_BY_ID["recon_roads"], world["players"]["sam"]
self.assertEqual(game.recon_gain(world, p, action), 1)
world["city"]["buildings"]["spymaster"]["level"] = 2
self.assertEqual(game.recon_gain(world, action), 1.5)
self.assertEqual(game.recon_gain(world, p, action), 1.5)
game.tick(world, T0 + 2 * game.HOUR, random.Random(2))
self.assertAlmostEqual(raid["recon"], 2.0)
def test_victory_rewards_garrison(self):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
raid = self.announce(world)
p = world["players"]["wil"]
p["xp"]["combat"] = game.XP_TABLE[60]
@ -427,7 +432,7 @@ class EventTest(unittest.TestCase):
self.assertFalse(p["garrison"])
def test_defeat_culls_and_loots_but_spares_banks(self):
world = world_with(("fred", "warrior"))
world = world_with(("fred", "commoner"))
p = world["players"]["fred"]
p["bank"] = {"stone": 500}
city = world["city"]
@ -453,7 +458,7 @@ class EventTest(unittest.TestCase):
self.assertLess(qty, full[item])
def test_sabotage_needs_a_located_camp(self):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
p["xp"]["combat"] = game.XP_TABLE[40]
p["hp"] = game.max_hp(p)
@ -471,7 +476,7 @@ class EventTest(unittest.TestCase):
class CombatTest(unittest.TestCase):
def fighter(self, level=10, **equip):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
p["xp"]["combat"] = game.XP_TABLE[level]
p["hp"] = game.max_hp(p)
@ -524,7 +529,7 @@ class CombatTest(unittest.TestCase):
class NewSkillsTest(unittest.TestCase):
def test_tools_speed_up_gathering(self):
world = world_with(("pia", "warrior"))
world = world_with(("pia", "commoner"))
p = world["players"]["pia"]
plain = game.action_time(world, p, game.ACTION_BY_ID["mine_stone"])
p["equip"]["tool_mining"] = "iron_pickaxe"
@ -532,19 +537,19 @@ class NewSkillsTest(unittest.TestCase):
self.assertEqual(game.action_time(world, p, game.ACTION_BY_ID["cut_logs"]), plain)
def test_fish_to_plate(self):
world = world_with(("sam", "scout"))
world = world_with(("sam", "ranger"))
p = world["players"]["sam"]
game.start_action(world, p, "fish_shrimp", T0)
game.process_player(world, p, T0 + 200, random.Random(1))
self.assertEqual(p["bank"]["raw_shrimp"], 10) # 20s with the scout bonus
p["bank"]["logs"] = 10
game.start_action(world, p, "cook_shrimp", T0 + 200)
game.process_player(world, p, T0 + 1000, random.Random(1))
self.assertEqual(p["bank"]["cooked_shrimp"], 10)
summary = game.process_player(world, p, T0 + 1000, random.Random(1))
self.assertEqual(p["bank"]["cooked_shrimp"], 10 + summary["lucky"]) # rangers cook their catch, sometimes double
self.assertIn("cooked_shrimp", game.FOOD_ORDER)
def test_hides_to_armour(self):
world = world_with(("kay", "artisan"))
world = world_with(("kay", "miner"))
p = world["players"]["kay"]
p["bank"] = {"hide": 4}
game.start_action(world, p, "tan_leather", T0)
@ -554,7 +559,7 @@ class NewSkillsTest(unittest.TestCase):
self.assertIsNone(game.start_action(world, p, "lw_body", T0 + 1000))
def test_v2_worlds_gain_hp_and_slots(self):
world = world_with(("fred", "pioneer"))
world = world_with(("fred", "miner"))
p = world["players"]["fred"]
world["version"] = 2
for key in ("hp", "hp_at", "food"):
@ -572,7 +577,7 @@ class NewSkillsTest(unittest.TestCase):
class FavourTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("fred", "pioneer"))
self.world = world_with(("fred", "miner"))
self.p = self.world["players"]["fred"]
self.p["favour"] = 5000
@ -585,7 +590,7 @@ class FavourTest(unittest.TestCase):
self.assertEqual(game.favours(world, T0)["builders"]["cost"], 225)
self.assertEqual(world["city"]["construction"]["labour"], 0) # nothing is built for you
game.start_action(world, p, "labour", T0)
summary = game.process_player(world, p, T0 + 24 * 60, random.Random(1)) # pioneer: 24s an action
summary = game.process_player(world, p, T0 + 30 * 60, random.Random(1)) # 30s an action
self.assertEqual(summary["done"], 60)
self.assertEqual(summary["labour"], 60 + 50) # the first 50 labour counted double
self.assertEqual(world["city"]["site_boost"], 0)
@ -610,13 +615,12 @@ class FavourTest(unittest.TestCase):
world["next_event_at"] = T0
game.tick(world, T0, random.Random(2))
event = game.upcoming_raid(world)
self.assertEqual(game.spend_favour(world, p, "informant", T0), "No scout in the guild to follow up the leads")
world["players"]["sam"] = scout = game.new_player("sam", "Sam", "scout", T0)
world["players"]["sam"] = scout = game.new_player("sam", "Sam", "commoner", T0)
self.assertIsNone(game.spend_favour(world, p, "informant", T0))
self.assertEqual(event["recon"], 0) # leads, not intel
self.assertEqual(game.favours(world, T0)["informant"]["cost"], 375)
game.start_action(world, scout, "recon_roads", T0)
game.process_player(world, scout, T0 + 48 * 10 + 1, random.Random(1)) # ten scouting runs
game.process_player(world, scout, T0 + 60 * 10 + 1, random.Random(1)) # ten scouting runs
self.assertEqual(event["recon"], 20)
self.assertEqual(scout["xp"]["reconnaissance"], 240)
self.assertEqual(world["city"]["recon_boost"], 20)
@ -675,7 +679,7 @@ class FavourTest(unittest.TestCase):
class TierTest(unittest.TestCase):
def test_steel_needs_coal_and_the_smithy(self):
world = world_with(("kay", "artisan"))
world = world_with(("kay", "miner"))
p = world["players"]["kay"]
p["xp"]["smithing"] = game.XP_TABLE[30]
p["bank"] = {"iron_ore": 5, "coal": 10}
@ -697,7 +701,7 @@ class TierTest(unittest.TestCase):
self.assertEqual(game.ITEMS["rune_body"]["plural"], "Rune cuirasses")
def test_gear_needs_levels_to_wear(self):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
self.assertEqual(game.equip_problem(p, "rune_sword"), "Needs combat level 50")
self.assertEqual(game.equip_problem(p, "steel_pickaxe"), "Needs mining level 20")
@ -715,12 +719,12 @@ class TierTest(unittest.TestCase):
def test_higher_building_levels_need_higher_tiers(self):
self.assertNotIn("steel_bar", game.build_cost("walls", 2))
self.assertEqual(game.build_cost("walls", 3)["steel_bar"], 60)
self.assertEqual(game.build_cost("walls", 5)["mithril_bar"], 40)
self.assertEqual(game.build_cost("walls", 5)["steel_bar"], 180)
self.assertEqual(game.build_cost("walls", 3)["steel_bar"], 80)
self.assertEqual(game.build_cost("walls", 5)["mithril_bar"], 50)
self.assertEqual(game.build_cost("walls", 5)["steel_bar"], 240)
def test_dragons_drop_dragonhide(self):
world = world_with(("wil", "warrior"))
world = world_with(("wil", "commoner"))
p = world["players"]["wil"]
p["xp"]["combat"] = game.XP_TABLE[80]
p["equip"].update(weapon="rune_sword", shield="rune_shield", helmet="rune_helmet", body="rune_body", legs="rune_legs")
@ -734,7 +738,7 @@ class TierTest(unittest.TestCase):
class PrayerTest(unittest.TestCase):
def priest(self, level=1, combat=10):
world = world_with(("pat", "warrior"))
world = world_with(("pat", "commoner"))
p = world["players"]["pat"]
p["xp"]["prayer"] = game.XP_TABLE[level]
p["xp"]["combat"] = game.XP_TABLE[combat]
@ -802,7 +806,7 @@ class PrayerTest(unittest.TestCase):
self.assertEqual(game.start_action(world, p, "offer_bones", T0), "Needs Temple level 1")
def test_v4_worlds_get_prayer(self):
world = world_with(("fred", "pioneer"))
world = world_with(("fred", "miner"))
world["version"] = 4
p = world["players"]["fred"]
del p["pp"], p["prayers"], p["xp"]["prayer"]
@ -815,7 +819,7 @@ class PrayerTest(unittest.TestCase):
class HerbloreTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("hal", "warrior"))
self.world = world_with(("hal", "commoner"))
self.p = self.world["players"]["hal"]
def test_herbs_to_potion(self):
@ -901,7 +905,7 @@ class HerbloreTest(unittest.TestCase):
self.assertIn(action, game.ACTION_BY_ID)
def test_v5_worlds_get_herblore(self):
world = world_with(("fred", "pioneer"))
world = world_with(("fred", "miner"))
world["version"] = 5
p = world["players"]["fred"]
del p["potions"], p["xp"]["herblore"]
@ -912,7 +916,7 @@ class HerbloreTest(unittest.TestCase):
class ConstructionTest(unittest.TestCase):
def site(self):
world = world_with(("bob", "pioneer"), ("ann", "warrior"))
world = world_with(("bob", "miner"), ("ann", "commoner"))
world["city"]["construction"] = {"building": "walls", "repair": False, "start": T0, "labour": 0.0, "needed": 360}
world["next_event_at"] = None
return world
@ -950,7 +954,7 @@ class ConstructionTest(unittest.TestCase):
self.assertAlmostEqual(game.action_time(world, p, game.ACTION_BY_ID["labour"]), plain * 0.7)
def test_no_site_no_work(self):
world = world_with(("ann", "warrior"))
world = world_with(("ann", "commoner"))
self.assertEqual(game.start_action(world, world["players"]["ann"], "labour", T0), "Nothing is being built")
def test_planks_and_hammers_exist_for_every_tier(self):
@ -961,7 +965,7 @@ class ConstructionTest(unittest.TestCase):
self.assertIn("oak_plank", game.build_cost("houses", 3))
def test_v6_timer_becomes_labour(self):
world = world_with(("fred", "pioneer"))
world = world_with(("fred", "miner"))
world["version"] = 6
p = world["players"]["fred"]
del p["xp"]["construction"], p["equip"]["tool_construction"]
@ -977,7 +981,7 @@ class ConstructionTest(unittest.TestCase):
class HappeningsTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("ann", "warrior"), ("sam", "scout"))
self.world = world_with(("ann", "commoner"), ("sam", "ranger"))
self.world["next_event_at"] = None
for track in ("minor", "hardship", "fortune"):
self.world[f"next_{track}_at"] = None
@ -1156,7 +1160,7 @@ class HappeningsTest(unittest.TestCase):
class WearTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("fred", "warrior"))
self.world = world_with(("fred", "commoner"))
self.city = self.world["city"]
self.city["population"] = 40.0 # 20 defenders
@ -1186,9 +1190,65 @@ class WearTest(unittest.TestCase):
self.assertIn("broken in the fighting", text)
class ClassTest(unittest.TestCase):
def lucky_run(self, cls, action_id, bank):
world = world_with(("pat", cls))
p = world["players"]["pat"]
p["xp"] = {skill: game.XP_TABLE[50] for skill in p["xp"]}
p["bank"] = dict(bank)
game.start_action(world, p, action_id, T0)
return p, game.process_player(world, p, T0 + 3 * game.HOUR, random.Random(3))
def test_every_trade_sometimes_crafts_double(self):
for cls, action, bank in (("miner", "smith_copper_bar", {"copper_ore": 300, "logs": 300}),
("builder", "saw_plank", {"logs": 600}),
("homesteader", "cook_bread", {"wheat": 600, "logs": 300}),
("ranger", "cook_shrimp", {"raw_shrimp": 300, "logs": 300})):
_p, summary = self.lucky_run(cls, action, bank)
self.assertGreater(summary["lucky"], 0, cls)
_p, summary = self.lucky_run("commoner", action, bank)
self.assertEqual(summary["lucky"], 0, cls)
def test_trade_bonuses(self):
_p, summary = self.lucky_run("miner", "mine_stone", {})
self.assertGreater(summary["lucky"], 0) # prospector
_p, summary = self.lucky_run("builder", "mine_stone", {})
self.assertEqual(summary["lucky"], 0)
builder = game.new_player("b", "B", "builder", T0)
self.assertEqual(game.labour_per_action(builder), 2) # foreman
home = game.new_player("h", "H", "homesteader", T0)
home["bank"] = {"mining_potion_1": 1}
game.use_potion(home, "mining_potion_1")
self.assertEqual(home["potions"]["mining"]["charges"], 62) # steady hand
world = world_with()
ranger = game.new_player("r", "R", "ranger", T0)
self.assertEqual(game.recon_gain(world, ranger, game.ACTION_BY_ID["recon_roads"]), 1.5) # pathfinder
self.assertEqual(game.action_time(world, ranger, game.ACTION_BY_ID["cook_shrimp"]),
game.ACTION_BY_ID["cook_shrimp"]["time"] * game.CLASS_SPEED)
def test_old_classes_become_trades_with_a_free_pick(self):
world = world_with()
world["version"] = 11
for uid, cls in (("a", "pioneer"), ("b", "artisan"), ("c", "scout"), ("d", "warrior")):
world["players"][uid] = game.new_player(uid, uid, "miner", T0)
world["players"][uid]["cls"] = cls
game.migrate(world, T0)
self.assertEqual([p["cls"] for p in world["players"].values()], ["miner", "builder", "ranger", "builder"])
self.assertTrue(all(p["repick"] for p in world["players"].values()))
def test_overfunded_buildings_return_the_extra(self):
world = world_with()
world["version"] = 11
world["city"]["buildings"]["houses"]["fund"] = {"bricks": 400, "logs": 100}
world["city"]["stockpile"] = {}
game.migrate(world, T0)
self.assertEqual(world["city"]["buildings"]["houses"]["fund"], {"bricks": 150, "logs": 100})
self.assertEqual(world["city"]["stockpile"], {"bricks": 250})
class FittingTest(unittest.TestCase):
def setUp(self):
self.world = world_with(("fred", "artisan"))
self.world = world_with(("fred", "miner"))
self.p = self.world["players"]["fred"]
self.buildings = self.world["city"]["buildings"]