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()
|