first commit
This commit is contained in:
commit
c2fa745598
925
README.md
Normal file
925
README.md
Normal file
@ -0,0 +1,925 @@
|
||||
# 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()
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**Sherpa Walkthrough** - Guided spotlight walkthrough (re-exported from Sherpa package).
|
||||
|
||||
```swift
|
||||
// 1. Define walkthrough steps
|
||||
enum MyTags: SherpaTags {
|
||||
case bettingZone
|
||||
case dealButton
|
||||
|
||||
func makeCallout() -> Callout {
|
||||
switch self {
|
||||
case .bettingZone:
|
||||
return .localizedLabeled("walkthrough.bettingZone", systemImage: "hand.tap.fill")
|
||||
case .dealButton:
|
||||
return .localizedLabeled("walkthrough.dealButton", systemImage: "play.fill")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Wrap app in SherpaContainerView (in @main App)
|
||||
SherpaContainerView(configuration: .default) {
|
||||
ContentView()
|
||||
}
|
||||
|
||||
// 3. Tag views and activate walkthrough
|
||||
struct GameTableView: View, SherpaDelegate {
|
||||
@State private var isWalkthroughActive = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
BettingZone()
|
||||
.sherpaTag(MyTags.bettingZone)
|
||||
|
||||
DealButton()
|
||||
.sherpaTag(MyTags.dealButton)
|
||||
}
|
||||
.sherpa(isActive: isWalkthroughActive, tags: MyTags.self, delegate: self)
|
||||
}
|
||||
|
||||
func onWalkthroughComplete(sherpa: Sherpa) {
|
||||
isWalkthroughActive = false
|
||||
onboarding.completeWelcome()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**OnboardingState** - Track onboarding completion status.
|
||||
|
||||
```swift
|
||||
let onboarding = OnboardingState(gameIdentifier: "blackjack")
|
||||
|
||||
// Check if user has seen welcome
|
||||
if !onboarding.hasCompletedWelcome {
|
||||
showWelcome = true
|
||||
}
|
||||
|
||||
// Mark welcome/walkthrough as completed
|
||||
onboarding.completeWelcome()
|
||||
|
||||
// Skip onboarding entirely
|
||||
onboarding.skipOnboarding()
|
||||
|
||||
// Reset onboarding (for testing)
|
||||
onboarding.reset()
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
### 📈 Analytics
|
||||
|
||||
**AnalyticsService** - Lightweight event tracking with a configurable endpoint.
|
||||
|
||||
```swift
|
||||
let analytics: AnalyticsTracking = AnalyticsService()
|
||||
|
||||
await analytics.track(
|
||||
AnalyticsEvent(
|
||||
name: "round_started",
|
||||
properties: [
|
||||
"game": .string("blackjack"),
|
||||
"tableLimit": .int(25)
|
||||
]
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
**Configure an endpoint later:**
|
||||
```swift
|
||||
await analytics.updateEndpoint(URL(string: "https://example.com/analytics"))
|
||||
```
|
||||
|
||||
**No-op tracker for disabled analytics:**
|
||||
```swift
|
||||
let analytics: AnalyticsTracking = NoOpAnalyticsTracker()
|
||||
```
|
||||
|
||||
### 🎨 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
|
||||
│ │ └── 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
|
||||
│ │ │ └── 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
|
||||
│ ├── Analytics/
|
||||
│ │ ├── Models/
|
||||
│ │ │ ├── AnalyticsEvent.swift
|
||||
│ │ │ └── AnalyticsValue.swift
|
||||
│ │ ├── Protocols/
|
||||
│ │ │ └── AnalyticsTracking.swift
|
||||
│ │ └── Services/
|
||||
│ │ ├── AnalyticsService.swift
|
||||
│ │ └── NoOpAnalyticsTracker.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.
|
||||
Loading…
Reference in New Issue
Block a user