summaryrefslogtreecommitdiff
path: root/app/bot.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/bot.py')
-rw-r--r--app/bot.py137
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()