From 55fd30d77e2aabb1c73dc8704167949f53142c28 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sun, 28 Dec 2025 21:43:53 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- Baccarat/BaccaratTests/BaccaratTests.swift | 66 ++- Baccarat/GAME_CENTER_PLAN.md | 629 +++++++++++++++++++++ Blackjack/GAME_CENTER_PLAN.md | 554 ++++++++++++++++++ CasinoKit/GAME_CENTER_PLAN.md | 364 ++++++++++++ 4 files changed, 1598 insertions(+), 15 deletions(-) create mode 100644 Baccarat/GAME_CENTER_PLAN.md create mode 100644 Blackjack/GAME_CENTER_PLAN.md create mode 100644 CasinoKit/GAME_CENTER_PLAN.md diff --git a/Baccarat/BaccaratTests/BaccaratTests.swift b/Baccarat/BaccaratTests/BaccaratTests.swift index b6c23f8..843c0e0 100644 --- a/Baccarat/BaccaratTests/BaccaratTests.swift +++ b/Baccarat/BaccaratTests/BaccaratTests.swift @@ -24,28 +24,52 @@ struct BaccaratTests { // Test with a King (value 0) and an 8 (value 8) hand.addCard(Card(suit: .spades, rank: .king)) hand.addCard(Card(suit: .hearts, rank: .eight)) - #expect(hand.value == 8) + #expect(hand.value == 8, "King (0) + Eight (8) should equal 8") // Test with two cards that sum to more than 10 hand.clear() hand.addCard(Card(suit: .spades, rank: .seven)) hand.addCard(Card(suit: .hearts, rank: .five)) - #expect(hand.value == 2) // 7 + 5 = 12, 12 % 10 = 2 + #expect(hand.value == 2, "Seven (7) + Five (5) = 12, 12 % 10 should equal 2") // Test natural 9 hand.clear() hand.addCard(Card(suit: .clubs, rank: .four)) hand.addCard(Card(suit: .diamonds, rank: .five)) - #expect(hand.value == 9) - #expect(hand.isNatural == true) + #expect(hand.value == 9, "Four (4) + Five (5) should equal 9") + #expect(hand.isNatural == true, "Hand with 2 cards and value 9 is a natural") // Test with three cards hand.clear() hand.addCard(Card(suit: .spades, rank: .four)) hand.addCard(Card(suit: .hearts, rank: .three)) hand.addCard(Card(suit: .clubs, rank: .two)) - #expect(hand.value == 9) // 4 + 3 + 2 = 9 - #expect(hand.isNatural == false) // Not a natural (3 cards) + #expect(hand.value == 9, "Four (4) + Three (3) + Two (2) should equal 9") + #expect(hand.isNatural == false, "Hand with 3 cards is not a natural") + + // Test face cards (all should be 0) + hand.clear() + hand.addCard(Card(suit: .spades, rank: .king)) + hand.addCard(Card(suit: .hearts, rank: .queen)) + #expect(hand.value == 0, "King (0) + Queen (0) should equal 0") + + // Test 10 card (should be 0 in baccarat) + hand.clear() + hand.addCard(Card(suit: .diamonds, rank: .ten)) + hand.addCard(Card(suit: .clubs, rank: .nine)) + #expect(hand.value == 9, "Ten (0) + Nine (9) should equal 9") + + // Test Ace (should be 1) + hand.clear() + hand.addCard(Card(suit: .hearts, rank: .ace)) + hand.addCard(Card(suit: .spades, rank: .ace)) + #expect(hand.value == 2, "Ace (1) + Ace (1) should equal 2") + + // Test modulo 10 with higher values + hand.clear() + hand.addCard(Card(suit: .diamonds, rank: .nine)) + hand.addCard(Card(suit: .clubs, rank: .nine)) + #expect(hand.value == 8, "Nine (9) + Nine (9) = 18, 18 % 10 should equal 8") } @Test func engineHandValues() async throws { @@ -65,12 +89,12 @@ struct BaccaratTests { #expect(engine.bankerHand.value <= 9) } - @Test func gameStateHandValues() async throws { + @Test @MainActor func gameStateHandValues() async throws { let state = GameState() // Initially should be 0 - #expect(state.playerHandValue == 0) - #expect(state.bankerHandValue == 0) + #expect(state.playerHandValue == 0, "Player hand value should be 0 when no cards dealt") + #expect(state.bankerHandValue == 0, "Banker hand value should be 0 when no cards dealt") // Place a bet state.placeBet(type: .player, amount: 100) @@ -79,14 +103,26 @@ struct BaccaratTests { await state.deal() // After dealing, both hands should have values between 0-9 - #expect(state.playerHandValue >= 0) - #expect(state.playerHandValue <= 9) - #expect(state.bankerHandValue >= 0) - #expect(state.bankerHandValue <= 9) + #expect(state.playerHandValue >= 0, "Player hand value should be >= 0") + #expect(state.playerHandValue <= 9, "Player hand value should be <= 9") + #expect(state.bankerHandValue >= 0, "Banker hand value should be >= 0") + #expect(state.bankerHandValue <= 9, "Banker hand value should be <= 9") // Verify visible cards match engine cards - #expect(state.visiblePlayerCards.count >= 2) - #expect(state.visibleBankerCards.count >= 2) + #expect(state.visiblePlayerCards.count >= 2, "Player should have at least 2 cards") + #expect(state.visibleBankerCards.count >= 2, "Banker should have at least 2 cards") + + // Verify cards are face up (needed for badges to show) + #expect(state.playerCardsFaceUp.count >= 2, "Player should have face-up state for cards") + #expect(state.bankerCardsFaceUp.count >= 2, "Banker should have face-up state for cards") + #expect(state.playerCardsFaceUp.contains(true), "At least one player card should be face up") + #expect(state.bankerCardsFaceUp.contains(true), "At least one banker card should be face up") + + // Verify the visible hand values match what the engine calculates + let manualPlayerValue = state.visiblePlayerCards.reduce(0) { $0 + $1.baccaratValue } % 10 + let manualBankerValue = state.visibleBankerCards.reduce(0) { $0 + $1.baccaratValue } % 10 + #expect(state.playerHandValue == manualPlayerValue, "Visible cards should match calculated value for player") + #expect(state.bankerHandValue == manualBankerValue, "Visible cards should match calculated value for banker") } } diff --git a/Baccarat/GAME_CENTER_PLAN.md b/Baccarat/GAME_CENTER_PLAN.md new file mode 100644 index 0000000..7944774 --- /dev/null +++ b/Baccarat/GAME_CENTER_PLAN.md @@ -0,0 +1,629 @@ +# Game Center Integration - Baccarat + +This document outlines the Game Center achievements for Baccarat and the game-specific integration steps. + +## Prerequisites + +✅ Complete `CasinoKit/GAME_CENTER_PLAN.md` implementation first (shared infrastructure) + +## Achievement Design Philosophy + +Focus on **gameplay milestones**, **pattern recognition**, and **understanding Baccarat strategy**, not pure luck or bankroll metrics. + +Achievements celebrate: +- 🎲 Understanding Baccarat rules and betting +- 📊 Pattern recognition (road maps) +- 💎 High-stakes play (earned through gameplay) +- 🎯 Mastery of side bets + +## Baccarat Achievements + +### 🎓 Learning & First Steps Category + +#### 1. **First Hand** +- **ID:** `com.yourdomain.baccarat.first_hand` +- **Description:** Play your first hand of Baccarat +- **Progress:** Complete on first hand +- **Points:** 5 +- **Icon:** `play.circle.fill` +- **Trigger:** After first `finishRound()` completes + +#### 2. **Player Believer** +- **ID:** `com.yourdomain.baccarat.player_wins_10` +- **Description:** Win 10 times betting on Player +- **Progress:** Incremental (0-10) +- **Points:** 10 +- **Icon:** `person.fill` +- **Trigger:** When Player bet wins + +#### 3. **Banker's Trust** +- **ID:** `com.yourdomain.baccarat.banker_wins_25` +- **Description:** Win 25 times betting on Banker +- **Progress:** Incremental (0-25) +- **Points:** 15 +- **Icon:** `building.columns.fill` +- **Trigger:** When Banker bet wins + +#### 4. **Tie Hunter** +- **ID:** `com.yourdomain.baccarat.tie_wins_5` +- **Description:** Win 5 times betting on Tie +- **Progress:** Incremental (0-5) +- **Points:** 15 +- **Icon:** "equal.circle.fill" +- **Trigger:** When Tie bet wins +- **Note:** Higher points due to rarity (14.4% house edge) + +### 🎲 Natural & Special Hands Category + +#### 5. **Natural Talent** +- **ID:** `com.yourdomain.baccarat.naturals_10` +- **Description:** Get 10 naturals (8 or 9 on initial two cards) +- **Progress:** Incremental (0-10) +- **Points:** 10 +- **Icon:** `star.fill` +- **Trigger:** When either Player or Banker gets natural 8 or 9 + +#### 6. **Natural Legend** +- **ID:** `com.yourdomain.baccarat.naturals_50` +- **Description:** Get 50 naturals +- **Progress:** Incremental (0-50) +- **Points:** 25 +- **Icon:** `star.circle.fill` +- **Trigger:** When either Player or Banker gets natural (extended) + +#### 7. **Perfect Pair** +- **ID:** `com.yourdomain.baccarat.pair_bets_10` +- **Description:** Win Player Pair or Banker Pair side bet 10 times +- **Progress:** Incremental (0-10) +- **Points:** 15 +- **Icon:** `suit.heart.fill` +- **Trigger:** When either pair bet wins + +#### 8. **Dragon Master** +- **ID:** `com.yourdomain.baccarat.dragon_bonus_10` +- **Description:** Win Dragon Bonus side bet 10 times +- **Progress:** Incremental (0-10) +- **Points:** 15 +- **Icon:** `flame.fill` +- **Trigger:** When Dragon Bonus bet wins + +#### 9. **Dragon Legend** +- **ID:** `com.yourdomain.baccarat.dragon_bonus_win_by_9` +- **Description:** Win Dragon Bonus with a 9-point margin (30:1 payout) +- **Progress:** Complete when achieved +- **Points:** 25 +- **Icon:** `flame.circle.fill` +- **Trigger:** When Dragon Bonus pays 30:1 +- **Note:** Very rare achievement + +### 💪 Persistence & Mastery Category + +#### 10. **Beginner** +- **ID:** `com.yourdomain.baccarat.rounds_50` +- **Description:** Play 50 total rounds +- **Progress:** Incremental (0-50) +- **Points:** 10 +- **Icon:** `person.fill` +- **Trigger:** After each round completes + +#### 11. **Enthusiast** +- **ID:** `com.yourdomain.baccarat.rounds_250` +- **Description:** Play 250 total rounds +- **Progress:** Incremental (0-250) +- **Points:** 25 +- **Icon:** `person.2.fill` +- **Trigger:** After each round completes + +#### 12. **Expert** +- **ID:** `com.yourdomain.baccarat.rounds_500` +- **Description:** Play 500 total rounds +- **Progress:** Incremental (0-500) +- **Points:** 50 +- **Icon:** `person.3.fill` +- **Trigger:** After each round completes + +#### 13. **Comeback Kid** +- **ID:** `com.yourdomain.baccarat.comeback` +- **Description:** Recover from under $100 to $10,000 in a single session +- **Progress:** Complete when achieved +- **Points:** 25 +- **Icon:** `arrow.up.right.circle.fill` +- **Trigger:** When balance reaches $10k after being below $100 in same session +- **Implementation Note:** Track session low point + +### 📊 Pattern Recognition & Strategy Category + +#### 14. **Streak Spotter** +- **ID:** `com.yourdomain.baccarat.spot_streaks_5` +- **Description:** Correctly predict and bet on a streak 5 times (3+ same outcome in a row) +- **Progress:** Incremental (0-5) +- **Points:** 20 +- **Icon:** `chart.line.uptrend.xyaxis` +- **Trigger:** When player bets same side 3+ times in a row and all win +- **Implementation Note:** Track consecutive wins on same bet type + +#### 15. **Road Map Reader** +- **ID:** `com.yourdomain.baccarat.view_road_map_25` +- **Description:** View the road map history 25 times +- **Progress:** Incremental (0-25) +- **Points:** 10 +- **Icon:** `map.fill` +- **Trigger:** Track when road map view is expanded/scrolled +- **Implementation Note:** Encourages engagement with pattern tracking feature + +### 💎 High Stakes Category + +#### 16. **High Roller** +- **ID:** `com.yourdomain.baccarat.bet_50000` +- **Description:** Place a single bet of $50,000 or more +- **Progress:** Complete when achieved +- **Points:** 20 +- **Icon:** `dollarsign.circle.fill` +- **Trigger:** When any bet equals or exceeds $50,000 +- **Implementation Note:** Must be earned through gameplay (requires VIP table limits) + +#### 17. **Big Win** +- **ID:** `com.yourdomain.baccarat.single_win_100000` +- **Description:** Win $100,000 or more in a single hand +- **Progress:** Complete when achieved +- **Points:** 30 +- **Icon:** `banknote.fill` +- **Trigger:** When net profit from a single round ≥ $100,000 +- **Implementation Note:** Possible with Dragon Bonus on large bet + +### 🏆 Elite Category + +#### 18. **Side Bet Specialist** +- **ID:** `com.yourdomain.baccarat.side_bets_total_25` +- **Description:** Win any side bet 25 times total +- **Progress:** Incremental (0-25) +- **Points:** 25 +- **Icon:** `plus.circle.fill` +- **Trigger:** Any time a side bet wins (Pairs or Dragon Bonus) + +#### 19. **James Bond** (Hidden Achievement) +- **ID:** `com.yourdomain.baccarat.james_bond` +- **Description:** Play 100 consecutive hands betting only on Banker +- **Progress:** Incremental (0-100, resets if different bet placed) +- **Points:** 25 +- **Icon:** `tuxedo.fill` (or `theatermasks.fill`) +- **Trigger:** Track consecutive Banker-only bets +- **Implementation Note:** Resets to 0 if player bets on Player or Tie +- **Fun fact:** James Bond's favorite game and typical bet + +#### 20. **Table Explorer** (Hidden Achievement) +- **ID:** `com.yourdomain.baccarat.all_table_limits` +- **Description:** Play at least 10 rounds at each table limit level +- **Progress:** Incremental (0-50, 10 per table × 5 tables) +- **Points:** 15 +- **Icon:** `chart.bar.fill` +- **Trigger:** Track rounds played at each table limit (Casual, Low, Medium, High, VIP) + +## Implementation Steps + +### 1. Define Achievement Enum + +**File:** `Baccarat/Baccarat/Models/BaccaratAchievement.swift` (new file) + +```swift +import CasinoKit +import GameKit + +enum BaccaratAchievement: String, AchievementDefinition { + case firstHand = "first_hand" + case playerWins10 = "player_wins_10" + case bankerWins25 = "banker_wins_25" + case tieWins5 = "tie_wins_5" + case naturals10 = "naturals_10" + case naturals50 = "naturals_50" + case pairBets10 = "pair_bets_10" + case dragonBonus10 = "dragon_bonus_10" + case dragonBonusWinBy9 = "dragon_bonus_win_by_9" + case rounds50 = "rounds_50" + case rounds250 = "rounds_250" + case rounds500 = "rounds_500" + case comeback = "comeback" + case spotStreaks5 = "spot_streaks_5" + case viewRoadMap25 = "view_road_map_25" + case bet50000 = "bet_50000" + case singleWin100000 = "single_win_100000" + case sideBetsTotal25 = "side_bets_total_25" + case jamesBond = "james_bond" + case allTableLimits = "all_table_limits" + + var identifier: String { + "com.yourdomain.baccarat.\(rawValue)" + } + + var title: String { + switch self { + case .firstHand: return String(localized: "First Hand") + case .playerWins10: return String(localized: "Player Believer") + case .bankerWins25: return String(localized: "Banker's Trust") + case .tieWins5: return String(localized: "Tie Hunter") + case .naturals10: return String(localized: "Natural Talent") + case .naturals50: return String(localized: "Natural Legend") + case .pairBets10: return String(localized: "Perfect Pair") + case .dragonBonus10: return String(localized: "Dragon Master") + case .dragonBonusWinBy9: return String(localized: "Dragon Legend") + case .rounds50: return String(localized: "Beginner") + case .rounds250: return String(localized: "Enthusiast") + case .rounds500: return String(localized: "Expert") + case .comeback: return String(localized: "Comeback Kid") + case .spotStreaks5: return String(localized: "Streak Spotter") + case .viewRoadMap25: return String(localized: "Road Map Reader") + case .bet50000: return String(localized: "High Roller") + case .singleWin100000: return String(localized: "Big Win") + case .sideBetsTotal25: return String(localized: "Side Bet Specialist") + case .jamesBond: return String(localized: "James Bond") + case .allTableLimits: return String(localized: "Table Explorer") + } + } + + var description: String { + // Full descriptions for each achievement + } + + var maxProgress: Int { + switch self { + case .firstHand, .comeback, .dragonBonusWinBy9, .bet50000, .singleWin100000: return 1 + case .tieWins5, .spotStreaks5: return 5 + case .playerWins10, .naturals10, .pairBets10, .dragonBonus10: return 10 + case .bankerWins25, .sideBetsTotal25, .viewRoadMap25: return 25 + case .naturals50, .rounds50, .allTableLimits: return 50 + case .jamesBond: return 100 + case .rounds250: return 250 + case .rounds500: return 500 + } + } + + var isIncremental: Bool { + switch self { + case .firstHand, .comeback, .dragonBonusWinBy9, .bet50000, .singleWin100000: + return false + default: + return true + } + } + + var iconName: String { + switch self { + case .firstHand: return "play.circle.fill" + case .playerWins10: return "person.fill" + case .bankerWins25: return "building.columns.fill" + case .tieWins5: return "equal.circle.fill" + case .naturals10: return "star.fill" + case .naturals50: return "star.circle.fill" + case .pairBets10: return "suit.heart.fill" + case .dragonBonus10: return "flame.fill" + case .dragonBonusWinBy9: return "flame.circle.fill" + case .rounds50: return "person.fill" + case .rounds250: return "person.2.fill" + case .rounds500: return "person.3.fill" + case .comeback: return "arrow.up.right.circle.fill" + case .spotStreaks5: return "chart.line.uptrend.xyaxis" + case .viewRoadMap25: return "map.fill" + case .bet50000: return "dollarsign.circle.fill" + case .singleWin100000: return "banknote.fill" + case .sideBetsTotal25: return "plus.circle.fill" + case .jamesBond: return "theatermasks.fill" + case .allTableLimits: return "chart.bar.fill" + } + } +} +``` + +### 2. Add Achievement Tracker to GameState + +**File:** `Baccarat/Baccarat/Engine/GameState.swift` + +```swift +import CasinoKit + +@MainActor +@Observable +class GameState { + // ... existing properties ... + + // NEW: Achievement tracking + private(set) var achievementTracker: AchievementTracker + + // Track session-specific metrics + private var sessionLowBalance: Int = 0 + private var consecutiveBankerBets = 0 + private var consecutiveSameBetType: BetType? = nil + private var consecutiveSameBetWins = 0 + private var roundsPerTableLimit: [String: Int] = [:] + + init() { + // ... existing init ... + + self.achievementTracker = AchievementTracker() + self.sessionLowBalance = balance + } + + // NEW: Achievement checking methods + func checkAchievements(after action: GameAction) { + // Called after each game action + } + + private func updateSessionTracking() { + if balance < sessionLowBalance { + sessionLowBalance = balance + } + + // Check comeback achievement + if sessionLowBalance < 100 && balance >= 10_000 { + achievementTracker.complete(.comeback) + } + } +} +``` + +### 3. Add Achievement Triggers + +Add achievement checks throughout game logic: + +**In `BaccaratEngine.swift` or `GameState.swift`:** + +```swift +// After round completes +func finishRound() { + // ... existing logic ... + + // First hand achievement + if totalRoundsPlayed == 1 { + achievementTracker.complete(.firstHand) + } + + // Persistence achievements + achievementTracker.increment(.rounds50) + achievementTracker.increment(.rounds250) + achievementTracker.increment(.rounds500) + + // Track table limits + let limitKey = settings.tableLimit.displayName + roundsPerTableLimit[limitKey, default: 0] += 1 + + if roundsPerTableLimit.values.filter({ $0 >= 10 }).count == 5 { + achievementTracker.complete(.allTableLimits) + } + + updateSessionTracking() +} + +// When placing bets +func placeBet(type: BetType, amount: Int) { + // ... existing logic ... + + // High roller achievement + if amount >= 50_000 { + achievementTracker.complete(.bet50000) + } + + // James Bond tracking (consecutive Banker bets) + if type == .banker { + consecutiveBankerBets += 1 + if consecutiveBankerBets >= 100 { + achievementTracker.complete(.jamesBond) + } + } else { + consecutiveBankerBets = 0 + } + + // Streak tracking + if type == consecutiveSameBetType { + // Continue tracking + } else { + consecutiveSameBetType = type + consecutiveSameBetWins = 0 + } +} + +// After determining winner +func evaluateRound() { + // ... existing logic ... + + // Natural achievements + if playerHand.isNatural || bankerHand.isNatural { + achievementTracker.increment(.naturals10) + achievementTracker.increment(.naturals50) + } + + // Check main bet wins + if playerBetWon { + achievementTracker.increment(.playerWins10) + + if consecutiveSameBetType == .player { + consecutiveSameBetWins += 1 + if consecutiveSameBetWins >= 3 { + achievementTracker.increment(.spotStreaks5) + } + } + } + + if bankerBetWon { + achievementTracker.increment(.bankerWins25) + + if consecutiveSameBetType == .banker { + consecutiveSameBetWins += 1 + if consecutiveSameBetWins >= 3 { + achievementTracker.increment(.spotStreaks5) + } + } + } + + if tieBetWon { + achievementTracker.increment(.tieWins5) + } + + // Side bet achievements + if playerPairWon || bankerPairWon { + achievementTracker.increment(.pairBets10) + achievementTracker.increment(.sideBetsTotal25) + } + + if dragonBonusWon { + achievementTracker.increment(.dragonBonus10) + achievementTracker.increment(.sideBetsTotal25) + + // Check for 9-point margin win + if dragonBonusMargin == 9 { + achievementTracker.complete(.dragonBonusWinBy9) + } + } + + // Big win achievement + let netProfit = calculateNetProfit() + if netProfit >= 100_000 { + achievementTracker.complete(.singleWin100000) + } +} + +// When road map is viewed +func trackRoadMapView() { + achievementTracker.increment(.viewRoadMap25) +} +``` + +### 4. Add Road Map View Tracking + +**File:** `Baccarat/Baccarat/Views/Table/RoadMapView.swift` (or wherever road map is displayed) + +```swift +.onAppear { + state.trackRoadMapView() +} +``` + +### 5. Add Game Center to Settings + +**File:** `Baccarat/Baccarat/Views/Sheets/SettingsView.swift` + +```swift +import CasinoKit + +struct SettingsView: View { + // ... existing code ... + + var body: some View { + SheetContainerView(title: String(localized: "Settings")) { + // ... existing sections ... + + // NEW: Game Center section + GameCenterSettingsSection() + + } onDone: { + dismiss() + } + } +} +``` + +### 6. Optional: Add Game Center Access Point + +**File:** `Baccarat/Baccarat/Views/Game/GameTableView.swift` + +```swift +TopBarView( + balance: state.balance, + // ... existing parameters ... + leadingButtons: [ + TopBarButton( + icon: "gamecontroller.fill", + accessibilityLabel: "Game Center" + ) { + GameCenterManager.shared.showGameCenterDashboard() + } + ] +) +``` + +### 7. Initialize Game Center on App Launch + +**File:** `Baccarat/Baccarat/BaccaratApp.swift` + +```swift +import CasinoKit + +@main +struct BaccaratApp: App { + init() { + // Authenticate with Game Center silently + Task { + await GameCenterManager.shared.authenticate() + } + } + + // ... rest of app ... +} +``` + +### 8. Add Localized Strings + +**File:** `Baccarat/Baccarat/Resources/Localizable.xcstrings` + +Add all achievement titles, descriptions, and Game Center UI strings. + +## App Store Connect Configuration + +### For Each Achievement: + +1. Log into App Store Connect +2. Select Baccarat app +3. Go to Game Center → Achievements +4. Click "+" to add achievement +5. Configure: + - **Reference Name:** (internal only) + - **Achievement ID:** Must match enum identifier + - **Point Value:** As specified above + - **Hidden:** Set to "Yes" for James Bond and Table Explorer + - **Achievable More Than Once:** No for all + - **Localization:** Add EN, ES-MX, FR-CA + - **Images:** Upload 512x512 and 1024x1024 versions + +## Testing Checklist + +- [ ] All achievements can be triggered in game +- [ ] Incremental achievements show progress +- [ ] Hidden achievements don't appear until unlocked +- [ ] James Bond achievement resets on non-Banker bet +- [ ] Streak tracking works correctly (3+ consecutive wins) +- [ ] Road map view tracking increments +- [ ] Table limit tracking works across all 5 limits +- [ ] Big win achievement triggers at $100k profit +- [ ] Dragon Bonus 9-point margin detected correctly +- [ ] Comeback achievement tracks session low properly + +## Edge Cases to Handle + +1. **Player resets game mid-session** - Session achievements (comeback) should reset +2. **Multiple simultaneous bets** - Track all winning bet types for achievements +3. **Tie pushes main bet** - Don't count as "win" for streak tracking +4. **Switching table limits** - Properly track rounds per limit +5. **Offline play** - Queue achievements, submit when online + +## Future Enhancements + +- **Achievement notifications** - Use CasinoKit's AchievementToast +- **Progress UI** - Show achievement progress in statistics view +- **Pattern hints** - Suggest when player is on a streak (Road Map Reader tie-in) + +--- + +## Summary + +- ✅ 20 total achievements +- ✅ Focus on gameplay understanding, pattern recognition, and milestones +- ✅ Two hidden achievements (James Bond, Table Explorer) +- ✅ Incremental progress for most achievements +- ✅ Unique Baccarat-specific achievements (road maps, Dragon Bonus, streaks) +- ✅ Integration with existing GameState and road map system +- ✅ No code duplication (uses CasinoKit infrastructure) + +**Estimated Integration Time:** 2-3 hours after CasinoKit infrastructure is complete. + +--- + +*See `CasinoKit/GAME_CENTER_PLAN.md` for shared infrastructure implementation details.* + diff --git a/Blackjack/GAME_CENTER_PLAN.md b/Blackjack/GAME_CENTER_PLAN.md new file mode 100644 index 0000000..3ff14fb --- /dev/null +++ b/Blackjack/GAME_CENTER_PLAN.md @@ -0,0 +1,554 @@ +# Game Center Integration - Blackjack + +This document outlines the Game Center achievements for Blackjack and the game-specific integration steps. + +## Prerequisites + +✅ Complete `CasinoKit/GAME_CENTER_PLAN.md` implementation first (shared infrastructure) + +## Achievement Design Philosophy + +Focus on **learning milestones** and **skill demonstration**, not bankroll/luck-based metrics. + +Achievements celebrate: +- 📚 Learning basic strategy +- 🎓 Card counting practice +- 🎯 Gameplay milestones +- 💪 Persistence and mastery + +## Blackjack Achievements + +### 🎓 Learning & Strategy Category + +#### 1. **First Hand** +- **ID:** `com.yourdomain.blackjack.first_hand` +- **Description:** Play your first hand of Blackjack +- **Progress:** Complete on first hand +- **Points:** 5 +- **Icon:** `play.circle.fill` +- **Trigger:** After first `finishRound()` completes + +#### 2. **Strategy Student** +- **ID:** `com.yourdomain.blackjack.strategy_student` +- **Description:** Play 50 hands following perfect basic strategy +- **Progress:** Incremental (0-50) +- **Points:** 10 +- **Icon:** `book.fill` +- **Trigger:** Each hand where player follows all hint recommendations +- **Implementation Note:** Track in `GameState` when hint matches action taken + +#### 3. **Strategy Master** +- **ID:** `com.yourdomain.blackjack.strategy_master` +- **Description:** Play 100 hands following perfect basic strategy +- **Progress:** Incremental (0-100) +- **Points:** 25 +- **Icon:** `graduationcap.fill` +- **Trigger:** Same as Strategy Student, extended goal + +#### 4. **Card Counter** +- **ID:** `com.yourdomain.blackjack.card_counter` +- **Description:** Play 25 hands using the Hi-Lo counting system +- **Progress:** Incremental (0-25) +- **Points:** 15 +- **Icon:** `brain.head.profile` +- **Trigger:** When `settings.showCount` is enabled and hand completes +- **Implementation Note:** Must have counting enabled in settings + +#### 5. **True Count Master** +- **ID:** `com.yourdomain.blackjack.true_count_master` +- **Description:** Make 10 correct betting decisions based on true count +- **Progress:** Incremental (0-10) +- **Points:** 20 +- **Icon:** `chart.line.uptrend.xyaxis` +- **Trigger:** When player increases bet with positive true count (+2 or higher) +- **Implementation Note:** Track bet increases that align with count recommendations + +### 🎯 Gameplay Milestones Category + +#### 6. **Natural Talent** +- **ID:** `com.yourdomain.blackjack.natural_10` +- **Description:** Get 10 Blackjacks (natural 21) +- **Progress:** Incremental (0-10) +- **Points:** 10 +- **Icon:** `star.fill` +- **Trigger:** When player hand is natural blackjack + +#### 7. **Natural Legend** +- **ID:** `com.yourdomain.blackjack.natural_50` +- **Description:** Get 50 Blackjacks +- **Progress:** Incremental (0-50) +- **Points:** 25 +- **Icon:** `star.circle.fill` +- **Trigger:** When player hand is natural blackjack (extended) + +#### 8. **Split Decision** +- **ID:** `com.yourdomain.blackjack.split_wins_10` +- **Description:** Win with split hands 10 times +- **Progress:** Incremental (0-10) +- **Points:** 10 +- **Icon:** `rectangle.split.2x1.fill` +- **Trigger:** When a split hand wins against dealer + +#### 9. **Double Down Dynamo** +- **ID:** `com.yourdomain.blackjack.double_wins_15` +- **Description:** Win 15 hands after doubling down +- **Progress:** Incremental (0-15) +- **Points:** 15 +- **Icon:** `arrow.down.circle.fill` +- **Trigger:** When player wins a hand after doubling down + +#### 10. **Insurance Agent** +- **ID:** `com.yourdomain.blackjack.insurance_wins_5` +- **Description:** Win insurance bet 5 times +- **Progress:** Incremental (0-5) +- **Points:** 10 +- **Icon:** `shield.checkered` +- **Trigger:** When dealer has blackjack and player took insurance + +### 💪 Persistence & Mastery Category + +#### 11. **Rookie** +- **ID:** `com.yourdomain.blackjack.hands_100` +- **Description:** Play 100 total hands +- **Progress:** Incremental (0-100) +- **Points:** 10 +- **Icon:** `person.fill` +- **Trigger:** After each hand completes + +#### 12. **Regular** +- **ID:** `com.yourdomain.blackjack.hands_500` +- **Description:** Play 500 total hands +- **Progress:** Incremental (0-500) +- **Points:** 25 +- **Icon:** `person.2.fill` +- **Trigger:** After each hand completes + +#### 13. **Veteran** +- **ID:** `com.yourdomain.blackjack.hands_1000` +- **Description:** Play 1,000 total hands +- **Progress:** Incremental (0-1000) +- **Points:** 50 +- **Icon:** `person.3.fill` +- **Trigger:** After each hand completes + +#### 14. **Comeback Kid** +- **ID:** `com.yourdomain.blackjack.comeback` +- **Description:** Recover from under $100 to $10,000 in a single session +- **Progress:** Complete when achieved +- **Points:** 25 +- **Icon:** `arrow.up.right.circle.fill` +- **Trigger:** When balance reaches $10k after being below $100 in same session +- **Implementation Note:** Track session low point + +#### 15. **Perfect Pair Player** +- **ID:** `com.yourdomain.blackjack.perfect_pairs_5` +- **Description:** Win Perfect Pairs side bet 5 times +- **Progress:** Incremental (0-5) +- **Points:** 10 +- **Icon:** `suit.heart.fill` +- **Trigger:** When Perfect Pairs side bet wins + +#### 16. **21+3 Champion** +- **ID:** `com.yourdomain.blackjack.twentyone_plus_three_5` +- **Description:** Win 21+3 side bet 5 times +- **Progress:** Incremental (0-5) +- **Points:** 10 +- **Icon:** `suit.diamond.fill` +- **Trigger:** When 21+3 side bet wins + +### 🏆 Elite Category + +#### 17. **Side Bet Specialist** +- **ID:** `com.yourdomain.blackjack.side_bet_wins_25` +- **Description:** Win any side bet 25 times total +- **Progress:** Incremental (0-25) +- **Points:** 25 +- **Icon:** `plus.circle.fill` +- **Trigger:** Any time a side bet wins (Perfect Pairs or 21+3) + +#### 18. **Rule Breaker** (Hidden Achievement) +- **ID:** `com.yourdomain.blackjack.all_rule_variations` +- **Description:** Play at least 10 hands with each rule variation +- **Progress:** Incremental (0-40, 10 per variation × 4 variations) +- **Points:** 15 +- **Icon:** `gearshape.2.fill` +- **Trigger:** Track which rule sets have been played +- **Implementation Note:** Vegas Strip, Atlantic City, European, Custom (10 hands each) + +## Implementation Steps + +### 1. Define Achievement Enum + +**File:** `Blackjack/Blackjack/Models/BlackjackAchievement.swift` (new file) + +```swift +import CasinoKit +import GameKit + +enum BlackjackAchievement: String, AchievementDefinition { + case firstHand = "first_hand" + case strategyStudent = "strategy_student" + case strategyMaster = "strategy_master" + case cardCounter = "card_counter" + case trueCountMaster = "true_count_master" + case natural10 = "natural_10" + case natural50 = "natural_50" + case splitWins10 = "split_wins_10" + case doubleWins15 = "double_wins_15" + case insuranceWins5 = "insurance_wins_5" + case hands100 = "hands_100" + case hands500 = "hands_500" + case hands1000 = "hands_1000" + case comeback = "comeback" + case perfectPairs5 = "perfect_pairs_5" + case twentyOnePlusThree5 = "twentyone_plus_three_5" + case sideBetWins25 = "side_bet_wins_25" + case allRuleVariations = "all_rule_variations" + + var identifier: String { + "com.yourdomain.blackjack.\(rawValue)" + } + + var title: String { + switch self { + case .firstHand: return String(localized: "First Hand") + case .strategyStudent: return String(localized: "Strategy Student") + case .strategyMaster: return String(localized: "Strategy Master") + case .cardCounter: return String(localized: "Card Counter") + case .trueCountMaster: return String(localized: "True Count Master") + case .natural10: return String(localized: "Natural Talent") + case .natural50: return String(localized: "Natural Legend") + case .splitWins10: return String(localized: "Split Decision") + case .doubleWins15: return String(localized: "Double Down Dynamo") + case .insuranceWins5: return String(localized: "Insurance Agent") + case .hands100: return String(localized: "Rookie") + case .hands500: return String(localized: "Regular") + case .hands1000: return String(localized: "Veteran") + case .comeback: return String(localized: "Comeback Kid") + case .perfectPairs5: return String(localized: "Perfect Pair Player") + case .twentyOnePlusThree5: return String(localized: "21+3 Champion") + case .sideBetWins25: return String(localized: "Side Bet Specialist") + case .allRuleVariations: return String(localized: "Rule Breaker") + } + } + + var description: String { + // Full descriptions for each achievement + } + + var maxProgress: Int { + switch self { + case .firstHand, .comeback: return 1 + case .insuranceWins5, .perfectPairs5, .twentyOnePlusThree5: return 5 + case .splitWins10, .natural10: return 10 + case .doubleWins15: return 15 + case .cardCounter, .sideBetWins25, .strategyMaster: return 25 + case .strategyStudent, .natural50: return 50 + case .hands100: return 100 + case .hands500: return 500 + case .hands1000: return 1000 + case .trueCountMaster: return 10 + case .allRuleVariations: return 40 + } + } + + var isIncremental: Bool { + self != .firstHand && self != .comeback + } + + var iconName: String { + switch self { + case .firstHand: return "play.circle.fill" + case .strategyStudent: return "book.fill" + case .strategyMaster: return "graduationcap.fill" + case .cardCounter: return "brain.head.profile" + case .trueCountMaster: return "chart.line.uptrend.xyaxis" + case .natural10: return "star.fill" + case .natural50: return "star.circle.fill" + case .splitWins10: return "rectangle.split.2x1.fill" + case .doubleWins15: return "arrow.down.circle.fill" + case .insuranceWins5: return "shield.checkered" + case .hands100: return "person.fill" + case .hands500: return "person.2.fill" + case .hands1000: return "person.3.fill" + case .comeback: return "arrow.up.right.circle.fill" + case .perfectPairs5: return "suit.heart.fill" + case .twentyOnePlusThree5: return "suit.diamond.fill" + case .sideBetWins25: return "plus.circle.fill" + case .allRuleVariations: return "gearshape.2.fill" + } + } +} +``` + +### 2. Add Achievement Tracker to GameState + +**File:** `Blackjack/Blackjack/Engine/GameState.swift` + +```swift +import CasinoKit + +@MainActor +@Observable +class GameState { + // ... existing properties ... + + // NEW: Achievement tracking + private(set) var achievementTracker: AchievementTracker + + // Track session-specific metrics for achievements + private var sessionLowBalance: Int = 0 + private var handsPlayedThisSession = 0 + private var lastActionWasStrategyOptimal = false + + init() { + // ... existing init ... + + self.achievementTracker = AchievementTracker() + self.sessionLowBalance = balance + } + + // NEW: Achievement checking methods + func checkAchievements(after action: GameAction) { + // Called after each game action + // Check relevant achievements based on action type + } + + private func updateSessionTracking() { + if balance < sessionLowBalance { + sessionLowBalance = balance + } + + // Check comeback achievement + if sessionLowBalance < 100 && balance >= 10_000 { + achievementTracker.complete(.comeback) + } + } +} +``` + +### 3. Add Achievement Triggers + +Add achievement checks throughout game logic: + +**In `BlackjackEngine.swift` or `GameState.swift`:** + +```swift +// After dealing initial cards +if playerHand.isBlackjack { + achievementTracker.increment(.natural10) + achievementTracker.increment(.natural50) +} + +// After completing a hand +func finishRound() { + // ... existing logic ... + + // First hand achievement + if totalHandsPlayed == 1 { + achievementTracker.complete(.firstHand) + } + + // Persistence achievements + achievementTracker.increment(.hands100) + achievementTracker.increment(.hands500) + achievementTracker.increment(.hands1000) + + // Check if strategy was followed + if lastActionWasStrategyOptimal { + achievementTracker.increment(.strategyStudent) + achievementTracker.increment(.strategyMaster) + } + + // Card counting achievements + if settings.showCount { + achievementTracker.increment(.cardCounter) + + // Check if bet was adjusted based on count + if betWasIncreasedWithPositiveCount { + achievementTracker.increment(.trueCountMaster) + } + } + + updateSessionTracking() +} + +// After winning with split +func checkSplitWin() { + if handWasSplit && playerWon { + achievementTracker.increment(.splitWins10) + } +} + +// After winning with double down +func checkDoubleWin() { + if playerDoubledDown && playerWon { + achievementTracker.increment(.doubleWins15) + } +} + +// After insurance win +func checkInsuranceWin() { + if insuranceBetWon { + achievementTracker.increment(.insuranceWins5) + } +} + +// After side bet wins +func checkSideBetWins() { + if perfectPairsBetWon { + achievementTracker.increment(.perfectPairs5) + achievementTracker.increment(.sideBetWins25) + } + + if twentyOnePlusThreeBetWon { + achievementTracker.increment(.twentyOnePlusThree5) + achievementTracker.increment(.sideBetWins25) + } +} +``` + +### 4. Add Game Center to Settings + +**File:** `Blackjack/Blackjack/Views/Sheets/SettingsView.swift` + +```swift +import CasinoKit + +struct SettingsView: View { + // ... existing code ... + + var body: some View { + SheetContainerView(title: String(localized: "Settings")) { + // ... existing sections ... + + // NEW: Game Center section + GameCenterSettingsSection() + + } onDone: { + dismiss() + } + } +} +``` + +### 5. Optional: Add Game Center Access Point + +**File:** `Blackjack/Blackjack/Views/Game/GameTableView.swift` + +Add to top bar or as floating overlay: + +```swift +TopBarView( + balance: state.balance, + // ... existing parameters ... + leadingButtons: [ + TopBarButton( + icon: "gamecontroller.fill", + accessibilityLabel: "Game Center" + ) { + GameCenterManager.shared.showGameCenterDashboard() + } + ] +) +``` + +### 6. Initialize Game Center on App Launch + +**File:** `Blackjack/Blackjack/BlackjackApp.swift` + +```swift +import CasinoKit + +@main +struct BlackjackApp: App { + init() { + // Authenticate with Game Center silently + Task { + await GameCenterManager.shared.authenticate() + } + } + + // ... rest of app ... +} +``` + +### 7. Add Localized Strings + +**File:** `Blackjack/Blackjack/Resources/Localizable.xcstrings` + +Add all achievement titles, descriptions, and Game Center UI strings. + +## App Store Connect Configuration + +### For Each Achievement: + +1. Log into App Store Connect +2. Select Blackjack app +3. Go to Game Center → Achievements +4. Click "+" to add achievement +5. Configure: + - **Reference Name:** (internal only, e.g., "Strategy Student") + - **Achievement ID:** Must match enum identifier (e.g., `com.yourdomain.blackjack.strategy_student`) + - **Point Value:** As specified above + - **Hidden:** Set to "Yes" for hidden achievements (Rule Breaker) + - **Achievable More Than Once:** No for all + - **Pre-iOS 14 Gamers:** Leave unchecked + - **Localization:** Add EN, ES-MX, FR-CA with titles/descriptions + - **Images:** Upload 512x512 and 1024x1024 versions + +### Achievement Icon Assets + +Create icons matching the SF Symbols specified in the achievement enum. Use your app's color scheme (golds, blues, casino theme). + +## Testing Checklist + +- [ ] All achievements can be triggered in game +- [ ] Incremental achievements show progress correctly +- [ ] Achievements appear in Game Center dashboard +- [ ] Achievements work offline (queue and submit later) +- [ ] Achievement tracking persists across app launches +- [ ] Localization works for all three languages +- [ ] VoiceOver announces achievement unlocks +- [ ] Settings section shows correct Game Center status +- [ ] Strategy-based achievements verify correct play +- [ ] Card counting achievements only trigger when enabled + +## Edge Cases to Handle + +1. **Player resets game mid-session** - Session achievements (comeback) should reset +2. **Player changes rule variations** - Track separately for "Rule Breaker" +3. **Player disables hints** - Strategy achievements still trackable via engine logic +4. **Offline play** - Queue achievements, submit when online +5. **Multiple devices** - Game Center syncs achievement progress automatically + +## Analytics to Track (Optional) + +Consider adding local analytics to understand achievement engagement: +- Which achievements are earned most often? +- Average time to complete each achievement +- Percentage of players who enable card counting (for targeted education) + +## Future Enhancements + +- **Achievement notifications** - Show toast when unlocked (use CasinoKit's AchievementToast) +- **Progress tracking UI** - Show achievement progress in stats view +- **Suggested next achievement** - Guide players toward specific goals + +--- + +## Summary + +- ✅ 18 total achievements +- ✅ Focus on learning and skill, not luck/bankroll +- ✅ Incremental progress for most achievements +- ✅ Hidden achievement for exploring all rule variations +- ✅ Integration with existing GameState and statistics +- ✅ No code duplication (uses CasinoKit infrastructure) + +**Estimated Integration Time:** 2-3 hours after CasinoKit infrastructure is complete. + +--- + +*See `CasinoKit/GAME_CENTER_PLAN.md` for shared infrastructure implementation details.* + diff --git a/CasinoKit/GAME_CENTER_PLAN.md b/CasinoKit/GAME_CENTER_PLAN.md new file mode 100644 index 0000000..3346364 --- /dev/null +++ b/CasinoKit/GAME_CENTER_PLAN.md @@ -0,0 +1,364 @@ +# Game Center Integration - CasinoKit (Shared Infrastructure) + +This document outlines the shared Game Center infrastructure that will be used by all casino games in the workspace. + +## Overview + +Game Center will be integrated for **achievements only** (no leaderboards) to avoid cheating concerns with client-side casino games. All shared Game Center logic will live in CasinoKit to avoid code duplication. + +## Shared Components to Add + +### 1. GameCenterManager (Core Service) + +**Location:** `CasinoKit/Sources/CasinoKit/GameCenter/GameCenterManager.swift` + +**Purpose:** Centralized manager for all Game Center operations. + +**Features:** +- Authentication with Game Center +- Achievement submission +- Achievement progress tracking +- Error handling and retry logic +- Availability checking +- Privacy-friendly (doesn't force sign-in) + +**Key Methods:** +```swift +@MainActor +@Observable +class GameCenterManager { + static let shared = GameCenterManager() + + var isAuthenticated: Bool = false + var isAvailable: Bool = true + var localPlayer: GKLocalPlayer? + + // Authentication + func authenticate() + + // Achievement management + func submitAchievement(_ identifier: String, percentComplete: Double) + func incrementAchievement(_ identifier: String, by value: Double) + func resetAchievements() // For testing only + func loadAchievements() -> [GKAchievement] + + // UI presentation + func showGameCenterDashboard() +} +``` + +**Implementation Notes:** +- Use async/await APIs (iOS 14+) +- Silent authentication (no blocking UI) +- Gracefully handle Game Center being disabled +- Cache authentication state +- Support offline mode (queue submissions for later) + +### 2. Achievement Configuration Protocol + +**Location:** `CasinoKit/Sources/CasinoKit/GameCenter/AchievementDefinition.swift` + +**Purpose:** Type-safe achievement definitions per game. + +```swift +protocol AchievementDefinition { + var identifier: String { get } + var title: String { get } + var description: String { get } + var maxProgress: Int { get } + var iconName: String { get } + var isIncremental: Bool { get } +} + +// Example usage in game: +enum BlackjackAchievement: String, AchievementDefinition { + case strategyStudent = "blackjack_strategy_50" + case cardCounter = "blackjack_card_counting_25" + // ... + + var identifier: String { + "com.yourdomain.blackjack.\(rawValue)" + } + + var title: String { + switch self { + case .strategyStudent: return String(localized: "Strategy Student") + case .cardCounter: return String(localized: "Card Counter") + } + } + + // ... other properties +} +``` + +### 3. Game Center Access Point View + +**Location:** `CasinoKit/Sources/CasinoKit/Views/GameCenter/GameCenterAccessPoint.swift` + +**Purpose:** Standard UI component for showing Game Center status. + +**Features:** +- Shows authentication state +- Displays achievement progress +- Links to Game Center dashboard +- Optional placement (top bar or settings) +- Respects user privacy preferences + +```swift +struct GameCenterAccessPoint: View { + @State private var manager = GameCenterManager.shared + + var body: some View { + // Small floating button or status indicator + // Shows GC icon when authenticated + // Tapping opens achievement list + } +} +``` + +### 4. Achievement Tracking Helper + +**Location:** `CasinoKit/Sources/CasinoKit/GameCenter/AchievementTracker.swift` + +**Purpose:** Helper for games to track achievement progress locally before submitting. + +```swift +@MainActor +@Observable +class AchievementTracker { + private var progress: [String: Int] = [:] + + func increment(_ achievement: Achievement, by value: Int = 1) + func getProgress(_ achievement: Achievement) -> Int + func checkAndSubmit(_ achievement: Achievement) + func reset() // For new game sessions +} +``` + +**Why This Helps:** +- Games can track progress locally (fast) +- Batch submissions to Game Center (efficient) +- Handles the "report once at 100%" logic for non-incremental achievements + +### 5. Settings Integration + +**Location:** `CasinoKit/Sources/CasinoKit/Views/Settings/GameCenterSettingsSection.swift` + +**Purpose:** Standard settings UI for Game Center. + +```swift +struct GameCenterSettingsSection: View { + @State private var manager = GameCenterManager.shared + + var body: some View { + SheetSection(title: "GAME CENTER", icon: "gamecontroller.fill") { + if manager.isAvailable { + HStack { + Text("Status") + Spacer() + Text(manager.isAuthenticated ? "Connected" : "Not Connected") + .foregroundStyle(.secondary) + } + + if manager.isAuthenticated { + Button("View Achievements") { + manager.showGameCenterDashboard() + } + + Button("Sign Out") { + // Note: Can't actually sign out from app, + // just stop authenticating + } + } else { + Button("Connect to Game Center") { + Task { await manager.authenticate() } + } + } + } else { + Text("Game Center is not available") + .foregroundStyle(.secondary) + } + } + } +} +``` + +### 6. Achievement Toast Notification + +**Location:** `CasinoKit/Sources/CasinoKit/Views/GameCenter/AchievementToast.swift` + +**Purpose:** Show celebratory notification when achievement is earned (optional enhancement). + +**Features:** +- Brief animation when achievement unlocks +- Shows achievement icon and title +- Auto-dismisses after 3 seconds +- Doesn't block gameplay +- Similar to iOS system notifications + +```swift +struct AchievementToast: View { + let achievement: String + let title: String + @State private var isShowing = false + + var body: some View { + // Slide-in notification from top + // Shows achievement icon + title + // Fades out after delay + } +} +``` + +## File Structure + +``` +CasinoKit/Sources/CasinoKit/ +├── GameCenter/ +│ ├── GameCenterManager.swift # Core manager (authentication, submission) +│ ├── AchievementDefinition.swift # Protocol for type-safe achievements +│ ├── AchievementTracker.swift # Local progress tracking helper +│ └── GameCenterError.swift # Custom error types +├── Views/ +│ └── GameCenter/ +│ ├── GameCenterAccessPoint.swift # Status indicator/button +│ ├── GameCenterSettingsSection.swift # Settings UI component +│ └── AchievementToast.swift # Achievement unlock notification +└── Resources/ + └── Localizable.xcstrings # Add GC-related strings +``` + +## Localization Strings to Add + +Add to `CasinoKit/Resources/Localizable.xcstrings`: + +``` +Game Center +Connected +Not Connected +Sign Out +View Achievements +Game Center is not available +Achievement Unlocked! +Connect to Game Center +``` + +## App Store Connect Configuration + +**Note:** Each game will need its own achievements configured in App Store Connect. + +### Achievement Naming Convention + +Use consistent identifier format: +``` +com.yourdomain.{game}.{achievement_key} + +Examples: +com.yourdomain.blackjack.strategy_student +com.yourdomain.blackjack.card_counter +com.yourdomain.baccarat.dragon_master +com.yourdomain.baccarat.natural_high +``` + +### Achievement Assets + +Each achievement needs: +- 512x512px icon (1x) +- 1024x1024px icon (2x) + +**Design Guidelines:** +- Use SF Symbols where appropriate (consistent with app design) +- Match app color scheme +- Clear, recognizable icons +- Consider accessibility (high contrast) + +## Implementation Order + +1. **GameCenterManager** - Core authentication and submission logic +2. **AchievementDefinition protocol** - Type system +3. **AchievementTracker** - Helper for games to use +4. **GameCenterSettingsSection** - UI integration +5. **GameCenterAccessPoint** - Optional status indicator +6. **AchievementToast** - Optional enhancement + +## Testing Strategy + +### Local Testing +- Test with Game Center sandbox account +- Verify authentication flow +- Test achievement submission +- Test offline behavior +- Test achievement progress tracking + +### TestFlight Testing +- Required for full Game Center integration testing +- Verify achievements appear correctly +- Test achievement notifications +- Verify localization + +### Debug Features to Add +```swift +#if DEBUG +extension GameCenterManager { + func resetAllAchievements() { + // Only available in debug builds + } + + func logAchievementStatus() { + // Print all achievement progress + } +} +#endif +``` + +## Privacy Considerations + +- **No forced sign-in** - Game Center is entirely optional +- **Graceful degradation** - App works fully without Game Center +- **No data collection** - Only submit achievement progress, nothing else +- **User control** - Easy to see status and disconnect + +## Performance Considerations + +- **Authenticate once** - On app launch, silent background auth +- **Batch submissions** - Don't submit every increment immediately +- **Cache state** - Remember authentication status +- **Async operations** - Never block UI on Game Center calls +- **Offline queue** - Store failed submissions, retry later + +## Future Enhancements (Optional) + +- **Challenge Mode** - Separate game mode with fixed rules and leaderboards +- **Friend Comparison** - Show achievement progress vs friends +- **Weekly Challenges** - Time-limited achievement variants +- **Leaderboards** - Only if Challenge Mode is added with anti-cheat + +## Dependencies + +- **GameKit framework** - Apple's Game Center SDK +- **No third-party dependencies** - Pure Apple APIs + +## Estimated Effort + +- **CasinoKit infrastructure**: 4-6 hours +- **Per-game integration**: 2-3 hours each +- **App Store Connect setup**: 1-2 hours +- **Testing & polish**: 2-3 hours + +**Total: ~12-16 hours** for complete implementation across both games. + +--- + +## Next Steps + +1. Review this plan +2. See game-specific plans in `Blackjack/GAME_CENTER_PLAN.md` and `Baccarat/GAME_CENTER_PLAN.md` +3. Configure achievements in App Store Connect +4. Implement CasinoKit infrastructure first +5. Integrate into games second +6. Test with TestFlight + +--- + +*This shared infrastructure approach ensures consistency across all casino games while avoiding code duplication.* +