monop-state/test_players.py

337 lines
12 KiB
Python

#!/usr/bin/env python3
"""Unit tests for monop_players.py — test bot responses to monop messages."""
import sys
import unittest
from unittest.mock import MagicMock, patch
import time
# Import PlayerBot directly
sys.path.insert(0, "/tmp/monop-state")
from monop_players import PlayerBot
class FakePlayerBot(PlayerBot):
"""PlayerBot that captures messages instead of sending to IRC."""
def __init__(self, nick, player_names, player_index):
# Don't call super().__init__ — skip socket stuff
self.nick = nick
self.channel = "#monop"
self.host = "127.0.0.1"
self.port = 6667
self.player_names = player_names
self.player_index = player_index
self.num_players = len(player_names)
self.sock = None
self.buffer = ""
self.lock = __import__("threading").Lock()
# Game state
self.setup_phase = True
self.setup_registrations_seen = 0
self.current_player = None
self.my_money = 1500
self.in_jail = False
self.jail_turns = 0
self.in_debt = False
self.in_auction = False
self.auction_bid = 0
self.awaiting_prompt = None
self.game_started = False
self.game_over = False
self.rolled_this_turn = False
self.my_properties = []
self.mortgaged = set()
self._prompt_answered = False
self._first_player_announced = False
self.in_trade = False
self.trade_props_offered = 0
self.turns_played = 0
# Capture sent messages instead of IRC
self.sent_messages = []
def say(self, msg):
self.sent_messages.append(msg)
def say_delayed(self, msg, delay=None, force=False):
"""Immediate version — no threading, no delay."""
if force or self.is_my_turn():
self.sent_messages.append(msg)
def feed(self, msg):
"""Simulate receiving a message from the monop bot."""
self.sent_messages.clear()
self._handle_bot_msg(msg)
return list(self.sent_messages)
def feed_setup(self, msg):
"""Feed a setup-phase message."""
self.sent_messages.clear()
result = self._handle_setup(msg)
return list(self.sent_messages), result
class TestSetup(unittest.TestCase):
def test_player_count(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
msgs, handled = bot.feed_setup("How many players?")
self.assertTrue(handled)
self.assertEqual(msgs, ["2"])
def test_player_count_only_first(self):
bot = FakePlayerBot("bob", ["alice", "bob"], 1)
msgs, handled = bot.feed_setup("How many players?")
self.assertTrue(handled)
self.assertEqual(msgs, []) # bob doesn't send count
def test_register_correct_player(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
msgs, handled = bot.feed_setup("Player 1, say ''me'' please.")
self.assertTrue(handled)
self.assertEqual(msgs, ["alice"])
def test_register_wrong_player(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
msgs, handled = bot.feed_setup("Player 2, say ''me'' please.")
self.assertTrue(handled)
self.assertEqual(msgs, []) # alice doesn't respond to player 2
def test_goes_first(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
msgs, handled = bot.feed_setup("alice (1) goes first")
self.assertTrue(handled)
self.assertFalse(bot.setup_phase)
self.assertEqual(bot.current_player, "alice")
class TestTurns(unittest.TestCase):
def _make_bot(self, nick="alice", current_player=None):
bot = FakePlayerBot(nick, ["alice", "bob"], 0 if nick == "alice" else 1)
bot.setup_phase = False
bot.current_player = current_player
return bot
def test_checkpoint_triggers_roll(self):
bot = self._make_bot("alice")
msgs = bot.feed("alice (1) (cash $1500) on === GO ===")
self.assertIn("roll", msgs)
self.assertTrue(bot.rolled_this_turn)
def test_checkpoint_other_player_no_roll(self):
bot = self._make_bot("alice")
msgs = bot.feed("bob (2) (cash $1500) on === GO ===")
self.assertEqual(msgs, [])
self.assertEqual(bot.current_player, "bob")
def test_doubles_roll_again(self):
bot = self._make_bot("alice", "alice")
msgs = bot.feed("alice rolled doubles. Goes again")
self.assertIn("roll", msgs)
def test_buy_prompt(self):
bot = self._make_bot("alice", "alice")
msgs = bot.feed("Do you want to buy?")
self.assertIn("yes", msgs)
def test_buy_prompt_not_my_turn(self):
bot = self._make_bot("alice", "bob")
msgs = bot.feed("Do you want to buy?")
self.assertEqual(msgs, [])
def test_command_prompt_rolls_if_needed(self):
bot = self._make_bot("alice", "alice")
bot.rolled_this_turn = False
msgs = bot.feed("-- Command:")
self.assertIn("roll", msgs)
def test_command_prompt_no_double_roll(self):
bot = self._make_bot("alice", "alice")
bot.rolled_this_turn = True
msgs = bot.feed("-- Command:")
self.assertEqual(msgs, [])
class TestJail(unittest.TestCase):
def test_jail_turn_rolls(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
msgs = bot.feed("(This is your 1st turn in JAIL)")
self.assertIn("roll", msgs)
self.assertTrue(bot.in_jail)
class TestDebt(unittest.TestCase):
def test_debt_mortgages(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
msgs = bot.feed("How are you going to fix it up?")
self.assertIn("mortgage", msgs)
self.assertTrue(bot.in_debt)
def test_mortgage_prompt_sends_question(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
bot.in_debt = True
msgs = bot.feed("Which property do you want to mortgage?")
self.assertIn("?", msgs)
def test_mortgage_prompt_done_when_solvent(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
bot.in_debt = False
msgs = bot.feed("Which property do you want to mortgage?")
self.assertIn("done", msgs)
def test_no_property_sells_houses(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
bot.in_debt = True
msgs = bot.feed("You don't have any un-mortgaged property.")
self.assertIn("sell houses", msgs)
def test_no_houses_resigns(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
bot.in_debt = True
msgs = bot.feed("You don't have any houses to sell!!")
self.assertIn("resign", msgs)
class TestTax(unittest.TestCase):
def test_tax_choice(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
msgs = bot.feed("Do you wish to lose 10%% of your total worth or $200? ")
self.assertIn("10%", msgs)
class TestTrading(unittest.TestCase):
def _make_bot(self, nick="alice"):
bot = FakePlayerBot(nick, ["alice", "bob"], 0 if nick == "alice" else 1)
bot.setup_phase = False
bot.current_player = "alice"
return bot
@patch("monop_players.random")
def test_trade_initiated(self, mock_random):
"""With random < 0.10, bot initiates trade instead of rolling."""
mock_random.random.return_value = 0.05 # < 0.10
bot = self._make_bot("alice")
bot.turns_played = 10 # past turn 5
msgs = bot.feed("alice (1) (cash $1500) on === GO ===")
self.assertIn("trade", msgs)
self.assertTrue(bot.in_trade)
self.assertNotIn("roll", msgs)
@patch("monop_players.random")
def test_trade_not_initiated_early(self, mock_random):
"""No trades before turn 5."""
mock_random.random.return_value = 0.05
bot = self._make_bot("alice")
bot.turns_played = 2
msgs = bot.feed("alice (1) (cash $1500) on === GO ===")
self.assertIn("roll", msgs)
self.assertFalse(bot.in_trade)
def test_trade_property_prompt_sends_done(self):
bot = self._make_bot("alice")
bot.in_trade = True
bot.trade_props_offered = 1 # already offered one
msgs = bot.feed("Which property do you wish to trade?")
self.assertIn("done", msgs)
def test_trade_cash_prompt(self):
bot = self._make_bot("alice")
bot.in_trade = True
msgs = bot.feed("You have $1500. How much are you trading?")
self.assertEqual(len(msgs), 1)
amount = int(msgs[0])
self.assertGreaterEqual(amount, 0)
self.assertLessEqual(amount, 375) # max 25% of 1500
def test_trade_gojf_prompt(self):
bot = self._make_bot("alice")
bot.in_trade = True
msgs = bot.feed("You have 2 get-out-of-jail-free cards. How many are you trading?")
self.assertEqual(msgs, ["0"])
@patch("monop_players.random")
def test_trade_accepted(self, mock_random):
mock_random.random.return_value = 0.3 # < 0.5 → accept
bot = self._make_bot("alice")
bot.in_trade = True
# alice is asked to confirm (she's the tradee)
bot.current_player = "bob" # bob initiated
msgs = bot.feed("alice, is the trade ok?")
self.assertIn("yes", msgs)
@patch("monop_players.random")
def test_trade_rejected(self, mock_random):
mock_random.random.return_value = 0.7 # >= 0.5 → reject
bot = self._make_bot("alice")
bot.in_trade = True
bot.current_player = "bob"
msgs = bot.feed("alice, is the trade ok?")
self.assertIn("no", msgs)
def test_trade_done_resets(self):
bot = self._make_bot("alice")
bot.in_trade = True
msgs = bot.feed("Trade is done!")
self.assertFalse(bot.in_trade)
def test_trade_nobody_around(self):
bot = self._make_bot("alice")
bot.in_trade = True
msgs = bot.feed("There ain't no-one around to trade WITH!!")
self.assertFalse(bot.in_trade)
self.assertIn("roll", msgs)
def test_command_prompt_resets_trade(self):
bot = self._make_bot("alice")
bot.in_trade = True
bot.rolled_this_turn = False
msgs = bot.feed("-- Command:")
self.assertFalse(bot.in_trade)
self.assertIn("roll", msgs)
class TestValidInputs(unittest.TestCase):
def test_picks_first_option(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
msgs = bot.feed("Valid inputs are: Mediterranean ave. (P), Baltic ave. (P), done")
self.assertIn("Mediterranean ave. (P)", msgs)
def test_picks_done(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "alice"
msgs = bot.feed("Valid inputs are: done")
self.assertIn("done", msgs)
class TestBadPlayer(unittest.TestCase):
def test_turn_correction(self):
bot = FakePlayerBot("alice", ["alice", "bob"], 0)
bot.setup_phase = False
bot.current_player = "bob"
msgs = bot.feed("Illegal action: bad player (alice's turn, not bob)")
self.assertEqual(bot.current_player, "alice")
# alice should roll
self.assertIn("roll", msgs)
if __name__ == "__main__":
unittest.main(verbosity=2)