# CasinoKit

A reusable Swift Package for building casino card games with SwiftUI. This package provides common components, themes, and utilities shared across casino game apps.

## Requirements

- iOS 17.0+
- Swift 6.0+
- Xcode 16.0+

## Installation

This package is included as a local package in the workspace. To use in another project:

1. Copy the `CasinoKit` folder to your project
2. In Xcode: File → Add Package Dependencies → Add Local
3. Select the `CasinoKit` folder

## Features

### 🎴 Cards

**CardView** - A playing card with flip animation support.

```swift
import CasinoKit

// Face-up card
CardView(card: Card(suit: .hearts, rank: .ace), faceUp: true)

// Face-down card
CardView(card: card, faceUp: false)

// Empty placeholder (dotted outline)
CardPlaceholderView()
```

**Card Model**
```swift
let card = Card(suit: .spades, rank: .king)
print(card.displayValue)  // "K"
print(card.suit.symbol)   // "♠"
```

### 🎰 Chips

**ChipView** - A casino chip with denomination display.

```swift
ChipView(denomination: .hundred, size: 60, isSelected: true)
```

**ChipSelectorView** - Horizontal chip selector.

```swift
@State var selectedChip: ChipDenomination = .hundred

ChipSelectorView(
    denominations: ChipDenomination.allCases,
    selectedDenomination: $selectedChip
)
```

**ChipStackView** - Stacked chips showing bet amount.

```swift
ChipStackView(amount: 500, chipColor: .red)
```

**Chip Denominations** (with standard casino colors)
- `.one` ($1) - White/Light Blue
- `.five` ($5) - Red
- `.twentyFive` ($25) - Green
- `.hundred` ($100) - Black
- `.fiveHundred` ($500) - Purple
- `.thousand` ($1,000) - Yellow
- `.fiveThousand` ($5,000) - Brown
- `.tenThousand` ($10,000) - Gray (custom)
- `.twentyFiveThousand` ($25,000) - Teal (plaque style)
- `.hundredThousand` ($100,000) - Burgundy (VIP)

### 📋 Sheets & Popups

**SheetContainerView** - Consistent modal sheet styling.

```swift
SheetContainerView(
    title: "Settings",
    content: {
        SheetSection(title: "DISPLAY", icon: "eye") {
            Toggle("Dark Mode", isOn: $darkMode)
        }
        
        SheetSection(title: "SOUND", icon: "speaker.wave.2") {
            Slider(value: $volume)
        }
    },
    onCancel: { dismiss() },
    onDone: { save(); dismiss() },
    doneButtonText: String(localized: "Done"),
    cancelButtonText: String(localized: "Cancel")
)
```

**SheetSection** - Styled section within sheets.

```swift
SheetSection(title: "SECTION TITLE", icon: "star.fill") {
    // Your content
}
```

### 🎓 Onboarding & Tutorials

**WelcomeSheet** - First-launch welcome screen with features list.

```swift
WelcomeSheet(
    gameName: "Blackjack",
    features: [
        WelcomeFeature(
            icon: "target",
            title: "Beat the Dealer",
            description: "Get closer to 21 than the dealer without going over"
        ),
        WelcomeFeature(
            icon: "lightbulb.fill",
            title: "Learn Strategy",
            description: "Built-in hints show optimal plays"
        )
    ],
    onStartTutorial: {
        // Enable tutorial mode
        gameState.onboarding.startTutorialMode()
    },
    onStartPlaying: {
        // Skip to game
        gameState.onboarding.completeWelcome()
    }
)
```

**ContextualTooltip** - Show hints at the right moment.

```swift
@State private var showBettingHint = false

var body: some View {
    BettingZone(/* ... */)
        .contextualTooltip(
            "Tap chips, then tap here to bet",
            icon: "hand.tap.fill",
            position: .bottom,
            isShowing: $showBettingHint,
            onDismiss: {
                gameState.onboarding.markHintShown("bettingZone")
            }
        )
}
```

**OnboardingState** - Track which hints have been shown.

```swift
let onboarding = OnboardingState(gameIdentifier: "blackjack")

// Check if a hint should be shown
if onboarding.shouldShowHint("bettingZone") {
    showBettingHint = true
}

// Mark hint as shown (persisted automatically)
onboarding.markHintShown("bettingZone")

// Check if user has seen welcome
if !onboarding.hasCompletedWelcome {
    showWelcome = true
}

// Enable tutorial mode (shows all hints again)
onboarding.startTutorialMode()

// Reset onboarding (for testing)
onboarding.reset()
```

**TooltipManager** - Generic, scalable tooltip management (recommended).

```swift
@State private var tooltipManager: TooltipManager?

var body: some View {
    GameView()
        .onAppear {
            tooltipManager = TooltipManager(onboarding: gameState.onboarding)
        }
        .dynamicTooltip(tooltipManager ?? TooltipManager(onboarding: gameState.onboarding))
}

// Show a tooltip with automatic dismiss tracking
private func showBettingHint() {
    tooltipManager?.show(
        key: "bettingZone",
        message: "Select a chip and tap here to bet",
        icon: "hand.tap.fill",
        position: .bottom,
        delay: 1.0
    )
}

// Dismiss current tooltip manually
tooltipManager?.dismiss()
```

The `TooltipManager` automatically:
- Ensures only one tooltip shows at a time
- Marks tooltips as shown when dismissed or replaced
- Respects `OnboardingState.shouldShowHint()` checks
- Handles delayed presentation with cancellation if needed

**PulsingModifier** - Draw attention to interactive elements.

```swift
Button("Deal", action: deal)
    .pulsing(
        isActive: shouldHighlight,
        color: .white,
        scale: 1.3
    )
```

### 🎲 Game Table Components

**TableBackgroundView** - Casino felt background with pattern.

```swift
TableBackgroundView()  // Default casino green
TableBackgroundView(feltColor: .blue, edgeColor: .darkBlue)  // Custom
```

**TopBarView** - Balance display and toolbar buttons.

```swift
TopBarView(
    balance: 10_500,
    secondaryInfo: "411",
    secondaryIcon: "rectangle.portrait.on.rectangle.portrait.fill",
    onSettings: { showSettings = true },
    onHelp: { showRules = true },
    onStats: { showStats = true }
)
```

**TopBarButton** - Add custom buttons to the toolbar.

```swift
// Add game-specific buttons at the front of the toolbar
TopBarView(
    balance: 10_500,
    leadingButtons: [
        TopBarButton(icon: "arrow.counterclockwise", accessibilityLabel: "Reset Game") {
            resetGame()
        },
        TopBarButton(icon: "creditcard", accessibilityLabel: "Buy Chips") {
            showBuyChips = true
        }
    ],
    onSettings: { showSettings = true },
    onHelp: { showRules = true },
    onStats: { showStats = true }
)
```

**HandDisplayView** - Generic card hand display with labels.

```swift
HandDisplayView(
    cards: [card1, card2],
    cardsFaceUp: [true, true],
    isWinner: true,
    label: "PLAYER",
    value: 21,
    valueColor: .blue,
    maxCards: 5  // Reserve space for up to 5 cards
)
```

**BettingZone** - Tappable betting area with chip badge.

```swift
BettingZone(
    label: "PLAYER",
    payoutInfo: "1:1",
    betAmount: 500,
    backgroundColor: .blue.opacity(0.2),
    onTap: { placeBet(.player) }
)
```

**ActionButton** - Styled action buttons.

```swift
ActionButton("Deal", icon: "play.fill", style: .primary) { deal() }
ActionButton("Clear", icon: "xmark.circle", style: .destructive) { clear() }
ActionButton("Stand", style: .secondary, isEnabled: canStand) { stand() }
```

### 🎉 Effects & Overlays

**ConfettiView** - Win celebration effect.

```swift
if playerWon {
    ConfettiView()  // Colorful falling confetti
    ConfettiView(colors: [.yellow, .gold], count: 30)  // Customized
}
```

**GameOverView** - Game over modal when out of chips.

```swift
GameOverView(
    roundsPlayed: 25,
    additionalStats: [
        ("Biggest Win", "$5,000"),
        ("Blackjacks", "3")
    ],
    onPlayAgain: { resetGame() }
)
```

**ValueBadge** - Circular numeric badge.

```swift
ValueBadge(value: 21, color: .blue)  // Hand value display
```

### ⚙️ Settings Components

**SettingsToggle** - Toggle with title and subtitle.

```swift
SettingsToggle(
    title: "Sound Effects",
    subtitle: "Chips, cards, and results",
    isOn: $settings.soundEnabled
)
```

**SpeedPicker** - Fast/Normal/Slow animation speed.

```swift
SpeedPicker(speed: $settings.dealingSpeed)
```

**VolumePicker** - Volume slider with icons.

```swift
VolumePicker(volume: $settings.soundVolume)
```

**BalancePicker** - Starting balance grid.

```swift
BalancePicker(
    balance: $settings.startingBalance,
    options: [1_000, 5_000, 10_000, 25_000, 50_000, 100_000]
)
```

**TableLimits** - Betting limit presets.

```swift
let limits = TableLimits.medium
print(limits.minBet)   // 25
print(limits.maxBet)   // 5000
print(limits.displayName)  // "Medium Stakes"
```

### 🎨 Branding & Icons

**AppIconView** - Generate app icons with consistent styling.

```swift
// Use a preset
AppIconView(config: .baccarat, size: 1024)

// Custom configuration
let config = AppIconConfig(
    title: "BLACKJACK",
    subtitle: "21",
    iconSymbol: "suit.club.fill",
    primaryColor: Color(red: 0.1, green: 0.2, blue: 0.35),
    secondaryColor: Color(red: 0.05, green: 0.12, blue: 0.25),
    accentColor: .yellow
)
AppIconView(config: config, size: 1024)
```

**Preset Configurations:**
- `.baccarat` - Spade symbol
- `.blackjack` - Club symbol with "21" subtitle
- `.poker` - Diamond symbol, red accent
- `.roulette` - Grid symbol, red theme

**LaunchScreenView** - Animated splash screen.

```swift
LaunchScreenView(config: .baccarat)
```

**IconRenderer** - Render views to images.

```swift
// Render single icon
let image = IconRenderer.renderAppIcon(config: .baccarat, size: 1024)

// Render all iOS sizes
let allImages = IconRenderer.renderAllSizes(config: .baccarat)
```

### 🔊 Audio & Haptics

**SoundManager** - Manages game sounds and haptic feedback.

```swift
// Access shared instance
let sound = SoundManager.shared

// Configure settings
sound.soundEnabled = true
sound.hapticsEnabled = true
sound.volume = 1.0

// Play sounds
sound.play(.chipPlace)
sound.play(.cardDeal)
sound.play(.win)

// Convenience methods (include haptics)
sound.playChipPlace()   // Chip sound + light haptic
sound.playCardFlip()    // Card sound + light haptic
sound.playWin()         // Win sound + success haptic
sound.playLose()        // Lose sound + error haptic
sound.playGameOver()    // Game over sound + error haptic
```

**Available Sounds:**
| Sound | Description |
|-------|-------------|
| `.chipPlace` | Placing a bet |
| `.chipStack` | Stacking chips |
| `.cardDeal` | Dealing a card |
| `.cardFlip` | Flipping a card |
| `.cardShuffle` | Shuffling deck |
| `.win` | Player wins |
| `.lose` | Player loses |
| `.push` | Tie/push |
| `.bigWin` | Large payout |
| `.buttonTap` | UI tap |
| `.newRound` | Starting new round |
| `.clearBets` | Clearing bets |
| `.gameOver` | Out of chips |

**Custom Sound Files:**

The manager uses iOS system sounds as fallback. To use custom sounds:
1. Add `.mp3` files named: `chip_place.mp3`, `card_deal.mp3`, `win.mp3`, etc.
2. Add them to your app bundle's Resources folder
3. Files are automatically detected and used

**Haptic Methods:**
```swift
sound.hapticLight()    // Light tap
sound.hapticMedium()   // Medium tap
sound.hapticHeavy()    // Heavy tap
sound.hapticSuccess()  // Success notification
sound.hapticError()    // Error notification
sound.hapticWarning()  // Warning notification
```

### 📊 Session Management

**GameSession** - Track play sessions with common and game-specific stats.

```swift
// Create a game-specific stats type
struct MyGameStats: GameSpecificStats {
    var specialWins: Int = 0
    
    init() {}
    
    var displayItems: [StatDisplayItem] {
        [StatDisplayItem(icon: "star.fill", iconColor: .yellow, 
                        label: "Special Wins", value: "\(specialWins)")]
    }
}

// Create session type alias
typealias MyGameSession = GameSession<MyGameStats>
```

**SessionManagedGame Protocol** - Add session management to your game state.

```swift
@Observable
class GameState: SessionManagedGame {
    typealias Stats = MyGameStats
    
    var currentSession: MyGameSession?
    var sessionHistory: [MyGameSession] = []
    
    // Record round results
    func completeRound(winnings: Int, bet: Int) {
        recordSessionRound(
            winnings: winnings,
            betAmount: bet,
            outcome: winnings > 0 ? .win : .lose
        ) { stats in
            if wasSpecialWin {
                stats.specialWins += 1
            }
        }
    }
}
```

**Session UI Components:**

```swift
// End session button
EndSessionButton {
    state.showEndSessionConfirmation = true
}

// Current session header with live stats
CurrentSessionHeader(
    duration: session.duration,
    roundsPlayed: session.roundsPlayed,
    netResult: session.netResult,
    onEndSession: { /* confirm */ }
)

// Session row for history list
SessionSummaryRow(
    styleDisplayName: "Vegas Strip",
    duration: session.duration,
    roundsPlayed: session.roundsPlayed,
    netResult: session.netResult,
    startTime: session.startTime,
    isActive: false,
    endReason: .manualEnd
)
```

**SessionFormatter** - Format session data for display.

```swift
SessionFormatter.formatDuration(3600)     // "01h 00min"
SessionFormatter.formatMoney(500)         // "$500"
SessionFormatter.formatMoney(-250)        // "-$250"
SessionFormatter.formatPercent(65.5)      // "65.5%"
```

For detailed documentation, see [SESSION_SYSTEM.md](SESSION_SYSTEM.md).

### 💾 Cloud Storage

**CloudSyncManager** - Saves game data locally and syncs with iCloud.

```swift
// 1. Define your game's data structure
struct MyGameData: PersistableGameData {
    static let gameIdentifier = "mygame"
    var roundsPlayed: Int { rounds.count }
    var lastModified: Date
    
    static var empty: MyGameData {
        MyGameData(rounds: [], balance: 10000, lastModified: Date())
    }
    
    var rounds: [RoundData]
    var balance: Int
}

// 2. Create sync manager
let persistence = CloudSyncManager<MyGameData>()

// 3. Save data (auto-syncs to iCloud)
var data = persistence.data
data.balance = 5000
persistence.save(data)

// 4. Data is automatically loaded on init
print(persistence.data.balance)

// 5. Check sync status
if persistence.iCloudAvailable {
    print("Last sync: \(persistence.lastSyncDate)")
}

// 6. Force sync
persistence.sync()

// 7. Listen for changes from other devices
persistence.onCloudDataReceived = { newData in
    print("Got \(newData.roundsPlayed) rounds from iCloud")
}

// 8. Reset all data
persistence.reset()
```

**PersistableGameData Protocol:**
```swift
public protocol PersistableGameData: Codable, Sendable {
    static var gameIdentifier: String { get }  // e.g., "baccarat"
    var roundsPlayed: Int { get }               // For conflict resolution
    var lastModified: Date { get set }          // Updated automatically
    static var empty: Self { get }              // Default/new game state
}
```

**Features:**
- 📱 **Local Storage** - Always saved to UserDefaults
- ☁️ **iCloud Sync** - Automatic sync when signed in
- 🔄 **Conflict Resolution** - Uses `roundsPlayed` to pick newer data
- 📢 **Change Notifications** - Callbacks when data changes from other devices
- 🔒 **Privacy** - Uses Apple ID, no Game Center required

### 🎨 Design System

**CasinoDesign** - Shared design constants.

```swift
// Spacing
CasinoDesign.Spacing.small      // 8
CasinoDesign.Spacing.medium     // 12
CasinoDesign.Spacing.large      // 16

// Corner Radius
CasinoDesign.CornerRadius.small   // 8
CasinoDesign.CornerRadius.medium  // 12
CasinoDesign.CornerRadius.large   // 16

// Font Sizes (base values for @ScaledMetric)
CasinoDesign.BaseFontSize.small   // 12
CasinoDesign.BaseFontSize.body    // 14
CasinoDesign.BaseFontSize.large   // 20

// Opacity
CasinoDesign.Opacity.subtle   // 0.05
CasinoDesign.Opacity.light    // 0.2
CasinoDesign.Opacity.medium   // 0.5
CasinoDesign.Opacity.heavy    // 0.8

// Animation
CasinoDesign.Animation.quick          // 0.2
CasinoDesign.Animation.standard       // 0.3
CasinoDesign.Animation.springDuration // 0.4
```

**Color.Sheet** - Sheet/popup colors.

```swift
Color.Sheet.background      // Dark background
Color.Sheet.sectionFill     // Section card fill
Color.Sheet.accent          // Yellow accent
Color.Sheet.secondaryText   // Muted text
Color.Sheet.cancelText      // Cancel button text
```

### 🌍 Localization

CasinoKit includes localization for:
- English (en)
- Spanish - Mexico (es-MX)
- French - Canada (fr-CA)

**Localized Strings:**
- Card names (Ace, King, Queen, etc.)
- Suit names (Hearts, Diamonds, Clubs, Spades)
- Chip-related strings
- Accessibility labels

**Adding Localizations:**

The package uses String Catalogs (`.xcstrings`). Edit:
```
CasinoKit/Sources/CasinoKit/Resources/Localizable.xcstrings
```

## Usage in a New Game

### 1. Import the Package

```swift
import SwiftUI
import CasinoKit
```

### 2. Create Your Game View

```swift
struct BlackjackTableView: View {
    @State private var deck = Deck(numberOfDecks: 6)
    @State private var selectedChip: ChipDenomination = .hundred
    
    var body: some View {
        VStack {
            // Player's hand
            HStack {
                ForEach(playerCards) { card in
                    CardView(card: card, faceUp: true)
                }
            }
            
            // Chip selector
            ChipSelectorView(
                denominations: ChipDenomination.allCases,
                selectedDenomination: $selectedChip
            )
        }
    }
}
```

### 3. Create Settings Sheet

```swift
struct SettingsView: View {
    @Environment(\.dismiss) var dismiss
    
    var body: some View {
        SheetContainerView(title: "Settings") {
            SheetSection(title: "GAME OPTIONS", icon: "gearshape") {
                // Your settings
            }
        } onDone: {
            dismiss()
        }
    }
}
```

### 4. Generate App Icon

```swift
// In a preview or development view
#Preview {
    let config = AppIconConfig(
        title: "BLACKJACK",
        subtitle: "21",
        iconSymbol: "suit.club.fill"
    )
    return AppIconView(config: config, size: 512)
}
```

Screenshot the preview and add to your Assets.xcassets.

## File Structure

```
CasinoKit/
├── Package.swift
├── README.md
├── GAME_TEMPLATE.md              # Guide for creating new games
├── SESSION_SYSTEM.md             # Session tracking documentation
├── Sources/CasinoKit/
│   ├── CasinoKit.swift
│   ├── Exports.swift
│   ├── Models/
│   │   ├── Card.swift
│   │   ├── Deck.swift
│   │   ├── ChipDenomination.swift
│   │   ├── TableLimits.swift      # Betting limit presets
│   │   ├── OnboardingState.swift  # Onboarding tracking
│   │   ├── TooltipManager.swift   # Tooltip management
│   │   └── Session/
│   │       ├── GameSession.swift        # Generic session with stats
│   │       ├── GameSessionProtocol.swift # Session protocols
│   │       └── SessionFormatter.swift   # Formatting utilities
│   ├── Views/
│   │   ├── Cards/
│   │   │   ├── CardView.swift
│   │   │   └── HandDisplayView.swift  # Generic hand display
│   │   ├── Chips/
│   │   │   ├── ChipView.swift
│   │   │   ├── ChipSelectorView.swift
│   │   │   ├── ChipStackView.swift
│   │   │   └── ChipOnTableView.swift
│   │   ├── Sheets/
│   │   │   └── SheetContainerView.swift
│   │   ├── Onboarding/
│   │   │   ├── WelcomeSheet.swift         # First-launch welcome
│   │   │   ├── ContextualTooltip.swift    # In-game hints
│   │   │   └── PulsingModifier.swift      # Attention-grabbing pulse
│   │   ├── Branding/
│   │   │   ├── AppIconView.swift
│   │   │   ├── LaunchScreenView.swift
│   │   │   └── IconRenderer.swift
│   │   ├── Effects/
│   │   │   └── ConfettiView.swift     # Win celebration
│   │   ├── Overlays/
│   │   │   └── GameOverView.swift     # Game over modal
│   │   ├── Table/
│   │   │   └── TableBackgroundView.swift  # Felt background
│   │   ├── Bars/
│   │   │   └── TopBarView.swift       # Balance & toolbar
│   │   ├── Badges/
│   │   │   └── ValueBadge.swift       # Numeric badge
│   │   ├── Buttons/
│   │   │   └── ActionButton.swift     # Deal/Hit/Stand buttons
│   │   ├── Zones/
│   │   │   └── BettingZone.swift      # Tappable betting area
│   │   ├── Settings/
│   │   │   └── SettingsComponents.swift  # Toggle, pickers
│   │   └── Session/
│   │       └── SessionViews.swift       # Session UI components
│   ├── Audio/
│   │   └── SoundManager.swift
│   ├── Storage/
│   │   └── CloudSyncManager.swift     # iCloud persistence
│   ├── Theme/
│   │   ├── CasinoTheme.swift
│   │   └── CasinoDesign.swift
│   └── Resources/
│       ├── Localizable.xcstrings
│       └── Sounds/
│           └── (audio files)
└── Tests/CasinoKitTests/
    └── CasinoKitTests.swift
```

## Best Practices

### Design Constants

Always use `CasinoDesign` constants instead of magic numbers:

```swift
// ✅ Good
.padding(CasinoDesign.Spacing.medium)
.opacity(CasinoDesign.Opacity.heavy)

// ❌ Bad
.padding(12)
.opacity(0.8)
```

### Localization

Always pass localized strings for button text:

```swift
SheetContainerView(
    title: String(localized: "Settings"),
    content: { ... },
    onDone: { dismiss() },
    doneButtonText: String(localized: "Done")
)
```

### Accessibility

All components include VoiceOver support:
- Cards announce suit and rank
- Chips announce denomination
- Interactive elements have labels and hints

### Dynamic Type

Use `@ScaledMetric` with base font sizes:

```swift
@ScaledMetric(relativeTo: .body) 
private var fontSize: CGFloat = CasinoDesign.BaseFontSize.body
```

## Apps Using CasinoKit

- **Blackjack** - Classic 21 with basic strategy hints and card counting
- **Baccarat** - The classic casino card game with road maps

## Version History

- **1.1.0** - Session system
  - Generic `GameSession<Stats>` for tracking play sessions
  - `SessionManagedGame` protocol for easy integration
  - Session UI components (header, summary rows, end button)
  - `SessionFormatter` for consistent data display
  - Aggregated statistics across sessions

- **1.0.0** - Initial release
  - Card and Chip components
  - Sheet container views
  - App icon and launch screen generators
  - Localization support (EN, ES-MX, FR-CA)

## License

This package is for personal use in your casino game projects.

