Parse player commands and holdings displays for full state tracking

Player command tracking:
- .card: detect GOJF card usage in jail (previously invisible)
- .mortgage/.mor: set property_mortgaged flag using cost+name disambiguation
- .unmortgage/.unm: clear property_mortgaged flag using cost+name disambiguation
- Command context (_command_context) disambiguates 'That cost you $X' between
  jail pay and unmortgage

Holdings display parsing:
- Parse 'NAME's (N) holdings' header, then printsq-format property lines
- Full resync of property_owner, property_mortgaged, property_houses
- Reuses resolve_trade_property() for name matching
- Holdings end on any non-property line (checkpoint, command prompt, etc.)

13 new tests in test_parser_commands.py:
- GOJF card (4): exit jail, no card, not in jail, log entry
- Unmortgage (2): unique property, disambiguation with user input
- Mortgage (1): flag set correctly
- Holdings (5): ownership, mortgage, houses, stale clearing, real log replay
- Real log (1): unmortgage at line 36-41

All 1551 checkpoints + 80 unit tests passing.
This commit is contained in:
Jarvis 2026-02-21 19:30:21 +00:00
parent 1aadcddabe
commit 22e98794d3
3 changed files with 602 additions and 8 deletions

View file

@ -225,6 +225,9 @@ class MonopParser:
# Trade property line: " NAME OWNER GROUP COST ..."
# C's printsq uses %-10.10s for name, then owner+1, group, cost
TRADE_PROP_RE = re.compile(r'^\s(.{10})\s+(\d+)\s+\S*\s+(\d+)')
# Holdings property line (same format but with mortgage/houses/rent)
HOLDINGS_PROP_RE = re.compile(r'^\s(.{10})\s+(\d+)\s+(\S*)\s+(\d+)\s*(\*)?\s*(\d+)?\s+(\d+)?')
HOLDINGS_HEADER_RE = re.compile(r'^\s+Name\s+Own\s+Price')
SOLVENT_RE = re.compile(r'^-- You are now Solvent ---$')
DEBT_RE = re.compile(r'^That leaves you \$(\d+) in debt$')
@ -260,6 +263,9 @@ class MonopParser:
self._resign_target = None
self._waiting_resign_target = False
self._last_user_input = ""
self._command_context = None # tracks what command was issued for disambiguation
self._holdings_player = None # player number during holdings display parsing
self._in_holdings = False
self._last_debt_amount = None
# Card context
self._in_card_block = False
@ -273,6 +279,51 @@ class MonopParser:
self.games.append(self.game)
self._awaiting_player_count = True
def _process_player_command(self, sender, cmd, timestamp):
"""Track player commands to infer silent state changes."""
g = self.game
if not g:
return
cmd_lower = cmd.lower().strip()
# `.card` — use GOJF card to exit jail (no output on success)
if cmd_lower in ("card", "c"):
player = g.get_player(name=sender)
if player and player.in_jail and player.get_out_of_jail_free_cards > 0:
player.get_out_of_jail_free_cards -= 1
player.in_jail = False
player.jail_turns = 0
player.location = 10 # Just Visiting
g.add_log("Used Get Out of Jail Free card", player=sender, timestamp=timestamp)
return
# `.unmortgage` / `.unm` — next "That cost you $X" is an unmortgage
if cmd_lower.startswith("unm"):
self._command_context = "unmortgage"
return
# `.mortgage` / `.mor` — next "That got you $X" is a mortgage
if cmd_lower.startswith("mor"):
self._command_context = "mortgage"
return
# `.buy` — entering house buying mode
if cmd_lower in ("buy", "b"):
self._command_context = "buy_houses"
self._house_buy_props = {} # prop_name -> current houses (from prompts)
return
# `.sell` — entering house selling mode
if cmd_lower.startswith("sell"):
self._command_context = "sell_houses"
return
# `.holdings` / `.hold` — triggers holdings display (parsed from output)
if cmd_lower.startswith("hold"):
self._command_context = "holdings"
return
def _handle_setup_input(self, sender, msg, timestamp):
"""Handle user input during setup phase."""
g = self.game
@ -320,6 +371,7 @@ class MonopParser:
user_msg = message.lstrip('.')
if user_msg:
self._last_user_input = user_msg
self._process_player_command(sender, user_msg, timestamp)
# During setup, capture player count and registrations
if self.game and self.game.phase == "setup":
self._handle_setup_input(sender, user_msg, timestamp)
@ -833,16 +885,23 @@ class MonopParser:
amount = int(m.group(1))
if cp:
cp.money += amount
# Try to identify which property was mortgaged
# Mortgage value = cost/2, so cost = amount * 2
self._resolve_mortgage(cp, amount)
return
# ===== UNMORTGAGE =====
# "That cost you $X" - but also used for jail pay
# ===== UNMORTGAGE / JAIL PAY =====
# "That cost you $X" — ambiguous: could be unmortgage or jail pay
# Use _command_context to disambiguate
m = self.UNMORTGAGE_COST_RE.match(msg)
if m:
amount = int(m.group(1))
if cp and not cp.in_jail:
if self._command_context == "unmortgage" and cp:
cp.money -= amount
elif cp and cp.in_jail and amount == 50:
# Try to identify which property was unmortgaged
self._resolve_unmortgage(cp, amount)
self._command_context = None
elif cp and cp.in_jail and amount == 50 and self._command_context != "unmortgage":
# Jail pay
cp.money -= 50
cp.location = 10
@ -1011,10 +1070,45 @@ class MonopParser:
return
# ===== Holdings display =====
# ===== HOLDINGS DISPLAY =====
m = self.HOLDINGS_RE.match(msg)
if m:
name = m.group(1)
num = int(m.group(2))
self._holdings_player = num
self._in_holdings = True
return
if self._in_holdings:
# Header line
if self.HOLDINGS_HEADER_RE.match(msg_raw):
return
# Property line
m_h = self.HOLDINGS_PROP_RE.match(msg_raw)
if m_h:
trunc_name = m_h.group(1).rstrip()
cost = int(m_h.group(4))
mortgaged = m_h.group(5) == '*'
houses = int(m_h.group(6)) if m_h.group(6) else 0
sq_id = resolve_trade_property(trunc_name, cost)
if sq_id is not None:
# Resync property state from holdings
g.property_owner[sq_id] = self._holdings_player
if mortgaged:
g.property_mortgaged[sq_id] = True
else:
g.property_mortgaged.pop(sq_id, None)
if houses > 0:
g.property_houses[sq_id] = houses
else:
g.property_houses.pop(sq_id, None)
return
# Any non-matching line ends the holdings display
self._in_holdings = False
self._holdings_player = None
# Fall through to process this line normally
# ===== Various prompts and info =====
if msg.startswith("-- Command:"):
return
@ -1141,6 +1235,63 @@ class MonopParser:
g.game_active = False
return
def _resolve_mortgage(self, player, amount):
"""Try to identify which property was mortgaged and set its mortgage flag."""
g = self.game
if not g:
return
# Mortgage value = cost/2, so find properties where cost/2 == amount
candidates = []
for sq_id, owner_num in g.property_owner.items():
if owner_num != player.number:
continue
if g.property_mortgaged.get(sq_id):
continue # already mortgaged
sq = BOARD[sq_id] if sq_id < len(BOARD) else None
if sq and sq["cost"] // 2 == amount:
candidates.append(sq_id)
if len(candidates) == 1:
g.property_mortgaged[candidates[0]] = True
elif len(candidates) > 1 and self._last_user_input:
inp = self._last_user_input.lower()
for sq_id in candidates:
sq_name = BOARD[sq_id]["name"].lower()
if sq_name.startswith(inp) or inp in sq_name:
g.property_mortgaged[sq_id] = True
break
def _resolve_unmortgage(self, player, cost):
"""Try to identify which property was unmortgaged and clear its mortgage flag."""
g = self.game
if not g:
return
# Candidates: mortgaged properties owned by this player where unmortgage cost matches
candidates = []
for sq_id, mortgaged in list(g.property_mortgaged.items()):
if not mortgaged:
continue
if g.property_owner.get(sq_id) != player.number:
continue
sq = BOARD[sq_id] if sq_id < len(BOARD) else None
if sq:
half = sq["cost"] // 2
expected_cost = half + half // 10
if expected_cost == cost:
candidates.append(sq_id)
if len(candidates) == 1:
# Unambiguous
g.property_mortgaged.pop(candidates[0], None)
elif len(candidates) > 1 and self._last_user_input:
# Use user's input to disambiguate (prefix match like C's getinp)
inp = self._last_user_input.lower()
for sq_id in candidates:
sq_name = BOARD[sq_id]["name"].lower()
if sq_name.startswith(inp) or inp in sq_name:
g.property_mortgaged.pop(sq_id, None)
break
def _pay_rent(self, amount):
g = self.game
if not g:

View file

@ -225,6 +225,9 @@ class MonopParser:
# Trade property line: " NAME OWNER GROUP COST ..."
# C's printsq uses %-10.10s for name, then owner+1, group, cost
TRADE_PROP_RE = re.compile(r'^\s(.{10})\s+(\d+)\s+\S*\s+(\d+)')
# Holdings property line (same format but with mortgage/houses/rent)
HOLDINGS_PROP_RE = re.compile(r'^\s(.{10})\s+(\d+)\s+(\S*)\s+(\d+)\s*(\*)?\s*(\d+)?\s+(\d+)?')
HOLDINGS_HEADER_RE = re.compile(r'^\s+Name\s+Own\s+Price')
SOLVENT_RE = re.compile(r'^-- You are now Solvent ---$')
DEBT_RE = re.compile(r'^That leaves you \$(\d+) in debt$')
@ -260,6 +263,9 @@ class MonopParser:
self._resign_target = None
self._waiting_resign_target = False
self._last_user_input = ""
self._command_context = None # tracks what command was issued for disambiguation
self._holdings_player = None # player number during holdings display parsing
self._in_holdings = False
self._last_debt_amount = None
# Card context
self._in_card_block = False
@ -273,6 +279,51 @@ class MonopParser:
self.games.append(self.game)
self._awaiting_player_count = True
def _process_player_command(self, sender, cmd, timestamp):
"""Track player commands to infer silent state changes."""
g = self.game
if not g:
return
cmd_lower = cmd.lower().strip()
# `.card` — use GOJF card to exit jail (no output on success)
if cmd_lower in ("card", "c"):
player = g.get_player(name=sender)
if player and player.in_jail and player.get_out_of_jail_free_cards > 0:
player.get_out_of_jail_free_cards -= 1
player.in_jail = False
player.jail_turns = 0
player.location = 10 # Just Visiting
g.add_log("Used Get Out of Jail Free card", player=sender, timestamp=timestamp)
return
# `.unmortgage` / `.unm` — next "That cost you $X" is an unmortgage
if cmd_lower.startswith("unm"):
self._command_context = "unmortgage"
return
# `.mortgage` / `.mor` — next "That got you $X" is a mortgage
if cmd_lower.startswith("mor"):
self._command_context = "mortgage"
return
# `.buy` — entering house buying mode
if cmd_lower in ("buy", "b"):
self._command_context = "buy_houses"
self._house_buy_props = {} # prop_name -> current houses (from prompts)
return
# `.sell` — entering house selling mode
if cmd_lower.startswith("sell"):
self._command_context = "sell_houses"
return
# `.holdings` / `.hold` — triggers holdings display (parsed from output)
if cmd_lower.startswith("hold"):
self._command_context = "holdings"
return
def _handle_setup_input(self, sender, msg, timestamp):
"""Handle user input during setup phase."""
g = self.game
@ -320,6 +371,7 @@ class MonopParser:
user_msg = message.lstrip('.')
if user_msg:
self._last_user_input = user_msg
self._process_player_command(sender, user_msg, timestamp)
# During setup, capture player count and registrations
if self.game and self.game.phase == "setup":
self._handle_setup_input(sender, user_msg, timestamp)
@ -833,16 +885,23 @@ class MonopParser:
amount = int(m.group(1))
if cp:
cp.money += amount
# Try to identify which property was mortgaged
# Mortgage value = cost/2, so cost = amount * 2
self._resolve_mortgage(cp, amount)
return
# ===== UNMORTGAGE =====
# "That cost you $X" - but also used for jail pay
# ===== UNMORTGAGE / JAIL PAY =====
# "That cost you $X" — ambiguous: could be unmortgage or jail pay
# Use _command_context to disambiguate
m = self.UNMORTGAGE_COST_RE.match(msg)
if m:
amount = int(m.group(1))
if cp and not cp.in_jail:
if self._command_context == "unmortgage" and cp:
cp.money -= amount
elif cp and cp.in_jail and amount == 50:
# Try to identify which property was unmortgaged
self._resolve_unmortgage(cp, amount)
self._command_context = None
elif cp and cp.in_jail and amount == 50 and self._command_context != "unmortgage":
# Jail pay
cp.money -= 50
cp.location = 10
@ -1011,10 +1070,45 @@ class MonopParser:
return
# ===== Holdings display =====
# ===== HOLDINGS DISPLAY =====
m = self.HOLDINGS_RE.match(msg)
if m:
name = m.group(1)
num = int(m.group(2))
self._holdings_player = num
self._in_holdings = True
return
if self._in_holdings:
# Header line
if self.HOLDINGS_HEADER_RE.match(msg_raw):
return
# Property line
m_h = self.HOLDINGS_PROP_RE.match(msg_raw)
if m_h:
trunc_name = m_h.group(1).rstrip()
cost = int(m_h.group(4))
mortgaged = m_h.group(5) == '*'
houses = int(m_h.group(6)) if m_h.group(6) else 0
sq_id = resolve_trade_property(trunc_name, cost)
if sq_id is not None:
# Resync property state from holdings
g.property_owner[sq_id] = self._holdings_player
if mortgaged:
g.property_mortgaged[sq_id] = True
else:
g.property_mortgaged.pop(sq_id, None)
if houses > 0:
g.property_houses[sq_id] = houses
else:
g.property_houses.pop(sq_id, None)
return
# Any non-matching line ends the holdings display
self._in_holdings = False
self._holdings_player = None
# Fall through to process this line normally
# ===== Various prompts and info =====
if msg.startswith("-- Command:"):
return
@ -1141,6 +1235,63 @@ class MonopParser:
g.game_active = False
return
def _resolve_mortgage(self, player, amount):
"""Try to identify which property was mortgaged and set its mortgage flag."""
g = self.game
if not g:
return
# Mortgage value = cost/2, so find properties where cost/2 == amount
candidates = []
for sq_id, owner_num in g.property_owner.items():
if owner_num != player.number:
continue
if g.property_mortgaged.get(sq_id):
continue # already mortgaged
sq = BOARD[sq_id] if sq_id < len(BOARD) else None
if sq and sq["cost"] // 2 == amount:
candidates.append(sq_id)
if len(candidates) == 1:
g.property_mortgaged[candidates[0]] = True
elif len(candidates) > 1 and self._last_user_input:
inp = self._last_user_input.lower()
for sq_id in candidates:
sq_name = BOARD[sq_id]["name"].lower()
if sq_name.startswith(inp) or inp in sq_name:
g.property_mortgaged[sq_id] = True
break
def _resolve_unmortgage(self, player, cost):
"""Try to identify which property was unmortgaged and clear its mortgage flag."""
g = self.game
if not g:
return
# Candidates: mortgaged properties owned by this player where unmortgage cost matches
candidates = []
for sq_id, mortgaged in list(g.property_mortgaged.items()):
if not mortgaged:
continue
if g.property_owner.get(sq_id) != player.number:
continue
sq = BOARD[sq_id] if sq_id < len(BOARD) else None
if sq:
half = sq["cost"] // 2
expected_cost = half + half // 10
if expected_cost == cost:
candidates.append(sq_id)
if len(candidates) == 1:
# Unambiguous
g.property_mortgaged.pop(candidates[0], None)
elif len(candidates) > 1 and self._last_user_input:
# Use user's input to disambiguate (prefix match like C's getinp)
inp = self._last_user_input.lower()
for sq_id in candidates:
sq_name = BOARD[sq_id]["name"].lower()
if sq_name.startswith(inp) or inp in sq_name:
g.property_mortgaged.pop(sq_id, None)
break
def _pay_rent(self, amount):
g = self.game
if not g:

292
test_parser_commands.py Normal file
View file

@ -0,0 +1,292 @@
#!/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)