PokeR/src/lib/components/BetControls.svelte
Veit F. a07117efaf fix: implement correct Texas Hold'em betting rules and game flow
Rewrite core game logic to fix 10 critical bugs violating poker rules:

- Add betMatched flag to separate chip tracking from bet-matching state
- Implement last-aggressor tracking for proper betting round completion
- Rewrite all action functions with validation enforcement
- Add side pot support for multi-level all-in scenarios
- Replace nested setTimeout AI turns with async promise chain
- Add aiActing guard to prevent race conditions during AI play
- Fix currentTurn advancement to always land on active players
2026-05-17 18:46:08 +02:00

121 lines
2.9 KiB
Svelte

<script lang="ts">
import type { GameState } from '$lib/types/game-state';
let { gameState, onAction, disabled = false }: {
gameState: GameState;
onAction: (type: string, amount?: number) => void;
disabled?: boolean;
} = $props();
const humanPlayer = $derived(gameState.players[0]);
const isMyTurn = $derived(
!disabled &&
gameState.currentTurn === 0 &&
humanPlayer?.status === 'active' &&
gameState.bettingRound !== 'idle' &&
gameState.bettingRound !== 'showdown'
);
const canCheck = $derived(humanPlayer && humanPlayer.currentBet >= gameState.currentBet);
const callAmount = $derived(humanPlayer ? gameState.currentBet - humanPlayer.currentBet : 0);
const minRaise = $derived(gameState.currentBet + (gameState.lastRaiseAmount || gameState.bigBlind));
let raiseAmount = $derived(minRaise);
</script>
<div class="bet-controls" class:hidden={!isMyTurn}>
{#if isMyTurn}
<button class="btn check" onclick={() => onAction('check')} disabled={!canCheck}>
Check
</button>
{#if !canCheck}
<button class="btn call" onclick={() => onAction('call')}>
Call ${callAmount}
</button>
{/if}
<div class="raise-group">
<input
type="number"
bind:value={raiseAmount}
min={minRaise}
max={humanPlayer.chips + humanPlayer.currentBet}
class="raise-input"
aria-label="Raise amount"
/>
<button class="btn raise" onclick={() => onAction('raise', raiseAmount)}>
Raise
</button>
</div>
{#if humanPlayer && (humanPlayer.chips > callAmount)}
<button class="btn all-in" onclick={() => onAction('all-in')}>
All-In ${humanPlayer.chips}
</button>
{/if}
<button class="btn fold" onclick={() => onAction('fold')}>
Fold
</button>
{/if}
</div>
<style>
.bet-controls {
display: flex;
gap: 8px;
justify-content: center;
align-items: center;
padding: 12px;
flex-wrap: wrap;
}
.bet-controls.hidden {
visibility: hidden;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 6px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.15s ease;
}
.btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.btn:not(:disabled):hover {
transform: translateY(-1px);
filter: brightness(1.1);
}
.check { background: #3498db; color: white; }
.call { background: #2ecc71; color: white; }
.raise { background: #e67e22; color: white; }
.fold { background: #e74c3c; color: white; }
.all-in { background: #9b59b6; color: white; }
.raise-group {
display: flex;
gap: 4px;
align-items: center;
}
.raise-input {
width: 70px;
padding: 8px;
border: 2px solid #555;
border-radius: 6px;
background: rgba(0, 0, 0, 0.3);
color: white;
font-size: 14px;
text-align: center;
}
</style>