fix: resolve settings persistence, timer reset, and card reveal bugs
This commit is contained in:
parent
422fa5b3ab
commit
c7542679e7
@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-05-17
|
||||||
32
openspec/changes/archive/2026-05-17-fix-game-bugs/design.md
Normal file
32
openspec/changes/archive/2026-05-17-fix-game-bugs/design.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
## Context
|
||||||
|
|
||||||
|
The game page (`+page.svelte`) hardcodes the `trainingMix` preset at initialization. The setup page saves user-selected config to `sessionStorage`, but nothing reads it. The bot timer uses a `$effect` that re-runs on every reactive update, resetting the countdown. Card visibility only checks `isHuman`, ignoring poker rules for showdown and all-in reveals.
|
||||||
|
|
||||||
|
## Goals / Non-Goals
|
||||||
|
|
||||||
|
**Goals:**
|
||||||
|
- Settings from `/setup` page apply to the game session
|
||||||
|
- Bot countdown timer decrements correctly (13→12→11...→0)
|
||||||
|
- Opponent cards reveal at showdown and when all-in with no further betting
|
||||||
|
|
||||||
|
**Non-Goals:**
|
||||||
|
- Persistent config across browser sessions (localStorage/database)
|
||||||
|
- Animated card reveals or showmanship
|
||||||
|
- Multi-player online gameplay
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
### Settings via sessionStorage
|
||||||
|
Using `sessionStorage` (already in use by setup page) rather than URL params or a state store. It's simple, survives the navigation redirect, and clears on tab close — appropriate for a single-session training game.
|
||||||
|
|
||||||
|
### Timer fix: $derived.timeRemaining with setInterval outside effect
|
||||||
|
The `$effect` will only anchor on `active` and `duration`, not `remaining`. The interval will update `remaining` directly. This breaks the reactivity cycle since the effect won't re-run when `remaining` changes.
|
||||||
|
|
||||||
|
### Card reveal: computed function in PokerTable, passed to PlayerSeat
|
||||||
|
Rather than passing the entire GameState to each PlayerSeat (which it already has via player prop), we add a `revealCards` boolean prop. PokerTable computes this based on betting round, player status, and all-in conditions.
|
||||||
|
|
||||||
|
## Risks / Trade-offs
|
||||||
|
|
||||||
|
[sessionStorage read fails] → Parse with fallback to default preset
|
||||||
|
[Timer still flickers in dev mode] → Svelte 5 fine-grained reactivity may need `$state.raw` for the counter
|
||||||
|
[All-in reveal timing edge cases] → The "last betting round complete" check requires knowing if any active player can still bet — we approximate by checking if all non-folded players are all-in or matched
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
## Why
|
||||||
|
|
||||||
|
Three bugs prevent proper gameplay: table settings from the setup page are ignored by the game, the bot decision timer resets incorrectly causing wrong countdowns, and opponent hand cards never reveal during showdown or all-in situations per poker rules.
|
||||||
|
|
||||||
|
## What Changes
|
||||||
|
|
||||||
|
- **Settings persistence**: Read `sessionStorage` config in the main game page and apply it to initialize game state instead of hardcoding the `trainingMix` preset
|
||||||
|
- **Bot timer fix**: Break the reactivity loop in `DecisionTimer.svelte` so the countdown decrements correctly without being reset by `$effect` re-runs
|
||||||
|
- **Card reveal rules**: Implement proper poker card visibility — show opponent hands at showdown, and when all-in players can no longer bet
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
### New Capabilities
|
||||||
|
- `game-config-persistence`: Loading table configuration from setup page into the game session
|
||||||
|
- `card-reveal-rules`: Showing opponent hole cards according to poker showdown and all-in rules
|
||||||
|
|
||||||
|
### Modified Capabilities
|
||||||
|
- `decision-timer`: Fixing countdown reset behavior so timer decrements correctly per second
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- `src/routes/+page.svelte` — read sessionStorage config, apply to game state
|
||||||
|
- `src/lib/components/DecisionTimer.svelte` — fix reactivity loop
|
||||||
|
- `src/lib/components/PlayerSeat.svelte` — add card reveal logic
|
||||||
|
- `src/lib/components/PokerTable.svelte` — pass reveal context to PlayerSeat
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Showdown reveals all remaining player cards
|
||||||
|
When the betting round reaches `showdown`, the hole cards of all players who have not folded SHALL be visible to the human player.
|
||||||
|
|
||||||
|
#### Scenario: Normal showdown with multiple players
|
||||||
|
- **WHEN** `bettingRound` is `showdown` and two or more players are still active or all-in
|
||||||
|
- **THEN** all non-folded players' hole cards are revealed on the table
|
||||||
|
|
||||||
|
### Requirement: All-in player cards reveal when betting ends
|
||||||
|
When a player goes all-in and no further betting is possible among the remaining active players, the all-in player's hole cards SHALL be revealed.
|
||||||
|
|
||||||
|
#### Scenario: All-in with no further bets possible
|
||||||
|
- **WHEN** a player is `all-in` and all other active players have matched their bet (no more raises or calls possible)
|
||||||
|
- **THEN** the all-in player's hole cards become visible
|
||||||
|
|
||||||
|
### Requirement: Folded player cards remain hidden
|
||||||
|
Players who have folded SHALL never have their hole cards revealed, regardless of game state.
|
||||||
|
|
||||||
|
#### Scenario: Folded player at showdown
|
||||||
|
- **WHEN** a player has `status === 'folded'` and the hand reaches showdown
|
||||||
|
- **THEN** the folded player's cards remain face-down (mucked)
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Timer decrements correctly without resetting
|
||||||
|
The decision timer SHALL count down from the configured duration to zero, decrementing exactly once per second, without being reset by reactive effect re-runs.
|
||||||
|
|
||||||
|
#### Scenario: Normal countdown
|
||||||
|
- **WHEN** a timer is activated with `duration` of 10 seconds
|
||||||
|
- **THEN** the displayed remaining time follows the sequence: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 — each value displayed for approximately one second
|
||||||
|
|
||||||
|
#### Scenario: Timer does not reset mid-countdown
|
||||||
|
- **WHEN** the timer is counting down and a reactive state change occurs in the component
|
||||||
|
- **THEN** the countdown continues without being interrupted or reset to the initial duration
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Game reads setup config from sessionStorage
|
||||||
|
When the game page loads, it SHALL read the configuration stored in `sessionStorage` under the key `poker-config` and apply those settings to initialize the game state.
|
||||||
|
|
||||||
|
#### Scenario: Config exists in sessionStorage
|
||||||
|
- **WHEN** a user navigates from `/setup` after configuring their table
|
||||||
|
- **THEN** the game page reads `sessionStorage.getItem('poker-config')` and uses the parsed values to create the initial game state
|
||||||
|
|
||||||
|
#### Scenario: No config exists (first visit)
|
||||||
|
- **WHEN** a user navigates directly to `/` without visiting `/setup`
|
||||||
|
- **THEN** the game falls back to the default `trainingMix` preset
|
||||||
|
|
||||||
|
### Requirement: All setup parameters are applied
|
||||||
|
The parsed configuration SHALL include values for `numPlayers`, `startingStack`, `smallBlind`, `bigBlind`, `infoLevel`, `feedbackLevel`, `timerEnabled`, `timerDuration`, `humanTimerMode`, and `humanTimerDuration`.
|
||||||
|
|
||||||
|
#### Scenario: Custom settings are preserved
|
||||||
|
- **WHEN** a user sets small blind to 50, big blind to 100, and starting stack to 5000
|
||||||
|
- **THEN** the initialized game state reflects those exact values in `smallBlind`, `bigBlind`, and player chip counts
|
||||||
19
openspec/changes/archive/2026-05-17-fix-game-bugs/tasks.md
Normal file
19
openspec/changes/archive/2026-05-17-fix-game-bugs/tasks.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## 1. Settings Persistence
|
||||||
|
|
||||||
|
- [x] 1.1 Add sessionStorage read logic in `+page.svelte` to parse `poker-config` on mount
|
||||||
|
- [x] 1.2 Apply parsed config values to `createInitialState` instead of hardcoding `trainingMix` preset
|
||||||
|
- [x] 1.3 Add fallback to `trainingMix` preset when no config exists or parsing fails
|
||||||
|
|
||||||
|
## 2. Bot Timer Fix
|
||||||
|
|
||||||
|
- [x] 2.1 Refactor `DecisionTimer.svelte` $effect to anchor only on `active` and `duration`, not `remaining`
|
||||||
|
- [x] 2.2 Ensure interval callback updates `remaining` without triggering effect re-run
|
||||||
|
- [x] 2.3 Verify countdown follows exact sequence (e.g., 10→9→8→...→0) without resets
|
||||||
|
|
||||||
|
## 3. Card Reveal Rules
|
||||||
|
|
||||||
|
- [x] 3.1 Create `shouldRevealCards(player, gameState)` helper function in PokerTable or utility module
|
||||||
|
- [x] 3.2 Implement showdown reveal: show cards when `bettingRound === 'showdown'` and player hasn't folded
|
||||||
|
- [x] 3.3 Implement all-in reveal: detect when betting is no longer possible and all-in players should show
|
||||||
|
- [x] 3.4 Pass `revealCards` boolean prop from PokerTable to each PlayerSeat
|
||||||
|
- [x] 3.5 Update PlayerSeat to use `revealCards` prop instead of current `isHuman || holeCards.length === 0` check
|
||||||
27
openspec/specs/card-reveal-rules/spec.md
Normal file
27
openspec/specs/card-reveal-rules/spec.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Card Reveal Rules
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Defines when hole cards are revealed during gameplay, ensuring proper showdown and all-in card visibility while keeping folded cards hidden.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: Showdown reveals all remaining player cards
|
||||||
|
When the betting round reaches `showdown`, the hole cards of all players who have not folded SHALL be visible to the human player.
|
||||||
|
|
||||||
|
#### Scenario: Normal showdown with multiple players
|
||||||
|
- **WHEN** `bettingRound` is `showdown` and two or more players are still active or all-in
|
||||||
|
- **THEN** all non-folded players' hole cards are revealed on the table
|
||||||
|
|
||||||
|
### Requirement: All-in player cards reveal when betting ends
|
||||||
|
When a player goes all-in and no further betting is possible among the remaining active players, the all-in player's hole cards SHALL be revealed.
|
||||||
|
|
||||||
|
#### Scenario: All-in with no further bets possible
|
||||||
|
- **WHEN** a player is `all-in` and all other active players have matched their bet (no more raises or calls possible)
|
||||||
|
- **THEN** the all-in player's hole cards become visible
|
||||||
|
|
||||||
|
### Requirement: Folded player cards remain hidden
|
||||||
|
Players who have folded SHALL never have their hole cards revealed, regardless of game state.
|
||||||
|
|
||||||
|
#### Scenario: Folded player at showdown
|
||||||
|
- **WHEN** a player has `status === 'folded'` and the hand reaches showdown
|
||||||
|
- **THEN** the folded player's cards remain face-down (mucked)
|
||||||
@ -48,3 +48,14 @@ The system SHALL record the decision time (in seconds) for every action taken by
|
|||||||
#### Scenario: Slow hesitation is recorded
|
#### Scenario: Slow hesitation is recorded
|
||||||
- **WHEN** Bot #5 takes 8.2 seconds to decide before folding
|
- **WHEN** Bot #5 takes 8.2 seconds to decide before folding
|
||||||
- **THEN** the action history records decision_time: 8.2 for that fold
|
- **THEN** the action history records decision_time: 8.2 for that fold
|
||||||
|
|
||||||
|
### Requirement: Timer decrements correctly without resetting
|
||||||
|
The decision timer SHALL count down from the configured duration to zero, decrementing exactly once per second, without being reset by reactive effect re-runs.
|
||||||
|
|
||||||
|
#### Scenario: Normal countdown
|
||||||
|
- **WHEN** a timer is activated with `duration` of 10 seconds
|
||||||
|
- **THEN** the displayed remaining time follows the sequence: 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 — each value displayed for approximately one second
|
||||||
|
|
||||||
|
#### Scenario: Timer does not reset mid-countdown
|
||||||
|
- **WHEN** the timer is counting down and a reactive state change occurs in the component
|
||||||
|
- **THEN** the countdown continues without being interrupted or reset to the initial duration
|
||||||
|
|||||||
24
openspec/specs/game-config-persistence/spec.md
Normal file
24
openspec/specs/game-config-persistence/spec.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Game Config Persistence
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Ensures table setup configuration is persisted and restored when the game page loads, so user preferences from `/setup` are applied to the game session.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: Game reads setup config from sessionStorage
|
||||||
|
When the game page loads, it SHALL read the configuration stored in `sessionStorage` under the key `poker-config` and apply those settings to initialize the game state.
|
||||||
|
|
||||||
|
#### Scenario: Config exists in sessionStorage
|
||||||
|
- **WHEN** a user navigates from `/setup` after configuring their table
|
||||||
|
- **THEN** the game page reads `sessionStorage.getItem('poker-config')` and uses the parsed values to create the initial game state
|
||||||
|
|
||||||
|
#### Scenario: No config exists (first visit)
|
||||||
|
- **WHEN** a user navigates directly to `/` without visiting `/setup`
|
||||||
|
- **THEN** the game falls back to the default `trainingMix` preset
|
||||||
|
|
||||||
|
### Requirement: All setup parameters are applied
|
||||||
|
The parsed configuration SHALL include values for `numPlayers`, `startingStack`, `smallBlind`, `bigBlind`, `infoLevel`, `feedbackLevel`, `timerEnabled`, `timerDuration`, `humanTimerMode`, and `humanTimerDuration`.
|
||||||
|
|
||||||
|
#### Scenario: Custom settings are preserved
|
||||||
|
- **WHEN** a user sets small blind to 50, big blind to 100, and starting stack to 5000
|
||||||
|
- **THEN** the initialized game state reflects those exact values in `smallBlind`, `bigBlind`, and player chip counts
|
||||||
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
let remaining = $state(0);
|
let remaining = $state(0);
|
||||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||||
|
let tick = $state(0);
|
||||||
|
|
||||||
const percent = $derived(duration > 0 ? (remaining / duration) * 100 : 0);
|
const percent = $derived(duration > 0 ? (remaining / duration) * 100 : 0);
|
||||||
const warning = $derived(remaining <= 3 && active);
|
const warning = $derived(remaining <= 3 && active);
|
||||||
@ -26,18 +27,15 @@
|
|||||||
remaining = duration;
|
remaining = duration;
|
||||||
intervalId = setInterval(() => {
|
intervalId = setInterval(() => {
|
||||||
remaining--;
|
remaining--;
|
||||||
|
tick++;
|
||||||
if (remaining <= 0) {
|
if (remaining <= 0) {
|
||||||
if (intervalId) {
|
if (intervalId) clearInterval(intervalId);
|
||||||
clearInterval(intervalId);
|
|
||||||
intervalId = null;
|
|
||||||
}
|
|
||||||
onTimeout?.();
|
onTimeout?.();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
if (intervalId) {
|
if (intervalId) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
intervalId = null;
|
|
||||||
}
|
}
|
||||||
remaining = 0;
|
remaining = 0;
|
||||||
}
|
}
|
||||||
@ -45,7 +43,6 @@
|
|||||||
return () => {
|
return () => {
|
||||||
if (intervalId) {
|
if (intervalId) {
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
intervalId = null;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
import Card from './Card.svelte';
|
import Card from './Card.svelte';
|
||||||
import type { PlayerSeat as PlayerSeatType } from '$lib/types/player';
|
import type { PlayerSeat as PlayerSeatType } from '$lib/types/player';
|
||||||
|
|
||||||
let { player, isCurrentTurn = false, isHuman = false }: {
|
let { player, isCurrentTurn = false, isHuman = false, revealCards = false }: {
|
||||||
player: PlayerSeatType;
|
player: PlayerSeatType;
|
||||||
isCurrentTurn?: boolean;
|
isCurrentTurn?: boolean;
|
||||||
isHuman?: boolean;
|
isHuman?: boolean;
|
||||||
|
revealCards?: boolean;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const showCards = $derived(isHuman || player.holeCards.length === 0);
|
const showCards = $derived(isHuman || revealCards || player.holeCards.length === 0);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="seat" class:active={isCurrentTurn} class:folded={player.status === 'folded'}>
|
<div class="seat" class:active={isCurrentTurn} class:folded={player.status === 'folded'}>
|
||||||
|
|||||||
@ -3,11 +3,26 @@
|
|||||||
import PlayerSeat from './PlayerSeat.svelte';
|
import PlayerSeat from './PlayerSeat.svelte';
|
||||||
import OpponentInfo from './OpponentInfo.svelte';
|
import OpponentInfo from './OpponentInfo.svelte';
|
||||||
import type { GameState } from '$lib/types/game-state';
|
import type { GameState } from '$lib/types/game-state';
|
||||||
|
import type { PlayerSeat as PlayerSeatType } from '$lib/types/player';
|
||||||
|
|
||||||
let { gameState }: {
|
let { gameState }: {
|
||||||
gameState: GameState;
|
gameState: GameState;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
|
function shouldRevealCards(player: PlayerSeatType): boolean {
|
||||||
|
if (player.status === 'folded') return false;
|
||||||
|
if (gameState.bettingRound === 'showdown') return true;
|
||||||
|
if (player.status === 'all-in') {
|
||||||
|
const nonFoldedOpponents = gameState.players.filter(
|
||||||
|
p => p.id !== player.id && p.status !== 'folded'
|
||||||
|
);
|
||||||
|
const activeOpponents = nonFoldedOpponents.filter(p => p.status === 'active');
|
||||||
|
if (activeOpponents.length === 0) return true;
|
||||||
|
if (nonFoldedOpponents.length <= 1) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const seatPositions = [
|
const seatPositions = [
|
||||||
{ label: 'seat-6', row: 1, col: 3 },
|
{ label: 'seat-6', row: 1, col: 3 },
|
||||||
{ label: 'seat-5', row: 2, col: 1 },
|
{ label: 'seat-5', row: 2, col: 1 },
|
||||||
@ -36,7 +51,7 @@
|
|||||||
<div class="pot-display">Pot: ${gameState.pot}</div>
|
<div class="pot-display">Pot: ${gameState.pot}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each seatPositions as pos, i}
|
{#each seatPositions as pos, i (pos.label)}
|
||||||
{@const player = getPlayerAtPosition(i)}
|
{@const player = getPlayerAtPosition(i)}
|
||||||
{#if player}
|
{#if player}
|
||||||
<div class="seat-wrapper" style="grid-row: {pos.row}; grid-column: {pos.col};">
|
<div class="seat-wrapper" style="grid-row: {pos.row}; grid-column: {pos.col};">
|
||||||
@ -44,6 +59,7 @@
|
|||||||
{player}
|
{player}
|
||||||
isCurrentTurn={gameState.currentTurn === i}
|
isCurrentTurn={gameState.currentTurn === i}
|
||||||
isHuman={i === 0}
|
isHuman={i === 0}
|
||||||
|
revealCards={shouldRevealCards(player)}
|
||||||
/>
|
/>
|
||||||
{#if gameState.dealerPosition === i}
|
{#if gameState.dealerPosition === i}
|
||||||
<div class="dealer-button" aria-label="Dealer">D</div>
|
<div class="dealer-button" aria-label="Dealer">D</div>
|
||||||
|
|||||||
@ -17,25 +17,59 @@
|
|||||||
import { generatePostHandAnalysis, detectReadConfirmation } from '$lib/game/teaching-coach';
|
import { generatePostHandAnalysis, detectReadConfirmation } from '$lib/game/teaching-coach';
|
||||||
import { applyPreset, PRESETS } from '$lib/game/table-presets';
|
import { applyPreset, PRESETS } from '$lib/game/table-presets';
|
||||||
|
|
||||||
import type { GameState } from '$lib/types/game-state';
|
import type { GameState, InfoLevel, FeedbackLevel } from '$lib/types/game-state';
|
||||||
import type { PostHandAnalysis as PostHandAnalysisType } from '$lib/game/teaching-coach';
|
import type { PostHandAnalysis as PostHandAnalysisType } from '$lib/game/teaching-coach';
|
||||||
import type { BotArchetype } from '$lib/types/bot-archetype';
|
import type { BotArchetype } from '$lib/types/bot-archetype';
|
||||||
import type { TableSetupConfig } from '$lib/game/table-presets';
|
import type { TablePreset, TableSetupConfig } from '$lib/game/table-presets';
|
||||||
|
|
||||||
// Initialize with training mix preset
|
// Load config from sessionStorage or fall back to trainingMix preset
|
||||||
const preset = applyPreset('trainingMix');
|
let loadedPreset: TablePreset = 'trainingMix';
|
||||||
let baseState = createInitialState(preset.numPlayers, preset.startingStack, {
|
let loadedNumPlayers = PRESETS.trainingMix.numPlayers;
|
||||||
infoLevel: preset.infoLevel,
|
let loadedStartingStack = PRESETS.trainingMix.startingStack;
|
||||||
feedbackLevel: preset.feedbackLevel,
|
let loadedSmallBlind = PRESETS.trainingMix.smallBlind;
|
||||||
timerEnabled: preset.timerEnabled,
|
let loadedBigBlind = PRESETS.trainingMix.bigBlind;
|
||||||
timerDuration: preset.timerDuration,
|
let loadedInfoLevel: InfoLevel = PRESETS.trainingMix.infoLevel;
|
||||||
humanTimerMode: preset.humanTimerMode,
|
let loadedFeedbackLevel: FeedbackLevel = PRESETS.trainingMix.feedbackLevel;
|
||||||
humanTimerDuration: preset.humanTimerDuration
|
let loadedTimerEnabled = PRESETS.trainingMix.timerEnabled;
|
||||||
|
let loadedTimerDuration = PRESETS.trainingMix.timerDuration;
|
||||||
|
let loadedHumanTimerMode: 'same' | 'noLimit' | 'custom' = PRESETS.trainingMix.humanTimerMode;
|
||||||
|
let loadedHumanTimerDuration = PRESETS.trainingMix.humanTimerDuration;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stored = sessionStorage.getItem('poker-config');
|
||||||
|
if (stored) {
|
||||||
|
const config = JSON.parse(stored);
|
||||||
|
loadedPreset = config.preset || 'trainingMix';
|
||||||
|
loadedNumPlayers = config.numPlayers ?? loadedNumPlayers;
|
||||||
|
loadedStartingStack = config.startingStack ?? loadedStartingStack;
|
||||||
|
loadedSmallBlind = config.smallBlind ?? loadedSmallBlind;
|
||||||
|
loadedBigBlind = config.bigBlind ?? loadedBigBlind;
|
||||||
|
loadedInfoLevel = config.infoLevel ?? loadedInfoLevel;
|
||||||
|
loadedFeedbackLevel = config.feedbackLevel ?? loadedFeedbackLevel;
|
||||||
|
loadedTimerEnabled = config.timerEnabled ?? loadedTimerEnabled;
|
||||||
|
loadedTimerDuration = config.timerDuration ?? loadedTimerDuration;
|
||||||
|
loadedHumanTimerMode = config.humanTimerMode ?? loadedHumanTimerMode;
|
||||||
|
loadedHumanTimerDuration = config.humanTimerDuration ?? loadedHumanTimerDuration;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Parsing failed, use defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
const preset = applyPreset(loadedPreset, loadedNumPlayers);
|
||||||
|
let baseState = createInitialState(loadedNumPlayers, loadedStartingStack, {
|
||||||
|
infoLevel: loadedInfoLevel,
|
||||||
|
feedbackLevel: loadedFeedbackLevel,
|
||||||
|
timerEnabled: loadedTimerEnabled,
|
||||||
|
timerDuration: loadedTimerDuration,
|
||||||
|
humanTimerMode: loadedHumanTimerMode,
|
||||||
|
humanTimerDuration: loadedHumanTimerDuration
|
||||||
});
|
});
|
||||||
|
baseState.smallBlind = loadedSmallBlind;
|
||||||
|
baseState.bigBlind = loadedBigBlind;
|
||||||
|
|
||||||
// Apply bot personalities from preset
|
// Apply bot personalities from preset
|
||||||
const seatConfigs = PRESETS.trainingMix.seatConfigs;
|
const seatConfigs = PRESETS[loadedPreset].seatConfigs;
|
||||||
for (let i = 0; i < preset.numPlayers - 1 && i < seatConfigs.length; i++) {
|
for (let i = 0; i < loadedNumPlayers - 1 && i < seatConfigs.length; i++) {
|
||||||
baseState.players[i + 1]!.personality = seatConfigs[i].archetype;
|
baseState.players[i + 1]!.personality = seatConfigs[i].archetype;
|
||||||
baseState.players[i + 1]!.skillLevel = seatConfigs[i].skillLevel;
|
baseState.players[i + 1]!.skillLevel = seatConfigs[i].skillLevel;
|
||||||
}
|
}
|
||||||
@ -317,10 +351,10 @@
|
|||||||
|
|
||||||
<PokerTable {gameState} />
|
<PokerTable {gameState} />
|
||||||
|
|
||||||
{#if aiActing && timerActive}
|
{#if gameState.tableConfig.timerEnabled}
|
||||||
<DecisionTimer
|
<DecisionTimer
|
||||||
duration={gameState.tableConfig.timerDuration}
|
duration={gameState.tableConfig.timerDuration}
|
||||||
active={timerActive}
|
active={aiActing && timerActive}
|
||||||
playerName={currentTimerPlayer}
|
playerName={currentTimerPlayer}
|
||||||
archetype={currentTimerArchetype || undefined}
|
archetype={currentTimerArchetype || undefined}
|
||||||
onTimeout={handleTimerTimeout}
|
onTimeout={handleTimerTimeout}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user