#!/usr/bin/env python3 """Tests for player command tracking and holdings display parsing.""" import sys import os import unittest sys.path.insert(0, os.path.dirname(__file__)) from monop_parser import MonopParser TS = "2026-01-01 00:00:00" def feed(parser, lines): for line in lines: parser.parse_line(f"{TS}\t{line}") def setup_3player_game(): """fbs goes first (turn order: fbs→merp→hiro).""" p = MonopParser() feed(p, [ "monop\tHow many players? ", "monop\tPlayer 1, say 'me' please.", "monop\tmerp (1) rolls 5", "monop\tPlayer 2, say 'me' please.", "monop\thiro (2) rolls 3", "monop\tPlayer 3, say 'me' please.", "monop\tfbs (3) rolls 8", "monop\tfbs (3) goes first", "monop\tfbs (3) (cash $1500) on === GO ===", "monop\t-- Command: ", ]) return p def give_properties(game, player_num, sq_ids): for sq_id in sq_ids: game.property_owner[sq_id] = player_num # ===================================================================== # GOJF card usage from player command # ===================================================================== class TestGOJFCardCommand(unittest.TestCase): def test_card_command_exits_jail(self): """Player types .card while in jail with GOJF card.""" p = setup_3player_game() g = p.game # Put fbs in jail with a GOJF card fbs = g.get_player(name="fbs") fbs.in_jail = True fbs.jail_turns = 1 fbs.location = 40 fbs.get_out_of_jail_free_cards = 1 feed(p, ["fbs\t.card"]) self.assertFalse(fbs.in_jail) self.assertEqual(fbs.location, 10) # Just Visiting self.assertEqual(fbs.get_out_of_jail_free_cards, 0) self.assertEqual(fbs.jail_turns, 0) def test_card_command_no_gojf(self): """Player types .card but has no GOJF cards — nothing happens.""" p = setup_3player_game() g = p.game fbs = g.get_player(name="fbs") fbs.in_jail = True fbs.location = 40 fbs.get_out_of_jail_free_cards = 0 feed(p, ["fbs\t.card"]) # Should still be in jail self.assertTrue(fbs.in_jail) self.assertEqual(fbs.location, 40) def test_card_command_not_in_jail(self): """Player types .card but isn't in jail — nothing happens.""" p = setup_3player_game() g = p.game fbs = g.get_player(name="fbs") fbs.get_out_of_jail_free_cards = 1 feed(p, ["fbs\t.card"]) # GOJF card should not be consumed self.assertEqual(fbs.get_out_of_jail_free_cards, 1) def test_card_log_entry(self): p = setup_3player_game() g = p.game fbs = g.get_player(name="fbs") fbs.in_jail = True fbs.location = 40 fbs.get_out_of_jail_free_cards = 2 feed(p, ["fbs\t.card"]) log_texts = [e["text"] for e in g.log] self.assertTrue(any("Get Out of Jail" in t for t in log_texts)) self.assertEqual(fbs.get_out_of_jail_free_cards, 1) # ===================================================================== # Unmortgage tracking via command context # ===================================================================== class TestUnmortgageTracking(unittest.TestCase): def test_unmortgage_clears_flag(self): """Player unmortgages a uniquely-identifiable property.""" p = setup_3player_game() g = p.game # fbs owns Boardwalk (39), mortgaged give_properties(g, 3, [39]) g.property_mortgaged[39] = True feed(p, [ "fbs\t.unm", "monop\tYour only mortaged property is Boardwalk (D)", "monop\tDo you want to unmortgage it? ", "fbs\t.y", "monop\tThat cost you $220", # 400/2 + 400/2/10 = 220 "monop\tfbs (3) (cash $1280) on === GO ===", "monop\t-- Command: ", ]) self.assertNotIn(39, g.property_mortgaged) def test_unmortgage_with_disambiguation(self): """Player unmortgages one of two same-cost properties.""" p = setup_3player_game() g = p.game # fbs owns both Kentucky (21) and Indiana (23), both mortgaged # Both cost $220 → unmortgage cost $121 each give_properties(g, 3, [21, 23]) g.property_mortgaged[21] = True g.property_mortgaged[23] = True feed(p, [ "fbs\t.unm", "monop\tWhich property do you want to unmortgage? ", "fbs\t.indiana", # user specifies Indiana "monop\tThat cost you $121", "monop\tfbs (3) (cash $1379) on === GO ===", "monop\t-- Command: ", ]) # Indiana unmortgaged, Kentucky still mortgaged self.assertNotIn(23, g.property_mortgaged) self.assertTrue(g.property_mortgaged.get(21)) # ===================================================================== # Mortgage tracking # ===================================================================== class TestMortgageTracking(unittest.TestCase): def test_mortgage_sets_flag(self): """Player mortgages a property — flag should be set.""" p = setup_3player_game() g = p.game give_properties(g, 3, [39]) # Boardwalk feed(p, [ "fbs\t.mor", "monop\tWhich property do you want to mortgage? ", "fbs\t.boardwalk", "monop\tThat got you $200", # 400/2 "monop\tfbs (3) (cash $1700) on === GO ===", "monop\t-- Command: ", ]) self.assertTrue(g.property_mortgaged.get(39)) # ===================================================================== # Holdings display parsing # ===================================================================== class TestHoldingsDisplay(unittest.TestCase): def test_holdings_syncs_properties(self): """Holdings display should resync property ownership.""" p = setup_3player_game() g = p.game feed(p, [ "monop\tmerp's (1) holdings (Total worth: $1806):", "monop\t Name Own Price Mg # Rent", "monop\t Electric C 1 150 1", "monop\t New York a 1 Orange 200 16", "monop\t Kentucky a 1 RED 220 0 36", "monop\tmerp (1) (cash $496) on Community Chest ii", "monop\t-- Command: ", ]) # Electric Co (12), New York ave (19), Kentucky ave (21) should be owned by merp (1) self.assertEqual(g.property_owner.get(12), 1) self.assertEqual(g.property_owner.get(19), 1) self.assertEqual(g.property_owner.get(21), 1) def test_holdings_syncs_mortgage_status(self): """Holdings display should set/clear mortgage flags.""" p = setup_3player_game() g = p.game feed(p, [ "monop\tmerp's (1) holdings (Total worth: $1806):", "monop\t Name Own Price Mg # Rent", "monop\t Indiana av 1 RED 220 * 0 36", "monop\t Illinois a 1 RED 240 0 40", "monop\tmerp (1) (cash $496) on Community Chest ii", "monop\t-- Command: ", ]) self.assertTrue(g.property_mortgaged.get(23)) # Indiana mortgaged self.assertNotIn(24, g.property_mortgaged) # Illinois not mortgaged def test_holdings_syncs_house_counts(self): """Holdings display should update house counts.""" p = setup_3player_game() g = p.game feed(p, [ "monop\tfbs's (3) holdings (Total worth: $5000):", "monop\t Name Own Price Mg # Rent", "monop\t Kentucky a 3 RED 220 3 180", "monop\t Indiana av 3 RED 220 3 180", "monop\t Illinois a 3 RED 240 4 220", "monop\tfbs (3) (cash $2000) on === GO ===", "monop\t-- Command: ", ]) self.assertEqual(g.property_houses.get(21), 3) # Kentucky 3 houses self.assertEqual(g.property_houses.get(23), 3) # Indiana 3 houses self.assertEqual(g.property_houses.get(24), 4) # Illinois 4 houses def test_holdings_clears_stale_houses(self): """If holdings shows 0 houses, clear any stale house count.""" p = setup_3player_game() g = p.game g.property_houses[21] = 3 # stale: Kentucky had 3 houses feed(p, [ "monop\tmerp's (1) holdings (Total worth: $1000):", "monop\t Name Own Price Mg # Rent", "monop\t Kentucky a 1 RED 220 0 36", "monop\tmerp (1) (cash $500) on === GO ===", "monop\t-- Command: ", ]) self.assertNotIn(21, g.property_houses) # houses cleared def test_real_log_holdings_line_26(self): """Replay real holdings display from line 26 of test log.""" p = MonopParser() with open("test_data/monop.log") as f: for i, line in enumerate(f, 1): p.parse_line(line.rstrip('\n')) if i >= 35: break g = p.game self.assertIsNotNone(g) # merp (1) should own Electric Co (12) and others from the holdings dump self.assertEqual(g.property_owner.get(12), 1) # Electric Co # ===================================================================== # Real log: unmortgage at line 36-41 # ===================================================================== class TestRealLogUnmortgage(unittest.TestCase): def test_real_unmortgage(self): """Real log: merp unmortgages Indiana ave around line 36-41.""" p = MonopParser() with open("test_data/monop.log") as f: for i, line in enumerate(f, 1): p.parse_line(line.rstrip('\n')) if i >= 42: break g = p.game # Indiana ave (23) should NOT be mortgaged after unmortgage self.assertFalse(g.property_mortgaged.get(23, False), "Indiana ave should be unmortgaged after .unm at line 36") if __name__ == "__main__": unittest.main(verbosity=2)