Additional house rules to accomodate more common game variants.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
23657f6b0c
commit
33e3f124ed
@ -163,6 +163,11 @@ class GolfGame {
|
|||||||
this.tiedShameCheckbox = document.getElementById('tied-shame');
|
this.tiedShameCheckbox = document.getElementById('tied-shame');
|
||||||
this.blackjackCheckbox = document.getElementById('blackjack');
|
this.blackjackCheckbox = document.getElementById('blackjack');
|
||||||
this.wolfpackCheckbox = document.getElementById('wolfpack');
|
this.wolfpackCheckbox = document.getElementById('wolfpack');
|
||||||
|
// House Rules - New Variants
|
||||||
|
this.flipAsActionCheckbox = document.getElementById('flip-as-action');
|
||||||
|
this.fourOfAKindCheckbox = document.getElementById('four-of-a-kind');
|
||||||
|
this.negativePairsCheckbox = document.getElementById('negative-pairs-keep-value');
|
||||||
|
this.oneEyedJacksCheckbox = document.getElementById('one-eyed-jacks');
|
||||||
this.startGameBtn = document.getElementById('start-game-btn');
|
this.startGameBtn = document.getElementById('start-game-btn');
|
||||||
this.leaveRoomBtn = document.getElementById('leave-room-btn');
|
this.leaveRoomBtn = document.getElementById('leave-room-btn');
|
||||||
this.addCpuBtn = document.getElementById('add-cpu-btn');
|
this.addCpuBtn = document.getElementById('add-cpu-btn');
|
||||||
@ -397,7 +402,11 @@ class GolfGame {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 'your_turn':
|
case 'your_turn':
|
||||||
|
if (this.gameState && this.gameState.flip_as_action) {
|
||||||
|
this.showToast('Your turn! Draw or flip a card', 'your-turn');
|
||||||
|
} else {
|
||||||
this.showToast('Your turn! Draw a card', 'your-turn');
|
this.showToast('Your turn! Draw a card', 'your-turn');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'card_drawn':
|
case 'card_drawn':
|
||||||
@ -516,6 +525,12 @@ class GolfGame {
|
|||||||
const blackjack = this.blackjackCheckbox.checked;
|
const blackjack = this.blackjackCheckbox.checked;
|
||||||
const wolfpack = this.wolfpackCheckbox.checked;
|
const wolfpack = this.wolfpackCheckbox.checked;
|
||||||
|
|
||||||
|
// House Rules - New Variants
|
||||||
|
const flip_as_action = this.flipAsActionCheckbox.checked;
|
||||||
|
const four_of_a_kind = this.fourOfAKindCheckbox.checked;
|
||||||
|
const negative_pairs_keep_value = this.negativePairsCheckbox.checked;
|
||||||
|
const one_eyed_jacks = this.oneEyedJacksCheckbox.checked;
|
||||||
|
|
||||||
this.send({
|
this.send({
|
||||||
type: 'start_game',
|
type: 'start_game',
|
||||||
decks,
|
decks,
|
||||||
@ -532,7 +547,11 @@ class GolfGame {
|
|||||||
tied_shame,
|
tied_shame,
|
||||||
blackjack,
|
blackjack,
|
||||||
eagle_eye,
|
eagle_eye,
|
||||||
wolfpack
|
wolfpack,
|
||||||
|
flip_as_action,
|
||||||
|
four_of_a_kind,
|
||||||
|
negative_pairs_keep_value,
|
||||||
|
one_eyed_jacks
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1192,6 +1211,21 @@ class GolfGame {
|
|||||||
|
|
||||||
const card = myData.cards[position];
|
const card = myData.cards[position];
|
||||||
|
|
||||||
|
// Check for flip-as-action: can flip face-down card instead of drawing
|
||||||
|
const canFlipAsAction = this.gameState.flip_as_action &&
|
||||||
|
this.isMyTurn() &&
|
||||||
|
!this.drawnCard &&
|
||||||
|
!this.gameState.has_drawn_card &&
|
||||||
|
!card.face_up &&
|
||||||
|
!this.gameState.waiting_for_initial_flip;
|
||||||
|
if (canFlipAsAction) {
|
||||||
|
this.playSound('flip');
|
||||||
|
this.fireLocalFlipAnimation(position, card);
|
||||||
|
this.send({ type: 'flip_as_action', position });
|
||||||
|
this.hideToast();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if action is allowed - if not, play reject sound
|
// Check if action is allowed - if not, play reject sound
|
||||||
const canAct = this.gameState.waiting_for_initial_flip ||
|
const canAct = this.gameState.waiting_for_initial_flip ||
|
||||||
this.drawnCard ||
|
this.drawnCard ||
|
||||||
@ -1449,7 +1483,11 @@ class GolfGame {
|
|||||||
if (currentPlayer && currentPlayer.id !== this.playerId) {
|
if (currentPlayer && currentPlayer.id !== this.playerId) {
|
||||||
this.setStatus(`${currentPlayer.name}'s turn`);
|
this.setStatus(`${currentPlayer.name}'s turn`);
|
||||||
} else if (this.isMyTurn()) {
|
} else if (this.isMyTurn()) {
|
||||||
|
if (this.gameState.flip_as_action && !this.drawnCard && !this.gameState.has_drawn_card) {
|
||||||
|
this.setStatus('Your turn - draw a card or flip one', 'your-turn');
|
||||||
|
} else {
|
||||||
this.setStatus('Your turn - draw a card', 'your-turn');
|
this.setStatus('Your turn - draw a card', 'your-turn');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
this.setStatus('');
|
this.setStatus('');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
<select id="flip-mode">
|
<select id="flip-mode">
|
||||||
<option value="never">Standard - No flip after discarding</option>
|
<option value="never">Standard - No flip after discarding</option>
|
||||||
<option value="always">Speed Golf - MUST flip a card after discarding</option>
|
<option value="always">Speed Golf - MUST flip a card after discarding</option>
|
||||||
<option value="endgame">Endgame - Flip after discard if a player has 1 hidden card left</option>
|
<option value="endgame">Endgame - Optional flip to help trailing players catch up</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="rule-desc">What happens when you draw from deck and discard</span>
|
<span class="rule-desc">What happens when you draw from deck and discard</span>
|
||||||
</div>
|
</div>
|
||||||
@ -186,7 +186,33 @@
|
|||||||
<label class="checkbox-label inline">
|
<label class="checkbox-label inline">
|
||||||
<input type="checkbox" id="wolfpack">
|
<input type="checkbox" id="wolfpack">
|
||||||
<span>Wolfpack</span>
|
<span>Wolfpack</span>
|
||||||
<span class="rule-desc">2 pairs of Jacks = -5 pts</span>
|
<span class="rule-desc">All 4 Jacks = -20 pts</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="options-category">
|
||||||
|
<h4>New Variants</h4>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="flip-as-action">
|
||||||
|
<span>Flip as Action</span>
|
||||||
|
<span class="rule-desc">Use turn to flip a card without drawing</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="four-of-a-kind">
|
||||||
|
<span>Four of a Kind</span>
|
||||||
|
<span class="rule-desc">4 matching cards in 2 columns = -20 pts</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="negative-pairs-keep-value">
|
||||||
|
<span>Negative Pairs Keep Value</span>
|
||||||
|
<span class="rule-desc">Paired 2s/Jokers stay at -4 (not 0)</span>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input type="checkbox" id="one-eyed-jacks">
|
||||||
|
<span>One-Eyed Jacks</span>
|
||||||
|
<span class="rule-desc">J♥ and J♠ worth 0 pts</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -296,8 +322,47 @@
|
|||||||
<!-- Rules Screen -->
|
<!-- Rules Screen -->
|
||||||
<div id="rules-screen" class="screen">
|
<div id="rules-screen" class="screen">
|
||||||
<div class="rules-container">
|
<div class="rules-container">
|
||||||
<button id="rules-back-btn" class="btn btn-secondary back-btn">← Back</button>
|
<button id="rules-back-btn" class="btn rules-back-btn">« Back</button>
|
||||||
<h1>Game Rules</h1>
|
|
||||||
|
<div class="rules-header">
|
||||||
|
<h1><span class="golfer-logo">🏌️</span> <span class="golf-title">Golf Rules</span></h1>
|
||||||
|
<p class="rules-subtitle">6-Card Golf Card Game - Complete Guide</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table of Contents -->
|
||||||
|
<nav class="rules-toc">
|
||||||
|
<div class="toc-title">Quick Navigation</div>
|
||||||
|
<div class="toc-links">
|
||||||
|
<a href="#rules-basic" class="toc-link">
|
||||||
|
<span class="toc-icon">🎯</span>
|
||||||
|
<span class="toc-text">Basic Rules</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-card-values" class="toc-link">
|
||||||
|
<span class="toc-icon">🃏</span>
|
||||||
|
<span class="toc-text">Card Values</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-pairing" class="toc-link">
|
||||||
|
<span class="toc-icon">👯</span>
|
||||||
|
<span class="toc-text">Column Pairing</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-turn" class="toc-link">
|
||||||
|
<span class="toc-icon">🔄</span>
|
||||||
|
<span class="toc-text">Turn Structure</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-flip-mode" class="toc-link">
|
||||||
|
<span class="toc-icon">🔃</span>
|
||||||
|
<span class="toc-text">Flip Modes</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-house-rules" class="toc-link">
|
||||||
|
<span class="toc-icon">🏠</span>
|
||||||
|
<span class="toc-text">House Rules</span>
|
||||||
|
</a>
|
||||||
|
<a href="#rules-faq" class="toc-link">
|
||||||
|
<span class="toc-icon">❓</span>
|
||||||
|
<span class="toc-text">FAQ</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<section id="rules-basic" class="rules-section">
|
<section id="rules-basic" class="rules-section">
|
||||||
<h2>Basic Rules</h2>
|
<h2>Basic Rules</h2>
|
||||||
@ -331,24 +396,24 @@
|
|||||||
<section id="rules-pairing" class="rules-section">
|
<section id="rules-pairing" class="rules-section">
|
||||||
<h2>Column Pairing (IMPORTANT!)</h2>
|
<h2>Column Pairing (IMPORTANT!)</h2>
|
||||||
<p><strong>This is the most important rule to understand:</strong></p>
|
<p><strong>This is the most important rule to understand:</strong></p>
|
||||||
<p>If both cards in a <strong>vertical column</strong> have the <strong>same rank</strong> (like two Kings, or two 7s), that entire column scores <strong>0 points</strong> - regardless of what the cards are worth individually!</p>
|
<p>If both cards in a <strong>vertical column</strong> have the <strong>same rank</strong> (like two 8s or two Jacks), that entire column scores <strong>0 points</strong> - regardless of what the cards are worth individually!</p>
|
||||||
|
|
||||||
<div class="rules-example">
|
<div class="rules-example">
|
||||||
<h4>Example:</h4>
|
<h4>Example:</h4>
|
||||||
<pre>
|
<pre>
|
||||||
Your 6-card grid:
|
Your 6-card grid:
|
||||||
Col1 Col2 Col3
|
Col1 Col2 Col3
|
||||||
[K] [5] [7] ← Top row
|
[8] [5] [7] ← Top row
|
||||||
[K] [3] [9] ← Bottom row
|
[8] [3] [9] ← Bottom row
|
||||||
|
|
||||||
Column 1: K + K = PAIR! = 0 points (not 0+0)
|
Column 1: 8 + 8 = PAIR! = 0 points (not 16!)
|
||||||
Column 2: 5 + 3 = 8 points
|
Column 2: 5 + 3 = 8 points
|
||||||
Column 3: 7 + 9 = 16 points
|
Column 3: 7 + 9 = 16 points
|
||||||
|
|
||||||
TOTAL: 0 + 8 + 16 = 24 points</pre>
|
TOTAL: 0 + 8 + 16 = 24 points</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="rules-warning"><strong>IMPORTANT:</strong> When you pair cards, you get 0 points for that column - even if the cards have negative values! Two 2s paired = 0 points (not -4). Two Jokers paired = 0 points (not -4).</p>
|
<p class="rules-warning"><strong>IMPORTANT:</strong> When you pair cards, you get 0 points for that column - even if the cards have negative values! Two 2s paired = 0 points (not -4). Two Jokers paired = 0 points (not -4). <em>Exception: The "Negative Pairs Keep Value" house rule changes this - paired negative cards keep their -4 value!</em></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="rules-turn" class="rules-section">
|
<section id="rules-turn" class="rules-section">
|
||||||
@ -386,55 +451,129 @@ TOTAL: 0 + 8 + 16 = 24 points</pre>
|
|||||||
<h3>Standard Mode (No Flip)</h3>
|
<h3>Standard Mode (No Flip)</h3>
|
||||||
<p class="mode-summary">Default setting. Discarding ends your turn immediately.</p>
|
<p class="mode-summary">Default setting. Discarding ends your turn immediately.</p>
|
||||||
<p><strong>How it works:</strong> When you draw from the deck and decide not to use it, you simply discard it and your turn is over. Nothing else happens.</p>
|
<p><strong>How it works:</strong> When you draw from the deck and decide not to use it, you simply discard it and your turn is over. Nothing else happens.</p>
|
||||||
<p><strong>Best for:</strong> Traditional gameplay, longer games, maximum hidden information.</p>
|
<p><strong>Strategic impact:</strong> Information is precious. You only learn what's in your hand by actively swapping cards, so there's more gambling on face-down cards. Rewards good memory and tracking what opponents discard.</p>
|
||||||
|
<p><strong>Best for:</strong> Traditional gameplay, longer games, players who enjoy mystery and risk.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rules-mode">
|
<div class="rules-mode">
|
||||||
<h3>Speed Golf Mode (Must Flip)</h3>
|
<h3>Speed Golf Mode (Must Flip)</h3>
|
||||||
<p class="mode-summary">Every discard reveals one of your hidden cards.</p>
|
<p class="mode-summary">Every discard reveals one of your hidden cards.</p>
|
||||||
<p><strong>How it works:</strong> When you draw from the deck and discard, you MUST also flip over one of your face-down cards. This is mandatory - you cannot skip it.</p>
|
<p><strong>How it works:</strong> When you draw from the deck and discard, you MUST also flip over one of your face-down cards. This is mandatory - you cannot skip it.</p>
|
||||||
<p><strong>Why use it:</strong> Games go much faster because more cards get revealed every turn. More information for everyone = more strategic decisions.</p>
|
<p><strong>Strategic impact:</strong> Even "bad" draws give you information. Reduces the luck factor since everyone makes more informed decisions. Games naturally end faster with less hidden information.</p>
|
||||||
<p><strong>Best for:</strong> Quick games, players who like faster-paced action.</p>
|
<p><strong>Best for:</strong> Quick games, players who prefer skill over luck.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rules-mode">
|
<div class="rules-mode">
|
||||||
<h3>Endgame Mode (Flip When Close to Finishing)</h3>
|
<h3>Endgame Mode (Catch-Up Flip)</h3>
|
||||||
<p class="mode-summary">Flip activates when any player has only 1 hidden card remaining.</p>
|
<p class="mode-summary">Optional flip activates when any player has only 1 hidden card left.</p>
|
||||||
<p><strong>How it works:</strong></p>
|
<p><strong>How it works:</strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Early in the round: Discarding ends your turn (like Standard mode)</li>
|
<li>Early in the round: Discarding ends your turn (like Standard mode)</li>
|
||||||
<li><strong>When ANY player has 1 or fewer face-down cards:</strong> After discarding, you MAY choose to flip one of your hidden cards OR skip the flip</li>
|
<li><strong>When ANY player has 1 or fewer face-down cards:</strong> After discarding, you MAY choose to flip one of your hidden cards OR skip</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>Why use it:</strong> Creates dramatic tension near the end of rounds. Do you reveal more to try to improve your score, or keep cards hidden to maintain mystery?</p>
|
<p><strong>Strategic impact:</strong> This is a <strong>catch-up mechanic</strong>. When someone is about to go out, trailing players can accelerate their information gathering to find pairs or swap out bad cards. The leader (who triggered this) doesn't benefit since they have no hidden cards left. Reduces the "runaway leader" problem and keeps games competitive.</p>
|
||||||
<p><strong>Best for:</strong> Players who enjoy dramatic finishes and tough end-game decisions.</p>
|
<p><strong>Best for:</strong> Competitive play where you want trailing players to have a fighting chance.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="rules-house-rules" class="rules-section">
|
<section id="rules-house-rules" class="rules-section">
|
||||||
<h2>House Rules (Optional Variants)</h2>
|
<h2>House Rules (Optional Variants)</h2>
|
||||||
|
|
||||||
|
<div class="rules-mode">
|
||||||
<h3>Point Modifiers</h3>
|
<h3>Point Modifiers</h3>
|
||||||
<ul>
|
<div class="house-rule">
|
||||||
<li><strong>Super Kings:</strong> Kings are worth -2 points instead of 0 (makes them even better!)</li>
|
<h4>Super Kings</h4>
|
||||||
<li><strong>Ten Penny:</strong> 10s are worth only 1 point instead of 10 (makes 10s less scary)</li>
|
<p>Kings are worth <strong>-2 points</strong> instead of 0.</p>
|
||||||
</ul>
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Kings become valuable to keep unpaired, not just pairing fodder. Creates interesting decisions - do you pair Kings for 0, or keep them separate for -4 total?</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Ten Penny</h4>
|
||||||
|
<p>10s are worth <strong>1 point</strong> instead of 10.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Removes the "10 disaster" - drawing a 10 is no longer a crisis. Queens and Jacks become the only truly bad cards. Makes the game more forgiving.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rules-mode">
|
||||||
<h3>Joker Variants</h3>
|
<h3>Joker Variants</h3>
|
||||||
<ul>
|
<div class="house-rule">
|
||||||
<li><strong>Standard Jokers:</strong> 2 Jokers per deck, each worth -2 points (paired Jokers = 0 points)</li>
|
<h4>Standard Jokers</h4>
|
||||||
<li><strong>Lucky Swing:</strong> Only 1 Joker in the entire deck, but it's worth -5 points! (Rare and powerful)</li>
|
<p>2 Jokers per deck, each worth <strong>-2 points</strong>.</p>
|
||||||
<li><strong>Eagle Eye:</strong> Jokers are worth +2 points unpaired, but -4 points when paired (rewards finding both Jokers)</li>
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Jokers are great to find but pairing them is wasteful (0 points instead of -4). Best kept in different columns. Adds 2 premium cards to hunt for.</p>
|
||||||
</ul>
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Lucky Swing</h4>
|
||||||
|
<p>Only <strong>1 Joker</strong> in the entire deck, worth <strong>-5 points</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> High variance. Whoever finds this rare card gets a significant advantage. Increases the luck factor - sometimes you get it, sometimes your opponent does.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Eagle Eye</h4>
|
||||||
|
<p>Jokers are worth <strong>+2 unpaired</strong>, but <strong>-4 when paired</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Risk/reward Jokers. Finding one actually hurts you (+2) until you commit to finding the second. Rewards aggressive searching and creates tense decisions about whether to keep hunting or cut your losses.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Bonuses & Penalties</h3>
|
<div class="rules-mode">
|
||||||
<ul>
|
<h3>Going Out Rules</h3>
|
||||||
<li><strong>Knock Penalty:</strong> If you "go out" (reveal all cards first) but DON'T have the lowest score, you get +10 penalty points. Risk vs reward!</li>
|
<div class="house-rule">
|
||||||
<li><strong>Knock Bonus:</strong> Get -5 points (subtracted from your score) for going out first.</li>
|
<h4>Knock Penalty</h4>
|
||||||
<li><strong>Underdog Bonus:</strong> The player with the lowest score each hole gets -3 points.</li>
|
<p><strong>+10 points</strong> if you go out but don't have the lowest score.</p>
|
||||||
<li><strong>Tied Shame:</strong> If you tie with another player's score, both of you get +5 penalty points.</li>
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Discourages reckless rushing. You need to be confident you're winning before going out. Rewards patience and reading your opponents' likely scores. Can backfire spectacularly if you misjudge.</p>
|
||||||
<li><strong>Blackjack:</strong> If your exact score is 21, it becomes 0 instead!</li>
|
</div>
|
||||||
<li><strong>Wolfpack:</strong> If you have exactly 2 pairs of Jacks (all 4 Jacks), you get -5 bonus points.</li>
|
<div class="house-rule">
|
||||||
</ul>
|
<h4>Knock Bonus</h4>
|
||||||
|
<p><strong>-5 points</strong> for going out first (regardless of who wins).</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Encourages racing to finish, even with a mediocre hand. The 5-point bonus might make up for a slightly worse score. Speeds up gameplay.</p>
|
||||||
|
</div>
|
||||||
|
<p class="combo-note"><em>Combining Knock Penalty + Knock Bonus creates high-stakes "going out" decisions: -5 if you win, +10 if you lose!</em></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rules-mode">
|
||||||
|
<h3>Scoring Bonuses</h3>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Underdog Bonus</h4>
|
||||||
|
<p>Round winner gets <strong>-3 points</strong> extra.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Amplifies winning - the best player each round pulls further ahead. Can lead to snowballing leads over multiple holes. Rewards consistency.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Tied Shame</h4>
|
||||||
|
<p>If you tie another player's score, <strong>both get +5 penalty</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Punishes playing it safe. If you suspect a tie, you need to take risks to differentiate your score. Creates interesting late-round decisions.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Blackjack</h4>
|
||||||
|
<p>Score of exactly <strong>21 becomes 0</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> A "hail mary" comeback. If you're stuck at 21, you're suddenly in great shape. Mostly luck, but adds exciting moments when it happens.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Wolfpack</h4>
|
||||||
|
<p>Having <strong>all 4 Jacks</strong> (2 pairs) gives <strong>-20 bonus</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Extremely rare but now a significant reward! Turns a potential disaster (40 points of Jacks) into a triumph. The huge bonus makes it worth celebrating when achieved, though still not worth actively pursuing.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rules-mode">
|
||||||
|
<h3>New Variants</h3>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Flip as Action</h4>
|
||||||
|
<p>Use your turn to flip one of your face-down cards without drawing. Ends your turn immediately.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Lets you gather information without risking a bad deck draw. Conservative players can learn their hand safely. However, you miss the chance to actively improve your hand - you're just learning what you have.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Four of a Kind</h4>
|
||||||
|
<p>Having 4 cards of the same rank across two columns scores <strong>-20 bonus</strong>.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Rewards collecting matching cards beyond just column pairs. Changes whether you should take a third or fourth copy of a rank. If you already have two pairs of 8s, that's -20 extra! Stacks with Wolfpack: four Jacks = -40 total.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>Negative Pairs Keep Value</h4>
|
||||||
|
<p>When you pair 2s or Jokers in a column, they keep their combined <strong>-4 points</strong> instead of becoming 0.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Major change! Pairing your best cards is now beneficial. Two 2s paired = -4 points, not 0. This encourages hunting for duplicate negative cards and fundamentally changes how you value 2s and Jokers.</p>
|
||||||
|
</div>
|
||||||
|
<div class="house-rule">
|
||||||
|
<h4>One-Eyed Jacks</h4>
|
||||||
|
<p>The Jack of Hearts (J♥) and Jack of Spades (J♠) - the "one-eyed" Jacks - are worth <strong>0 points</strong> instead of 10.</p>
|
||||||
|
<p class="strategic-impact"><strong>Strategic impact:</strong> Two of the four Jacks become safe cards, comparable to Kings. J♥ and J♠ are now good cards to keep! Only J♣ and J♦ remain dangerous. Reduces the "Jack disaster" probability by half.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="rules-faq" class="rules-section">
|
<section id="rules-faq" class="rules-section">
|
||||||
@ -481,8 +620,13 @@ TOTAL: 0 + 8 + 16 = 24 points</pre>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="faq-item">
|
<div class="faq-item">
|
||||||
<h4>Q: Why would I NOT flip in Endgame mode?</h4>
|
<h4>Q: How does Endgame mode help trailing players?</h4>
|
||||||
<p>A: Maybe you have a hidden card you hope is good, and you don't want to reveal a potential disaster. Or maybe you want to keep your opponents guessing about your score. It's a strategic choice!</p>
|
<p>A: When someone is close to going out, they've likely optimized their hand already. The optional flip lets everyone else accelerate their information gathering - flipping cards to find pairs or identify which cards to swap out. The leader doesn't benefit (they have no hidden cards left), so it's purely a catch-up mechanic.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="faq-item">
|
||||||
|
<h4>Q: Why would I skip the flip in Endgame mode?</h4>
|
||||||
|
<p>A: If you're already winning or your remaining hidden cards are statistically likely to be good, you might prefer not to risk revealing a disaster. It's a calculated gamble!</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
114
server/ai.py
114
server/ai.py
@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from game import Card, Player, Game, GamePhase, GameOptions, RANK_VALUES, Rank, get_card_value
|
from game import Card, Player, Game, GamePhase, GameOptions, RANK_VALUES, Rank, Suit, get_card_value
|
||||||
|
|
||||||
|
|
||||||
# Debug logging configuration
|
# Debug logging configuration
|
||||||
@ -226,6 +226,10 @@ def filter_bad_pair_positions(
|
|||||||
if options.eagle_eye and drawn_card.rank == Rank.JOKER:
|
if options.eagle_eye and drawn_card.rank == Rank.JOKER:
|
||||||
return positions
|
return positions
|
||||||
|
|
||||||
|
# Exception: Negative Pairs Keep Value makes pairing negative cards GOOD
|
||||||
|
if options.negative_pairs_keep_value:
|
||||||
|
return positions
|
||||||
|
|
||||||
filtered = []
|
filtered = []
|
||||||
for pos in positions:
|
for pos in positions:
|
||||||
partner_pos = get_column_partner_position(pos)
|
partner_pos = get_column_partner_position(pos)
|
||||||
@ -475,6 +479,12 @@ class GolfAI:
|
|||||||
ai_log(f" >> TAKE: King (always take)")
|
ai_log(f" >> TAKE: King (always take)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# One-eyed Jacks: J♥ and J♠ are worth 0, always take them
|
||||||
|
if options.one_eyed_jacks:
|
||||||
|
if discard_card.rank == Rank.JACK and discard_card.suit in (Suit.HEARTS, Suit.SPADES):
|
||||||
|
ai_log(f" >> TAKE: One-eyed Jack (worth 0)")
|
||||||
|
return True
|
||||||
|
|
||||||
# Auto-take 10s when ten_penny enabled (they're worth 1)
|
# Auto-take 10s when ten_penny enabled (they're worth 1)
|
||||||
if discard_card.rank == Rank.TEN and options.ten_penny:
|
if discard_card.rank == Rank.TEN and options.ten_penny:
|
||||||
ai_log(f" >> TAKE: 10 (ten_penny rule)")
|
ai_log(f" >> TAKE: 10 (ten_penny rule)")
|
||||||
@ -578,11 +588,17 @@ class GolfAI:
|
|||||||
pair_bonus = drawn_value + partner_value
|
pair_bonus = drawn_value + partner_value
|
||||||
score += pair_bonus * pair_weight # Pair hunters value this more
|
score += pair_bonus * pair_weight # Pair hunters value this more
|
||||||
else:
|
else:
|
||||||
# Pairing negative cards - usually bad
|
# Pairing negative cards
|
||||||
if options.eagle_eye and drawn_card.rank == Rank.JOKER:
|
if options.eagle_eye and drawn_card.rank == Rank.JOKER:
|
||||||
score += 8 * pair_weight # Eagle Eye Joker pairs
|
score += 8 * pair_weight # Eagle Eye Joker pairs = -4
|
||||||
|
elif options.negative_pairs_keep_value:
|
||||||
|
# Negative Pairs Keep Value: pairing 2s/Jokers is NOW good!
|
||||||
|
# Pair of 2s = -4, pair of Jokers = -4 (instead of 0)
|
||||||
|
pair_benefit = abs(drawn_value + partner_value)
|
||||||
|
score += pair_benefit * pair_weight
|
||||||
|
ai_log(f" Negative pair keep value bonus: +{pair_benefit * pair_weight:.1f}")
|
||||||
else:
|
else:
|
||||||
# Penalty, but pair hunters might still do it
|
# Standard rules: penalty for wasting negative cards
|
||||||
penalty = abs(drawn_value) * 2 * (2.0 - profile.pair_hope)
|
penalty = abs(drawn_value) * 2 * (2.0 - profile.pair_hope)
|
||||||
score -= penalty
|
score -= penalty
|
||||||
|
|
||||||
@ -632,6 +648,20 @@ class GolfAI:
|
|||||||
pair_viability = get_pair_viability(drawn_card.rank, game)
|
pair_viability = get_pair_viability(drawn_card.rank, game)
|
||||||
score += pair_viability * pair_weight * 0.5
|
score += pair_viability * pair_weight * 0.5
|
||||||
|
|
||||||
|
# 4b. FOUR OF A KIND PURSUIT
|
||||||
|
# When four_of_a_kind rule is enabled, boost score for collecting 3rd/4th card
|
||||||
|
if options.four_of_a_kind:
|
||||||
|
# Count how many of this rank player already has visible (excluding current position)
|
||||||
|
rank_count = sum(
|
||||||
|
1 for i, c in enumerate(player.cards)
|
||||||
|
if c.face_up and c.rank == drawn_card.rank and i != pos
|
||||||
|
)
|
||||||
|
if rank_count >= 2:
|
||||||
|
# Already have 2+ of this rank, getting more is great for 4-of-a-kind
|
||||||
|
four_kind_bonus = rank_count * 4 # 8 for 2 cards, 12 for 3 cards
|
||||||
|
score += four_kind_bonus
|
||||||
|
ai_log(f" Four-of-a-kind pursuit bonus: +{four_kind_bonus}")
|
||||||
|
|
||||||
# 5. GO-OUT SAFETY - Penalty for going out with bad score
|
# 5. GO-OUT SAFETY - Penalty for going out with bad score
|
||||||
face_down_positions = [i for i, c in enumerate(player.cards) if not c.face_up]
|
face_down_positions = [i for i, c in enumerate(player.cards) if not c.face_up]
|
||||||
if len(face_down_positions) == 1 and pos == face_down_positions[0]:
|
if len(face_down_positions) == 1 and pos == face_down_positions[0]:
|
||||||
@ -862,6 +892,62 @@ class GolfAI:
|
|||||||
ai_log(f" >> FLIP: choosing to reveal for information")
|
ai_log(f" >> FLIP: choosing to reveal for information")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def should_use_flip_action(game: Game, player: Player, profile: CPUProfile) -> Optional[int]:
|
||||||
|
"""
|
||||||
|
Decide whether to use flip-as-action instead of drawing.
|
||||||
|
|
||||||
|
Returns card index to flip, or None to draw normally.
|
||||||
|
|
||||||
|
Only available when flip_as_action house rule is enabled.
|
||||||
|
Conservative players may prefer this to avoid risky deck draws.
|
||||||
|
"""
|
||||||
|
if not game.options.flip_as_action:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Find face-down cards
|
||||||
|
face_down = [(i, c) for i, c in enumerate(player.cards) if not c.face_up]
|
||||||
|
if not face_down:
|
||||||
|
return None # No cards to flip
|
||||||
|
|
||||||
|
# Check if discard has a good card we want - if so, don't use flip action
|
||||||
|
discard_top = game.discard_top()
|
||||||
|
if discard_top:
|
||||||
|
discard_value = get_ai_card_value(discard_top, game.options)
|
||||||
|
if discard_value <= 2: # Good card available
|
||||||
|
ai_log(f" Flip-as-action: skipping, good discard available ({discard_value})")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Aggressive players prefer drawing (more action, chance to improve)
|
||||||
|
if profile.aggression > 0.6:
|
||||||
|
ai_log(f" Flip-as-action: skipping, too aggressive ({profile.aggression:.2f})")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Consider flip action with probability based on personality
|
||||||
|
# Conservative players (low aggression) are more likely to use it
|
||||||
|
flip_chance = (1.0 - profile.aggression) * 0.25 # Max 25% for most conservative
|
||||||
|
|
||||||
|
# Increase chance if we have many hidden cards (info is valuable)
|
||||||
|
if len(face_down) >= 4:
|
||||||
|
flip_chance *= 1.5
|
||||||
|
|
||||||
|
if random.random() > flip_chance:
|
||||||
|
return None
|
||||||
|
|
||||||
|
ai_log(f" Flip-as-action: choosing to flip instead of draw")
|
||||||
|
|
||||||
|
# Prioritize positions where column partner is visible (pair info)
|
||||||
|
for idx, card in face_down:
|
||||||
|
partner_idx = idx + 3 if idx < 3 else idx - 3
|
||||||
|
if player.cards[partner_idx].face_up:
|
||||||
|
ai_log(f" Flipping position {idx} (partner visible)")
|
||||||
|
return idx
|
||||||
|
|
||||||
|
# Random face-down card
|
||||||
|
choice = random.choice(face_down)[0]
|
||||||
|
ai_log(f" Flipping position {choice} (random)")
|
||||||
|
return choice
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def should_go_out_early(player: Player, game: Game, profile: CPUProfile) -> bool:
|
def should_go_out_early(player: Player, game: Game, profile: CPUProfile) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -943,6 +1029,26 @@ async def process_cpu_turn(
|
|||||||
# Check if we should try to go out early
|
# Check if we should try to go out early
|
||||||
GolfAI.should_go_out_early(cpu_player, game, profile)
|
GolfAI.should_go_out_early(cpu_player, game, profile)
|
||||||
|
|
||||||
|
# Check if we should use flip-as-action instead of drawing
|
||||||
|
flip_action_pos = GolfAI.should_use_flip_action(game, cpu_player, profile)
|
||||||
|
if flip_action_pos is not None:
|
||||||
|
if game.flip_card_as_action(cpu_player.id, flip_action_pos):
|
||||||
|
# Log flip-as-action
|
||||||
|
if logger and game_id:
|
||||||
|
flipped_card = cpu_player.cards[flip_action_pos]
|
||||||
|
logger.log_move(
|
||||||
|
game_id=game_id,
|
||||||
|
player=cpu_player,
|
||||||
|
is_cpu=True,
|
||||||
|
action="flip_as_action",
|
||||||
|
card=flipped_card,
|
||||||
|
position=flip_action_pos,
|
||||||
|
game=game,
|
||||||
|
decision_reason=f"used flip-as-action to reveal position {flip_action_pos}",
|
||||||
|
)
|
||||||
|
await broadcast_callback()
|
||||||
|
return # Turn is over
|
||||||
|
|
||||||
# Decide whether to draw from discard or deck
|
# Decide whether to draw from discard or deck
|
||||||
discard_top = game.discard_top()
|
discard_top = game.discard_top()
|
||||||
take_discard = GolfAI.should_take_discard(discard_top, cpu_player, profile, game)
|
take_discard = GolfAI.should_take_discard(discard_top, cpu_player, profile, game)
|
||||||
|
|||||||
@ -59,6 +59,14 @@ else:
|
|||||||
LUCKY_SWING_JOKER_VALUE: int = -5 # Single joker worth -5
|
LUCKY_SWING_JOKER_VALUE: int = -5 # Single joker worth -5
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Bonus/Penalty Constants
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
WOLFPACK_BONUS: int = -20 # All 4 Jacks (2 pairs) bonus (was -5, buffed)
|
||||||
|
FOUR_OF_A_KIND_BONUS: int = -20 # Four equal cards in two columns bonus
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Game Constants
|
# Game Constants
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
@ -29,6 +29,8 @@ from constants import (
|
|||||||
SUPER_KINGS_VALUE,
|
SUPER_KINGS_VALUE,
|
||||||
TEN_PENNY_VALUE,
|
TEN_PENNY_VALUE,
|
||||||
LUCKY_SWING_JOKER_VALUE,
|
LUCKY_SWING_JOKER_VALUE,
|
||||||
|
WOLFPACK_BONUS,
|
||||||
|
FOUR_OF_A_KIND_BONUS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -110,6 +112,10 @@ def get_card_value(card: "Card", options: Optional["GameOptions"] = None) -> int
|
|||||||
return SUPER_KINGS_VALUE
|
return SUPER_KINGS_VALUE
|
||||||
if card.rank == Rank.TEN and options.ten_penny:
|
if card.rank == Rank.TEN and options.ten_penny:
|
||||||
return TEN_PENNY_VALUE
|
return TEN_PENNY_VALUE
|
||||||
|
# One-eyed Jacks: J♥ and J♠ are worth 0 instead of 10
|
||||||
|
if options.one_eyed_jacks:
|
||||||
|
if card.rank == Rank.JACK and card.suit in (Suit.HEARTS, Suit.SPADES):
|
||||||
|
return 0
|
||||||
return RANK_VALUES[card.rank]
|
return RANK_VALUES[card.rank]
|
||||||
|
|
||||||
|
|
||||||
@ -301,6 +307,7 @@ class Player:
|
|||||||
|
|
||||||
total = 0
|
total = 0
|
||||||
jack_pairs = 0 # Track paired Jacks for Wolfpack bonus
|
jack_pairs = 0 # Track paired Jacks for Wolfpack bonus
|
||||||
|
paired_ranks: list[Rank] = [] # Track all paired ranks for four-of-a-kind
|
||||||
|
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
top_idx = col
|
top_idx = col
|
||||||
@ -310,6 +317,8 @@ class Player:
|
|||||||
|
|
||||||
# Check if column pair matches (same rank cancels out)
|
# Check if column pair matches (same rank cancels out)
|
||||||
if top_card.rank == bottom_card.rank:
|
if top_card.rank == bottom_card.rank:
|
||||||
|
paired_ranks.append(top_card.rank)
|
||||||
|
|
||||||
# Track Jack pairs for Wolfpack bonus
|
# Track Jack pairs for Wolfpack bonus
|
||||||
if top_card.rank == Rank.JACK:
|
if top_card.rank == Rank.JACK:
|
||||||
jack_pairs += 1
|
jack_pairs += 1
|
||||||
@ -320,6 +329,15 @@ class Player:
|
|||||||
total -= 4
|
total -= 4
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Negative Pairs Keep Value: paired 2s/Jokers keep their negative value
|
||||||
|
if options and options.negative_pairs_keep_value:
|
||||||
|
top_val = get_card_value(top_card, options)
|
||||||
|
bottom_val = get_card_value(bottom_card, options)
|
||||||
|
if top_val < 0 or bottom_val < 0:
|
||||||
|
# Keep negative value instead of 0
|
||||||
|
total += top_val + bottom_val
|
||||||
|
continue
|
||||||
|
|
||||||
# Normal matching pair: scores 0 (skip adding values)
|
# Normal matching pair: scores 0 (skip adding values)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -327,9 +345,18 @@ class Player:
|
|||||||
total += get_card_value(top_card, options)
|
total += get_card_value(top_card, options)
|
||||||
total += get_card_value(bottom_card, options)
|
total += get_card_value(bottom_card, options)
|
||||||
|
|
||||||
# Wolfpack bonus: 2+ pairs of Jacks = -5 pts
|
# Wolfpack bonus: 2+ pairs of Jacks
|
||||||
if options and options.wolfpack and jack_pairs >= 2:
|
if options and options.wolfpack and jack_pairs >= 2:
|
||||||
total -= 5
|
total += WOLFPACK_BONUS # -20
|
||||||
|
|
||||||
|
# Four of a Kind bonus: same rank appears twice in paired_ranks
|
||||||
|
# (meaning 4 cards of that rank across 2 columns)
|
||||||
|
if options and options.four_of_a_kind:
|
||||||
|
rank_counts = Counter(paired_ranks)
|
||||||
|
for rank, count in rank_counts.items():
|
||||||
|
if count >= 2:
|
||||||
|
# Four of a kind! Apply bonus
|
||||||
|
total += FOUR_OF_A_KIND_BONUS # -20
|
||||||
|
|
||||||
self.score = total
|
self.score = total
|
||||||
return total
|
return total
|
||||||
@ -415,6 +442,19 @@ class GameOptions:
|
|||||||
eagle_eye: bool = False
|
eagle_eye: bool = False
|
||||||
"""Jokers worth +2 unpaired, -4 when paired (instead of -2/0)."""
|
"""Jokers worth +2 unpaired, -4 when paired (instead of -2/0)."""
|
||||||
|
|
||||||
|
# --- House Rules: New Variants (all OFF by default for classic gameplay) ---
|
||||||
|
flip_as_action: bool = False
|
||||||
|
"""Allow using turn to flip a face-down card without drawing."""
|
||||||
|
|
||||||
|
four_of_a_kind: bool = False
|
||||||
|
"""Four equal cards in two columns scores -20 points bonus."""
|
||||||
|
|
||||||
|
negative_pairs_keep_value: bool = False
|
||||||
|
"""Paired 2s and Jokers keep their negative value (-4) instead of becoming 0."""
|
||||||
|
|
||||||
|
one_eyed_jacks: bool = False
|
||||||
|
"""One-eyed Jacks (J♥ and J♠) are worth 0 points instead of 10."""
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Game:
|
class Game:
|
||||||
@ -878,6 +918,45 @@ class Game:
|
|||||||
self._check_end_turn(player)
|
self._check_end_turn(player)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def flip_card_as_action(self, player_id: str, card_index: int) -> bool:
|
||||||
|
"""
|
||||||
|
Use turn to flip a face-down card without drawing.
|
||||||
|
|
||||||
|
Only valid if flip_as_action house rule is enabled.
|
||||||
|
This is an alternative to drawing - player flips one of their
|
||||||
|
face-down cards to see what it is, then their turn ends.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
player_id: ID of the player using this action.
|
||||||
|
card_index: Index 0-5 of the card to flip.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if action was valid and turn ended, False otherwise.
|
||||||
|
"""
|
||||||
|
if not self.options.flip_as_action:
|
||||||
|
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
|
||||||
|
|
||||||
|
if not (0 <= card_index < len(player.cards)):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if player.cards[card_index].face_up:
|
||||||
|
return False # Already face-up, can't flip
|
||||||
|
|
||||||
|
player.cards[card_index].face_up = True
|
||||||
|
self._check_end_turn(player)
|
||||||
|
return True
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Turn & Round Flow (Internal)
|
# Turn & Round Flow (Internal)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
@ -1089,6 +1168,15 @@ class Game:
|
|||||||
active_rules.append("Blackjack")
|
active_rules.append("Blackjack")
|
||||||
if self.options.wolfpack:
|
if self.options.wolfpack:
|
||||||
active_rules.append("Wolfpack")
|
active_rules.append("Wolfpack")
|
||||||
|
# New house rules
|
||||||
|
if self.options.flip_as_action:
|
||||||
|
active_rules.append("Flip as Action")
|
||||||
|
if self.options.four_of_a_kind:
|
||||||
|
active_rules.append("Four of a Kind")
|
||||||
|
if self.options.negative_pairs_keep_value:
|
||||||
|
active_rules.append("Negative Pairs Keep Value")
|
||||||
|
if self.options.one_eyed_jacks:
|
||||||
|
active_rules.append("One-Eyed Jacks")
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"phase": self.phase.value,
|
"phase": self.phase.value,
|
||||||
@ -1108,6 +1196,7 @@ class Game:
|
|||||||
"flip_on_discard": self.flip_on_discard,
|
"flip_on_discard": self.flip_on_discard,
|
||||||
"flip_mode": self.options.flip_mode,
|
"flip_mode": self.options.flip_mode,
|
||||||
"flip_is_optional": self.flip_is_optional,
|
"flip_is_optional": self.flip_is_optional,
|
||||||
|
"flip_as_action": self.options.flip_as_action,
|
||||||
"card_values": self.get_card_values(),
|
"card_values": self.get_card_values(),
|
||||||
"active_rules": active_rules,
|
"active_rules": active_rules,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -575,6 +575,11 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
blackjack=data.get("blackjack", False),
|
blackjack=data.get("blackjack", False),
|
||||||
eagle_eye=data.get("eagle_eye", False),
|
eagle_eye=data.get("eagle_eye", False),
|
||||||
wolfpack=data.get("wolfpack", False),
|
wolfpack=data.get("wolfpack", False),
|
||||||
|
# House Rules - New Variants
|
||||||
|
flip_as_action=data.get("flip_as_action", False),
|
||||||
|
four_of_a_kind=data.get("four_of_a_kind", False),
|
||||||
|
negative_pairs_keep_value=data.get("negative_pairs_keep_value", False),
|
||||||
|
one_eyed_jacks=data.get("one_eyed_jacks", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate settings
|
# Validate settings
|
||||||
@ -688,6 +693,15 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
await broadcast_game_state(current_room)
|
await broadcast_game_state(current_room)
|
||||||
await check_and_run_cpu_turn(current_room)
|
await check_and_run_cpu_turn(current_room)
|
||||||
|
|
||||||
|
elif msg_type == "flip_as_action":
|
||||||
|
if not current_room:
|
||||||
|
continue
|
||||||
|
|
||||||
|
position = data.get("position", 0)
|
||||||
|
if current_room.game.flip_card_as_action(player_id, position):
|
||||||
|
await broadcast_game_state(current_room)
|
||||||
|
await check_and_run_cpu_turn(current_room)
|
||||||
|
|
||||||
elif msg_type == "next_round":
|
elif msg_type == "next_round":
|
||||||
if not current_room:
|
if not current_room:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user