// // ChipView.swift // Baccarat // // Casino-style betting chips with realistic design. // import SwiftUI /// The available chip denominations. enum ChipDenomination: Int, CaseIterable, Identifiable { case ten = 10 case twentyFive = 25 case fifty = 50 case hundred = 100 case fiveHundred = 500 case thousand = 1_000 case fiveThousand = 5_000 case tenThousand = 10_000 case twentyFiveThousand = 25_000 case fiftyThousand = 50_000 case hundredThousand = 100_000 var id: Int { rawValue } /// The display text for this denomination. var displayText: String { switch self { case .ten: return "10" case .twentyFive: return "25" case .fifty: return "50" case .hundred: return "100" case .fiveHundred: return "500" case .thousand: return "1K" case .fiveThousand: return "5K" case .tenThousand: return "10K" case .twentyFiveThousand: return "25K" case .fiftyThousand: return "50K" case .hundredThousand: return "100K" } } /// The minimum balance required to show this chip in the selector. /// Higher chips unlock as you win more! var unlockBalance: Int { switch self { case .ten, .twentyFive, .fifty, .hundred: return 0 // Always available case .fiveHundred: return 500 case .thousand: return 1_000 case .fiveThousand: return 5_000 case .tenThousand: return 10_000 case .twentyFiveThousand: return 25_000 case .fiftyThousand: return 50_000 case .hundredThousand: return 100_000 } } /// Whether this chip should be shown based on the player's balance. func isUnlocked(forBalance balance: Int) -> Bool { balance >= unlockBalance } /// Returns chips that should be visible for a given balance. static func availableChips(forBalance balance: Int) -> [ChipDenomination] { allCases.filter { $0.isUnlocked(forBalance: balance) } } /// The primary color for this chip. var primaryColor: Color { switch self { case .ten: return Color.Chip.tenBase case .twentyFive: return Color.Chip.twentyFiveBase case .fifty: return Color.Chip.fiftyBase case .hundred: return Color.Chip.hundredBase case .fiveHundred: return Color.Chip.fiveHundredBase case .thousand: return Color.Chip.thousandBase case .fiveThousand: return Color.Chip.fiveThousandBase case .tenThousand: return Color.Chip.tenThousandBase case .twentyFiveThousand: return Color.Chip.twentyFiveThousandBase case .fiftyThousand: return Color.Chip.fiftyThousandBase case .hundredThousand: return Color.Chip.hundredThousandBase } } /// The secondary/accent color for this chip. var secondaryColor: Color { switch self { case .ten: return Color.Chip.tenHighlight case .twentyFive: return Color.Chip.twentyFiveHighlight case .fifty: return Color.Chip.fiftyHighlight case .hundred: return Color.Chip.hundredHighlight case .fiveHundred: return Color.Chip.fiveHundredHighlight case .thousand: return Color.Chip.thousandHighlight case .fiveThousand: return Color.Chip.fiveThousandHighlight case .tenThousand: return Color.Chip.tenThousandHighlight case .twentyFiveThousand: return Color.Chip.twentyFiveThousandHighlight case .fiftyThousand: return Color.Chip.fiftyThousandHighlight case .hundredThousand: return Color.Chip.hundredThousandHighlight } } /// The edge stripe color for this chip. var stripeColor: Color { switch self { case .ten, .twentyFive, .fifty: return .white case .hundred: return Color.Chip.goldStripe case .fiveHundred: return .white case .thousand: return .black case .fiveThousand: return Color.Chip.goldStripe case .tenThousand: return .white case .twentyFiveThousand: return Color.Chip.goldStripe case .fiftyThousand: return Color.Chip.darkStripe case .hundredThousand: return Color.Chip.goldRubyStripe } } } /// A realistic casino-style betting chip. struct ChipView: View { let denomination: ChipDenomination let size: CGFloat var isSelected: Bool = false init(denomination: ChipDenomination, size: CGFloat = 60, isSelected: Bool = false) { self.denomination = denomination self.size = size self.isSelected = isSelected } var body: some View { ZStack { // Base circle with gradient Circle() .fill( RadialGradient( colors: [ denomination.secondaryColor, denomination.primaryColor, denomination.primaryColor.opacity(0.8) ], center: .topLeading, startRadius: 0, endRadius: size ) ) // Edge stripes pattern ChipEdgePattern(stripeColor: denomination.stripeColor) .clipShape(.circle) // Inner circle Circle() .fill( RadialGradient( colors: [ denomination.secondaryColor, denomination.primaryColor ], center: .topLeading, startRadius: 0, endRadius: size * 0.4 ) ) .frame(width: size * 0.65, height: size * 0.65) // Inner border Circle() .strokeBorder( denomination.stripeColor.opacity(0.8), lineWidth: 2 ) .frame(width: size * 0.65, height: size * 0.65) // Denomination text Text(denomination.displayText) .font(.system(size: size * 0.25, weight: .heavy, design: .rounded)) .foregroundStyle(denomination.stripeColor) .shadow(color: .black.opacity(0.3), radius: 1, x: 1, y: 1) // Outer border Circle() .strokeBorder( LinearGradient( colors: [ Color.white.opacity(0.4), Color.black.opacity(0.3) ], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 2 ) // Selection glow if isSelected { Circle() .strokeBorder(Color.yellow, lineWidth: 3) .frame(width: size + 6, height: size + 6) } } .frame(width: size, height: size) .shadow(color: .black.opacity(0.4), radius: isSelected ? 8 : 4, x: 2, y: 3) .scaleEffect(isSelected ? 1.1 : 1.0) .animation(.spring(duration: 0.2), value: isSelected) } } /// The edge stripe pattern for chips. struct ChipEdgePattern: View { let stripeColor: Color var body: some View { Canvas { context, size in let center = CGPoint(x: size.width / 2, y: size.height / 2) let radius = min(size.width, size.height) / 2 let stripeCount = 16 let stripeWidth: CGFloat = 6 for i in 0.. 0 { result.append((denom, min(count, maxChips))) remaining -= count * denom.rawValue } } return result } var body: some View { ZStack { ForEach(chipBreakdown.indices, id: \.self) { index in let (denom, _) = chipBreakdown[index] ChipView(denomination: denom, size: 40) .offset(y: CGFloat(-index * 4)) } } } } #Preview { ZStack { Color.Table.preview .ignoresSafeArea() VStack(spacing: Design.Spacing.xxxLarge) { HStack(spacing: Design.Spacing.xLarge) { ForEach(ChipDenomination.allCases) { denom in ChipView(denomination: denom, size: Design.Size.chipSelector) } } ChipView(denomination: .hundred, size: 80, isSelected: true) } } }