monop-state/test_parser_commands.py

389 lines
14 KiB
Python
Raw Normal View History

#!/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")
# =====================================================================
# House buying/selling integration (via HouseParser sub-parser)
# =====================================================================
class TestHouseIntegration(unittest.TestCase):
def test_buy_houses_updates_state(self):
"""Full house buying flow updates property_houses in main parser."""
p = setup_3player_game()
g = p.game
give_properties(g, 3, [1, 3]) # fbs owns purple monopoly
feed(p, [
"monop\tMediterranean ave. (P) (0) Baltic ave. (P) (0) ",
"monop\tHouses will cost $50",
"monop\tHow many houses do you wish to buy for",
"monop\tMediterranean ave. (P) (0): ",
"fbs\t.2",
"monop\tBaltic ave. (P) (0): ",
"fbs\t.2",
"monop\tYou asked for 4 houses for $200",
"monop\tIs that ok? ",
"fbs\t.y",
"monop\tfbs (3) (cash $1300) on === GO ===",
"monop\t-- Command: ",
])
self.assertEqual(g.property_houses.get(1), 2) # Mediterranean
self.assertEqual(g.property_houses.get(3), 2) # Baltic
def test_sell_houses_updates_state(self):
"""Full house selling flow updates property_houses."""
p = setup_3player_game()
g = p.game
give_properties(g, 3, [21, 23, 24]) # fbs owns Red
g.property_houses[21] = 2
g.property_houses[23] = 2
g.property_houses[24] = 3
feed(p, [
"monop\tHouses will get you $75 apiece",
"monop\tKentucky ave. (R) (2) Indiana ave. (R) (2) Illinois ave. (R) (3) ",
"monop\tHow many houses do you wish to sell from",
"monop\tKentucky ave. (R) (2): ",
"fbs\t.2",
"monop\tIndiana ave. (R) (2): ",
"fbs\t.2",
"monop\tIllinois ave. (R) (3): ",
"fbs\t.3",
"monop\tYou asked to sell 7 houses for $525",
"monop\tIs that ok? ",
"fbs\t.y",
"monop\tfbs (3) (cash $2025) on === GO ===",
"monop\t-- Command: ",
])
self.assertNotIn(21, g.property_houses) # all gone
self.assertNotIn(23, g.property_houses)
self.assertNotIn(24, g.property_houses)
def test_buy_rejected_no_change(self):
"""Rejected house purchase doesn't change state."""
p = setup_3player_game()
g = p.game
give_properties(g, 3, [1, 3])
feed(p, [
"monop\tHouses will cost $50",
"monop\tHow many houses do you wish to buy for",
"monop\tMediterranean ave. (P) (0): ",
"fbs\t.2",
"monop\tBaltic ave. (P) (0): ",
"fbs\t.2",
"monop\tYou asked for 4 houses for $200",
"monop\tIs that ok? ",
"fbs\t.n",
])
self.assertNotIn(1, g.property_houses)
self.assertNotIn(3, g.property_houses)
def test_real_log_house_buy_line_59(self):
"""Real log: merp buys houses on Red at line 59-73."""
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 >= 73:
break
g = p.game
# Kentucky(21)=1, Indiana(23)=1, Illinois(24)=2
self.assertEqual(g.property_houses.get(21), 1)
self.assertEqual(g.property_houses.get(23), 1)
self.assertEqual(g.property_houses.get(24), 2)
if __name__ == "__main__":
unittest.main(verbosity=2)