293 lines
10 KiB
Python
293 lines
10 KiB
Python
|
|
#!/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)
|