CasinoGames/CasinoKit
2025-12-23 16:23:47 -06:00
..
Sources/CasinoKit Signed-off-by: Matt Bruce <mbrucedogs@gmail.com> 2025-12-23 16:23:47 -06:00
Tests/CasinoKitTests Reorganize repo: move git to root, add Blackjack and CasinoKit 2025-12-22 13:18:29 -06:00
Package.swift removed dependency 2025-12-23 08:59:55 -06:00
README.md Reorganize repo: move git to root, add Blackjack and CasinoKit 2025-12-22 13:18:29 -06:00

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.

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

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

🎰 Chips

ChipView - A casino chip with denomination display.

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

ChipSelectorView - Horizontal chip selector.

@State var selectedChip: ChipDenomination = .hundred

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

ChipStackView - Stacked chips showing bet amount.

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.

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.

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

🎲 Game Table Components

TableBackgroundView - Casino felt background with pattern.

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

TopBarView - Balance display and toolbar buttons.

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

HandDisplayView - Generic card hand display with labels.

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.

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

ActionButton - Styled action buttons.

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.

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

GameOverView - Game over modal when out of chips.

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

ValueBadge - Circular numeric badge.

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

⚙️ Settings Components

SettingsToggle - Toggle with title and subtitle.

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

SpeedPicker - Fast/Normal/Slow animation speed.

SpeedPicker(speed: $settings.dealingSpeed)

VolumePicker - Volume slider with icons.

VolumePicker(volume: $settings.soundVolume)

BalancePicker - Starting balance grid.

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

TableLimits - Betting limit presets.

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.

// 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.

LaunchScreenView(config: .baccarat)

IconRenderer - Render views to images.

// 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.

// 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:

sound.hapticLight()    // Light tap
sound.hapticMedium()   // Medium tap
sound.hapticHeavy()    // Heavy tap
sound.hapticSuccess()  // Success notification
sound.hapticError()    // Error notification
sound.hapticWarning()  // Warning notification

💾 Cloud Storage

CloudSyncManager - Saves game data locally and syncs with iCloud.

// 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:

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.

// 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.

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

import SwiftUI
import CasinoKit

2. Create Your Game View

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

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

// 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
├── Sources/CasinoKit/
│   ├── CasinoKit.swift
│   ├── Exports.swift
│   ├── Models/
│   │   ├── Card.swift
│   │   ├── Deck.swift
│   │   ├── ChipDenomination.swift
│   │   └── TableLimits.swift      # Betting limit presets
│   ├── Views/
│   │   ├── Cards/
│   │   │   ├── CardView.swift
│   │   │   └── HandDisplayView.swift  # Generic hand display
│   │   ├── Chips/
│   │   │   ├── ChipView.swift
│   │   │   ├── ChipSelectorView.swift
│   │   │   ├── ChipStackView.swift
│   │   │   └── ChipOnTableView.swift
│   │   ├── Sheets/
│   │   │   └── SheetContainerView.swift
│   │   ├── 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
│   ├── 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:

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

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

Localization

Always pass localized strings for button text:

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:

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

Apps Using CasinoKit

  • Baccarat - The classic casino card game

Version History

  • 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.