// // RulesHelpView.swift // Baccarat // // A paginated help view explaining game rules and side bets. // import SwiftUI import CasinoKit /// The available rule pages. enum RulesPage: Int, CaseIterable, Identifiable { case basicRules = 0 case thirdCardRules = 1 case dragonBonus = 2 case pairBonus = 3 var id: Int { rawValue } var title: String { switch self { case .basicRules: return String(localized: "How to Play") case .thirdCardRules: return String(localized: "Third Card Rules") case .dragonBonus: return String(localized: "Dragon Bonus") case .pairBonus: return String(localized: "Pair Bonus") } } } /// A multi-page help view explaining baccarat rules. struct RulesHelpView: View { @Environment(\.dismiss) private var dismiss @State private var currentPage: RulesPage = .basicRules // MARK: - Layout Constants private let modalCornerRadius = Design.CornerRadius.xxxLarge private let contentCornerRadius = Design.CornerRadius.xLarge private let contentPadding = Design.Spacing.large private let buttonSize: CGFloat = 44 // MARK: - Body var body: some View { ZStack { // Background - same as other sheets Color.Settings.background .ignoresSafeArea() VStack(spacing: Design.Spacing.medium) { // Header with logo headerView // Content card contentCard .padding(.horizontal) // Navigation navigationView .padding(.bottom, Design.Spacing.large) } } } // MARK: - Subviews private var headerView: some View { VStack(spacing: Design.Spacing.small) { // Cards icon HStack(spacing: -Design.Spacing.small) { Image(systemName: "suit.spade.fill") .foregroundStyle(.white) Image(systemName: "suit.heart.fill") .foregroundStyle(.red) } .font(.system(size: Design.BaseFontSize.title)) Text("BACCARAT") .font(.system(size: Design.BaseFontSize.title - Design.Spacing.xSmall, weight: .black, design: .rounded)) .foregroundStyle( LinearGradient( colors: [.yellow, .orange], startPoint: .top, endPoint: .bottom ) ) .tracking(3) } .padding(.top, Design.Spacing.xLarge) } private var contentCard: some View { VStack(spacing: 0) { // Page title Text(currentPage.title) .font(.system(size: Design.BaseFontSize.xxLarge + Design.Spacing.xxSmall, weight: .bold, design: .rounded)) .foregroundStyle(.yellow) .padding(.top, Design.Spacing.large) .padding(.bottom, Design.Spacing.medium) // Scrollable content ScrollView { pageContent .padding(.horizontal, contentPadding) .padding(.bottom, Design.Spacing.large) } } .frame(maxWidth: .infinity) .background( RoundedRectangle(cornerRadius: contentCornerRadius) .fill(Color.white.opacity(Design.Opacity.verySubtle)) ) .clipShape(RoundedRectangle(cornerRadius: contentCornerRadius)) } @ViewBuilder private var pageContent: some View { switch currentPage { case .basicRules: BasicRulesContent() case .thirdCardRules: ThirdCardRulesContent() case .dragonBonus: DragonBonusContent() case .pairBonus: PairBonusContent() } } private var navigationView: some View { HStack(spacing: Design.Spacing.medium) { // Previous button Button { withAnimation(.spring(duration: Design.Animation.quick)) { goToPreviousPage() } } label: { Image(systemName: "chevron.left.circle.fill") .font(.system(size: Design.BaseFontSize.largeTitle)) .foregroundStyle(currentPage.rawValue > 0 ? .yellow : .gray.opacity(Design.Opacity.medium)) } .disabled(currentPage.rawValue == 0) // Back to game button Button { dismiss() } label: { Text("BACK TO GAME") .font(.system(size: Design.BaseFontSize.large, weight: .bold)) .foregroundStyle(.black) .padding(.horizontal, Design.Spacing.xLarge) .padding(.vertical, Design.Spacing.medium) .background( Capsule() .fill( LinearGradient( colors: [Color.Button.goldLight, Color.Button.goldDark], startPoint: .top, endPoint: .bottom ) ) ) } // Next button Button { withAnimation(.spring(duration: Design.Animation.quick)) { goToNextPage() } } label: { Image(systemName: "chevron.right.circle.fill") .font(.system(size: Design.BaseFontSize.largeTitle)) .foregroundStyle(currentPage.rawValue < RulesPage.allCases.count - 1 ? .green : .gray.opacity(Design.Opacity.medium)) } .disabled(currentPage.rawValue >= RulesPage.allCases.count - 1) } } // MARK: - Navigation private func goToPreviousPage() { if currentPage.rawValue > 0 { currentPage = RulesPage(rawValue: currentPage.rawValue - 1) ?? .basicRules } } private func goToNextPage() { if currentPage.rawValue < RulesPage.allCases.count - 1 { currentPage = RulesPage(rawValue: currentPage.rawValue + 1) ?? .pairBonus } } } // MARK: - Basic Rules Content private struct BasicRulesContent: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { RuleSection(text: "Two hands are dealt: one for the Player and one for the Banker. You may bet on which hand will win, or that they will tie.") RuleSection(title: "Payouts", items: [ "Player wins: pays 1 to 1", "Banker wins: pays 0.95 to 1 (5% commission)", "Tie: pays 8 to 1" ]) Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Card Values") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(items: [ "Aces = 1 point", "2-9 = Face value", "10, J, Q, K = 0 points" ]) RuleSection(text: "Hand values are the sum of cards, keeping only the last digit. For example: 7 + 8 = 15, so the hand value is 5.") Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Natural Win") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(text: "If either hand totals 8 or 9 with the first two cards, it's a \"Natural\" and the round ends immediately.") } } } // MARK: - Third Card Rules Content private struct ThirdCardRulesContent: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { RuleSection(text: "If neither hand has a Natural, additional cards may be drawn according to fixed rules.") Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Player Rules") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(items: [ "0-5: Player draws a third card", "6-7: Player stands" ]) Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Banker Rules") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(text: "If Player stood (6-7), Banker draws on 0-5 and stands on 6-7.") RuleSection(text: "If Player drew a third card, Banker's action depends on both the Banker's total and the Player's third card:") VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { BankerRuleRow(bankerTotal: "0-2", action: "Always draws") BankerRuleRow(bankerTotal: "3", action: "Draws unless Player's 3rd was 8") BankerRuleRow(bankerTotal: "4", action: "Draws if Player's 3rd was 2-7") BankerRuleRow(bankerTotal: "5", action: "Draws if Player's 3rd was 4-7") BankerRuleRow(bankerTotal: "6", action: "Draws if Player's 3rd was 6-7") BankerRuleRow(bankerTotal: "7", action: "Always stands") } .padding() .background( RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .fill(Color.black.opacity(Design.Opacity.hint)) ) } } } private struct BankerRuleRow: View { let bankerTotal: String let action: String private let labelWidth: CGFloat = 80 var body: some View { HStack { Text("Banker \(bankerTotal):") .font(.system(size: Design.BaseFontSize.callout, weight: .semibold)) .foregroundStyle(.yellow) .frame(width: labelWidth, alignment: .leading) Text(action) .font(.system(size: Design.BaseFontSize.callout)) .foregroundStyle(.white.opacity(Design.Opacity.almostFull)) } } } // MARK: - Dragon Bonus Content private struct DragonBonusContent: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { RuleSection(text: "The Dragon Bonus is a side bet available for both Player and Banker. It pays based on how the winning hand wins.") Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Payout Table") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) VStack(spacing: Design.Spacing.xSmall) { PayoutRow(condition: "Natural Win (8 or 9)", payout: "1 to 1") PayoutRow(condition: "Win by 9 points", payout: "30 to 1") PayoutRow(condition: "Win by 8 points", payout: "10 to 1") PayoutRow(condition: "Win by 7 points", payout: "6 to 1") PayoutRow(condition: "Win by 6 points", payout: "4 to 1") PayoutRow(condition: "Win by 5 points", payout: "2 to 1") PayoutRow(condition: "Win by 4 points", payout: "1 to 1") } .padding() .background( RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .fill(Color.black.opacity(Design.Opacity.hint)) ) Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Important") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(items: [ "Dragon Bonus loses if your side doesn't win", "Dragon Bonus loses on a tie", "Wins by less than 4 points also lose" ]) } } } private struct PayoutRow: View { let condition: String let payout: String var body: some View { HStack { Text(condition) .font(.system(size: Design.BaseFontSize.medium)) .foregroundStyle(.white.opacity(Design.Opacity.almostFull)) Spacer() Text(payout) .font(.system(size: Design.BaseFontSize.medium, weight: .bold)) .foregroundStyle(.yellow) } .padding(.vertical, Design.Spacing.xxSmall) } } // MARK: - Pair Bonus Content private struct PairBonusContent: View { var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.medium) { RuleSection(text: "Pair Bonus bets are available for both Player and Banker. They pay when the first two cards dealt to that hand form a pair.") Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Payout") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) HStack { VStack { Text("11:1") .font(.system(size: Design.BaseFontSize.largeTitle + Design.Spacing.medium, weight: .black, design: .rounded)) .foregroundStyle( LinearGradient( colors: [.yellow, .orange], startPoint: .top, endPoint: .bottom ) ) Text("Pair Pays") .font(.system(size: Design.BaseFontSize.medium, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.strong)) } .frame(maxWidth: .infinity) } .padding(.vertical, Design.Spacing.medium) Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Examples") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(items: [ "5♥ + 5♣ = Pair (wins 11:1)", "J♦ + J♠ = Pair (wins 11:1)", "A♥ + A♥ = Pair (wins 11:1)" ]) RuleSection(text: "Note: Suits are disregarded. Only the rank matters for a pair.") Divider() .background(Color.white.opacity(Design.Opacity.light)) Text("Tips") .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) .foregroundStyle(.white) RuleSection(items: [ "Pair bets are independent of the main game result", "You can bet on Player Pair, Banker Pair, or both", "Pairs occur roughly once every 15 hands" ]) } } } // MARK: - Helper Views private struct RuleSection: View { var title: String? = nil var text: String? = nil var items: [String]? = nil var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { if let title = title { Text(title) .font(.system(size: Design.BaseFontSize.large, weight: .semibold)) .foregroundStyle(.yellow) } if let text = text { Text(text) .font(.system(size: Design.BaseFontSize.medium)) .foregroundStyle(.white.opacity(Design.Opacity.almostFull)) .fixedSize(horizontal: false, vertical: true) } if let items = items { ForEach(items, id: \.self) { item in HStack(alignment: .top, spacing: Design.Spacing.small) { Text("•") .foregroundStyle(.yellow) Text(item) .foregroundStyle(.white.opacity(Design.Opacity.almostFull)) } .font(.system(size: Design.BaseFontSize.medium)) } } } } } #Preview { RulesHelpView() }