diff options
Diffstat (limited to 'app/bot.py')
| -rw-r--r-- | app/bot.py | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/app/bot.py b/app/bot.py new file mode 100644 index 0000000..c289c85 --- /dev/null +++ b/app/bot.py @@ -0,0 +1,137 @@ +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() |