Source code for haive.games.clue.example

"""Clue example module.

This module provides an example of running a Clue game with the rich UI visualization.

It demonstrates how to:
    - Configure and initialize the Clue game
    - Set up the game state
    - Visualize the game with the rich UI
    - Process game turns with guesses and responses
    - Handle game over conditions

The module uses a standard CLI interface with argument parsing
to allow customization of game behavior.

Example:
    Run this script directly to start a Clue game:
        python -m haive.games.clue.example

    Command-line options:
        --debug: Enable debug mode with detailed logging
        --turns: Set maximum number of turns (default: 10)
        --delay: Set delay between moves in seconds (default: 1.0)

"""

import argparse
import logging
import random
import time
import traceback

from haive.games.clue.models import (
    CardType,
    ClueCard,
    ClueGuess,
    ClueHypothesis,
    ClueResponse,
    ClueSolution,
    ValidRoom,
    ValidSuspect,
    ValidWeapon,
)
from haive.games.clue.state import ClueState
from haive.games.clue.state_manager import ClueStateManager
from haive.games.clue.ui import ClueUI

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


[docs] def run_clue_game(debug: bool = False, max_turns: int = 10, delay: float = 1.0): """Run a Clue game with rich UI visualization. This function sets up and runs a Clue game with test moves (for demonstration) with rich terminal visualization. It handles a simplified game flow with a few predetermined moves for UI testing. Args: debug (bool): Enable debug mode with detailed logging max_turns (int): Maximum number of turns before the game ends delay (float): Delay between moves in seconds for better readability Returns: None Example: >>> run_clue_game(debug=True, max_turns=5, delay=0.5) # Runs a test game with debug logging, 5 max turns, and 0.5s delay """ # Create the UI ui = ClueUI() try: # For testing, we'll create a state manually instead of using the # ClueStateManager.initialize which expects a different format # Create a basic board setup [suspect.value for suspect in ValidSuspect] [weapon.value for weapon in ValidWeapon] [room.value for room in ValidRoom] # Define a predetermined solution for testing solution = ClueSolution( suspect=ValidSuspect.COLONEL_MUSTARD, weapon=ValidWeapon.KNIFE, room=ValidRoom.KITCHEN, ) # Create a list of remaining cards (excluding solution) remaining_suspects = [ s.value for s in ValidSuspect if s != ValidSuspect.COLONEL_MUSTARD ] remaining_weapons = [w.value for w in ValidWeapon if w != ValidWeapon.KNIFE] remaining_rooms = [r.value for r in ValidRoom if r != ValidRoom.KITCHEN] # Combine all remaining cards all_remaining_cards = remaining_suspects + remaining_weapons + remaining_rooms # Shuffle and split between players random.shuffle(all_remaining_cards) midpoint = len(all_remaining_cards) // 2 player1_cards = all_remaining_cards[:midpoint] player2_cards = all_remaining_cards[midpoint:] # Create the state state = ClueState( solution=solution, guesses=[], responses=[], player1_cards=player1_cards, player2_cards=player2_cards, current_player="player1", max_turns=max_turns, game_status="ongoing", player1_hypotheses=[], player2_hypotheses=[], ) # Display initial state ui.display_state(state) time.sleep(delay) # Define some mock hypotheses player1_hypothesis = ClueHypothesis( prime_suspect=ValidSuspect.PROFESSOR_PLUM, prime_weapon=ValidWeapon.KNIFE, prime_room=ValidRoom.LIBRARY, confidence=0.4, excluded_suspects=[ValidSuspect.MRS_WHITE, ValidSuspect.MR_GREEN], excluded_weapons=[ValidWeapon.ROPE], excluded_rooms=[ValidRoom.BALLROOM, ValidRoom.CONSERVATORY], reasoning="Based on the cards in my hand and guesses so far.", ) player2_hypothesis = ClueHypothesis( prime_suspect=ValidSuspect.COLONEL_MUSTARD, prime_weapon=ValidWeapon.WRENCH, prime_room=ValidRoom.KITCHEN, confidence=0.35, excluded_suspects=[ValidSuspect.MISS_SCARLET], excluded_weapons=[ValidWeapon.CANDLESTICK, ValidWeapon.REVOLVER], excluded_rooms=[ValidRoom.STUDY, ValidRoom.HALL], reasoning="Process of elimination from previous guesses.", ) # Add the hypotheses to the state state = ClueStateManager.add_analysis( state, "player1", player1_hypothesis.to_dict() ) state = ClueStateManager.add_analysis( state, "player2", player2_hypothesis.to_dict() ) # Display updated state with hypotheses ui.display_state(state) time.sleep(delay) # Predefined guesses for testing test_guesses = [ # Player 1's guesses ( ClueGuess( suspect=ValidSuspect.PROFESSOR_PLUM, weapon=ValidWeapon.ROPE, room=ValidRoom.LIBRARY, ), "player1", ), # Player 2's guesses ( ClueGuess( suspect=ValidSuspect.MISS_SCARLET, weapon=ValidWeapon.CANDLESTICK, room=ValidRoom.BALLROOM, ), "player2", ), # Player 1's guesses ( ClueGuess( suspect=ValidSuspect.COLONEL_MUSTARD, weapon=ValidWeapon.WRENCH, room=ValidRoom.KITCHEN, ), "player1", ), # Player 2's guesses ( ClueGuess( suspect=ValidSuspect.COLONEL_MUSTARD, weapon=ValidWeapon.KNIFE, room=ValidRoom.STUDY, ), "player2", ), # Player 1's winning guess ( ClueGuess( suspect=ValidSuspect.COLONEL_MUSTARD, weapon=ValidWeapon.KNIFE, room=ValidRoom.KITCHEN, ), "player1", ), ] # Process each guess for guess, player in test_guesses: # Make sure it's the right player's turn if state.current_player != player: # Skip this guess if it's not this player's turn continue # Show thinking animation ui.show_thinking(player, "Analyzing clues...") time.sleep(delay * 0.5) # Show the guess ui.show_guess(guess, player) # Check if the guess matches the solution if ( guess.suspect == solution.suspect and guess.weapon == solution.weapon and guess.room == solution.room ): # Winning move response = ClueResponse(is_correct=True) ui.show_response(response, player) # Update state state.guesses.append(guess) state.responses.append(response) state.game_status = f"{player}_win" state.winner = player # Display final state ui.display_state(state) ui.show_game_over(state) break # Find any refuting cards other_player = "player2" if player == "player1" else "player1" other_player_cards = ( state.player2_cards if player == "player1" else state.player1_cards ) refuting_cards = [] # Check if other player has any matching cards if guess.suspect.value in other_player_cards: refuting_cards.append( ClueCard(name=guess.suspect.value, card_type=CardType.SUSPECT) ) if guess.weapon.value in other_player_cards: refuting_cards.append( ClueCard(name=guess.weapon.value, card_type=CardType.WEAPON) ) if guess.room.value in other_player_cards: refuting_cards.append( ClueCard(name=guess.room.value, card_type=CardType.ROOM) ) if refuting_cards: # Other player refutes with a random card refuting_card = random.choice(refuting_cards) response = ClueResponse( is_correct=False, responding_player=other_player, refuting_card=refuting_card, ) else: # No one can refute response = ClueResponse( is_correct=False, responding_player=None, refuting_card=None, ) # Show the response ui.show_response(response, player) # Update state state.guesses.append(guess) state.responses.append(response) state.current_player = "player2" if player == "player1" else "player1" # Display updated state ui.display_state(state) time.sleep(delay) # Add new hypotheses if player == "player1": # Update hypothesis based on new information player1_hypothesis.confidence += 0.1 player1_hypothesis.reasoning += " Updated with new information." state = ClueStateManager.add_analysis( state, "player1", player1_hypothesis.to_dict() ) else: # Update hypothesis based on new information player2_hypothesis.confidence += 0.15 player2_hypothesis.reasoning += " Getting closer to the solution." state = ClueStateManager.add_analysis( state, "player2", player2_hypothesis.to_dict() ) # Check if max turns reached if len(state.guesses) >= max_turns: # Game over - no winner state.game_status = "draw" ui.display_state(state) ui.show_game_over(state) break except Exception as e: logger.error(f"Error during game: {e}") traceback.print_exc() logger.info("Game complete!")
if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run a Clue game with rich UI") parser.add_argument("--debug", action="store_true", help="Enable debug mode") parser.add_argument("--turns", type=int, default=10, help="Maximum number of turns") parser.add_argument( "--delay", type=float, default=1.0, help="Delay between moves (seconds)" ) args = parser.parse_args() # Set logging level based on debug flag if args.debug: logging.basicConfig(level=logging.DEBUG) # Run the game run_clue_game(debug=args.debug, max_turns=args.turns, delay=args.delay)