diff --git a/server/games/humanitycards/bot.py b/server/games/humanitycards/bot.py new file mode 100644 index 00000000..b5a0dd43 --- /dev/null +++ b/server/games/humanitycards/bot.py @@ -0,0 +1,58 @@ +"""Heuristic bot logic for Humanity Cards.""" + +from __future__ import annotations + +import random +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from .game import HumanityCardsGame, HumanityCardsPlayer + + +def bot_think(game: "HumanityCardsGame", player: "HumanityCardsPlayer") -> str | None: + if game.phase == "submitting": + return _think_submitting(game, player) + if game.phase == "judging": + return _think_judging(game, player) + return None + + +def _think_submitting(game: "HumanityCardsGame", player: "HumanityCardsPlayer") -> str | None: + if game._is_judge(player) and not game._all_players_are_judges(): + return None + if player.submitted_cards is not None: + return None + + required = game.current_black_card["pick"] if game.current_black_card else 1 + + if len(player.selected_indices) < required: + available = [i for i in range(len(player.hand)) if i not in player.selected_indices] + if available: + return f"toggle_card_{random.choice(available)}" # nosec B311 + + if len(player.selected_indices) == required: + return "submit_cards" + + return None + + +def _think_judging(game: "HumanityCardsGame", player: "HumanityCardsPlayer") -> str | None: + if not game._is_judge(player): + return None + if player.id in game.judge_picks: + return None + if not game.submission_order: + return None + + valid_picks = list(range(len(game.submission_order))) + if game._all_players_are_judges(): + valid_picks = [ + i for i in valid_picks + if game.submission_order[i] < len(game.submissions) + and game.submissions[game.submission_order[i]]["player_id"] != player.id + ] + + if not valid_picks: + return None + + return f"judge_pick_{random.choice(valid_picks)}" # nosec B311 diff --git a/server/games/humanitycards/game.py b/server/games/humanitycards/game.py index d82a36ae..e1a9baef 100644 --- a/server/games/humanitycards/game.py +++ b/server/games/humanitycards/game.py @@ -15,6 +15,7 @@ from ..registry import register_game from ...game_utils.actions import Action, ActionSet, Visibility from ...game_utils.bot_helper import BotHelper +from .bot import bot_think as _bot_think from ...game_utils.game_result import GameResult, PlayerResult from ...game_utils.options import ( IntOption, @@ -203,6 +204,22 @@ class HumanityCardsOptions(GameOptions): change_msg="hc-option-changed-num-judges", ) ) + judging_method: str = option_field( + MenuOption( + default="Independent", + choices=["Independent", "Jury", "Random"], + value_key="mode", + label="hc-set-judging-method", + prompt="hc-select-judging-method", + change_msg="hc-option-changed-judging-method", + choice_labels={ + "Independent": "hc-judging-method-independent", + "Jury": "hc-judging-method-jury", + "Random": "hc-judging-method-random", + }, + description="hc-desc-judging-method", + ) + ) # ========================================================================== @@ -235,6 +252,8 @@ class HumanityCardsGame(Game): last_winner_index: int = -1 # For "Most Recent Winner" czar selection submissions: list[dict] = field(default_factory=list) # [{"player_id": str, "cards": [str]}] submission_order: list[int] = field(default_factory=list) # Shuffled indices into submissions + judge_picks: dict[str, str] = field(default_factory=dict) # judge_id → picked player_id + active_judging_method: str = "" # resolved method this round (handles Random) round_end_ticks: int = 0 # Countdown ticks before next round starts @classmethod @@ -386,12 +405,24 @@ def _get_non_judges(self) -> list[HumanityCardsPlayer]: judge_ids = {j.id for j in self._get_judges()} return [p for p in self.get_active_players() if p.id not in judge_ids] + def _all_players_are_judges(self) -> bool: + return len(self._get_judges()) >= len(self.get_active_players()) + + @staticmethod + def _format_names(locale: str, names: list[str]) -> str: + """Format a list of names using the recipient's locale rules.""" + return Localization.format_list_and(locale, names) + + def _get_submitters(self) -> list[HumanityCardsPlayer]: + """Players expected to submit this round (everyone when all are judges).""" + if self._all_players_are_judges(): + return list(self.get_active_players()) + return self._get_non_judges() + def _select_judges(self) -> None: """Select judge(s) for the current round based on czar_selection option.""" active = self.get_active_players() - num_judges = min(self.options.num_judges, len(active) - 1) # At least 1 non-judge - if num_judges < 1: - num_judges = 1 + num_judges = max(1, min(self.options.num_judges, len(active))) mode = self.options.czar_selection @@ -522,25 +553,13 @@ def create_turn_action_set(self, player: HumanityCardsPlayer) -> ActionSet: ) ) - # View scores (always visible at bottom) - action_set.add( - Action( - id="view_scores", - label=Localization.get(locale, "hc-view-scores"), - handler="_action_view_scores", - is_enabled="_is_view_scores_enabled", - is_hidden="_is_view_scores_hidden", - show_in_actions_menu=True, - ) - ) - # Whose judge (keybind-only, J key) action_set.add( Action( id="whose_judge", label=Localization.get(locale, "hc-whose-judge"), handler="_action_whose_judge", - is_enabled="_is_view_scores_enabled", + is_enabled="_is_playing_enabled", is_hidden="_is_whose_judge_hidden", ) ) @@ -586,15 +605,6 @@ def setup_keybinds(self) -> None: state=KeybindState.ACTIVE, ) - # S to view scores - self.define_keybind( - "s", - "View scores", - ["view_scores"], - state=KeybindState.ACTIVE, - include_spectators=True, - ) - # J to announce judges self.define_keybind( "j", @@ -614,7 +624,7 @@ def _is_toggle_card_enabled(self, player: Player, action_id: str) -> str | None: if player.is_spectator: return "action-spectator" hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return "action-spectator" if hcp.submitted_cards is not None: return "hc-already-submitted" @@ -631,7 +641,7 @@ def _is_toggle_card_hidden(self, player: Player, action_id: str) -> Visibility: if player.is_spectator: return Visibility.HIDDEN hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return Visibility.HIDDEN if hcp.submitted_cards is not None: return Visibility.HIDDEN @@ -659,21 +669,18 @@ def _get_toggle_card_sound(self, player: Player, action_id: str) -> str | None: return "game_humanitycards/cardselect.ogg" return None - def _is_submit_enabled(self, player: Player) -> str | tuple[str, dict] | None: + def _is_submit_enabled(self, player: Player) -> str | None: if self.status != "playing": return "action-not-playing" if player.is_spectator: return "action-spectator" hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return "action-spectator" if hcp.submitted_cards is not None: return "hc-already-submitted" if self.phase != "submitting": return "action-not-playing" - required = self.current_black_card["pick"] if self.current_black_card else 1 - if len(hcp.selected_indices) != required: - return ("hc-wrong-card-count", {"count": required}) return None def _is_submit_hidden(self, player: Player) -> Visibility: @@ -682,7 +689,7 @@ def _is_submit_hidden(self, player: Player) -> Visibility: if player.is_spectator: return Visibility.HIDDEN hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return Visibility.HIDDEN if hcp.submitted_cards is not None: return Visibility.HIDDEN @@ -712,9 +719,14 @@ def _is_judge_pick_enabled(self, player: Player, action_id: str) -> str | None: return "action-spectator" if self.phase != "judging": return "action-not-playing" + if hcp.id in self.judge_picks: + return "action-already-done" idx = int(action_id.removeprefix("judge_pick_")) if idx >= len(self.submission_order): return "action-not-playing" + sub_idx = self.submission_order[idx] + if sub_idx < len(self.submissions) and self.submissions[sub_idx]["player_id"] == hcp.id: + return "action-not-playing" return None def _is_judge_pick_hidden(self, player: Player, action_id: str) -> Visibility: @@ -726,6 +738,9 @@ def _is_judge_pick_hidden(self, player: Player, action_id: str) -> Visibility: idx = int(action_id.removeprefix("judge_pick_")) if idx >= len(self.submission_order): return Visibility.HIDDEN + sub_idx = self.submission_order[idx] + if sub_idx < len(self.submissions) and self.submissions[sub_idx]["player_id"] == hcp.id: + return Visibility.HIDDEN return Visibility.VISIBLE def _get_judge_pick_label(self, player: Player, action_id: str) -> str: @@ -778,36 +793,14 @@ def _get_submission_options(self, player: Player) -> list[str]: return options # ========================================================================== - # View scores callbacks + # Whose judge / whose turn overrides # ========================================================================== - def _is_view_scores_enabled(self, player: Player) -> str | None: + def _is_playing_enabled(self, player: Player) -> str | None: if self.status != "playing": return "action-not-playing" return None - def _is_view_scores_hidden(self, player: Player) -> Visibility: - if self.status != "playing": - return Visibility.HIDDEN - return Visibility.VISIBLE - - def _action_view_scores(self, player: Player, action_id: str) -> None: - """View the current scores.""" - user = self.get_user(player) - if not user: - return - sorted_players = sorted( - self.get_active_players(), - key=lambda p: p.score, # type: ignore - reverse=True, - ) - parts = [f"{p.name}: {p.score}" for p in sorted_players] # type: ignore - user.speak(". ".join(parts) + ".") - - # ========================================================================== - # Whose judge / whose turn overrides - # ========================================================================== - def _is_whose_judge_hidden(self, player: Player) -> Visibility: # Keybind-only — always hidden from menu return Visibility.HIDDEN @@ -818,30 +811,35 @@ def _action_whose_judge(self, player: Player, action_id: str) -> None: if not user: return judges = self._get_judges() - if len(judges) == 1: - user.speak_l("hc-judge-is", player=judges[0].name, count=1, others="") - elif judges: - others = ", ".join(j.name for j in judges[1:]) - user.speak_l("hc-judge-is", player=judges[0].name, count=len(judges), others=others) + if judges: + names = self._format_names(user.locale, [j.name for j in judges]) + user.speak_l("hc-judge-is", names=names, count=len(judges)) def _action_whose_turn(self, player: Player, action_id: str) -> None: - """Override default whose_turn to show submission status.""" + """Override default whose_turn to show submission/judging status.""" user = self.get_user(player) if not user: return - judges = self._get_judges() - judge_names = ", ".join(j.name for j in judges) - if self.phase == "submitting": - # List who hasn't submitted - waiting = [p.name for p in self._get_non_judges() if p.submitted_cards is None] + waiting = [p.name for p in self._get_submitters() if p.submitted_cards is None] if waiting: - user.speak_l("hc-waiting-for", names=", ".join(waiting)) + user.speak_l("hc-waiting-for", names=self._format_names(user.locale, waiting)) else: - user.speak_l("hc-all-submitted-waiting-judge", judge=judge_names) + judges = self._get_judges() + user.speak_l( + "hc-all-submitted-waiting-judge", + judge=self._format_names(user.locale, [j.name for j in judges]), + ) elif self.phase == "judging": - user.speak_l("hc-all-submitted-waiting-judge", judge=judge_names) + pending = [j for j in self._get_judges() if j.id not in self.judge_picks] + if pending: + user.speak_l( + "hc-waiting-for-judges", + names=self._format_names(user.locale, [j.name for j in pending]), + ) + else: + user.speak_l("game-no-turn") else: user.speak_l("game-no-turn") @@ -863,7 +861,7 @@ def _is_view_submission_enabled(self, player: Player) -> str | None: if self.status != "playing": return "action-not-playing" hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return "action-spectator" if self.phase != "submitting" and self.phase != "judging": return "action-not-playing" @@ -878,7 +876,7 @@ def _is_view_submission_hidden(self, player: Player) -> Visibility: if self.phase not in ("submitting", "judging"): return Visibility.HIDDEN hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return Visibility.HIDDEN return Visibility.VISIBLE @@ -899,7 +897,7 @@ def _toggle_card(self, player: Player, index: int) -> None: hcp: HumanityCardsPlayer = player # type: ignore if self.phase != "submitting" or hcp.submitted_cards is not None: return - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return if index >= len(hcp.hand): return @@ -1037,7 +1035,7 @@ def _action_submit_cards(self, player: Player, action_id: str) -> None: hcp: HumanityCardsPlayer = player # type: ignore if self.phase != "submitting" or hcp.submitted_cards is not None: return - if self._is_judge(hcp): + if self._is_judge(hcp) and not self._all_players_are_judges(): return required = self.current_black_card["pick"] if self.current_black_card else 1 @@ -1071,19 +1069,12 @@ def _action_submit_cards(self, player: Player, action_id: str) -> None: if user: user.speak_l("hc-submitted") - # Broadcast progress - non_judges = self._get_non_judges() - submitted_count = sum(1 for p in non_judges if p.submitted_cards is not None) - total = len(non_judges) - self.broadcast_l( - "hc-submission-progress", - submitted=submitted_count, - total=total, - ) - self.rebuild_all_menus() # Check if all have submitted + submitters = self._get_submitters() + submitted_count = sum(1 for p in submitters if p.submitted_cards is not None) + total = len(submitters) if submitted_count >= total: self._start_judging() @@ -1094,82 +1085,127 @@ def _judge_pick(self, player: Player, pick_index: int) -> None: hcp: HumanityCardsPlayer = player # type: ignore if not self._is_judge(hcp): return + if hcp.id in self.judge_picks: + return if pick_index >= len(self.submission_order): return actual_idx = self.submission_order[pick_index] if actual_idx >= len(self.submissions): return - winning_sub = self.submissions[actual_idx] - winner = self.get_player_by_id(winning_sub["player_id"]) - if not winner: - return - - hc_winner: HumanityCardsPlayer = winner # type: ignore - - # Award point - hc_winner.score += 1 - active = self.get_active_players() - self.last_winner_index = next((i for i, p in enumerate(active) if p.id == winner.id), -1) - - # Announce winner - winning_text = self._fill_in_blanks( - self.current_black_card["text"] if self.current_black_card else "", - winning_sub["cards"], - ) + picked_player_id = self.submissions[actual_idx]["player_id"] + if picked_player_id == hcp.id: + return # Cannot vote for own submission + self.judge_picks[hcp.id] = picked_player_id - # Play judge choice sound - self.play_sound( - f"game_humanitycards/judgechoice{random.randint(1, 3)}.ogg" # nosec B311 - ) - - self.broadcast_l( - "hc-winner-announcement", - player=winner.name, - score=hc_winner.score, - ) + judges = self._get_judges() + judges_still_pending = [j for j in judges if j.id not in self.judge_picks] - # Announce winner's submission first - self.broadcast_l( - "hc-submission-reveal", - player=winner.name, - text=winning_text, - ) + if judges_still_pending: + self.play_sound(f"game_humanitycards/judgechoice{random.randint(1, 3)}.ogg") # nosec B311 + self.rebuild_all_menus() + return - # Then announce other submissions - self.broadcast_l("hc-all-submissions") - for sub in self.submissions: - if sub["player_id"] == winner.id: + # All judges have voted — resolve + self._resolve_judging() + + def _submission_order_pos(self, player_id: str) -> int: + for pos, sub_idx in enumerate(self.submission_order): + if sub_idx < len(self.submissions) and self.submissions[sub_idx]["player_id"] == player_id: + return pos + return len(self.submission_order) + + def _announce_and_score_winners(self, scored: list[tuple[str, int]]) -> None: + """Announce each winner in order (highest points first), award scores.""" + self.play_sound(f"game_humanitycards/judgechoice{random.randint(1, 3)}.ogg") # nosec B311 + for player_id, points in scored: + player = self.get_player_by_id(player_id) + if not player: continue + hcp: HumanityCardsPlayer = player # type: ignore + hcp.score += points + self._team_manager.add_to_team_score(player.name, points) + sub = next((s for s in self.submissions if s["player_id"] == player_id), None) + text = self._fill_in_blanks( + self.current_black_card["text"] if self.current_black_card else "", + sub["cards"] if sub else [], + ).rstrip(".") + self.broadcast_l("hc-winner-announcement", player=player.name, points=points, text=text) + + def _announce_losing_submissions(self, winner_ids: set[str]) -> None: + losers = [s for s in self.submissions if s["player_id"] not in winner_ids] + if not losers: + return + self.broadcast_l("hc-all-submissions") + for sub in losers: sub_player = self.get_player_by_id(sub["player_id"]) if sub_player: filled = self._fill_in_blanks( self.current_black_card["text"] if self.current_black_card else "", sub["cards"], ) - self.broadcast_l( - "hc-submission-reveal", - player=sub_player.name, - text=filled, - ) + self.broadcast_l("hc-submission-reveal", player=sub_player.name, text=filled) - # Play draw card sound as players receive new cards + def _finish_round(self, primary_winner_id: str) -> None: + active = self.get_active_players() + self.last_winner_index = next( + (i for i, p in enumerate(active) if p.id == primary_winner_id), -1 + ) self.play_sound(f"game_cards/draw{random.randint(1, 4)}.ogg") # nosec B311 + # Primary winner checked first; with independent multiple scorers, pick highest + game_winner = self.get_player_by_id(primary_winner_id) + if not game_winner or game_winner.score < self.options.winning_score: # type: ignore + game_winner = next( + (p for p in active if p.score >= self.options.winning_score), # type: ignore + None, + ) + if game_winner and game_winner.score >= self.options.winning_score: # type: ignore + self._end_game(game_winner) # type: ignore + return + self.phase = "round_end" + self.round_end_ticks = 100 + if self.current_black_card: + self.black_discard.append(self.current_black_card) + self.current_black_card = None + self.rebuild_all_menus() - # Check win condition - if hc_winner.score >= self.options.winning_score: - self._end_game(hc_winner) + def _resolve_judging(self) -> None: + if self.active_judging_method == "Independent": + self._resolve_independent() else: - # Transition to round_end with delay before next round - self.phase = "round_end" - self.round_end_ticks = 100 # ~5 seconds at 20 ticks/sec - - # Discard current black card - if self.current_black_card: - self.black_discard.append(self.current_black_card) - self.current_black_card = None - - self.rebuild_all_menus() + self._resolve_jury() + + def _resolve_independent(self) -> None: + """Each judge's vote awards 1 point. All vote-getters score.""" + vote_counts: dict[str, int] = {} + for voted_id in self.judge_picks.values(): + vote_counts[voted_id] = vote_counts.get(voted_id, 0) + 1 + if not vote_counts: + return + # Sort: most votes first, tiebreak by submission_order position + scored = sorted( + vote_counts.items(), + key=lambda x: (-x[1], self._submission_order_pos(x[0])), + ) + self._announce_and_score_winners(scored) + self._announce_losing_submissions({pid for pid, _ in scored}) + self._finish_round(scored[0][0]) + + def _resolve_jury(self) -> None: + """Majority wins. Tied winners each get 1 point.""" + vote_counts: dict[str, int] = {} + for voted_id in self.judge_picks.values(): + vote_counts[voted_id] = vote_counts.get(voted_id, 0) + 1 + if not vote_counts: + return + max_votes = max(vote_counts.values()) + tied = [pid for pid, v in vote_counts.items() if v == max_votes] + # Sort tied winners by submission_order for consistent announcement order + tied.sort(key=self._submission_order_pos) + scored = [(pid, 1) for pid in tied] + self._announce_and_score_winners(scored) + self._announce_losing_submissions(set(tied)) + self._finish_round(tied[0]) def _action_view_black_card(self, player: Player, action_id: str) -> None: """View the current black card prompt.""" @@ -1197,47 +1233,6 @@ def _action_view_submission(self, player: Player, action_id: str) -> None: else: user.speak_l("hc-select-cards-first") - # ========================================================================== - # Score overrides - # ========================================================================== - - def _is_check_scores_enabled(self, player: Player) -> str | None: - if self.status != "playing": - return "action-not-playing" - return None - - def _is_check_scores_detailed_enabled(self, player: Player) -> str | None: - if self.status != "playing": - return "action-not-playing" - return None - - def _action_check_scores(self, player: Player, action_id: str) -> None: - user = self.get_user(player) - if not user: - return - sorted_players = sorted( - self.get_active_players(), - key=lambda p: p.score, # type: ignore - reverse=True, - ) - parts = [f"{p.name}: {p.score}" for p in sorted_players] # type: ignore - user.speak(". ".join(parts) + ".") - - def _action_check_scores_detailed(self, player: Player, action_id: str) -> None: - user = self.get_user(player) - if not user: - return - sorted_players = sorted( - self.get_active_players(), - key=lambda p: p.score, # type: ignore - reverse=True, - ) - lines = [ - f"{p.name}: {p.score} points" # type: ignore - for p in sorted_players - ] - self.status_box(player, lines) - # ========================================================================== # Game lifecycle # ========================================================================== @@ -1255,6 +1250,10 @@ def on_start(self) -> None: active_players = self.get_active_players() + # Set up individual teams for score tracking (S / Shift+S) + self._team_manager.team_mode = "individual" + self._team_manager.setup_teams([p.name for p in active_players]) + # Check we have enough cards total_whites_needed = len(active_players) * self.options.hand_size if len(self.white_deck) < total_whites_needed: @@ -1316,13 +1315,32 @@ def _start_round(self) -> None: # Announce round self.broadcast_l("hc-round-start", round=self.round) - # Announce judge(s) + # Announce judge(s) — skip in all-judge mode (everyone knows) judges = self._get_judges() - if len(judges) == 1: - self.broadcast_l("hc-judge-is", player=judges[0].name, count=1, others="") - else: - others = ", ".join(j.name for j in judges[1:]) - self.broadcast_l("hc-judge-is", player=judges[0].name, count=len(judges), others=others) + if not self._all_players_are_judges(): + judge_names = [j.name for j in judges] + for player in self.players: + user = self.get_user(player) + locale = user.locale if user else "en" + localized = Localization.get( + locale, + "hc-judge-is", + names=self._format_names(locale, judge_names), + count=len(judges), + ) + if hasattr(self, "record_transcript_event"): + self.record_transcript_event(player, localized, "table") + if user: + user.speak_l( + "hc-judge-is", + "table", + names=self._format_names(locale, judge_names), + count=len(judges), + ) + for judge in judges: + user = self.get_user(judge) + if user: + user.speak_l("hc-you-are-judge") # Announce black card black_text = self._speech_friendly_black(self.current_black_card["text"]) @@ -1330,15 +1348,15 @@ def _start_round(self) -> None: if pick_count > 1: self.broadcast_l("hc-black-card-pick", count=pick_count) - # Tell non-judges to select cards - for p in self._get_non_judges(): + # Tell submitters to select cards + for p in self._get_submitters(): user = self.get_user(p) if user: user.speak_l("hc-select-cards", count=pick_count) # Jolt bots for p in active_players: - if p.is_bot and not self._is_judge(p): + if p.is_bot and (not self._is_judge(p) or self._all_players_are_judges()): BotHelper.jolt_bot(p, ticks=random.randint(20, 40)) # nosec B311 self.rebuild_all_menus() @@ -1349,7 +1367,7 @@ def _start_judging(self) -> None: # Collect submissions self.submissions = [] - for p in self._get_non_judges(): + for p in self._get_submitters(): if p.submitted_cards is not None: self.submissions.append( { @@ -1362,6 +1380,13 @@ def _start_judging(self) -> None: self.submission_order = list(range(len(self.submissions))) random.shuffle(self.submission_order) # nosec B311 + self.judge_picks = {} + method = self.options.judging_method + if len(self._get_judges()) <= 1: + method = "Independent" # enforce per spec + elif method == "Random": + method = random.choice(["Independent", "Jury"]) # nosec B311 + self.active_judging_method = method self.play_sound("game_humanitycards/judging.ogg") self.broadcast_l("hc-judging-start") @@ -1388,24 +1413,7 @@ def _end_game(self, winner: HumanityCardsPlayer) -> None: # ========================================================================== def bot_think(self, player: HumanityCardsPlayer) -> str | None: - """Bot AI decision making.""" - if self.phase == "submitting" and not self._is_judge(player): - if player.submitted_cards is not None: - return None - required = self.current_black_card["pick"] if self.current_black_card else 1 - - # Select random cards if not enough selected - if len(player.selected_indices) < required: - available = [i for i in range(len(player.hand)) if i not in player.selected_indices] - if available: - pick = random.choice(available) # nosec B311 - return f"toggle_card_{pick}" - - # Submit when we have enough - if len(player.selected_indices) == required: - return "submit_cards" - - return None + return _bot_think(self, player) def on_tick(self) -> None: """Called every tick.""" @@ -1428,14 +1436,10 @@ def on_tick(self) -> None: self._process_judging_bots() def _process_submission_bots(self) -> None: - """Process all bot actions during submission phase.""" for player in self.players: if not player.is_bot or player.is_spectator: continue hcp: HumanityCardsPlayer = player # type: ignore - if self._is_judge(hcp) or hcp.submitted_cards is not None: - continue - BotHelper.process_bot_action( player, think_fn=lambda p=hcp: self.bot_think(p), @@ -1443,25 +1447,14 @@ def _process_submission_bots(self) -> None: ) def _process_judging_bots(self) -> None: - """Process judge bot actions during judging phase.""" for judge in self._get_judges(): if not judge.is_bot: continue - - if judge.bot_think_ticks > 0: - judge.bot_think_ticks -= 1 - continue - - if judge.bot_pending_action: - action_id = judge.bot_pending_action - judge.bot_pending_action = None - self.execute_action(judge, action_id) - continue - - # Bot judge picks a random submission - if self.submission_order: - pick = random.randint(0, len(self.submission_order) - 1) # nosec B311 - judge.bot_pending_action = f"judge_pick_{pick}" + BotHelper.process_bot_action( + judge, + think_fn=lambda j=judge: self.bot_think(j), + execute_fn=lambda action_id, j=judge: self.execute_action(j, action_id), + ) # ========================================================================== # Game result diff --git a/server/locales/ar/humanitycards.ftl b/server/locales/ar/humanitycards.ftl index 56a75520..3d464068 100644 --- a/server/locales/ar/humanitycards.ftl +++ b/server/locales/ar/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } هو قيصر البطاقات. + *[other] { $names } هم قياصرة البطاقات. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = يحصل { $player } على { $points } { $points -> + [one] نقطة + *[other] نقاط +} عن { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = بانتظار { $names } لتقديم الإجابة. +hc-all-submitted-waiting-judge = قدّم جميع اللاعبين إجاباتهم. بانتظار { $judge } للحكم. +hc-waiting-for-judges = بانتظار { $names } ليحكموا. diff --git a/server/locales/cs/humanitycards.ftl b/server/locales/cs/humanitycards.ftl index 56a75520..080be5b3 100644 --- a/server/locales/cs/humanitycards.ftl +++ b/server/locales/cs/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } je karetní car. + *[other] { $names } jsou karetní caři. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } získává { $points } { $points -> + [one] bod + *[other] body +} za { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Čeká se na { $names }, až odevzdají karty. +hc-all-submitted-waiting-judge = Všichni hráči odevzdali karty. Čeká se na rozhodnutí od { $judge }. +hc-waiting-for-judges = Čeká se na rozhodnutí od { $names }. diff --git a/server/locales/de/humanitycards.ftl b/server/locales/de/humanitycards.ftl index 56a75520..b2352b0e 100644 --- a/server/locales/de/humanitycards.ftl +++ b/server/locales/de/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } ist der Kartenzar. + *[other] { $names } sind die Kartenzare. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } erhält { $points } { $points -> + [one] Punkt + *[other] Punkte +} für { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Es wird auf { $names } gewartet, bis die Karten eingereicht werden. +hc-all-submitted-waiting-judge = Alle Spieler haben ihre Karten eingereicht. Es wird auf die Entscheidung von { $judge } gewartet. +hc-waiting-for-judges = Es wird auf die Entscheidung von { $names } gewartet. diff --git a/server/locales/en/humanitycards.ftl b/server/locales/en/humanitycards.ftl index aa8aa537..b943a8e9 100644 --- a/server/locales/en/humanitycards.ftl +++ b/server/locales/en/humanitycards.ftl @@ -15,6 +15,14 @@ hc-set-card-packs = Card packs ({ $count } of { $total } selected) hc-desc-card-packs = Which card packs to use hc-option-changed-card-packs = Card pack selection changed. +hc-set-judging-method = Judging method: { $mode } +hc-select-judging-method = Select judging method +hc-desc-judging-method = How winning submissions are chosen. Independent: each judge picks a winner, one point per vote. Jury: majority wins; ties award all tied players one point. Random: method chosen randomly each round. +hc-option-changed-judging-method = Judging method set to { $mode }. +hc-judging-method-independent = Independent +hc-judging-method-jury = Jury +hc-judging-method-random = Random + hc-set-czar-selection = Card Czar selection: { $mode } hc-select-czar-selection = Select Card Czar selection mode hc-option-changed-czar-selection = Card Czar selection set to { $mode }. @@ -33,13 +41,15 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } is the Card Czar. + *[other] { $names } are the Card Czars. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. +hc-waiting-for-judges = Waiting for { $names } to judge. + # Black card hc-black-card = The prompt is: { $text } hc-black-card-pick = Pick { $count }. @@ -56,7 +66,6 @@ hc-submit-cards = Submit ({ $selected } of { $required } selected) hc-submitted = You submitted your cards. hc-player-submitted = { $player } submitted. hc-submission-progress = { $submitted } of { $total } players submitted. -hc-waiting-for-submissions = Waiting for submissions... hc-already-submitted = You already submitted your cards. hc-wrong-card-count = You need to select exactly { $count } { $count -> [one] card @@ -65,12 +74,15 @@ hc-wrong-card-count = You need to select exactly { $count } { $count -> # Judging phase hc-judging-start = All cards are in! Time to judge. +hc-judge-voted = { $player } has made their choice. hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. -hc-winner-card = Winning answer: { $text } +hc-winner-announcement = { $player } gets { $points } { $points -> + [one] point + *[other] points +} for { $text }. hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> [one] point @@ -99,9 +111,6 @@ hc-not-enough-cards = Not enough cards. Try enabling more packs. hc-view-hand = View hand # Scores -hc-view-scores = View scores -hc-no-scores = No scores yet. - # Whose turn / whose judge hc-whose-judge = Who is judging hc-waiting-for = Waiting for { $names } to submit. diff --git a/server/locales/es/humanitycards.ftl b/server/locales/es/humanitycards.ftl index 56a75520..64afb587 100644 --- a/server/locales/es/humanitycards.ftl +++ b/server/locales/es/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } es el zar de las cartas. + *[other] { $names } son los zares de las cartas. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } obtiene { $points } { $points -> + [one] punto + *[other] puntos +} por { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Esperando a { $names } para enviar sus cartas. +hc-all-submitted-waiting-judge = Todos los jugadores han enviado sus cartas. Esperando a { $judge } para juzgar. +hc-waiting-for-judges = Esperando a { $names } para juzgar. diff --git a/server/locales/fa/humanitycards.ftl b/server/locales/fa/humanitycards.ftl index 56a75520..84e822dd 100644 --- a/server/locales/fa/humanitycards.ftl +++ b/server/locales/fa/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } تزار کارت است. + *[other] { $names } تزارهای کارت هستند. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } برای { $text }، { $points } { $points -> + [one] امتیاز + *[other] امتیاز +} می‌گیرد. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = در انتظار { $names } برای ارسال پاسخ. +hc-all-submitted-waiting-judge = همه بازیکنان پاسخ خود را فرستاده‌اند. در انتظار داوری { $judge }. +hc-waiting-for-judges = در انتظار { $names } برای داوری. diff --git a/server/locales/fr/humanitycards.ftl b/server/locales/fr/humanitycards.ftl index 56a75520..46d17f98 100644 --- a/server/locales/fr/humanitycards.ftl +++ b/server/locales/fr/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } est le tsar des cartes. + *[other] { $names } sont les tsars des cartes. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } obtient { $points } { $points -> + [one] point + *[other] points +} pour { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = En attente de la soumission de { $names }. +hc-all-submitted-waiting-judge = Tous les joueurs ont soumis leurs cartes. En attente du jugement de { $judge }. +hc-waiting-for-judges = En attente du jugement de { $names }. diff --git a/server/locales/hi/humanitycards.ftl b/server/locales/hi/humanitycards.ftl index 56a75520..3803d409 100644 --- a/server/locales/hi/humanitycards.ftl +++ b/server/locales/hi/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } कार्ड ज़ार है। + *[other] { $names } कार्ड ज़ार हैं। +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } को { $text } के लिए { $points } { $points -> + [one] अंक + *[other] अंक +} मिलते हैं। hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = { $names } के जमा करने का इंतज़ार है। +hc-all-submitted-waiting-judge = सभी खिलाड़ियों ने अपने जवाब जमा कर दिए हैं। { $judge } के निर्णय का इंतज़ार है। +hc-waiting-for-judges = { $names } के निर्णय का इंतज़ार है। diff --git a/server/locales/hr/humanitycards.ftl b/server/locales/hr/humanitycards.ftl index 56a75520..a6a3f400 100644 --- a/server/locales/hr/humanitycards.ftl +++ b/server/locales/hr/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } je car karata. + *[other] { $names } su carevi karata. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } dobiva { $points } { $points -> + [one] bod + *[other] bodova +} za { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Čeka se da { $names } predaju karte. +hc-all-submitted-waiting-judge = Svi igrači su predali karte. Čeka se da { $judge } presudi. +hc-waiting-for-judges = Čeka se da { $names } presude. diff --git a/server/locales/hu/humanitycards.ftl b/server/locales/hu/humanitycards.ftl index 56a75520..c5aba35d 100644 --- a/server/locales/hu/humanitycards.ftl +++ b/server/locales/hu/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } a kártyacár. + *[other] { $names } a kártyacárok. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } { $points } { $points -> + [one] pontot + *[other] pontot +} kap ezért: { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Várakozás erre: { $names }, hogy beküldjék a lapjaikat. +hc-all-submitted-waiting-judge = Minden játékos beküldte a lapjait. Várakozás erre: { $judge }, hogy bíráljon. +hc-waiting-for-judges = Várakozás erre: { $names }, hogy bíráljanak. diff --git a/server/locales/id/humanitycards.ftl b/server/locales/id/humanitycards.ftl index 56a75520..9c82110c 100644 --- a/server/locales/id/humanitycards.ftl +++ b/server/locales/id/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } adalah Card Czar. + *[other] { $names } adalah para Card Czar. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } mendapat { $points } { $points -> + [one] poin + *[other] poin +} untuk { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Menunggu { $names } mengirimkan kartu. +hc-all-submitted-waiting-judge = Semua pemain sudah mengirimkan kartu. Menunggu { $judge } untuk menilai. +hc-waiting-for-judges = Menunggu { $names } untuk menilai. diff --git a/server/locales/it/humanitycards.ftl b/server/locales/it/humanitycards.ftl index 56a75520..c886b053 100644 --- a/server/locales/it/humanitycards.ftl +++ b/server/locales/it/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } è lo zar delle carte. + *[other] { $names } sono gli zar delle carte. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } ottiene { $points } { $points -> + [one] punto + *[other] punti +} per { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = In attesa che { $names } inviino le carte. +hc-all-submitted-waiting-judge = Tutti i giocatori hanno inviato le carte. In attesa del giudizio di { $judge }. +hc-waiting-for-judges = In attesa del giudizio di { $names }. diff --git a/server/locales/ja/humanitycards.ftl b/server/locales/ja/humanitycards.ftl index 56a75520..df026bae 100644 --- a/server/locales/ja/humanitycards.ftl +++ b/server/locales/ja/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } がカードツァーです。 + *[other] { $names } がカードツァーです。 +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } は { $text } で { $points } { $points -> + [one] 点 + *[other] 点 +} を獲得します。 hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = { $names } の提出を待っています。 +hc-all-submitted-waiting-judge = 全員が提出しました。{ $judge } の判定を待っています。 +hc-waiting-for-judges = { $names } の判定を待っています。 diff --git a/server/locales/ko/humanitycards.ftl b/server/locales/ko/humanitycards.ftl index 56a75520..61a6e74a 100644 --- a/server/locales/ko/humanitycards.ftl +++ b/server/locales/ko/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } 님이 카드 차르입니다. + *[other] { $names } 님이 카드 차르들입니다. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } 님이 { $text }(으)로 { $points } { $points -> + [one]점 + *[other]점 +}을 얻었습니다. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = { $names } 님의 제출을 기다리는 중입니다. +hc-all-submitted-waiting-judge = 모든 플레이어가 제출했습니다. { $judge } 님의 심사를 기다리는 중입니다. +hc-waiting-for-judges = { $names } 님의 심사를 기다리는 중입니다. diff --git a/server/locales/mn/humanitycards.ftl b/server/locales/mn/humanitycards.ftl index 56a75520..8aaa7754 100644 --- a/server/locales/mn/humanitycards.ftl +++ b/server/locales/mn/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } бол картын хаан. + *[other] { $names } бол картын хаад. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } { $text }-д { $points } { $points -> + [one] оноо + *[other] оноо +} авлаа. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = { $names } илгээхийг хүлээж байна. +hc-all-submitted-waiting-judge = Бүх тоглогч хариугаа илгээсэн. { $judge } шүүхийг хүлээж байна. +hc-waiting-for-judges = { $names } шүүхийг хүлээж байна. diff --git a/server/locales/nl/humanitycards.ftl b/server/locales/nl/humanitycards.ftl index 56a75520..dbab0462 100644 --- a/server/locales/nl/humanitycards.ftl +++ b/server/locales/nl/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } is de kaarttsaar. + *[other] { $names } zijn de kaarttsaren. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } krijgt { $points } { $points -> + [one] punt + *[other] punten +} voor { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Wachten op { $names } om in te dienen. +hc-all-submitted-waiting-judge = Alle spelers hebben ingezonden. Wachten op het oordeel van { $judge }. +hc-waiting-for-judges = Wachten op het oordeel van { $names }. diff --git a/server/locales/pl/humanitycards.ftl b/server/locales/pl/humanitycards.ftl index 56a75520..1bc9bbf7 100644 --- a/server/locales/pl/humanitycards.ftl +++ b/server/locales/pl/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } jest carem kart. + *[other] { $names } są carami kart. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } zdobywa { $points } { $points -> + [one] punkt + *[other] punkty +} za { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Oczekiwanie na ruch od { $names }. +hc-all-submitted-waiting-judge = Wszyscy gracze już zagrali. Oczekiwanie na ocenę od { $judge }. +hc-waiting-for-judges = Oczekiwanie na ocenę od { $names }. diff --git a/server/locales/pt/humanitycards.ftl b/server/locales/pt/humanitycards.ftl index 56a75520..e2c4f4b4 100644 --- a/server/locales/pt/humanitycards.ftl +++ b/server/locales/pt/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } é o czar das cartas. + *[other] { $names } são os czares das cartas. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } recebe { $points } { $points -> + [one] ponto + *[other] pontos +} por { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Aguardando { $names } enviarem as cartas. +hc-all-submitted-waiting-judge = Todos os jogadores enviaram suas cartas. Aguardando o julgamento de { $judge }. +hc-waiting-for-judges = Aguardando o julgamento de { $names }. diff --git a/server/locales/ro/humanitycards.ftl b/server/locales/ro/humanitycards.ftl index 56a75520..c6445fce 100644 --- a/server/locales/ro/humanitycards.ftl +++ b/server/locales/ro/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } este țarul cărților. + *[other] { $names } sunt țarii cărților. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } primește { $points } { $points -> + [one] punct + *[other] puncte +} pentru { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Se așteaptă ca { $names } să trimită cărțile. +hc-all-submitted-waiting-judge = Toți jucătorii au trimis cărțile. Se așteaptă verdictul lui { $judge }. +hc-waiting-for-judges = Se așteaptă verdictul lui { $names }. diff --git a/server/locales/ru/humanitycards.ftl b/server/locales/ru/humanitycards.ftl index 56a75520..9833db1c 100644 --- a/server/locales/ru/humanitycards.ftl +++ b/server/locales/ru/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } — карточный царь. + *[other] { $names } — карточные цари. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } получает { $points } { $points -> + [one] очко + *[other] очков +} за { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Ожидание, пока { $names } сдадут карты. +hc-all-submitted-waiting-judge = Все игроки уже сдали карты. Ожидание решения от { $judge }. +hc-waiting-for-judges = Ожидание решения от { $names }. diff --git a/server/locales/sk/humanitycards.ftl b/server/locales/sk/humanitycards.ftl index 56a75520..e6b0b707 100644 --- a/server/locales/sk/humanitycards.ftl +++ b/server/locales/sk/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } je kartový cár. + *[other] { $names } sú kartoví cári. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } získava { $points } { $points -> + [one] bod + *[other] body +} za { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Čaká sa na { $names }, kým odovzdajú karty. +hc-all-submitted-waiting-judge = Všetci hráči už odovzdali karty. Čaká sa na rozhodnutie od { $judge }. +hc-waiting-for-judges = Čaká sa na rozhodnutie od { $names }. diff --git a/server/locales/sl/humanitycards.ftl b/server/locales/sl/humanitycards.ftl index 56a75520..f2a38999 100644 --- a/server/locales/sl/humanitycards.ftl +++ b/server/locales/sl/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } je car kart. + *[other] { $names } so carji kart. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } dobi { $points } { $points -> + [one] točko + *[other] točke +} za { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Čaka se na { $names }, da oddajo karte. +hc-all-submitted-waiting-judge = Vsi igralci so oddali karte. Čaka se na odločitev od { $judge }. +hc-waiting-for-judges = Čaka se na odločitev od { $names }. diff --git a/server/locales/sr/humanitycards.ftl b/server/locales/sr/humanitycards.ftl index 420a7bcf..42e29032 100644 --- a/server/locales/sr/humanitycards.ftl +++ b/server/locales/sr/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Deljenje { $count } karata svakom igraču. hc-round-start = Runda { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] je sudija - *[other] i { $others } su sudije -}. +hc-judge-is = { $count -> + [one] { $names } je sudija. + *[other] { $names } su sudije. +} hc-you-are-judge = Vi ste sudija u ovoj rundi. hc-you-are-not-judge = Niste sudija u ovoj rundi. @@ -68,7 +68,10 @@ hc-select-winner-prompt = Izaberite pobednički predlog hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } dobija rundu! Rezultat: { $score }. +hc-winner-announcement = { $player } dobija { $points } { $points -> + [one] poen + *[other] poena +} za { $text }. hc-winner-card = Pobednički odgovor: { $text } hc-round-scores = Rezultat nakon runde { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -103,5 +106,6 @@ hc-no-scores = Još uvek nema rezultata. # Whose turn / whose judge hc-whose-judge = Ko je sudija -hc-waiting-for = Čeka se da { $names } igraju. -hc-all-submitted-waiting-judge = Svi igrači su poslali svoje predloge. Čeka se da { $judge } sudi. +hc-waiting-for = Čeka se da { $names } odigraju. +hc-all-submitted-waiting-judge = Svi igrači su poslali svoje predloge. Čeka se da { $judge } presudi. +hc-waiting-for-judges = Čeka se da { $names } presude. diff --git a/server/locales/sv/humanitycards.ftl b/server/locales/sv/humanitycards.ftl index 56a75520..b8d651ca 100644 --- a/server/locales/sv/humanitycards.ftl +++ b/server/locales/sv/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } är korttsaren. + *[other] { $names } är korttsarerna. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } får { $points } { $points -> + [one] poäng + *[other] poäng +} för { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Väntar på att { $names } ska lämna in. +hc-all-submitted-waiting-judge = Alla spelare har lämnat in. Väntar på att { $judge } ska döma. +hc-waiting-for-judges = Väntar på att { $names } ska döma. diff --git a/server/locales/th/humanitycards.ftl b/server/locales/th/humanitycards.ftl index 56a75520..34c63e48 100644 --- a/server/locales/th/humanitycards.ftl +++ b/server/locales/th/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } คือซาร์ไพ่ + *[other] { $names } คือซาร์ไพ่ +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } ได้ { $points } { $points -> + [one] คะแนน + *[other] คะแนน +} จาก { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = กำลังรอ { $names } ส่งคำตอบ +hc-all-submitted-waiting-judge = ผู้เล่นทุกคนส่งคำตอบแล้ว กำลังรอ { $judge } ตัดสิน +hc-waiting-for-judges = กำลังรอ { $names } ตัดสิน diff --git a/server/locales/tr/humanitycards.ftl b/server/locales/tr/humanitycards.ftl index 56a75520..661a9e8c 100644 --- a/server/locales/tr/humanitycards.ftl +++ b/server/locales/tr/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } kart çarıdır. + *[other] { $names } kart çarlarıdır. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player }, { $text } için { $points } { $points -> + [one] puan + *[other] puan +} alır. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = { $names } gönderene kadar bekleniyor. +hc-all-submitted-waiting-judge = Tüm oyuncular kartlarını gönderdi. { $judge } karar verene kadar bekleniyor. +hc-waiting-for-judges = { $names } karar verene kadar bekleniyor. diff --git a/server/locales/uk/humanitycards.ftl b/server/locales/uk/humanitycards.ftl index 56a75520..e8d111f3 100644 --- a/server/locales/uk/humanitycards.ftl +++ b/server/locales/uk/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } — картковий цар. + *[other] { $names } — карткові царі. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } отримує { $points } { $points -> + [one] очко + *[other] очок +} за { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Очікування, поки { $names } подадуть карти. +hc-all-submitted-waiting-judge = Усі гравці вже подали карти. Очікування рішення від { $judge }. +hc-waiting-for-judges = Очікування рішення від { $names }. diff --git a/server/locales/vi/humanitycards.ftl b/server/locales/vi/humanitycards.ftl index 56a75520..336a01bf 100644 --- a/server/locales/vi/humanitycards.ftl +++ b/server/locales/vi/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } là Sa hoàng thẻ bài. + *[other] { $names } là các Sa hoàng thẻ bài. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } nhận được { $points } { $points -> + [one] điểm + *[other] điểm +} cho { $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Đang chờ { $names } nộp bài. +hc-all-submitted-waiting-judge = Tất cả người chơi đã nộp bài. Đang chờ { $judge } chấm. +hc-waiting-for-judges = Đang chờ { $names } chấm. diff --git a/server/locales/zh/humanitycards.ftl b/server/locales/zh/humanitycards.ftl index 56a75520..756715c2 100644 --- a/server/locales/zh/humanitycards.ftl +++ b/server/locales/zh/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } 是本轮判官。 + *[other] { $names } 是本轮判官。 +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } 凭借 { $text } 获得 { $points } { $points -> + [one] 分 + *[other] 分 +}。 hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = 正在等待 { $names } 提交答案。 +hc-all-submitted-waiting-judge = 所有玩家都已提交答案。正在等待 { $judge } 评判。 +hc-waiting-for-judges = 正在等待 { $names } 评判。 diff --git a/server/locales/zu/humanitycards.ftl b/server/locales/zu/humanitycards.ftl index 56a75520..009af276 100644 --- a/server/locales/zu/humanitycards.ftl +++ b/server/locales/zu/humanitycards.ftl @@ -30,10 +30,10 @@ hc-dealing-cards = Dealing { $count } cards to each player. hc-round-start = Round { $round }. # Judge announcement -hc-judge-is = { $player } { $count -> - [one] is the Card Czar - *[other] and { $others } are the Card Czars -}. +hc-judge-is = { $count -> + [one] { $names } uyinkosi yamakhadi. + *[other] { $names } bayizinkosi zamakhadi. +} hc-you-are-judge = You are the Card Czar this round. hc-you-are-not-judge = You are not the Card Czar this round. @@ -66,7 +66,10 @@ hc-select-winner-prompt = Select the winning submission hc-submission-option = { $text } # Results -hc-winner-announcement = { $player } wins the round! Score: { $score }. +hc-winner-announcement = { $player } uthola { $points } { $points -> + [one] iphuzu + *[other] amaphuzu +} ngo-{ $text }. hc-winner-card = Winning answer: { $text } hc-round-scores = Scores after round { $round }: hc-score-line = { $player }: { $score } { $score -> @@ -101,5 +104,6 @@ hc-no-scores = No scores yet. # Whose turn / whose judge hc-whose-judge = Who is judging -hc-waiting-for = Waiting for { $names } to submit. -hc-all-submitted-waiting-judge = All players have submitted. Waiting for { $judge } to judge. +hc-waiting-for = Kulindwe { $names } ukuthi bathumele. +hc-all-submitted-waiting-judge = Bonke abadlali sebethumele. Kulindwe { $judge } ukuthi ahlulele. +hc-waiting-for-judges = Kulindwe { $names } ukuthi bahlulele. diff --git a/server/tests/test_humanitycards.py b/server/tests/test_humanitycards.py new file mode 100644 index 00000000..ed22efcd --- /dev/null +++ b/server/tests/test_humanitycards.py @@ -0,0 +1,611 @@ +"""Tests for Humanity Cards (Cards Against Humanity) game.""" + +from server.core.users.bot import Bot +from server.core.users.test_user import MockUser +from server.game_utils.actions import Visibility +from server.games.humanitycards.game import HumanityCardsGame, HumanityCardsOptions + + +# ========================================================================== +# Helpers +# ========================================================================== + + +def _make_white(count: int, start: int = 0) -> list[dict]: + return [{"text": f"White card {i}", "pack": "Test", "id": i} for i in range(start, start + count)] + + +def _make_black(text: str = "Why is _ so funny?", pick: int = 1) -> dict: + return {"text": text, "pick": pick, "pack": "Test"} + + +def _inject_decks(game: HumanityCardsGame, white_count: int = 200, black_count: int = 50) -> None: + game.white_deck = _make_white(white_count) + game.black_deck = [_make_black(f"Question {i} _") for i in range(black_count)] + game.white_discard = [] + game.black_discard = [] + + +def _setup_game( + num_players: int = 3, + options: HumanityCardsOptions | None = None, +) -> tuple[HumanityCardsGame, list[MockUser]]: + opts = options or HumanityCardsOptions() + game = HumanityCardsGame(options=opts) + game._build_decks = lambda: _inject_decks(game) # type: ignore[method-assign] + users = [] + for i in range(num_players): + name = f"Player{i}" + user = MockUser(name) + game.add_player(name, user) + users.append(user) + game.on_start() + return game, users + + +def _get_to_judging(num_players: int = 3, options: HumanityCardsOptions | None = None): + game, users = _setup_game(num_players=num_players, options=options) + for p in game._get_submitters(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + assert game.phase == "judging" + return game, users + + +def _setup_multi_judge(num_judges: int = 2, num_players: int = 4, judging_method: str = "Independent"): + return _get_to_judging( + num_players=num_players, + options=HumanityCardsOptions(num_judges=num_judges, judging_method=judging_method), + ) + + +# ========================================================================== +# Metadata +# ========================================================================== + + +def test_game_metadata(): + game = HumanityCardsGame() + assert game.get_name() == "Cards Against Humanity" + assert game.get_type() == "humanitycards" + assert game.get_min_players() == 3 + assert game.get_max_players() >= 6 + + +# ========================================================================== +# Startup +# ========================================================================== + + +def test_game_starts_in_submitting_phase(): + game, _ = _setup_game() + assert game.phase == "submitting" + assert game.status == "playing" + assert game.round == 1 + + +def test_players_dealt_hands_on_start(): + game, _ = _setup_game(num_players=3) + for p in game.get_active_players(): + assert len(p.hand) == game.options.hand_size # type: ignore[union-attr] + + +def test_player_scores_zero_on_start(): + game, _ = _setup_game() + for p in game.get_active_players(): + assert p.score == 0 # type: ignore[union-attr] + + +def test_black_card_dealt_on_start(): + game, _ = _setup_game() + assert game.current_black_card is not None + assert "text" in game.current_black_card + + +# ========================================================================== +# Judge selection +# ========================================================================== + + +def test_one_judge_on_start(): + game, _ = _setup_game() + assert len(game._get_judges()) == 1 + + +def test_rotating_judge_advances_each_round(): + game, _ = _setup_game(num_players=4) + first_id = game._get_judges()[0].id + game._start_round() + assert game._get_judges()[0].id != first_id + + +def test_judge_count_capped_at_active_player_count(): + game, _ = _setup_game(num_players=3, options=HumanityCardsOptions(num_judges=5)) + assert len(game._get_judges()) == len(game.get_active_players()) + + +def test_random_judge_selection_picks_valid_player(): + game, _ = _setup_game(options=HumanityCardsOptions(czar_selection="Random")) + active_ids = {p.id for p in game.get_active_players()} + for j in game._get_judges(): + assert j.id in active_ids + + +def test_winner_judge_selection_uses_last_winner(): + game, _ = _setup_game( + num_players=4, options=HumanityCardsOptions(czar_selection="Most Recent Winner") + ) + active = game.get_active_players() + game.last_winner_index = 2 + game._start_round() + assert game._get_judges()[0].id == active[2].id + + +def test_judge_personal_announcement_spoken(): + game, users = _setup_game(num_players=3) + judge = game._get_judges()[0] + judge_user = next(u for u in users if u.username == judge.name) + assert any("Card Czar" in m for m in judge_user.get_spoken_messages()) + + +# ========================================================================== +# Utility +# ========================================================================== + + +def test_fill_in_blanks(): + game, _ = _setup_game() + assert game._fill_in_blanks("I love _.", ["cats"]) == "I love cats." + assert game._fill_in_blanks("_ meets _.", ["Alice", "Bob"]) == "Alice meets Bob." + assert game._fill_in_blanks("Why?", ["Because"]) == "Why? Because" + + +def test_speech_friendly_black_replaces_underscore(): + game, _ = _setup_game() + assert game._speech_friendly_black("I love _.") == "I love blank." + + +# ========================================================================== +# Deck reshuffle +# ========================================================================== + + +def test_white_deck_reshuffles_from_discard(): + game, _ = _setup_game() + game.white_deck = [] + game.white_discard = _make_white(5, start=100) + drawn = game._draw_white(3) + assert len(drawn) == 3 + assert len(game.white_deck) + len(drawn) == 5 + + +def test_white_deck_reshuffle_broadcasts(): + game, users = _setup_game() + game.white_deck = [] + game.white_discard = _make_white(5, start=100) + for u in users: + u.clear_messages() + game._draw_white(1) + all_spoken = [m for u in users for m in u.get_spoken_messages()] + assert any("reshuffled" in m.lower() for m in all_spoken) + + +def test_black_deck_reshuffles_from_discard(): + game, _ = _setup_game() + game.black_deck = [] + game.black_discard = [_make_black("Test _ card") for _ in range(3)] + assert game._draw_black() is not None + + +# ========================================================================== +# Card toggling +# ========================================================================== + + +def test_toggle_card_selects_and_deselects(): + game, _ = _setup_game() + non_judge = game._get_non_judges()[0] + game.execute_action(non_judge, "toggle_card_0") + assert 0 in non_judge.selected_indices + game.execute_action(non_judge, "toggle_card_0") + assert 0 not in non_judge.selected_indices + + +def test_judge_cannot_toggle_cards(): + game, _ = _setup_game() + judge = game._get_judges()[0] + game.execute_action(judge, "toggle_card_0") + assert 0 not in judge.selected_indices # type: ignore[union-attr] + + +# ========================================================================== +# Submission +# ========================================================================== + + +def test_submit_removes_card_from_hand_and_records(): + game, _ = _setup_game() + non_judge = game._get_non_judges()[0] + expected_text = non_judge.hand[0]["text"] + hand_size = len(non_judge.hand) + game.execute_action(non_judge, "toggle_card_0") + game.execute_action(non_judge, "submit_cards") + assert non_judge.submitted_cards == [expected_text] + assert len(non_judge.hand) == hand_size - 1 + + +def test_submit_wrong_count_rejected(): + game, users = _setup_game() + game.current_black_card = _make_black("_ loves _ forever.", pick=2) + non_judge = game._get_non_judges()[0] + non_judge_user = next(u for u in users if u.username == non_judge.name) + game.execute_action(non_judge, "toggle_card_0") + non_judge_user.clear_messages() + game.execute_action(non_judge, "submit_cards") + assert non_judge.submitted_cards is None + assert any("2" in m for m in non_judge_user.get_spoken_messages()) + + +def test_submit_already_submitted_rejected(): + game, _ = _setup_game() + non_judge = game._get_non_judges()[0] + game.execute_action(non_judge, "toggle_card_0") + game.execute_action(non_judge, "submit_cards") + first = list(non_judge.submitted_cards) # type: ignore[arg-type] + game.execute_action(non_judge, "submit_cards") + assert non_judge.submitted_cards == first + + +def test_judge_cannot_submit(): + game, _ = _setup_game() + judge = game._get_judges()[0] + game.execute_action(judge, "toggle_card_0") + game.execute_action(judge, "submit_cards") + assert judge.submitted_cards is None # type: ignore[union-attr] + + +def test_all_submit_triggers_judging_phase(): + game, _ = _setup_game(num_players=3) + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + assert game.phase == "judging" + + +def test_pick_two_black_card_accepts_two_cards(): + game, _ = _setup_game() + game.current_black_card = _make_black("_ with _ always.", pick=2) + non_judge = game._get_non_judges()[0] + game.execute_action(non_judge, "toggle_card_0") + game.execute_action(non_judge, "toggle_card_1") + game.execute_action(non_judge, "submit_cards") + assert non_judge.submitted_cards is not None + assert len(non_judge.submitted_cards) == 2 + + +# ========================================================================== +# Judging +# ========================================================================== + + +def test_judge_pick_awards_point_and_ends_round(): + game, _ = _get_to_judging() + judge = game._get_judges()[0] + winner_id = game.submissions[game.submission_order[0]]["player_id"] + game.execute_action(judge, "judge_pick_0") + winner = game.get_player_by_id(winner_id) + assert winner.score == 1 # type: ignore[union-attr] + assert game.phase in ("round_end", "finished") + + +def test_winner_announcement_broadcast(): + game, users = _get_to_judging() + for u in users: + u.clear_messages() + game.execute_action(game._get_judges()[0], "judge_pick_0") + all_spoken = [m for u in users for m in u.get_spoken_messages()] + assert any("gets" in m.lower() and "point" in m.lower() for m in all_spoken) + + +def test_non_judge_cannot_pick(): + game, _ = _get_to_judging() + non_judge = game._get_non_judges()[0] + game.execute_action(non_judge, "judge_pick_0") + assert game.phase == "judging" + + +def test_no_losing_submissions_heading_when_all_are_winners(): + game, users = _setup_game(num_players=3) + game.submissions = [ + {"player_id": "p1", "cards": ["foo"]}, + {"player_id": "p2", "cards": ["bar"]}, + ] + game.current_black_card = _make_black("_ question", pick=1) + for u in users: + u.clear_messages() + game._announce_losing_submissions({"p1", "p2"}) + all_spoken = [m for u in users for m in u.get_spoken_messages()] + assert not any("other submissions" in m.lower() for m in all_spoken) + + +# ========================================================================== +# Win condition +# ========================================================================== + + +def test_game_ends_when_winning_score_reached(): + game, _ = _setup_game(options=HumanityCardsOptions(winning_score=1)) + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + game.execute_action(game._get_judges()[0], "judge_pick_0") + assert game.status == "finished" + + +def test_round_continues_when_score_below_winning(): + game, _ = _setup_game(options=HumanityCardsOptions(winning_score=5)) + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + game.execute_action(game._get_judges()[0], "judge_pick_0") + assert game.status == "playing" + assert game.phase == "round_end" + + +# ========================================================================== +# Score display +# ========================================================================== + + +def test_check_scores_shows_all_players_and_values(): + game, users = _setup_game(num_players=3) + player0 = game.get_active_players()[0] + player0.score = 5 # type: ignore[union-attr] + game._team_manager.add_to_team_score(player0.name, 5) + user0 = next(u for u in users if u.username == player0.name) + user0.clear_messages() + game.execute_action(player0, "check_scores") + all_text = " ".join(user0.get_spoken_messages()) + assert "5" in all_text + for p in game.get_active_players(): + assert p.name in all_text + + +# ========================================================================== +# Round transition +# ========================================================================== + + +def test_round_end_ticks_advance_to_next_round(): + game, _ = _setup_game() + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + game.execute_action(game._get_judges()[0], "judge_pick_0") + assert game.phase == "round_end" + game.round_end_ticks = 1 + game.on_tick() + assert game.phase == "submitting" + assert game.round == 2 + + +def test_judge_announcement_fires_each_round(): + game, users = _setup_game(num_players=3) + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + game.execute_action(game._get_judges()[0], "judge_pick_0") + game.round_end_ticks = 1 + game.on_tick() + new_judge = game._get_judges()[0] + new_judge_user = next(u for u in users if u.username == new_judge.name) + assert any("Card Czar" in m for m in new_judge_user.get_spoken_messages()) + + +# ========================================================================== +# Multi-judge voting +# ========================================================================== + + +def test_multi_judge_waits_for_all_judges(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4) + judges = game._get_judges() + game.execute_action(judges[0], "judge_pick_0") + assert game.phase == "judging" + assert len(game.judge_picks) == 1 + + +def test_multi_judge_resolves_after_all_pick(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4) + judges = game._get_judges() + game.execute_action(judges[0], "judge_pick_0") + game.execute_action(judges[1], "judge_pick_0") + assert game.phase in ("round_end", "finished") + + +def test_multi_judge_cannot_vote_twice(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4) + judges = game._get_judges() + game.execute_action(judges[0], "judge_pick_0") + first_picks = dict(game.judge_picks) + game.execute_action(judges[0], "judge_pick_1") + assert game.judge_picks == first_picks + + +def test_multi_judge_intermediate_vote_plays_sound_not_speech(): + game, users = _setup_multi_judge(num_judges=2, num_players=4) + for u in users: + u.clear_messages() + game.execute_action(game._get_judges()[0], "judge_pick_0") + all_spoken = [m for u in users for m in u.get_spoken_messages()] + assert not any("made their choice" in m for m in all_spoken) + all_sounds = [s for u in users for s in u.get_sounds_played()] + assert any("judgechoice" in s for s in all_sounds) + + +# ========================================================================== +# Judging methods +# ========================================================================== + + +def test_single_judge_always_uses_independent(): + game, _ = _setup_game(options=HumanityCardsOptions(num_judges=1, judging_method="Jury")) + for p in game._get_non_judges(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + assert game.active_judging_method == "Independent" + + +def test_independent_awards_one_point_per_vote(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4, judging_method="Independent") + judges = game._get_judges() + sub0_id = game.submissions[game.submission_order[0]]["player_id"] + game.execute_action(judges[0], "judge_pick_0") + game.execute_action(judges[1], "judge_pick_0") + assert game.get_player_by_id(sub0_id).score == 2 # type: ignore[union-attr] + + +def test_independent_split_vote_both_score(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4, judging_method="Independent") + judges = game._get_judges() + sub0_id = game.submissions[game.submission_order[0]]["player_id"] + sub1_id = game.submissions[game.submission_order[1]]["player_id"] + game.execute_action(judges[0], "judge_pick_0") + game.execute_action(judges[1], "judge_pick_1") + assert game.get_player_by_id(sub0_id).score == 1 # type: ignore[union-attr] + assert game.get_player_by_id(sub1_id).score == 1 # type: ignore[union-attr] + + +def test_jury_sole_winner_gets_one_point(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4, judging_method="Jury") + judges = game._get_judges() + sub0_id = game.submissions[game.submission_order[0]]["player_id"] + game.execute_action(judges[0], "judge_pick_0") + game.execute_action(judges[1], "judge_pick_0") + assert game.get_player_by_id(sub0_id).score == 1 # type: ignore[union-attr] + + +def test_jury_tie_both_score_one_point(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4, judging_method="Jury") + judges = game._get_judges() + sub0_id = game.submissions[game.submission_order[0]]["player_id"] + sub1_id = game.submissions[game.submission_order[1]]["player_id"] + game.execute_action(judges[0], "judge_pick_0") + game.execute_action(judges[1], "judge_pick_1") + assert game.get_player_by_id(sub0_id).score == 1 # type: ignore[union-attr] + assert game.get_player_by_id(sub1_id).score == 1 # type: ignore[union-attr] + + +def test_random_method_resolves_to_independent_or_jury(): + game, _ = _setup_multi_judge(num_judges=2, num_players=4, judging_method="Random") + assert game.active_judging_method in ("Independent", "Jury") + + +# ========================================================================== +# Bot game +# ========================================================================== + + +def test_bot_game_completes(): + opts = HumanityCardsOptions(winning_score=3) + game = HumanityCardsGame(options=opts) + game._build_decks = lambda: _inject_decks(game, white_count=500, black_count=100) # type: ignore[method-assign] + for i in range(4): + game.add_player(f"Bot{i}", Bot(f"Bot{i}")) + game.on_start() + for _ in range(100_000): + if game.status == "finished": + break + game.on_tick() + assert game.status == "finished" + + +# ========================================================================== +# All-judge mode +# ========================================================================== + + +def test_all_judge_mode_everyone_submits_and_judges(): + game, _ = _setup_game(num_players=3, options=HumanityCardsOptions(num_judges=3)) + assert game._all_players_are_judges() + for p in game.get_active_players(): + game.execute_action(p, "toggle_card_0") + game.execute_action(p, "submit_cards") + assert game.phase == "judging" + assert len(game.submissions) == 3 + + +def test_all_judge_mode_self_vote_hidden(): + game, _ = _get_to_judging(options=HumanityCardsOptions(num_judges=3)) + for judge in game._get_judges(): + for i, sub_idx in enumerate(game.submission_order): + if game.submissions[sub_idx]["player_id"] == judge.id: + assert game._is_judge_pick_hidden(judge, f"judge_pick_{i}") == Visibility.HIDDEN + + +def test_all_judge_mode_self_vote_blocked(): + game, _ = _get_to_judging(options=HumanityCardsOptions(num_judges=3)) + for judge in game._get_judges(): + for i, sub_idx in enumerate(game.submission_order): + if game.submissions[sub_idx]["player_id"] == judge.id: + game._judge_pick(judge, i) + assert judge.id not in game.judge_picks + + +def test_all_judge_mode_no_czar_announcement(): + game, users = _setup_game(num_players=3, options=HumanityCardsOptions(num_judges=3)) + all_spoken = [m for u in users for m in u.get_spoken_messages()] + assert not any("Card Czar" in m for m in all_spoken) + + +def test_all_judge_bot_game_completes(): + opts = HumanityCardsOptions(winning_score=2, num_judges=3) + game = HumanityCardsGame(options=opts) + game._build_decks = lambda: _inject_decks(game, white_count=500, black_count=100) # type: ignore[method-assign] + for i in range(3): + game.add_player(f"Bot{i}", Bot(f"Bot{i}")) + game.on_start() + for _ in range(200_000): + if game.status == "finished": + break + game.on_tick() + assert game.status == "finished" + + +# ========================================================================== +# Judge announcement grammar +# ========================================================================== + + +def test_judge_announcement_two_judges_grammar(): + game, users = _setup_game(num_players=4, options=HumanityCardsOptions(num_judges=2)) + all_spoken = [m for u in users for m in u.get_spoken_messages()] + judge_names = [j.name for j in game._get_judges()] + msgs = [m for m in all_spoken if "Card Czar" in m] + assert msgs + msg = msgs[0] + assert all(n in msg for n in judge_names) + assert msg.count("and") == 1 + + +def test_judge_announcement_three_judges_oxford_comma(): + game, users = _setup_game(num_players=4, options=HumanityCardsOptions(num_judges=3)) + all_spoken = [m for u in users for m in u.get_spoken_messages()] + judge_names = [j.name for j in game._get_judges()] + msgs = [m for m in all_spoken if "Card Czar" in m] + assert msgs + assert all(n in msgs[0] for n in judge_names) + assert ", and " in msgs[0] + + +def test_whose_turn_judging_lists_pending_judges(): + game, users = _setup_multi_judge(num_judges=2, num_players=4) + judges = game._get_judges() + game.execute_action(judges[0], "judge_pick_0") + non_judge = game._get_non_judges()[0] + non_judge_user = next(u for u in users if u.username == non_judge.name) + non_judge_user.clear_messages() + game._action_whose_turn(non_judge, "whose_turn") + spoken = non_judge_user.get_spoken_messages() + assert any(judges[1].name in m for m in spoken) + assert not any("submitted" in m.lower() for m in spoken)