Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
9081337dc1
commit
28eb33439e
@ -159,11 +159,9 @@ final class GameState {
|
||||
|
||||
/// Handles data received from iCloud (e.g., after fresh install or from another device).
|
||||
private func handleCloudDataReceived(_ cloudData: BaccaratGameData) {
|
||||
print("GameState: Received cloud data with \(cloudData.roundsPlayed) rounds")
|
||||
|
||||
// Only update if cloud has more progress than current state
|
||||
guard cloudData.roundsPlayed > roundHistory.count else {
|
||||
print("GameState: Local data is newer, ignoring cloud data")
|
||||
return
|
||||
}
|
||||
|
||||
@ -181,8 +179,6 @@ final class GameState {
|
||||
bankerPair: saved.bankerPair
|
||||
)
|
||||
}
|
||||
|
||||
print("GameState: Restored from cloud - \(cloudData.roundsPlayed) rounds, balance: \(cloudData.balance)")
|
||||
}
|
||||
|
||||
// MARK: - Persistence
|
||||
@ -208,8 +204,6 @@ final class GameState {
|
||||
bankerPair: saved.bankerPair
|
||||
)
|
||||
}
|
||||
|
||||
print("GameState: Restored \(savedData.roundsPlayed) rounds, balance: \(savedData.balance)")
|
||||
}
|
||||
|
||||
/// Saves current game state to iCloud/local storage.
|
||||
|
||||
@ -295,8 +295,6 @@ final class GameSettings {
|
||||
if let volume = iCloudStore.object(forKey: Keys.soundVolume) as? Double {
|
||||
self.soundVolume = Float(volume)
|
||||
}
|
||||
|
||||
print("GameSettings: Loaded from iCloud")
|
||||
}
|
||||
|
||||
/// Saves settings to UserDefaults and iCloud.
|
||||
@ -327,7 +325,6 @@ final class GameSettings {
|
||||
iCloudStore.set(hapticsEnabled, forKey: Keys.hapticsEnabled)
|
||||
iCloudStore.set(Double(soundVolume), forKey: Keys.soundVolume)
|
||||
iCloudStore.synchronize()
|
||||
print("GameSettings: Saved to iCloud")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,8 +21,8 @@
|
||||
"value" : "-%lld $"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"%lld" : {
|
||||
"comment" : "The number of rounds a player has played in the game.",
|
||||
"localizations" : {
|
||||
@ -67,8 +67,8 @@
|
||||
"value" : "%lld."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"%lldpx" : {
|
||||
"comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.",
|
||||
"localizations" : {
|
||||
@ -113,8 +113,8 @@
|
||||
"value" : "•"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"• Add to Assets.xcassets/AppIcon" : {
|
||||
"comment" : "A step in the process of exporting app icons.",
|
||||
"localizations" : {
|
||||
@ -158,8 +158,8 @@
|
||||
"value" : "• Appeler IconRenderer.renderAppIcon(config: .baccarat)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"• Run the preview in Xcode" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -203,8 +203,8 @@
|
||||
"value" : "• Enregistrer l'UIImage résultante dans les fichiers"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"• Screenshot the 1024px icon" : {
|
||||
"comment" : "A step in the process of exporting app icons, describing how to take a screenshot of a 1024px icon.",
|
||||
"localizations" : {
|
||||
@ -249,8 +249,8 @@
|
||||
"value" : "• Utiliser un outil en ligne pour générer toutes les tailles"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"↓ then →" : {
|
||||
"comment" : "A textual instruction for using the road map in the game.",
|
||||
"localizations" : {
|
||||
@ -295,8 +295,8 @@
|
||||
"value" : "+%lld"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"+$%lld" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -340,8 +340,8 @@
|
||||
"value" : "2-9: Valeur faciale"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"8 : 1" : {
|
||||
"comment" : "The payout ratio for a tie bet.",
|
||||
"localizations" : {
|
||||
@ -386,8 +386,8 @@
|
||||
"value" : "10, Valet, Dame, Roi: 0 point"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"11 : 1" : {
|
||||
"comment" : "The payout ratio for a pair bet.",
|
||||
"localizations" : {
|
||||
@ -432,8 +432,8 @@
|
||||
"value" : "Un Naturel termine la manche immédiatement."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Ace: 1 point" : {
|
||||
"comment" : "Card value description for an Ace.",
|
||||
"localizations" : {
|
||||
@ -477,8 +477,8 @@
|
||||
"value" : "Ajoutez %lld$ de plus pour atteindre le minimum"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"After generating:" : {
|
||||
"comment" : "A heading for the instructions section of the icon generator view.",
|
||||
"localizations" : {
|
||||
@ -523,8 +523,8 @@
|
||||
"value" : "Toutes les tailles"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Alternative: Use an online tool" : {
|
||||
"comment" : "A section header that suggests using an online tool to generate app icon sizes.",
|
||||
"localizations" : {
|
||||
@ -569,8 +569,8 @@
|
||||
"value" : "Misez toujours sur le Banquier — il a les meilleures chances (1.06% d'avantage)."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Animate dealing and flipping" : {
|
||||
"comment" : "Subtitle for card animations toggle.",
|
||||
"localizations" : {
|
||||
@ -615,8 +615,8 @@
|
||||
"value" : "Icône de l'app"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"App Icon Preview" : {
|
||||
"comment" : "A header describing the preview of the app icon.",
|
||||
"localizations" : {
|
||||
@ -661,8 +661,8 @@
|
||||
"value" : "Évitez la mise sur Égalité — 14.4% d'avantage maison!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"B Pair" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -706,8 +706,8 @@
|
||||
"value" : "Le Baccarat a l'un des plus faibles avantages maison du casino."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Banker" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -751,8 +751,8 @@
|
||||
"value" : "BANQUE"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Banker 0-2: Always draws" : {
|
||||
"comment" : "Description of the third card rule for the Banker when the Player's third card is 0-2.",
|
||||
"localizations" : {
|
||||
@ -797,8 +797,8 @@
|
||||
"value" : "Banquier 3: Tire sauf si la 3e du Joueur était 8"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Banker 4: Draws if Player's 3rd was 2-7" : {
|
||||
"comment" : "Side bet rule for the Banker when the Player's third card is 4 and it falls between 2 and 7.",
|
||||
"localizations" : {
|
||||
@ -843,8 +843,8 @@
|
||||
"value" : "Banquier 5: Tire si la 3e du Joueur était 4-7"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Banker 6: Draws if Player's 3rd was 6-7" : {
|
||||
"comment" : "Description of the betting strategy for the Banker when the Player's third card is 6-7.",
|
||||
"localizations" : {
|
||||
@ -958,8 +958,8 @@
|
||||
"value" : "Main du banquier"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Bet on which hand will win: Player, Banker, or Tie." : {
|
||||
"comment" : "Text describing the objective of the baccarat game.",
|
||||
"localizations" : {
|
||||
@ -1004,8 +1004,8 @@
|
||||
"value" : "Blackjack"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"BONUS" : {
|
||||
"comment" : "The text displayed in the center of the bonus zone.",
|
||||
"localizations" : {
|
||||
@ -1232,8 +1232,8 @@
|
||||
"value" : "Effacer toutes les données?"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"CLOUD SYNC" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -1276,8 +1276,8 @@
|
||||
"value" : "DONNÉES"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Deal" : {
|
||||
"comment" : "The label of a button that deals cards in a game.",
|
||||
"localizations" : {
|
||||
@ -1322,8 +1322,8 @@
|
||||
"value" : "Distribution..."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"DECK SETTINGS" : {
|
||||
"comment" : "Section header for deck configuration settings.",
|
||||
"localizations" : {
|
||||
@ -1391,8 +1391,8 @@
|
||||
"value" : "Afficher le compteur de cartes en haut"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Display result road map" : {
|
||||
"comment" : "Subtitle for show history toggle.",
|
||||
"localizations" : {
|
||||
@ -1437,8 +1437,8 @@
|
||||
"value" : "Terminé"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Dragon Bonus" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -1482,8 +1482,8 @@
|
||||
"value" : "Le Bonus Dragon est amusant mais a ~2.7% d'avantage maison."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Example: 5♥ + 5♣ = Pair (wins!)" : {
|
||||
"comment" : "Example of a pair bet winning.",
|
||||
"localizations" : {
|
||||
@ -1528,10 +1528,11 @@
|
||||
"value" : "Historique"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Game Over" : {
|
||||
"comment" : "The title of the game over screen.",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@ -1555,6 +1556,7 @@
|
||||
},
|
||||
"GAME OVER" : {
|
||||
"comment" : "The title of the game over screen.",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@ -1574,8 +1576,8 @@
|
||||
"value" : "FIN DE PARTIE"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Generate & Save Icons" : {
|
||||
"comment" : "A button label that triggers the generation of app icons.",
|
||||
"localizations" : {
|
||||
@ -1620,8 +1622,8 @@
|
||||
"value" : "Icônes générées:"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Generating..." : {
|
||||
"comment" : "A text that appears while generating icons.",
|
||||
"localizations" : {
|
||||
@ -1666,8 +1668,8 @@
|
||||
"value" : "Les valeurs de main n'utilisent que le dernier chiffre (ex., 7+8=15 → 5)."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"handValueFormat" : {
|
||||
"comment" : "Format for displaying hand value. The argument is the numeric value of the hand.",
|
||||
"localizations" : {
|
||||
@ -1712,8 +1714,8 @@
|
||||
"value" : "Retour Haptique"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"HISTORY" : {
|
||||
"comment" : "A label displayed above the road map view, indicating that it shows a history of past game results.",
|
||||
"localizations" : {
|
||||
@ -1758,8 +1760,8 @@
|
||||
"value" : "%lld parties : %lld joueur, %lld banquier, %lld égalités"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"How to Export Icons" : {
|
||||
"comment" : "A section header explaining how to export app icons.",
|
||||
"localizations" : {
|
||||
@ -1804,8 +1806,8 @@
|
||||
"value" : "Comment jouer"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"iCloud Sync" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -1848,8 +1850,8 @@
|
||||
"value" : "iCloud non disponible"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Icon" : {
|
||||
"comment" : "The title for the tab that displays the app icon preview.",
|
||||
"localizations" : {
|
||||
@ -1894,8 +1896,8 @@
|
||||
"value" : "Générateur d'icônes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"If either hand totals 8 or 9 with two cards, it's a 'Natural'." : {
|
||||
"comment" : "Description of the 'Natural' hand in baccarat, explaining when it occurs and its significance.",
|
||||
"localizations" : {
|
||||
@ -1940,8 +1942,8 @@
|
||||
"value" : "Si aucune main n'a un Naturel, les règles de la troisième carte s'appliquent."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"If Player draws, Banker's action depends on Player's third card:" : {
|
||||
"comment" : "Explanation of the third card decision for the Banker in the Rules Help view.",
|
||||
"localizations" : {
|
||||
@ -1986,8 +1988,8 @@
|
||||
"value" : "Si le Joueur reste, le Banquier tire sur 0-5, reste sur 6-7."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Independent of the main game result." : {
|
||||
"comment" : "Note about the independence of the Pair Bonus from the main game result in the Rules Help view.",
|
||||
"localizations" : {
|
||||
@ -2098,8 +2100,8 @@
|
||||
"value" : "Tours joués"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Main Bets" : {
|
||||
"comment" : "Title of a rule page in the \"Rules\" help view, describing the main bets available in baccarat.",
|
||||
"localizations" : {
|
||||
@ -2144,8 +2146,8 @@
|
||||
"value" : "MAX"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Natural 9 beats Natural 8." : {
|
||||
"comment" : "Explanation of the payout for a Baccarat hand that is a Natural 9, compared to one that is a Natural 8.",
|
||||
"localizations" : {
|
||||
@ -2190,8 +2192,8 @@
|
||||
"value" : "Main naturelle"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Natural Win: 1:1" : {
|
||||
"comment" : "Description of the payout for a 'Natural Win' in the Rules Help view.",
|
||||
"localizations" : {
|
||||
@ -2371,8 +2373,8 @@
|
||||
"value" : "Objectif"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Only the rank matters (suits are ignored)." : {
|
||||
"comment" : "Explanation of how to determine if the first two cards in a hand form a pair, focusing on the rank rather than the suit.",
|
||||
"localizations" : {
|
||||
@ -2645,8 +2647,8 @@
|
||||
"value" : "Joueur"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"PLAYER" : {
|
||||
"comment" : "The label for the player's hand in the cards display area.",
|
||||
"localizations" : {
|
||||
@ -2737,8 +2739,8 @@
|
||||
"value" : "Joueur avec 0-5: Tire une troisième carte"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Player with 6-7: Stands" : {
|
||||
"comment" : "Description of the action for the Banker when the Player draws a third card.",
|
||||
"localizations" : {
|
||||
@ -2783,8 +2785,8 @@
|
||||
"value" : "Joueur avec 8-9: Naturel (pas de troisième carte)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Poker" : {
|
||||
"comment" : "The name of a poker game.",
|
||||
"localizations" : {
|
||||
@ -2828,8 +2830,8 @@
|
||||
"value" : "Politique de confidentialité"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Reset to Defaults" : {
|
||||
"comment" : "A button label that resets game settings to their default values.",
|
||||
"localizations" : {
|
||||
@ -2874,8 +2876,8 @@
|
||||
"value" : "Roulette"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Rounds" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -2900,6 +2902,7 @@
|
||||
},
|
||||
"Rounds Played" : {
|
||||
"comment" : "A label displayed next to the number of rounds played in the game over screen.",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@ -2988,8 +2991,8 @@
|
||||
"value" : "Afficher Cartes Restantes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Show History" : {
|
||||
"comment" : "Toggle label for showing game history.",
|
||||
"localizations" : {
|
||||
@ -3034,8 +3037,8 @@
|
||||
"value" : "Mise secondaire sur le Joueur ou Banquier gagnant par une marge."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Side bet on the first two cards being a pair." : {
|
||||
"comment" : "Description of a side bet where the player bets on whether the first two cards dealt in a hand are a pair.",
|
||||
"localizations" : {
|
||||
@ -3079,8 +3082,8 @@
|
||||
"value" : "Connectez-vous à iCloud pour synchroniser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"SOUND & HAPTICS" : {
|
||||
"comment" : "Section header for sound and haptic settings.",
|
||||
"localizations" : {
|
||||
@ -3125,8 +3128,8 @@
|
||||
"value" : "Effets Sonores"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"STARTING BALANCE" : {
|
||||
"comment" : "Section header for starting balance settings.",
|
||||
"localizations" : {
|
||||
@ -3170,8 +3173,8 @@
|
||||
"value" : "Statistiques"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"Strategy Tips" : {
|
||||
"comment" : "Title of a section in the Rules Help view focused on strategy tips.",
|
||||
"localizations" : {
|
||||
@ -3284,8 +3287,8 @@
|
||||
"value" : "%@ $ - %@ $"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"The hand closest to 9 wins." : {
|
||||
"comment" : "Explanation of how the hand closest to 9 wins in baccarat.",
|
||||
"localizations" : {
|
||||
@ -3330,8 +3333,8 @@
|
||||
"value" : "Aucune compétence requise — profitez simplement du jeu!"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"These show how the same pattern works for other games" : {
|
||||
"comment" : "A description below the section of the view that previews icons for other games.",
|
||||
"localizations" : {
|
||||
@ -3443,8 +3446,8 @@
|
||||
"value" : "Égalité"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"TIE" : {
|
||||
"comment" : "The text displayed in the TIE betting zone.",
|
||||
"localizations" : {
|
||||
@ -3489,8 +3492,8 @@
|
||||
"value" : "Mise Égalité: Paie 8:1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
"TOTAL" : {
|
||||
"comment" : "A label displayed next to the total winnings in the result banner.",
|
||||
"localizations" : {
|
||||
|
||||
@ -1,189 +0,0 @@
|
||||
//
|
||||
// GameOverView.swift
|
||||
// Baccarat
|
||||
//
|
||||
// Game over screen shown when player runs out of money.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CasinoKit
|
||||
|
||||
/// Game over screen shown when player runs out of money.
|
||||
struct GameOverView: View {
|
||||
let roundsPlayed: Int
|
||||
let onPlayAgain: () -> Void
|
||||
|
||||
@State private var showContent = false
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
|
||||
/// Maximum width for the modal card on iPad
|
||||
private var maxModalWidth: CGFloat {
|
||||
horizontalSizeClass == .regular ? CasinoDesign.Size.maxModalWidth : .infinity
|
||||
}
|
||||
|
||||
// MARK: - Scaled Font Sizes (Dynamic Type)
|
||||
|
||||
@ScaledMetric(relativeTo: .largeTitle) private var iconSize: CGFloat = Design.BaseFontSize.display
|
||||
@ScaledMetric(relativeTo: .largeTitle) private var titleFontSize: CGFloat = Design.BaseFontSize.largeTitle
|
||||
@ScaledMetric(relativeTo: .body) private var messageFontSize: CGFloat = Design.BaseFontSize.xLarge
|
||||
@ScaledMetric(relativeTo: .body) private var statsFontSize: CGFloat = 17
|
||||
@ScaledMetric(relativeTo: .headline) private var buttonFontSize: CGFloat = Design.BaseFontSize.xLarge
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let modalCornerRadius = Design.CornerRadius.xxxLarge
|
||||
private let statsCornerRadius = Design.CornerRadius.large
|
||||
private let cardPadding = Design.Spacing.xxxLarge
|
||||
private let contentSpacing: CGFloat = 28
|
||||
private let buttonHorizontalPadding: CGFloat = 48
|
||||
private let buttonVerticalPadding: CGFloat = 18
|
||||
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Solid dark backdrop - fully opaque
|
||||
Color.black
|
||||
.ignoresSafeArea()
|
||||
|
||||
// Modal card
|
||||
modalContent
|
||||
}
|
||||
.onAppear {
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
|
||||
showContent = true
|
||||
}
|
||||
}
|
||||
.accessibilityElement(children: .contain)
|
||||
.accessibilityLabel(String(localized: "Game Over"))
|
||||
.accessibilityAddTraits(.isModal)
|
||||
}
|
||||
|
||||
// MARK: - Private Views
|
||||
|
||||
private var modalContent: some View {
|
||||
VStack(spacing: contentSpacing) {
|
||||
// Broke icon
|
||||
Image(systemName: "creditcard.trianglebadge.exclamationmark")
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(.red)
|
||||
.symbolEffect(.pulse, options: .repeating)
|
||||
|
||||
// Title
|
||||
Text("GAME OVER")
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
// Message
|
||||
Text("You've run out of chips!")
|
||||
.font(.system(size: messageFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||
|
||||
// Stats card
|
||||
statsCard
|
||||
|
||||
// Play Again button
|
||||
playAgainButton
|
||||
}
|
||||
.padding(cardPadding)
|
||||
.background(modalBackground)
|
||||
.shadow(color: .red.opacity(Design.Opacity.hint), radius: Design.Shadow.radiusXXLarge)
|
||||
.frame(maxWidth: maxModalWidth)
|
||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||
.scaleEffect(showContent ? Design.Scale.normal : Design.Scale.slightShrink)
|
||||
.opacity(showContent ? 1.0 : 0)
|
||||
}
|
||||
|
||||
private var statsCard: some View {
|
||||
VStack(spacing: Design.Spacing.medium) {
|
||||
HStack {
|
||||
Text("Rounds Played")
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
Spacer()
|
||||
Text("\(roundsPlayed)")
|
||||
.bold()
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
.font(.system(size: statsFontSize))
|
||||
.padding()
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: statsCornerRadius)
|
||||
.fill(Color.white.opacity(Design.Opacity.subtle))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: statsCornerRadius)
|
||||
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin)
|
||||
)
|
||||
)
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
}
|
||||
|
||||
private var playAgainButton: some View {
|
||||
Button {
|
||||
onPlayAgain()
|
||||
} label: {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: "arrow.counterclockwise")
|
||||
Text("Play Again")
|
||||
}
|
||||
.font(.system(size: buttonFontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.padding(.horizontal, buttonHorizontalPadding)
|
||||
.padding(.vertical, buttonVerticalPadding)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
)
|
||||
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusXLarge)
|
||||
}
|
||||
.padding(.top, Design.Spacing.medium)
|
||||
}
|
||||
|
||||
private var modalBackground: some View {
|
||||
RoundedRectangle(cornerRadius: modalCornerRadius)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.CasinoModal.backgroundLight, Color.CasinoModal.backgroundDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: modalCornerRadius)
|
||||
.strokeBorder(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color.red.opacity(Design.Opacity.medium),
|
||||
Color.red.opacity(Design.Opacity.hint)
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: Design.LineWidth.medium
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
#Preview("Game Over") {
|
||||
GameOverView(
|
||||
roundsPlayed: 42,
|
||||
onPlayAgain: {}
|
||||
)
|
||||
}
|
||||
|
||||
#Preview("Few Rounds") {
|
||||
GameOverView(
|
||||
roundsPlayed: 3,
|
||||
onPlayAgain: {}
|
||||
)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,168 +0,0 @@
|
||||
//
|
||||
// BettingZone.swift
|
||||
// CasinoKit
|
||||
//
|
||||
// A reusable betting zone for casino table layouts.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
/// A tappable betting zone with label and chip display.
|
||||
public struct BettingZone: View {
|
||||
/// The zone label (e.g., "PLAYER", "TIE", "INSURANCE").
|
||||
public let label: String
|
||||
|
||||
/// Optional payout info (e.g., "1:1", "8:1").
|
||||
public let payoutInfo: String?
|
||||
|
||||
/// Current bet amount (0 if no bet).
|
||||
public let betAmount: Int
|
||||
|
||||
/// Whether the zone is enabled for betting.
|
||||
public let isEnabled: Bool
|
||||
|
||||
/// Action when the zone is tapped.
|
||||
public let onTap: () -> Void
|
||||
|
||||
/// Background color for the zone.
|
||||
public let backgroundColor: Color
|
||||
|
||||
/// Text color.
|
||||
public let textColor: Color
|
||||
|
||||
// Layout
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 16
|
||||
@ScaledMetric(relativeTo: .caption) private var payoutFontSize: CGFloat = 12
|
||||
|
||||
/// Creates a betting zone.
|
||||
public init(
|
||||
label: String,
|
||||
payoutInfo: String? = nil,
|
||||
betAmount: Int = 0,
|
||||
isEnabled: Bool = true,
|
||||
backgroundColor: Color = .blue.opacity(0.2),
|
||||
textColor: Color = .white,
|
||||
onTap: @escaping () -> Void
|
||||
) {
|
||||
self.label = label
|
||||
self.payoutInfo = payoutInfo
|
||||
self.betAmount = betAmount
|
||||
self.isEnabled = isEnabled
|
||||
self.backgroundColor = backgroundColor
|
||||
self.textColor = textColor
|
||||
self.onTap = onTap
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Button(action: onTap) {
|
||||
ZStack {
|
||||
// Background
|
||||
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.medium)
|
||||
.fill(backgroundColor)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: CasinoDesign.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
textColor.opacity(CasinoDesign.Opacity.light),
|
||||
lineWidth: CasinoDesign.LineWidth.thin
|
||||
)
|
||||
)
|
||||
|
||||
// Content
|
||||
VStack(spacing: CasinoDesign.Spacing.xxSmall) {
|
||||
Text(label)
|
||||
.font(.system(size: labelFontSize, weight: .bold))
|
||||
.foregroundStyle(textColor)
|
||||
|
||||
if let payout = payoutInfo {
|
||||
Text(payout)
|
||||
.font(.system(size: payoutFontSize, weight: .medium))
|
||||
.foregroundStyle(textColor.opacity(CasinoDesign.Opacity.medium))
|
||||
}
|
||||
}
|
||||
|
||||
// Chip badge for bet amount
|
||||
if betAmount > 0 {
|
||||
ChipBadge(amount: betAmount)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
.padding(CasinoDesign.Spacing.xSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(!isEnabled)
|
||||
.accessibilityLabel(label)
|
||||
.accessibilityValue(betAmount > 0 ? "$\(betAmount) bet" : "No bet")
|
||||
.accessibilityHint(isEnabled ? "Double tap to place bet" : "Betting disabled")
|
||||
}
|
||||
}
|
||||
|
||||
/// A small chip badge showing bet amount.
|
||||
public struct ChipBadge: View {
|
||||
public let amount: Int
|
||||
|
||||
private let badgeSize: CGFloat = 28
|
||||
private let fontSize: CGFloat = 10
|
||||
|
||||
public init(amount: Int) {
|
||||
self.amount = amount
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(Color.yellow)
|
||||
.frame(width: badgeSize, height: badgeSize)
|
||||
|
||||
Circle()
|
||||
.strokeBorder(Color.orange, lineWidth: 2)
|
||||
.frame(width: badgeSize - 4, height: badgeSize - 4)
|
||||
|
||||
Text(formattedAmount)
|
||||
.font(.system(size: fontSize, weight: .bold))
|
||||
.foregroundStyle(.black)
|
||||
.minimumScaleFactor(0.5)
|
||||
}
|
||||
}
|
||||
|
||||
private var formattedAmount: String {
|
||||
if amount >= 1_000_000 {
|
||||
return "\(amount / 1_000_000)M"
|
||||
} else if amount >= 1_000 {
|
||||
return "\(amount / 1_000)K"
|
||||
}
|
||||
return "\(amount)"
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ZStack {
|
||||
Color.CasinoTable.felt.ignoresSafeArea()
|
||||
|
||||
HStack(spacing: 20) {
|
||||
BettingZone(
|
||||
label: "PLAYER",
|
||||
payoutInfo: "1:1",
|
||||
betAmount: 0,
|
||||
backgroundColor: .blue.opacity(0.2)
|
||||
) { }
|
||||
.frame(width: 120, height: 80)
|
||||
|
||||
BettingZone(
|
||||
label: "TIE",
|
||||
payoutInfo: "8:1",
|
||||
betAmount: 500,
|
||||
backgroundColor: .green.opacity(0.2)
|
||||
) { }
|
||||
.frame(width: 80, height: 80)
|
||||
|
||||
BettingZone(
|
||||
label: "BANKER",
|
||||
payoutInfo: "0.95:1",
|
||||
betAmount: 2500,
|
||||
backgroundColor: .red.opacity(0.2)
|
||||
) { }
|
||||
.frame(width: 120, height: 80)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user