# Casino Game Development Guide

This guide explains how to build a new casino card game (like Blackjack, Poker, etc.) using CasinoKit, following the patterns established in the Baccarat app.

## Project Structure

```
YourGame/
├── YourGameApp.swift              # App entry point
├── ContentView.swift              # Root view (usually just GameTableView)
├── Engine/
│   ├── YourGameEngine.swift       # Game rules & logic
│   └── GameState.swift            # Game state machine
├── Models/
│   ├── BetType.swift              # Game-specific bet types
│   ├── GameResult.swift           # Win/loss/push outcomes
│   ├── GameSettings.swift         # User settings (can reuse pattern)
│   ├── Hand.swift                 # Hand representation (if needed)
│   └── Shoe.swift                 # Card shoe (if multi-deck)
├── Storage/
│   └── YourGameData.swift         # Persistence model (PersistableGameData)
├── Theme/
│   └── DesignConstants.swift      # Game-specific design tokens
├── Views/
│   ├── GameTableView.swift        # Main game screen
│   ├── YourTableLayoutView.swift  # Game-specific table layout
│   ├── ResultBannerView.swift     # Win/loss display
│   ├── RulesHelpView.swift        # Game rules explanation
│   ├── SettingsView.swift         # Settings screen
│   └── StatisticsSheetView.swift  # Stats display
└── Resources/
    └── Localizable.xcstrings      # Translations
```

## What CasinoKit Provides

### Core Components (Import `CasinoKit`)

| Category | Components | Usage |
|----------|------------|-------|
| **Cards** | `Card`, `Suit`, `Rank`, `Deck` | Card models |
| **Card Views** | `CardView`, `CardPlaceholderView` | Card display |
| **Chips** | `ChipDenomination`, `ChipView`, `ChipStackView`, `ChipSelectorView` | Betting chips |
| **Table** | `TableBackgroundView`, `FeltPatternView` | Casino felt background |
| **Overlays** | `GameOverView`, `ConfettiView` | Game over & celebrations |
| **Top Bar** | `TopBarView` | Balance, settings, stats buttons |
| **Badges** | `ValueBadge` | Numeric value display |
| **Settings** | `SettingsToggle`, `SpeedPicker`, `VolumePicker`, `BalancePicker` | Settings UI |
| **Sheets** | `SheetContainerView`, `SheetSection` | Modal sheets |
| **Branding** | `AppIconView`, `LaunchScreenView` | App icons & splash |
| **Audio** | `SoundManager`, `GameSound` | Sound effects & haptics |
| **Storage** | `CloudSyncManager`, `PersistableGameData` | iCloud persistence |
| **Models** | `TableLimits` | Betting limits presets |
| **Design** | `CasinoDesign` | Spacing, colors, animations |

### Using CasinoKit Components

```swift
import SwiftUI
import CasinoKit

struct GameTableView: View {
    @State private var settings = GameSettings()
    @State private var gameState: GameState?
    @State private var selectedChip: ChipDenomination = .hundred
    
    var body: some View {
        ZStack {
            // 1. Table Background (from CasinoKit)
            TableBackgroundView()
            
            VStack {
                // 2. Top Bar (from CasinoKit)
                TopBarView(
                    balance: state.balance,
                    secondaryInfo: "\(state.engine.shoe.cardsRemaining)",
                    secondaryIcon: "rectangle.portrait.on.rectangle.portrait.fill",
                    onReset: { state.resetGame() },
                    onSettings: { showSettings = true },
                    onHelp: { showRules = true },
                    onStats: { showStats = true }
                )
                
                // 3. Your Game-Specific Table Layout
                YourTableLayoutView(...)
                
                // 4. Chip Selector (from CasinoKit)
                ChipSelectorView(
                    selectedChip: $selectedChip,
                    availableChips: ChipDenomination.allCases
                )
                
                // 5. Action Buttons (game-specific)
                ActionButtonsView(...)
            }
            
            // 6. Result Banner (game-specific, but follows pattern)
            if state.showResultBanner {
                ResultBannerView(...)
            }
            
            // 7. Confetti for Wins (from CasinoKit)
            if state.lastWinnings > 0 {
                ConfettiView()
            }
            
            // 8. Game Over (from CasinoKit)
            if state.isGameOver {
                GameOverView(
                    roundsPlayed: state.roundsPlayed,
                    onPlayAgain: { state.resetGame() }
                )
            }
        }
        .sheet(isPresented: $showSettings) {
            SettingsView(settings: settings, gameState: state) { ... }
        }
    }
}
```

## Game-Specific Implementation

### 1. Game Engine (Required)

Create your game's rule engine. This handles:
- Card dealing logic
- Hand evaluation
- Win/loss determination
- Payout calculations

```swift
// Engine/YourGameEngine.swift
import CasinoKit

@Observable
@MainActor
final class YourGameEngine {
    var shoe: Shoe
    var playerHand: [Card] = []
    var dealerHand: [Card] = []
    
    init(deckCount: Int = 6) {
        self.shoe = Shoe(deckCount: deckCount)
    }
    
    func dealInitialCards() { ... }
    func evaluateHand(_ cards: [Card]) -> Int { ... }
    func determineWinner() -> GameResult { ... }
    func calculatePayout(bet: Int, result: GameResult) -> Int { ... }
}
```

### 2. Game State (Required)

Manages the state machine for your game:

```swift
// Engine/GameState.swift
import SwiftUI
import CasinoKit

enum GamePhase {
    case betting
    case dealing
    case playerTurn      // Blackjack-specific
    case dealerTurn      // Blackjack-specific
    case roundComplete
}

@Observable
@MainActor
final class GameState {
    // Core state
    var balance: Int
    var currentBets: [BetType: Int] = [:]
    var currentPhase: GamePhase = .betting
    var showResultBanner = false
    
    // Engine
    let engine: YourGameEngine
    
    // Persistence
    private let persistence: CloudSyncManager<YourGameData>
    
    // Sound
    private let sound = SoundManager.shared
    
    init(settings: GameSettings) {
        self.engine = YourGameEngine(deckCount: settings.deckCount.rawValue)
        self.balance = settings.startingBalance
        self.persistence = CloudSyncManager<YourGameData>()
        loadSavedGame()
    }
    
    func placeBet(type: BetType, amount: Int) {
        currentBets[type, default: 0] += amount
        balance -= amount
        sound.play(.chipPlace)
    }
    
    func deal() async {
        currentPhase = .dealing
        sound.play(.cardDeal)
        // Game-specific dealing logic
    }
    
    func newRound() {
        currentPhase = .betting
        showResultBanner = false
        sound.play(.newRound)
    }
}
```

### 3. Bet Types (Game-Specific)

```swift
// Models/BetType.swift

enum BetType: String, CaseIterable, Identifiable {
    // Blackjack example:
    case main = "main"
    case insurance = "insurance"
    case doubleDown = "double"
    case split = "split"
    
    // Baccarat example:
    // case player, banker, tie, playerPair, bankerPair, dragonBonusPlayer, dragonBonusBanker
    
    var id: String { rawValue }
    
    var displayName: String { ... }
    var payoutMultiplier: Double { ... }
}
```

### 4. Game Result (Game-Specific)

```swift
// Models/GameResult.swift

enum GameResult: Equatable {
    // Blackjack example:
    case playerWins
    case dealerWins
    case push
    case blackjack
    case bust
    
    var displayText: String { ... }
    var color: Color { ... }
}
```

### 5. Table Layout (Game-Specific)

This is the main visual difference between games:

```swift
// Views/YourTableLayoutView.swift

struct BlackjackTableView: View {
    // Shows dealer hand at top, player hand(s) below
    // Hit/Stand/Double/Split buttons
    // Insurance betting zone
}

struct BaccaratTableView: View {
    // Shows Player/Banker/Tie betting zones
    // Side bet zones (pairs, dragon bonus)
}

struct PokerTableView: View {
    // Community cards in center
    // Player positions around table
    // Pot display
}
```

### 6. Settings View (Mostly Reusable)

```swift
// Views/SettingsView.swift
import CasinoKit

struct SettingsView: View {
    @Bindable var settings: GameSettings
    let gameState: GameState
    
    var body: some View {
        SheetContainerView(title: "Settings") {
            // Table Limits (from CasinoKit pattern)
            SheetSection(title: "TABLE LIMITS", icon: "banknote") {
                // Use TableLimits enum from CasinoKit
            }
            
            // Deck Settings (game-specific)
            SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
                // DeckCount options
            }
            
            // Display Settings (reusable)
            SheetSection(title: "DISPLAY", icon: "eye") {
                SettingsToggle(title: "...", subtitle: "...", isOn: $settings.showX)
            }
            
            // Sound (from CasinoKit)
            SheetSection(title: "SOUND & HAPTICS", icon: "speaker.wave.2") {
                SettingsToggle(...)
                VolumePicker(volume: $settings.soundVolume)
            }
            
            // iCloud Sync (pattern from Baccarat)
            SheetSection(title: "CLOUD SYNC", icon: "icloud") { ... }
        }
    }
}
```

## Sound Integration

```swift
// In your GameState
let sound = SoundManager.shared

// Play sounds at appropriate moments:
sound.play(.chipPlace)    // When placing a bet
sound.play(.cardDeal)     // When dealing cards
sound.play(.cardFlip)     // When flipping cards
sound.play(.win)          // On player win
sound.play(.lose)         // On player loss
sound.play(.push)         // On tie/push
sound.play(.newRound)     // Starting new round
sound.play(.gameOver)     // When out of chips
```

## Persistence

```swift
// Storage/YourGameData.swift
import CasinoKit

struct BlackjackGameData: PersistableGameData {
    static let gameIdentifier = "blackjack"
    
    var roundsPlayed: Int { roundHistory.count }
    var lastModified: Date
    
    static var empty: BlackjackGameData {
        BlackjackGameData(
            lastModified: Date(),
            balance: 10_000,
            roundHistory: [],
            totalWinnings: 0,
            blackjackCount: 0,  // Game-specific stat
            bustCount: 0        // Game-specific stat
        )
    }
    
    var balance: Int
    var roundHistory: [SavedRoundResult]
    var totalWinnings: Int
    var blackjackCount: Int
    var bustCount: Int
}
```

## Design Constants

Extend CasinoDesign for game-specific values:

```swift
// Theme/DesignConstants.swift

enum Design {
    // Reuse CasinoDesign values
    typealias Spacing = CasinoDesign.Spacing
    typealias CornerRadius = CasinoDesign.CornerRadius
    typealias Animation = CasinoDesign.Animation
    
    // Game-specific sizes
    enum Size {
        static let playerCardWidth: CGFloat = 55
        static let dealerCardWidth: CGFloat = 50
        // ... game-specific dimensions
    }
    
    // Game-specific colors (extend Color)
}

extension Color {
    enum BettingZone {
        static let main = Color.blue.opacity(0.3)
        static let insurance = Color.yellow.opacity(0.3)
        // ... game-specific colors
    }
}
```

## Localization

Use String Catalogs (`.xcstrings`):

```swift
// Game-specific strings
Text(String(localized: "Hit"))
Text(String(localized: "Stand"))
Text(String(localized: "Double Down"))
Text(String(localized: "Split"))
Text(String(localized: "Blackjack!"))
Text(String(localized: "Bust!"))
```

## Responsive Layout (iPhone vs iPad)

### The Problem

On iPad, content that looks great on iPhone will **stretch to fill the screen**, making:
- Betting areas look awkward and disproportionate
- Cards appear too spread out
- Buttons become oversized
- Overlays cover the entire screen unnecessarily

### The Solution: Constrained Width Containers

Use `horizontalSizeClass` to detect iPad and constrain content width:

```swift
struct GameTableView: View {
    @Environment(\.horizontalSizeClass) private var horizontalSizeClass
    @Environment(\.verticalSizeClass) private var verticalSizeClass
    
    /// Whether we're on iPad (regular horizontal size class)
    private var isIPad: Bool {
        horizontalSizeClass == .regular
    }
    
    /// Maximum content width based on device and orientation
    private var maxContentWidth: CGFloat {
        if isIPad {
            // Landscape on iPad gets more width
            return verticalSizeClass == .compact 
                ? CasinoDesign.Size.maxContentWidthLandscape   // 800pt
                : CasinoDesign.Size.maxContentWidthPortrait    // 500pt
        }
        return .infinity  // iPhone uses full width
    }
    
    var body: some View {
        ZStack {
            TableBackgroundView()
            
            VStack {
                TopBarView(...)
                    .frame(maxWidth: maxContentWidth)
                
                // Game table - constrained on iPad
                YourTableLayoutView(...)
                    .frame(maxWidth: maxContentWidth)
                
                // Chip selector - constrained
                ChipSelectorView(...)
                    .frame(maxWidth: maxContentWidth)
                
                // Action buttons - constrained
                ActionButtonsView(...)
                    .frame(maxWidth: maxContentWidth)
            }
            .frame(maxWidth: .infinity) // Centers constrained content
            
            // Overlays - full screen background, constrained content
            if showResultBanner {
                ResultBannerView(...)
            }
        }
    }
}
```

### Overlay Pattern (Result Banner, Game Over)

Overlays need special handling: **full-screen dim background** with **constrained content card**:

```swift
struct ResultBannerView: View {
    var body: some View {
        ZStack {
            // 1. Full-screen dark background
            Color.black.opacity(0.7)
                .ignoresSafeArea()
            
            // 2. Constrained content card
            VStack {
                // Your content
            }
            .padding()
            .background(RoundedRectangle(cornerRadius: 24).fill(...))
            .frame(maxWidth: CasinoDesign.Size.maxModalWidth)  // 450pt
            // Centered automatically by ZStack
        }
    }
}
```

### Fixed-Size Containers (Prevent Layout Shifts)

When content changes (cards dealt, buttons appear/disappear), prevent jarring layout shifts:

```swift
// ❌ BAD: Container resizes as cards are added
HStack {
    ForEach(cards) { card in
        CardView(card: card)
    }
}

// ✅ GOOD: Fixed container based on max possible content
ZStack {
    // Reserve space for max cards (e.g., 3 cards with overlap)
    Color.clear
        .frame(width: calculateMaxWidth(), height: cardHeight)
    
    // Actual cards centered within
    HStack(spacing: cardSpacing) {
        ForEach(cards) { card in
            CardView(card: card)
        }
    }
}
```

Same for buttons:

```swift
// ✅ GOOD: Fixed height container for buttons
ZStack {
    Color.clear
        .frame(height: 60)  // Fixed height
    
    // Buttons animate in/out within fixed space
    if showDealButton {
        ActionButton("Deal", ...)
            .transition(.scale.combined(with: .opacity))
    }
}
.animation(.spring(duration: 0.3), value: currentPhase)
```

### Confetti Full-Screen Fix

Confetti must cover the entire screen on iPad:

```swift
struct ConfettiView: View {
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                ForEach(0..<50, id: \.self) { _ in
                    ConfettiPiece(containerSize: geometry.size)
                }
            }
        }
        .ignoresSafeArea()  // Critical!
        .allowsHitTesting(false)
    }
}
```

### Design Constants for Responsive Layout

```swift
// In CasinoDesign.swift
enum Size {
    // Max widths for iPad constraint
    static let maxContentWidthPortrait: CGFloat = 500
    static let maxContentWidthLandscape: CGFloat = 800
    static let maxModalWidth: CGFloat = 450
}
```

### Common Pitfalls

| Issue | Symptom | Fix |
|-------|---------|-----|
| Stretched table | Betting zones look huge on iPad | Add `.frame(maxWidth: maxContentWidth)` |
| Overlay too wide | Result banner covers entire iPad screen | Use full-screen bg + constrained content card |
| Layout shifts | Cards/buttons cause content to jump | Use fixed-size `ZStack` containers |
| Confetti cut off | Only shows in center portion | Use `GeometryReader` + `.ignoresSafeArea()` |
| Settings rows cramped | Title/subtitle too close to divider | Add `.padding(.vertical, ...)` |

### Testing Checklist

- [ ] iPhone SE (smallest)
- [ ] iPhone Pro Max (largest iPhone)
- [ ] iPad Portrait
- [ ] iPad Landscape
- [ ] iPad Split View
- [ ] Dynamic Type at maximum accessibility size

## Checklist for New Game

### Setup
- [ ] Create new target in Xcode
- [ ] Add CasinoKit as dependency
- [ ] Copy `DesignConstants.swift` and customize
- [ ] Create `Localizable.xcstrings`

### Models
- [ ] Define `BetType` enum
- [ ] Define `GameResult` enum
- [ ] Create `YourGameData` for persistence
- [ ] Create `GameSettings` (or reuse pattern)

### Engine
- [ ] Implement game rules in `YourGameEngine`
- [ ] Implement `GameState` with phases

### Views
- [ ] Create `GameTableView` (main container)
- [ ] Create game-specific table layout
- [ ] Create `ResultBannerView` (follow pattern)
- [ ] Create `RulesHelpView` (game rules)
- [ ] Customize `SettingsView`
- [ ] Create `StatisticsSheetView`

### Integration
- [ ] Wire up `SoundManager` for game events
- [ ] Implement `CloudSyncManager` for persistence
- [ ] Add accessibility labels
- [ ] Add localization for all strings

### Polish
- [ ] Test Dynamic Type scaling (all sizes including accessibility)
- [ ] Test VoiceOver navigation
- [ ] Create app icon using `AppIconView`

### Responsive Layout (iPad)
- [ ] Add `maxContentWidth` constraint to main views
- [ ] Test iPad Portrait - content centered, not stretched
- [ ] Test iPad Landscape - wider constraint works well
- [ ] Verify overlays: full-screen bg, constrained content
- [ ] Fixed-size containers prevent layout shifts
- [ ] Confetti covers full screen
- [ ] Settings rows have proper padding

## What Could Be Added to CasinoKit

The following patterns from Baccarat could be abstracted:

1. **Generic ResultBannerView** - Win/loss display with bet breakdown
2. **BettingZone protocol** - Common betting zone behavior
3. **GameStateProtocol** - Common state machine patterns
4. **HandDisplayView** - Generic card hand display
5. **ActionButtonsView** - Deal/Clear/New Round pattern
6. **StatisticsView** - Generic stats display

---

*This guide is based on the Baccarat implementation. For reference, see the Baccarat app structure and CasinoKit source code.*

