async def unjoin(self, ctx, *members: typing.Union[discord.Member, discord.Role]): if not members: members = (ctx.author,) playing_role = here(ctx).playing_role async def remove_player(member): room = here(ctx) if room.in_round and (member in room.game.players): await ctx.send(f"{member.mention} will leave after this round finishes.") room.add_member_to_leaver_queue(member) elif room.in_round and (member in room.queued_joiners): room.remove_member_from_joiner_queue(member) await ctx.send(f"{member.mention} will no longer join after this round.") elif member in room.room_players: room.remove_player(member) await ctx.send(f"{member.display_name} is no longer playing.") await member.remove_roles(playing_role) else: await ctx.send(f"{ctx.author.mention} {member.display_name} was already not playing.") for member_or_role in members: if isinstance(member_or_role, discord.Role): if member_or_role == here(ctx).playing_role: for player in here(ctx).room_players: await remove_player(player) else: raise commands.CheckFailure(f"Cannot remove `{member_or_role.name}`. Must be the current room's player role `{playing_role}`.") for player in here(ctx).room_players: await remove_player(player) else: await remove_player(member_or_role)
async def guess_team_helper(self, ctx, guesser, guessed_players, veto_timeout_override=False): here(ctx).resolve_team_guess( guesser, guessed_players, veto_timeout_override=veto_timeout_override) guessed_players_string = names_list_string(guessed_players) if (not here(ctx).game.include_veto_phase) or veto_timeout_override: # Full resolve correct = here(ctx).game.check_team_guess(guesser, guessed_players) correct_string = {True: "right", False: "wrong"}[correct] await ctx.send( f"**{guesser.display_name}** (team **{here(ctx).game.get_secret_word(guesser)}**) guessed {guessed_players_string} for the their team, which is __{correct_string}__. Winning team: **{here(ctx).game.winning_word}**" ) await self.reveal_teams(ctx) await self.end_round_and_clean_up(ctx) else: # Enter veto phase await ctx.send( f"**{guesser.display_name}** guessed {guessed_players_string} for the their team. Entering veto phase." ) await self.enter_veto_phase(ctx)
async def guessword(self, ctx, word: str): guesser = ctx.author if word not in here(ctx).game.words: raise commands.CheckFailure( f"`{word}` not in word list. Check spelling and capitalization. You can edit your message or enter a new one." ) correct = here(ctx).resolve_word_guess(guesser, word) correct_string = {True: "right", False: "wrong"}[correct] await ctx.send( f"**{guesser.display_name}** (team **{here(ctx).game.get_secret_word(guesser)}**) guessed **{word}** for the opposing word, which is __{correct_string}__. Winning team: **{here(ctx).game.winning_word}**" ) # If this overrode a veto, say whether it would have succeeded if here(ctx).game.in_veto_phase: orig_guesser, orig_guessed_players = here( ctx).game.vetoable_team_guess orig_guessed_players_string = names_list_string( orig_guessed_players) correct = here(ctx).game.check_team_guess(orig_guesser, orig_guessed_players) correct_string = {True: "right", False: "wrong"}[correct] await ctx.send( f"(The original guess by **{orig_guesser.display_name}** (team **{here(ctx).game.get_secret_word(orig_guesser)}**) of {orig_guessed_players_string} would have been _{correct_string}_.)" ) await self.reveal_teams(ctx) await self.end_round_and_clean_up(ctx)
async def numwords(self, ctx, *, num: int = None): if num is not None: if not 2 <= num <= 100: raise commands.CheckFailure(f"Invalid number of words {num}.") here(ctx).num_words = num num = here(ctx).num_words await ctx.send(f"Number of words: {num}")
async def start_round(self, ctx): here(ctx).start_round() await self.display_round_intro(ctx) await self.display_and_pin_wordlist(ctx) for player in here(ctx).game.players: await self.message_player_secret_word(ctx, player)
async def maxguess(self, ctx, *, size: int = None): if size is not None: if not 1 <= size <= 99: raise commands.CheckFailure(f"Invalid team guess size {size}.") here(ctx).max_guess = size await self.message_in_round(ctx) size = here(ctx).max_guess await ctx.send( f"Guess team subset of size {size} (counting yourself) in games with {2*size + 1}+ players." )
async def numwords(self, ctx, *, num: int = None): if num is not None: if not ((2 <= num <= 100) or (num == 0)): raise commands.CheckFailure(f"Invalid number of words {num}.") here(ctx).num_words = num await self.message_in_round(ctx) num = here(ctx).num_words if num == 0: await ctx.send( f"Number of words: 0 (automatically double the number of players)" ) else: await ctx.send(f"Number of words: {num}")
async def vetodur(self, ctx, *, duration: int = None): if duration is not None: if not 0 <= duration <= 999: raise commands.CheckFailure(f"Invalid duration {duration}.") here(ctx).veto_duration = duration await self.message_in_round(ctx) duration = here(ctx).veto_duration if duration == 0: description = "0 (no veto round)" else: description = f"{duration} seconds" await ctx.send(f"Veto duration: {description}")
async def guessword(self, ctx, word: str): guesser = ctx.author room = here(ctx) game = room.game if word not in game.words: raise commands.CheckFailure( f"`{word}` not in word list. Check spelling and capitalization. You can edit your message or enter a new one." ) correct = room.resolve_word_guess(guesser, word) correct_string = {True: "right", False: "wrong"}[correct] await ctx.send( f"**{guesser.display_name}** (team **{game.get_secret_word(guesser)}**) guessed **{word}** for the opposing word, which is __{correct_string}__. Winning team: **{game.winning_word}**" ) # If this overrode a veto, say whether it would have succeeded if game.in_veto_phase: orig_guesser, orig_guessed_players = game.vetoable_team_guess correctness_message = self.team_guess_correctness_message( ctx, orig_guesser, orig_guessed_players, is_hypothetical=True) await ctx.send(correctness_message) await self.reveal_teams(ctx) await self.end_round_and_clean_up(ctx)
async def join(self, ctx, *members: discord.Member): if not members: members = (ctx.author, ) room = here(ctx) for member in members: current_players = room.room_players if member.bot: await ctx.send( f"{ctx.author.mention} {member.display_name} is a bot.") elif room.in_round and (member in room.queued_leavers): room.remove_member_from_leaver_queue(member) await ctx.send( f"{member.mention} will no longer leave after this round.") elif member in current_players: await ctx.send( f"{ctx.author.mention} {member.display_name} was already playing." ) elif room.in_round: await ctx.send( f"{member.mention} will join and be pinged after this round ends." ) room.add_member_to_joiner_queue(member) else: room.add_player(member) if room.playing_role: await member.add_roles(room.playing_role) await ctx.send(f"{member.mention} is now playing")
async def guess_team_helper(self, ctx, guesser, guessed_players, veto_timeout_override=False): room = here(ctx) game = room.game room.resolve_team_guess(guesser, guessed_players, veto_timeout_override=veto_timeout_override) if (not game.include_veto_phase) or veto_timeout_override: # Full resolve correctness_message = self.team_guess_correctness_message( ctx, guesser, guessed_players) await ctx.send(correctness_message) await self.reveal_teams(ctx) await self.end_round_and_clean_up(ctx) else: # Enter veto phase guessed_players_string = names_list_string(guessed_players) await ctx.send( f"**{guesser.display_name}** guessed {guessed_players_string} for their team. Entering veto phase." ) await self.enter_veto_phase(ctx)
async def end_veto_round(self, ctx): await ctx.send("Veto phase over. Original guess goes through.") (guesser, guessed_players) = here(ctx).game.vetoable_team_guess await self.guess_team_helper(ctx, guesser, guessed_players, veto_timeout_override=True)
async def message_player_secret_word(self, ctx, player): secret_word = here(ctx).game.get_secret_word(player) return_url = link_to_channel(ctx.channel) await player.send( f"Round {here(ctx).round_num}: Your secret word is **{secret_word}** (back to #{ctx.channel.name}: {return_url})" )
async def guessteam(self, ctx, *players: discord.Member): guessed_players = players guessed_players = list(guessed_players) guesser = ctx.author players_set = set(here(ctx).game.players) if not set(guessed_players) <= players_set: extra_players = list(set(guessed_players) - players_set) extra_player_names = [ player.display_name for player in extra_players ] extra_player_name_string = " and ".join(extra_player_names) phrase = { False: "is not a player", True: "are not players" }[len(extra_players) > 1] raise commands.CheckFailure( f"{extra_player_name_string} {phrase}. Use @ to autocomplete names to avoid typos. Names are case-sensitive. You can edit your message or enter a new one." ) # As a convenience, include the guesser in their own team guess if guesser not in guessed_players: guessed_players = [guesser] + guessed_players await self.guess_team_helper(ctx, guesser, guessed_players)
async def skew(self, ctx, *, skew_chance: float = None): if skew_chance is not None: if not 0.0 <= skew_chance <= 1.0: raise commands.CheckFailure(f"Invalid chance {skew_chance}.") here(ctx).skew_chance = skew_chance await self.message_in_round(ctx) skew_chance = here(ctx).skew_chance if skew_chance == 0.0: description = "0% (never skew)" elif skew_chance == 1.0: description = "100% (always skew -- did you mean 0 maybe?)" else: description = f"{skew_chance:.1%}" await ctx.send(f"Skew chance: {description}")
async def howguess(self, ctx): guess_message = "Use `!gw word` to guess the opposing team's word.\nUse `!gt [teammates]` to guess your team. Write their names space-separated; you can use `@` to autocomplete. You can omit yourself." await ctx.send(guess_message) # If not in DM and round is ongoing, display additional info if not isinstance(ctx.channel, discord.channel.DMChannel) and here(ctx).in_round: await self.bot.get_cog("Status").show_team_sizes_message(ctx)
async def enter_veto_phase(self, ctx): assert here(ctx).game.include_veto_phase veto_time = here(ctx).veto_duration await ctx.send( f"You have **{veto_time} seconds** to guess a word and override this team guess, or it will resolve." ) warning_time = 10 # The below probably be better done with `bot.wait_for`. # We track if the same round is still ongoing by whether the round number hasn't changed. initial_round_num = here(ctx).round_num if veto_time > warning_time: await asyncio.sleep(veto_time - warning_time) if (here(ctx).game is not None) and (initial_round_num == here(ctx).round_num): await ctx.send(f"**{warning_time} seconds** to guess!") await asyncio.sleep(min(warning_time, veto_time)) if (here(ctx).game is not None) and (initial_round_num == here(ctx).round_num): await self.end_veto_round(ctx)
async def display_round_intro(self, ctx): room = here(ctx) assert room.in_round, "Can't display intro with no round ongoing." await ctx.send(f"__Round {room.round_num}__") await self.bot.get_cog("Status").players(ctx) start_message = "You've been messaged your secret word -- to see it, click the Home icon in the very top left. Use `!howguess` to show the commands to guess. Clue away!" await ctx.send(start_message)
async def display_round_intro(self, ctx): assert here(ctx).in_round, "Can't display intro with no round ongoing." player_mention_string = names_string(here(ctx).game.players) num_players = len(here(ctx).game.players) await ctx.send( f"**Round {here(ctx).round_num}**\nPlayers ({num_players}): {player_mention_string}" ) team_sizes_string = " or ".join( [str(size) for size in sorted(set(here(ctx).game.team_sizes))]) if here(ctx).game.team_guess_size is not None: team_guess_size_comment = f", of which you guess a subset of {here(ctx).game.team_guess_size} (counting yourself)" else: team_guess_size_comment = "" team_sizes_message = f"Teams are of size {team_sizes_string}{team_guess_size_comment}." await ctx.send(team_sizes_message) start_message = "You've been messaged your secret word. Use `!howguess` to show the commands to guess. Clue away!" await ctx.send(start_message)
async def reveal_teams(self, ctx): words = here(ctx).game.teams.keys() def is_winning_word(word): return word == here(ctx).game.winning_word words_with_winner_first = sorted(words, key=is_winning_word, reverse=True) team_strings = [ f"**{word}**: {names_string(here(ctx).game.teams[word])}" for word in words_with_winner_first ] await ctx.send(" || ".join(team_strings))
async def remove_player(member): room = here(ctx) if room.in_round and (member in room.game.players): await ctx.send(f"{member.mention} will leave after this round finishes.") room.add_member_to_leaver_queue(member) elif room.in_round and (member in room.queued_joiners): room.remove_member_from_joiner_queue(member) await ctx.send(f"{member.mention} will no longer join after this round.") elif member in room.room_players: room.remove_player(member) await ctx.send(f"{member.display_name} is no longer playing.") await member.remove_roles(playing_role) else: await ctx.send(f"{ctx.author.mention} {member.display_name} was already not playing.")
def team_guess_correctness_message(self, ctx, guesser, guessed_players, is_hypothetical=False): game = here(ctx).game guesser_word = game.get_secret_word(guesser) correct_team = game.players_with_word(guesser_word) def player_name_formatted_by_correctness(player): if player in correct_team: # Bold return f"**{player.display_name}**" else: # Bold italics return f"***{player.display_name}***" missing_players = set(correct_team) - set(guessed_players) if bool(missing_players): must_guess_exact = game.team_guess_size is None if must_guess_exact: label = "missing" else: label = "unguessed" missing_player_string = f" ({label}: " + ", ".join( [f"**{player.display_name}**" for player in missing_players]) + ")" else: missing_player_string = "" guessed_players_marked_string = "[" + ", ".join([ player_name_formatted_by_correctness(player) for player in guessed_players ]) + "]" correct = game.check_team_guess(guesser, guessed_players) correct_string = {True: "right", False: "wrong"}[correct] winning_word = { True: guesser_word, False: game.opposing_word(guesser_word) }[correct] if not is_hypothetical: correctness_message = f"**{guesser.display_name}** (team **{guesser_word}**) guessed {guessed_players_marked_string} for their team, which is __{correct_string}__{missing_player_string}. Winning team: **{winning_word}**." else: correctness_message = f"(The original guess by **{guesser.display_name}** (team **{guesser_word}**) of {guessed_players_marked_string} would have been __{correct_string}__{missing_player_string}, with winning team **{winning_word}**.)" return correctness_message
async def players(self, ctx): room = here(ctx) if room.in_round: game = room.game players = game.players player_mention_string = names_string_formatted(players) num_players = len(players) await ctx.send( f"__Players__ ({num_players}): {player_mention_string}") await self.show_team_sizes_message(ctx) else: players = room.room_players player_mention_string = names_string_formatted(players) num_players = len(players) await ctx.send(f"Players ({num_players}): {player_mention_string}")
async def show_team_sizes_message(self, ctx): room = here(ctx) game = room.game team_sizes_string = " or ".join( [str(size) for size in sorted(set(game.possible_team_sizes))]) if game.team_guess_size is not None: team_guess_size_comment = f", of which you guess a subset of {game.team_guess_size} (counting yourself)" else: team_guess_size_comment = ". Guess your whole team exactly" if game.might_skew: skew_comment = f" ({game.skew_chance:.1%} chance skewed)" else: skew_comment = "" team_sizes_message = f"Teams are of **size {team_sizes_string}**{skew_comment}{team_guess_size_comment}." await ctx.send(team_sizes_message)
async def enter_veto_phase(self, ctx): assert here(ctx).game.include_veto_phase veto_time = here(ctx).veto_duration await ctx.send( f"You have **{veto_time} seconds** to guess a word and override the preceding team guess, or it will resolve." ) warning_time = 10 initial_round_num = here(ctx).round_num if veto_time > warning_time: await asyncio.sleep(veto_time - warning_time) if (here(ctx).game is not None) and (initial_round_num == here(ctx).round_num): await ctx.send(f"**{warning_time} seconds** to guess!") await asyncio.sleep(min(warning_time, veto_time)) if (here(ctx).game is not None) and (initial_round_num == here(ctx).round_num): await self.end_veto_round(ctx)
async def status(self, ctx): await ctx.send(here(ctx).status_string)
async def unpause(self, ctx): here(ctx).unpause() await ctx.send(f"{ctx.author.mention} Resumed the round.")
async def pause(self, ctx): here(ctx).pause() await ctx.send( f"{ctx.author.mention} Paused the round. Use `!unpause` to resume." )
async def end_round_and_clean_up(self, ctx): here(ctx).end_round() lobby_cog = self.bot.get_cog("Lobby") await lobby_cog.resolve_joiner_queue(ctx) await lobby_cog.resolve_leaver_queue(ctx)
def is_winning_word(word): return word == here(ctx).game.winning_word