CasinoGames/Blackjack/Views/Sheets/RulesHelpView.swift

291 lines
12 KiB
Swift

//
// RulesHelpView.swift
// Blackjack
//
// Game rules and how to play guide.
//
import SwiftUI
import CasinoKit
struct RulesHelpView: View {
@Environment(\.dismiss) private var dismiss
@State private var currentPage = 0
private let pages: [RulePage] = [
RulePage(
title: String(localized: "Objective"),
icon: "target",
content: [
String(localized: "Beat the dealer by getting a hand value closer to 21 without going over."),
String(localized: "If you go over 21, you 'bust' and lose immediately."),
String(localized: "If the dealer busts and you haven't, you win.")
]
),
RulePage(
title: String(localized: "Card Values"),
icon: "suit.spade.fill",
content: [
String(localized: "2-10: Face value"),
String(localized: "Jack, Queen, King: 10"),
String(localized: "Ace: 1 or 11 (whichever helps your hand)"),
String(localized: "A 'soft' hand has an Ace counting as 11.")
]
),
RulePage(
title: String(localized: "Blackjack"),
icon: "star.fill",
content: [
String(localized: "An Ace + 10-value card dealt initially is 'Blackjack'."),
String(localized: "Blackjack pays 3:2 (1.5x your bet)."),
String(localized: "If both you and dealer have Blackjack, it's a push (tie).")
]
),
RulePage(
title: String(localized: "Actions"),
icon: "hand.tap.fill",
content: [
String(localized: "Hit: Take another card"),
String(localized: "Stand: Keep your current hand"),
String(localized: "Double Down: Double your bet, take one card, then stand"),
String(localized: "Split: If you have two cards of the same value, split into two hands"),
String(localized: "Surrender: Give up half your bet and end the hand")
]
),
RulePage(
title: String(localized: "Insurance"),
icon: "shield.fill",
content: [
String(localized: "Offered when dealer shows an Ace."),
String(localized: "Costs half your original bet."),
String(localized: "Pays 2:1 if dealer has Blackjack."),
String(localized: "Generally not recommended by basic strategy.")
]
),
RulePage(
title: String(localized: "Dealer Rules"),
icon: "person.fill",
content: [
String(localized: "Dealer must hit on 16 or less."),
String(localized: "Dealer must stand on 17 or more (varies by rules)."),
String(localized: "Some games: Dealer hits on 'soft 17' (Ace + 6).")
]
),
RulePage(
title: String(localized: "Payouts"),
icon: "dollarsign.circle.fill",
content: [
String(localized: "Win: 1:1 (even money)"),
String(localized: "Blackjack: 3:2"),
String(localized: "Insurance: 2:1"),
String(localized: "Push: Bet returned"),
String(localized: "Surrender: Half bet returned")
]
),
RulePage(
title: String(localized: "Vegas Strip"),
icon: "sparkles",
content: [
String(localized: "Most popular style on the Las Vegas Strip."),
String(localized: "6 decks shuffled together."),
String(localized: "Dealer stands on all 17s (including soft 17)."),
String(localized: "Double down allowed on any two cards."),
String(localized: "Double after split (DAS) allowed."),
String(localized: "Split up to 4 hands, but not aces."),
String(localized: "No surrender option."),
String(localized: "Blackjack pays 3:2.")
]
),
RulePage(
title: String(localized: "Atlantic City"),
icon: "building.2.fill",
content: [
String(localized: "Standard rules on the East Coast."),
String(localized: "8 decks shuffled together."),
String(localized: "Dealer stands on all 17s."),
String(localized: "Double down on any two cards."),
String(localized: "Double after split allowed."),
String(localized: "Re-split aces allowed."),
String(localized: "Late surrender available."),
String(localized: "Blackjack pays 3:2.")
]
),
RulePage(
title: String(localized: "European"),
icon: "globe.europe.africa.fill",
content: [
String(localized: "Traditional European casino style."),
String(localized: "6 decks shuffled together."),
String(localized: "No hole card: dealer takes second card after player acts."),
String(localized: "Dealer stands on all 17s."),
String(localized: "Double on 9, 10, or 11 only (some venues)."),
String(localized: "Double after split allowed."),
String(localized: "No surrender option."),
String(localized: "Higher house edge due to no hole card.")
]
),
RulePage(
title: String(localized: "Deck Count"),
icon: "rectangle.stack.fill",
content: [
String(localized: "1 Deck: Lowest house edge (~0.17%), rare to find."),
String(localized: "2 Decks: Low house edge (~0.35%), common online."),
String(localized: "4 Decks: Moderate house edge (~0.45%)."),
String(localized: "6 Decks: Standard in Vegas (~0.50%)."),
String(localized: "8 Decks: Standard in Atlantic City (~0.55%)."),
String(localized: "More decks = harder to count cards."),
String(localized: "Fewer decks favor the player slightly.")
]
),
RulePage(
title: String(localized: "Rule Variations"),
icon: "slider.horizontal.3",
content: [
String(localized: "Dealer Hits Soft 17: Increases house edge by ~0.2%."),
String(localized: "Double After Split (DAS): Reduces house edge by ~0.15%."),
String(localized: "Re-split Aces: Reduces house edge by ~0.05%."),
String(localized: "Late Surrender: Reduces house edge by ~0.07%."),
String(localized: "6:5 Blackjack (avoid!): Increases house edge by ~1.4%.")
]
),
RulePage(
title: String(localized: "Basic Strategy"),
icon: "lightbulb.fill",
content: [
String(localized: "Always split Aces and 8s."),
String(localized: "Never split 10s or 5s."),
String(localized: "Double on 11 vs dealer 2-10."),
String(localized: "Double on 10 vs dealer 2-9."),
String(localized: "Stand on 17+ always."),
String(localized: "Hit on soft 17 or less."),
String(localized: "Surrender 16 vs dealer 9, 10, Ace.")
]
),
RulePage(
title: String(localized: "Card Counting"),
icon: "number.circle.fill",
content: [
String(localized: "Hi-Lo is the most popular counting system."),
String(localized: "2-6: +1 (low cards favor house)"),
String(localized: "7-9: 0 (neutral cards)"),
String(localized: "10-A: -1 (high cards favor player)"),
String(localized: "Running Count: Sum of all card values seen."),
String(localized: "True Count: Running count ÷ decks remaining."),
String(localized: "Positive count = more high cards remain = player advantage.")
]
),
RulePage(
title: String(localized: "Using the Count"),
icon: "chart.line.uptrend.xyaxis",
content: [
String(localized: "True count of +2 or higher favors the player."),
String(localized: "Increase bets when the count is positive."),
String(localized: "Decrease bets when the count is negative."),
String(localized: "Fewer decks = easier to count accurately."),
String(localized: "Count resets to 0 when the shoe is shuffled."),
String(localized: "Enable 'Card Count' in Settings to practice.")
]
)
]
var body: some View {
NavigationStack {
ZStack {
Color.Settings.background
.ignoresSafeArea()
VStack(spacing: 0) {
// Page content
TabView(selection: $currentPage) {
ForEach(pages.indices, id: \.self) { index in
RulePageView(page: pages[index])
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .never))
// Page indicator
HStack(spacing: Design.Spacing.small) {
ForEach(pages.indices, id: \.self) { index in
Circle()
.fill(index == currentPage ? Color.Settings.accent : Color.white.opacity(Design.Opacity.light))
.frame(width: 8, height: 8)
}
}
.padding(.vertical, Design.Spacing.medium)
}
}
.navigationTitle(String(localized: "How to Play"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button(String(localized: "Done")) {
dismiss()
}
.foregroundStyle(Color.Settings.accent)
}
}
.toolbarBackground(Color.Settings.background, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar)
}
}
}
// MARK: - Rule Page Model
struct RulePage: Identifiable {
let id = UUID()
let title: String
let icon: String
let content: [String]
}
// MARK: - Rule Page View
struct RulePageView: View {
let page: RulePage
@ScaledMetric(relativeTo: .title) private var iconSize: CGFloat = Design.BaseFontSize.display
@ScaledMetric(relativeTo: .title) private var titleSize: CGFloat = Design.BaseFontSize.title
@ScaledMetric(relativeTo: .body) private var bodySize: CGFloat = Design.BaseFontSize.body
var body: some View {
ScrollView {
VStack(spacing: Design.Spacing.xLarge) {
// Icon
Image(systemName: page.icon)
.font(.system(size: iconSize))
.foregroundStyle(Color.Settings.accent)
.padding(.top, Design.Spacing.xxLarge)
// Title
Text(page.title)
.font(.system(size: titleSize, weight: .bold))
.foregroundStyle(.white)
// Content
VStack(alignment: .leading, spacing: Design.Spacing.medium) {
ForEach(page.content.indices, id: \.self) { index in
HStack(alignment: .top, spacing: Design.Spacing.medium) {
Text("")
.foregroundStyle(Color.Settings.accent)
Text(page.content[index])
.font(.system(size: bodySize))
.foregroundStyle(.white.opacity(Design.Opacity.heavy))
}
}
}
.padding(.horizontal, Design.Spacing.xxLarge)
Spacer()
}
}
}
}
#Preview {
RulesHelpView()
}