diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/.openspec.yaml b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/.openspec.yaml new file mode 100644 index 0000000..66da1ae --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-17 diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/design.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/design.md new file mode 100644 index 0000000..580812f --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/design.md @@ -0,0 +1,153 @@ +## Context + +The current PokeR app implements Texas Hold'em poker but contains 10 critical bugs in its game flow logic. The core issues stem from three architectural problems: (1) ambiguous `currentBet` semantics that conflate "player contribution" with "bet matched," (2) lack of last-aggressor tracking for betting round completion, and (3) no side pot support for all-in scenarios. The hand evaluator and card systems are solid; the game orchestration layer needs complete reconstruction. + +## Goals / Non-Goals + +**Goals:** +- Implement correct Texas Hold'em betting mechanics per official rules +- Fix all identified bugs without introducing new ones +- Support side pots for multi-player all-in scenarios +- Prevent race conditions between human input and AI turns +- Maintain existing Svelte 5 runes-based architecture and component structure + +**Non-Goals:** +- Adding new game variants (Omaha, Stud, etc.) +- Implementing GTO or strategic AI improvements +- Multiplayer networking +- Educational features (pot odds, hand ranges) +- UI/visual redesign + +## Decisions + +### Decision 1: Separate `betMatched` flag from `currentBet` + +**Current:** `PlayerSeat.currentBet` serves dual purpose — tracks actual chips contributed AND whether player has matched the current bet level. This causes `applyCheck` to corrupt state by setting `currentBet = state.currentBet`. + +**Decision:** Add explicit `betMatched: boolean` field to `PlayerSeat`. `currentBet` becomes purely "chips contributed this round." Check simply sets `betMatched = true` without modifying chip counts. + +``` +PlayerSeat { + // ... existing fields + currentBet: number // actual chips contributed this round + betMatched: boolean // has player matched or checked this round? +} +``` + +**Rationale:** Eliminates the fundamental semantic confusion that causes cascading bugs. Call amount becomes `state.currentBet - player.currentBet` with no corruption from check actions. + +### Decision 2: Last-aggressor betting round completion + +**Current:** `completeBettingRound` checks if all active players have `currentBet >= currentMaxBet`. This fails when all-in players are included and doesn't handle the "return to last aggressor" rule. + +**Decision:** Track `lastAggressorIndex` in GameState. A betting round ends when: +- Action returns to `lastAggressorIndex` AND that player has matched, OR +- All active (non-all-in) players have `betMatched = true`, OR +- Only one active player remains + +``` +// Round completion logic +function isRoundComplete(state): boolean { + const activePlayers = state.players.filter(p => p.status === 'active'); + + // Single active player — round over + if (activePlayers.length <= 1) return true; + + // No raises occurred — everyone checked/called + if (state.lastAggressorIndex === -1) { + return activePlayers.every(p => p.betMatched); + } + + // Raises occurred — must return to last aggressor who matches + const lastAgg = state.players[state.lastAggressorIndex]; + return lastAgg.status === 'active' && + state.currentTurn === state.lastAggressorIndex && + lastAgg.betMatched; +} +``` + +**Rationale:** Texas Hold'em rules require action to return to the last aggressor. This is the standard implementation pattern used by poker software. + +### Decision 3: Side pot algorithm + +**Current:** No side pot support. All chips go into a single pot, causing incorrect distributions when players go all-in at different levels. + +**Decision:** Implement standard side pot calculation: +1. Sort all-in players by their bet amount (ascending) +2. Create pots from bottom up — each pot contains chips above the next lower all-in level +3. Track which players are eligible for each pot (active or all-in at that level or above) + +``` +SidePot { + amount: number + eligiblePlayerIds: string[] +} + +// Algorithm sketch: +function calculatePots(players, currentPot): { mainPot: number, sidePots: SidePot[] } + // Sort players by bet amount + // Create nested pots from smallest all-in to largest + // Main pot = chips matched by all remaining active/all-in players +``` + +**Rationale:** Standard poker software approach. Keeps implementation clean and matches real-world rules exactly. + +### Decision 4: Unified game state machine in `+page.svelte` + +**Current:** `processGame()` has complex branching logic with async AI turns, state discarding, and multiple exit paths. Race conditions occur when human actions interrupt AI chains. + +**Decision:** Replace with a clear state machine pattern: +- `aiActing: boolean` flag disables controls during AI turns +- Single `processTurn()` function handles all post-action processing +- State transitions are always applied atomically (`gameState = newState`) +- AI turns use sequential promise chains, not nested timeouts + +``` +function processTurn() { + // 1. Check for early win (single active player) + // 2. Check if betting round is complete + // 3. If complete: advance to next stage or showdown + // 4. If not complete: check if next player is AI, schedule turn +} + +// In handleAction: +if (aiActing) return; // Guard against race conditions +gameState = applyAction(gameState, action); +processTurn(); +``` + +**Rationale:** Eliminates race conditions and state discarding bugs. Single source of truth for game flow logic. + +### Decision 5: Validation enforcement + +**Current:** `validateAction` exists but is never called by `applyRaise`, `applyFold`, etc. The AI and human actions bypass validation entirely. + +**Decision:** All action functions call `validateAction` internally and return unchanged state on invalid actions. The UI layer also validates before rendering buttons. + +``` +function applyRaise(state, playerId, amount) { + const validation = validateAction(playerId, 'raise', amount, state); + if (!validation.valid) return state; + // ... proceed with raise logic +} +``` + +**Rationale:** Defense in depth — both the action layer and UI layer enforce rules. Prevents illegal states from being created. + +## Risks / Trade-offs + +[Side pot complexity] → Side pots add significant complexity to showdown logic. Mitigation: Thoroughly test with edge cases (2 all-ins, 3 all-ins, all-in on flop/turn/river). + +[State expansion] → Adding `betMatched`, `lastAggressorIndex`, and `sidePots` to GameState increases mutation surface. Mitigation: Use immutable state updates consistently, reset all fields in `startNewHand`. + +[AI timing changes] → Switching from nested timeouts to promise chains changes AI behavior timing. Mitigation: Keep 400ms delay per AI action for consistent feel. + +[Performance] → Side pot calculation at showdown could be expensive with many players. Mitigation: 6-max table limits complexity; algorithm is O(n log n) with n ≤ 6. + +[currentTurn deadlock] → `completeBettingRound` originally set `currentTurn = dealerPosition + 1`, which could land on a folded/all-in player, causing an infinite loop in the game state machine. Fixed by using `findFirstActivePlayer()` to always point to an active player. + +## Open Questions + +- Should we implement burn cards (standard in real poker, currently not implemented)? +- How should all-in raises that don't constitute a full raise be handled (half-pot rule)? +- Should the BB option (BB can check pre-flop if no raise) be explicitly documented in the UI? diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/proposal.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/proposal.md new file mode 100644 index 0000000..f900744 --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/proposal.md @@ -0,0 +1,36 @@ +## Why + +The current poker game logic contains multiple critical bugs that violate Texas Hold'em rules, causing rounds to skip, bets to calculate incorrectly, and chips to disappear. The game needs a complete rewrite of its core game flow to produce legitimate, playable poker. + +## What Changes + +- **Fix `applyCheck`**: Remove phantom chip assignment that corrupts call calculations and round completion +- **Fix betting round completion**: Exclude all-in players from "all matched" check so active players aren't skipped +- **Fix game loop state management**: Always apply returned state from `completeBettingRound` instead of discarding it when rounds don't advance +- **Implement side pot support**: Create and distribute side pots when players go all-in at different levels +- **Enforce minimum raise validation**: Call `validateAction` before applying any action, reject illegal raises +- **Fix fold validation**: Allow folding in all situations per Texas Hold'em rules +- **Fix pot distribution**: Award remainder chips to first winner instead of losing them +- **Add input guardrails**: Disable player controls during AI turns to prevent race conditions +- **Rewrite turn advancement**: Properly track last aggressor and end betting rounds when action returns correctly + +## Capabilities + +### New Capabilities +- `side-pots`: Handle all-in scenarios by creating main pot and side pots with correct chip distribution among eligible players +- `betting-round-flow`: Complete rewrite of betting round completion logic with proper last-aggressor tracking, all-in handling, and turn advancement +- `game-loop-integrity`: Unified game state machine that prevents race conditions, always applies state transitions, and disables controls during AI turns + +### Modified Capabilities + + +## Impact + +- `src/lib/game/actions.ts` — Complete rewrite of check, call, raise, fold, all-in functions +- `src/lib/game/betting-round.ts` — Rewrite completion logic with side pot awareness and last-aggressor tracking +- `src/lib/game/state.ts` — Add last aggressor tracking, side pot structures to GameState +- `src/lib/types/game-state.ts` — Extend with `lastAggressor`, `sidePots`, betting round metadata +- `src/lib/game/showdown.ts` — Fix pot distribution, handle side pots at showdown +- `src/lib/game/validation.ts` — Fix fold validation, enforce minimum raise rules +- `src/routes/+page.svelte` — Add input disabling during AI turns, proper state machine flow +- `src/lib/game/turn.ts` — Rewrite turn advancement logic diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/betting-round-flow/spec.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/betting-round-flow/spec.md new file mode 100644 index 0000000..af7fdae --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/betting-round-flow/spec.md @@ -0,0 +1,45 @@ +## ADDED Requirements + +### Requirement: Check action does not modify player chip count or bet contribution +When a player checks, the system SHALL only mark the player as having matched the current bet level without deducting chips or modifying their `currentBet` value. + +#### Scenario: Check with zero current bet +- **WHEN** the current bet is 0 and Player A checks +- **THEN** Player A's `betMatched` becomes true, `currentBet` remains unchanged, and chip count remains unchanged + +#### Scenario: Check after matching previous bet +- **WHEN** Player A has already called a bet of 100 (currentBet = 100) and the next player checks (no raise occurred) +- **THEN** Player A's state remains unchanged except `betMatched` is set to true + +### Requirement: Betting round completes when action returns to last aggressor +The system SHALL end a betting round when all active players have matched the current bet and action returns to the position of the last player who raised or opened betting. + +#### Scenario: Pre-flop BB option (no raises) +- **WHEN** SB posts 10, BB posts 20, and all players check around back to BB +- **THEN** BB may check without posting additional chips, and the betting round completes + +#### Scenario: Raise requires action to return to raiser +- **WHEN** Player A raises to 100, Players B and C call, and Player D checks +- **THEN** the round does not complete until Player A either checks or faces no further raises + +### Requirement: All-in players are excluded from betting round completion check +When determining if a betting round is complete, the system SHALL only consider active (non-all-in) players for the "all matched" condition. + +#### Scenario: Active player skipped by all-in completion bug +- **WHEN** Player A bets 100, Player B goes all-in for 50, and Player C has not yet acted +- **THEN** the betting round does NOT complete; Player C must act before the round advances + +#### Scenario: Single active player ends round +- **WHEN** all players except one have folded or gone all-in +- **THEN** the betting round completes immediately and the game proceeds to the next stage + +### Requirement: Turn advancement skips non-active players correctly +The system SHALL advance turns to the next player with `status === 'active'`, skipping folded and all-in players. + +#### Scenario: Folded player is skipped +- **WHEN** Player A folds and it's their turn position +- **THEN** the turn advances to the next active player clockwise from Player A + +#### Scenario: All-in player is skipped during betting +- **WHEN** Player B goes all-in during a betting round +- **THEN** subsequent turns skip Player B and advance to the next active player diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/game-loop-integrity/spec.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/game-loop-integrity/spec.md new file mode 100644 index 0000000..a136f9b --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/game-loop-integrity/spec.md @@ -0,0 +1,45 @@ +## ADDED Requirements + +### Requirement: Game state transitions are always applied atomically +The system SHALL always apply the complete returned state from action functions and never discard intermediate state updates. + +#### Scenario: Betting round completion state is preserved +- **WHEN** `completeBettingRound` returns a new state with advanced `currentTurn` and reset bets +- **THEN** the game applies the entire returned state, including turn advancement and bet resets, even if the betting round stage does not change + +#### Scenario: Action function results are never lost +- **WHEN** a player performs any action (check, call, raise, fold, all-in) +- **THEN** the returned state from the action function is applied completely before any subsequent processing occurs + +### Requirement: Player controls are disabled during AI turns +The system SHALL disable human player input controls while AI players are taking their turns to prevent race conditions. + +#### Scenario: Human clicks during AI turn +- **WHEN** an AI player is processing their turn (400ms delay) and the human player clicks a bet control +- **THEN** the action is rejected, the UI shows disabled controls, and no state mutation occurs + +#### Scenario: Controls re-enable after AI turn completes +- **WHEN** an AI player completes their action and the turn advances to the human player +- **THEN** the bet controls become enabled and the human player may act + +### Requirement: Validation is enforced before applying any action +The system SHALL validate all actions against game rules before applying state changes, returning unchanged state for invalid actions. + +#### Scenario: Invalid raise is rejected +- **WHEN** a player attempts to raise below the minimum raise amount +- **THEN** the action is rejected, the state remains unchanged, and an error reason is returned + +#### Scenario: Fold always permitted +- **WHEN** a player attempts to fold during any betting round, including pre-flop +- **THEN** the action is accepted regardless of current bet level + +### Requirement: Pot distribution preserves all chips +When awarding pots to winners, the system SHALL distribute all chips without losing remainder amounts. + +#### Scenario: Odd pot split between two winners +- **WHEN** the pot contains 105 chips and two players tie for the win +- **THEN** one winner receives 53 chips, the other receives 52 chips, and zero chips are lost + +#### Scenario: Single winner receives full pot +- **WHEN** a single player wins the pot of 200 chips +- **THEN** the winner receives exactly 200 chips and the pot is reset to 0 diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/side-pots/spec.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/side-pots/spec.md new file mode 100644 index 0000000..77f82b4 --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/specs/side-pots/spec.md @@ -0,0 +1,34 @@ +## ADDED Requirements + +### Requirement: Side pots are created when players go all-in at different bet levels +When one or more players go all-in for less than the current bet level, the system SHALL create separate side pots to ensure only eligible players can win chips they contributed to. + +#### Scenario: Single all-in creates main pot and side pot +- **WHEN** Player A bets 100, Player B goes all-in for 50, and Player C calls 100 +- **THEN** a main pot of 150 (50×3) is created with all three players eligible, and a side pot of 100 (25×4) is created with Players A and C eligible + +#### Scenario: Multiple all-ins create multiple side pots +- **WHEN** Player A bets 200, Player B goes all-in for 100, Player C goes all-in for 150, and Player D calls 200 +- **THEN** a main pot of 400 (100×4) is created with all players eligible, a side pot of 100 (50×3) is created with Players A, C, D eligible, and a side pot of 100 (50×2) is created with Players A and D eligible + +### Requirement: Side pots are awarded to eligible winners only +When determining winners for each pot, the system SHALL only award a pot to players who contributed chips to that specific pot level. + +#### Scenario: All-in player wins main pot only +- **WHEN** Player B is all-in for 50 and has the best hand at showdown, Player A and C are still active with worse hands +- **THEN** Player B receives the main pot amount proportional to their contribution, and Players A/C compete for the side pot + +#### Scenario: All-in player loses all pots +- **WHEN** Player B is all-in for 50 and has the worst hand at showdown +- **THEN** Player B receives no chips from any pot, and eligible players split the main pot proportionally + +### Requirement: Side pot calculation handles edge cases correctly +The system SHALL correctly calculate side pots when all players go all-in or when an all-in does not constitute a raise. + +#### Scenario: All players all-in creates single pot +- **WHEN** all remaining players go all-in during a betting round +- **THEN** a single main pot containing all chips is created with all players eligible, and no side pots are generated + +#### Scenario: All-in that doesn't raise does not create side pot +- **WHEN** the current bet is 100 and Player B goes all-in for 80 (less than a full raise) +- **THEN** no side pot is created; the main pot contains all chips and active players may check to continue diff --git a/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/tasks.md b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/tasks.md new file mode 100644 index 0000000..7a34014 --- /dev/null +++ b/openspec/changes/archive/2026-05-17-fix-texas-holdem-rules/tasks.md @@ -0,0 +1,71 @@ +## 1. Type system updates + +- [x] 1.1 Add `betMatched: boolean` field to `PlayerSeat` interface in `src/lib/types/player.ts` +- [x] 1.2 Add `lastAggressorIndex: number` field to `GameState` interface in `src/lib/types/game-state.ts` +- [x] 1.3 Define `SidePot` interface with `amount` and `eligiblePlayerIds` fields +- [x] 1.4 Add `sidePots: SidePot[]` field to `GameState` interface + +## 2. State initialization & reset + +- [x] 2.1 Update `createInitialState` to initialize new fields (`betMatched`, `lastAggressorIndex`, `sidePots`) +- [x] 2.2 Update `startNewHand` to reset all per-round fields including new fields +- [x] 2.3 Update `postBlinds` to set `betMatched` correctly for SB and BB players + +## 3. Action function rewrites + +- [x] 3.1 Rewrite `applyCheck` to set `betMatched = true` without modifying `currentBet` or chips +- [x] 3.2 Rewrite `applyCall` to use `state.currentBet - player.currentBet` for call amount with correct chip deduction +- [x] 3.3 Rewrite `applyRaise` to update `lastAggressorIndex`, reset other players' `betMatched`, and enforce minimum raise +- [x] 3.4 Rewrite `applyFold` to correctly mark player as folded and advance turn +- [x] 3.5 Rewrite `applyAllIn` to handle partial all-in raises and update `lastAggressorIndex` if applicable +- [x] 3.6 Add `validateAction` call at the start of each action function with early return on invalid actions + +## 4. Validation fixes + +- [x] 4.1 Fix fold validation to always permit folding (remove pre-flop restriction) +- [x] 4.2 Fix raise validation to correctly calculate minimum raise based on `lastRaiseAmount` +- [x] 4.3 Add validation for all-in actions that don't constitute a full raise + +## 5. Betting round completion rewrite + +- [x] 5.1 Implement `isRoundComplete` function with last-aggressor tracking logic +- [x] 5.2 Rewrite `completeBettingRound` to use `betMatched` flag and exclude all-in players from match check +- [x] 5.3 Implement BB option: allow BB to check pre-flop when no raises occurred +- [x] 5.4 Ensure `currentTurn` advances correctly after round completion + +## 6. Side pot implementation + +- [x] 6.1 Implement `calculatePots` function to compute main pot and side pots from player bets +- [x] 6.2 Integrate side pot calculation into all-in action handling +- [x] 6.3 Update `awardPot` to distribute main pot and all side pots correctly +- [x] 6.4 Fix remainder chip distribution (award leftover chips to first winner) +- [x] 6.5 Update showdown logic to determine winners for each pot based on eligibility + +## 7. Turn advancement fixes + +- [x] 7.1 Rewrite `advanceTurn` to skip folded and all-in players correctly +- [x] 7.2 Ensure turn advancement respects dealer position and betting round start position +- [x] 7.3 Handle edge case where no active players remain for turn advancement + +## 8. Game loop rewrite (+page.svelte) + +- [x] 8.1 Add `aiActing: boolean` state flag to disable controls during AI turns +- [x] 8.2 Replace `processGame` with unified `processTurn` state machine function +- [x] 8.3 Ensure all state transitions apply returned state atomically (no discarding) +- [x] 8.4 Convert AI turn timing from nested setTimeout to sequential promise chain +- [x] 8.5 Add guard clause in `handleAction` to reject actions when `aiActing` is true +- [x] 8.6 Update BetControls component to accept and respond to `disabled` prop + +## 9. Bug fixes discovered during playtesting + +- [x] 9.1 Fix deadlock: `completeBettingRound` sets `currentTurn` to first active player instead of hardcoded `dealerPosition + 1` +- [x] 9.2 Simplify `processTurn` to use corrected `currentTurn` directly after round completion + +## 10. Testing & verification + +- [ ] 10.1 Test pre-flop betting round with all action combinations (check, call, raise, fold) +- [ ] 10.2 Test single all-in scenario verifies correct main pot and side pot creation +- [ ] 10.3 Test multiple all-ins at different levels verifies correct multi-side pot distribution +- [ ] 10.4 Test BB option pre-flop (BB checks when no raises) +- [ ] 10.5 Test full hand flow from deal through showdown with no state corruption +- [ ] 10.6 Test race condition prevention (rapid human clicks during AI turn are rejected) \ No newline at end of file diff --git a/openspec/specs/betting-round-flow/spec.md b/openspec/specs/betting-round-flow/spec.md new file mode 100644 index 0000000..7416f0b --- /dev/null +++ b/openspec/specs/betting-round-flow/spec.md @@ -0,0 +1,49 @@ +# betting-round-flow Specification + +## Purpose +TBD - created by archiving change fix-texas-holdem-rules. Update Purpose after archive. +## Requirements +### Requirement: Check action does not modify player chip count or bet contribution +When a player checks, the system SHALL only mark the player as having matched the current bet level without deducting chips or modifying their `currentBet` value. + +#### Scenario: Check with zero current bet +- **WHEN** the current bet is 0 and Player A checks +- **THEN** Player A's `betMatched` becomes true, `currentBet` remains unchanged, and chip count remains unchanged + +#### Scenario: Check after matching previous bet +- **WHEN** Player A has already called a bet of 100 (currentBet = 100) and the next player checks (no raise occurred) +- **THEN** Player A's state remains unchanged except `betMatched` is set to true + +### Requirement: Betting round completes when action returns to last aggressor +The system SHALL end a betting round when all active players have matched the current bet and action returns to the position of the last player who raised or opened betting. + +#### Scenario: Pre-flop BB option (no raises) +- **WHEN** SB posts 10, BB posts 20, and all players check around back to BB +- **THEN** BB may check without posting additional chips, and the betting round completes + +#### Scenario: Raise requires action to return to raiser +- **WHEN** Player A raises to 100, Players B and C call, and Player D checks +- **THEN** the round does not complete until Player A either checks or faces no further raises + +### Requirement: All-in players are excluded from betting round completion check +When determining if a betting round is complete, the system SHALL only consider active (non-all-in) players for the "all matched" condition. + +#### Scenario: Active player skipped by all-in completion bug +- **WHEN** Player A bets 100, Player B goes all-in for 50, and Player C has not yet acted +- **THEN** the betting round does NOT complete; Player C must act before the round advances + +#### Scenario: Single active player ends round +- **WHEN** all players except one have folded or gone all-in +- **THEN** the betting round completes immediately and the game proceeds to the next stage + +### Requirement: Turn advancement skips non-active players correctly +The system SHALL advance turns to the next player with `status === 'active'`, skipping folded and all-in players. + +#### Scenario: Folded player is skipped +- **WHEN** Player A folds and it's their turn position +- **THEN** the turn advances to the next active player clockwise from Player A + +#### Scenario: All-in player is skipped during betting +- **WHEN** Player B goes all-in during a betting round +- **THEN** subsequent turns skip Player B and advance to the next active player + diff --git a/openspec/specs/game-loop-integrity/spec.md b/openspec/specs/game-loop-integrity/spec.md new file mode 100644 index 0000000..1e93481 --- /dev/null +++ b/openspec/specs/game-loop-integrity/spec.md @@ -0,0 +1,49 @@ +# game-loop-integrity Specification + +## Purpose +TBD - created by archiving change fix-texas-holdem-rules. Update Purpose after archive. +## Requirements +### Requirement: Game state transitions are always applied atomically +The system SHALL always apply the complete returned state from action functions and never discard intermediate state updates. + +#### Scenario: Betting round completion state is preserved +- **WHEN** `completeBettingRound` returns a new state with advanced `currentTurn` and reset bets +- **THEN** the game applies the entire returned state, including turn advancement and bet resets, even if the betting round stage does not change + +#### Scenario: Action function results are never lost +- **WHEN** a player performs any action (check, call, raise, fold, all-in) +- **THEN** the returned state from the action function is applied completely before any subsequent processing occurs + +### Requirement: Player controls are disabled during AI turns +The system SHALL disable human player input controls while AI players are taking their turns to prevent race conditions. + +#### Scenario: Human clicks during AI turn +- **WHEN** an AI player is processing their turn (400ms delay) and the human player clicks a bet control +- **THEN** the action is rejected, the UI shows disabled controls, and no state mutation occurs + +#### Scenario: Controls re-enable after AI turn completes +- **WHEN** an AI player completes their action and the turn advances to the human player +- **THEN** the bet controls become enabled and the human player may act + +### Requirement: Validation is enforced before applying any action +The system SHALL validate all actions against game rules before applying state changes, returning unchanged state for invalid actions. + +#### Scenario: Invalid raise is rejected +- **WHEN** a player attempts to raise below the minimum raise amount +- **THEN** the action is rejected, the state remains unchanged, and an error reason is returned + +#### Scenario: Fold always permitted +- **WHEN** a player attempts to fold during any betting round, including pre-flop +- **THEN** the action is accepted regardless of current bet level + +### Requirement: Pot distribution preserves all chips +When awarding pots to winners, the system SHALL distribute all chips without losing remainder amounts. + +#### Scenario: Odd pot split between two winners +- **WHEN** the pot contains 105 chips and two players tie for the win +- **THEN** one winner receives 53 chips, the other receives 52 chips, and zero chips are lost + +#### Scenario: Single winner receives full pot +- **WHEN** a single player wins the pot of 200 chips +- **THEN** the winner receives exactly 200 chips and the pot is reset to 0 + diff --git a/openspec/specs/side-pots/spec.md b/openspec/specs/side-pots/spec.md new file mode 100644 index 0000000..f0bc314 --- /dev/null +++ b/openspec/specs/side-pots/spec.md @@ -0,0 +1,38 @@ +# side-pots Specification + +## Purpose +TBD - created by archiving change fix-texas-holdem-rules. Update Purpose after archive. +## Requirements +### Requirement: Side pots are created when players go all-in at different bet levels +When one or more players go all-in for less than the current bet level, the system SHALL create separate side pots to ensure only eligible players can win chips they contributed to. + +#### Scenario: Single all-in creates main pot and side pot +- **WHEN** Player A bets 100, Player B goes all-in for 50, and Player C calls 100 +- **THEN** a main pot of 150 (50×3) is created with all three players eligible, and a side pot of 100 (25×4) is created with Players A and C eligible + +#### Scenario: Multiple all-ins create multiple side pots +- **WHEN** Player A bets 200, Player B goes all-in for 100, Player C goes all-in for 150, and Player D calls 200 +- **THEN** a main pot of 400 (100×4) is created with all players eligible, a side pot of 100 (50×3) is created with Players A, C, D eligible, and a side pot of 100 (50×2) is created with Players A and D eligible + +### Requirement: Side pots are awarded to eligible winners only +When determining winners for each pot, the system SHALL only award a pot to players who contributed chips to that specific pot level. + +#### Scenario: All-in player wins main pot only +- **WHEN** Player B is all-in for 50 and has the best hand at showdown, Player A and C are still active with worse hands +- **THEN** Player B receives the main pot amount proportional to their contribution, and Players A/C compete for the side pot + +#### Scenario: All-in player loses all pots +- **WHEN** Player B is all-in for 50 and has the worst hand at showdown +- **THEN** Player B receives no chips from any pot, and eligible players split the main pot proportionally + +### Requirement: Side pot calculation handles edge cases correctly +The system SHALL correctly calculate side pots when all players go all-in or when an all-in does not constitute a raise. + +#### Scenario: All players all-in creates single pot +- **WHEN** all remaining players go all-in during a betting round +- **THEN** a single main pot containing all chips is created with all players eligible, and no side pots are generated + +#### Scenario: All-in that doesn't raise does not create side pot +- **WHEN** the current bet is 100 and Player B goes all-in for 80 (less than a full raise) +- **THEN** no side pot is created; the main pot contains all chips and active players may check to continue + diff --git a/src/lib/components/BetControls.svelte b/src/lib/components/BetControls.svelte index 0f83f65..62ce3d9 100644 --- a/src/lib/components/BetControls.svelte +++ b/src/lib/components/BetControls.svelte @@ -1,13 +1,15 @@ @@ -152,7 +177,7 @@ - + {#if message} @@ -216,4 +241,4 @@ background: #27ae60; transform: translateY(-1px); } - + \ No newline at end of file