Early Knock house rule and improved error handling.

- Add Early Knock variant: flip all remaining cards (≤2) to go out early
- Update RULES.md with comprehensive documentation for all new variants
- Shorten flip mode dropdown descriptions for cleaner UI
- Add try-catch and optional chaining in startGame() for robustness
- Add WebSocket connection error feedback with reject sound
- AI awareness for Early Knock decisions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Aaron D. Lee
2026-01-26 22:23:12 -05:00
parent 36a71799b5
commit c912a56c2d
6 changed files with 478 additions and 66 deletions

View File

@@ -455,6 +455,9 @@ class GameOptions:
one_eyed_jacks: bool = False
"""One-eyed Jacks (J♥ and J♠) are worth 0 points instead of 10."""
knock_early: bool = False
"""Allow going out early by flipping all remaining cards (max 2 face-down)."""
@dataclass
class Game:
@@ -957,6 +960,48 @@ class Game:
self._check_end_turn(player)
return True
def knock_early(self, player_id: str) -> bool:
"""
Flip all remaining face-down cards at once to go out early.
Only valid if knock_early house rule is enabled and player has
at most 2 face-down cards remaining. This is a gamble - you're
betting your hidden cards are good enough to win.
Args:
player_id: ID of the player knocking early.
Returns:
True if action was valid, False otherwise.
"""
if not self.options.knock_early:
return False
player = self.current_player()
if not player or player.id != player_id:
return False
if self.phase not in (GamePhase.PLAYING, GamePhase.FINAL_TURN):
return False
# Can't use this action if already drawn a card
if self.drawn_card is not None:
return False
# Count face-down cards
face_down_indices = [i for i, c in enumerate(player.cards) if not c.face_up]
# Must have at least 1 and at most 2 face-down cards
if len(face_down_indices) == 0 or len(face_down_indices) > 2:
return False
# Flip all remaining face-down cards
for idx in face_down_indices:
player.cards[idx].face_up = True
self._check_end_turn(player)
return True
# -------------------------------------------------------------------------
# Turn & Round Flow (Internal)
# -------------------------------------------------------------------------
@@ -1177,6 +1222,8 @@ class Game:
active_rules.append("Negative Pairs Keep Value")
if self.options.one_eyed_jacks:
active_rules.append("One-Eyed Jacks")
if self.options.knock_early:
active_rules.append("Early Knock")
return {
"phase": self.phase.value,
@@ -1197,6 +1244,7 @@ class Game:
"flip_mode": self.options.flip_mode,
"flip_is_optional": self.flip_is_optional,
"flip_as_action": self.options.flip_as_action,
"knock_early": self.options.knock_early,
"card_values": self.get_card_values(),
"active_rules": active_rules,
}