import logging import random from codenames.v1.types_pb2 import Role, Team from codenames.v1 import types_pb2, bot_pb2 logger = logging.getLogger(__name__) class CodenamesBot: """ A bot that can play Codenames as either a spymaster or a guesser. """ def __init__(self): self.game_id: str = "" self.my_team: Team = types_pb2.TEAM_UNSPECIFIED self.my_role: Role = types_pb2.ROLE_UNSPECIFIED self.words: list[str] = [] def handle_game_started( self, request: bot_pb2.GameStartedRequest ) -> bot_pb2.GameStartedResponse: """ Handle GameStarted request from the server. Initialize local game state from the board. """ if len(self.words) != 0: logger.warning( f"New game started while {self.game_id} was active, abandoning old game" ) self.words = [card.word for card in request.board.cards] # Set the game state self.game_id = request.game_id self.my_team = request.your_team self.my_role = request.your_role logger.info( f"Game started: {self.game_id} for bot {request.bot_id}, team={self.my_team}, role={self.my_role}" ) return bot_pb2.GameStartedResponse() def handle_give_clue( self, _request: bot_pb2.GiveClueRequest ) -> bot_pb2.GiveClueResponse: """Handle GiveClue request as spymaster.""" if len(self.words) == 0: raise ValueError("No active game") # Get clue from strategy word, count = self.get_clue() logger.info(f"Spymaster giving clue: {word} ({count})") response = bot_pb2.GiveClueResponse() response.give_clue.word = word response.give_clue.count = count return response def get_clue(self) -> tuple[str, int]: """Get a clue as the Spymaster.""" CLUE_WORDS = [ "think", "hint", "link", "match", "group", "connect", "relate", "bridge", "bond", "chain", "random", "guess", "word", "game", "play", "team", "card", "board", "code", "name", ] word = random.choice(CLUE_WORDS) count = random.randint(1, 3) return word, count def handle_make_guess( self, _request: bot_pb2.MakeGuessRequest ) -> bot_pb2.MakeGuessResponse: """Handle MakeGuess request as guesser.""" if len(self.words) == 0: raise ValueError("No active game") # Get guess from strategy word, should_guess = self.get_guess() response = bot_pb2.MakeGuessResponse() if should_guess and word: logger.info(f"Guesser making guess: {word}") response.guess_word.word = word else: logger.info("Guesser ending turn") response.end_turn.SetInParent() return response def get_guess(self) -> tuple[str, bool]: return random.choice(self.words), True def handle_game_ended( self, request: bot_pb2.GameEndedRequest ) -> bot_pb2.GameEndedResponse: """ Handle GameEnded request. Clean up game state after game completion. """ game_id = request.game_id winner = request.winner result = "won" if winner == self.my_team else "lost" logger.info(f"Game ended: {game_id}, result={result}") # Clear the game state self.game_id = "" self.words = [] self.my_team = types_pb2.TEAM_UNSPECIFIED self.my_role = types_pb2.ROLE_UNSPECIFIED return bot_pb2.GameEndedResponse()