summaryrefslogtreecommitdiff
path: root/app/bot.py
blob: c289c8505affe8b5bb6612b1272cb1183f2cb87f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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()