// // StatisticsSheetView.swift // Baccarat // // Detailed statistics and scoreboard view. // import SwiftUI import CasinoKit /// A sheet that displays detailed game statistics and Big Road scoreboard. struct StatisticsSheetView: View { let results: [RoundResult] @Environment(\.dismiss) private var dismiss // MARK: - Computed Statistics private var totalRounds: Int { results.count } private var playerWins: Int { results.filter { $0.result == .playerWins }.count } private var bankerWins: Int { results.filter { $0.result == .bankerWins }.count } private var tieCount: Int { results.filter { $0.result == .tie }.count } private var playerPairs: Int { results.filter { $0.playerPair }.count } private var bankerPairs: Int { results.filter { $0.bankerPair }.count } private var naturals: Int { results.filter { $0.isNatural }.count } private func percentage(_ count: Int) -> String { guard totalRounds > 0 else { return "0%" } let pct = Double(count) / Double(totalRounds) * 100 return String(format: "%.0f%%", pct) } var body: some View { SheetContainerView( title: String(localized: "Statistics"), content: { // Summary stats summarySection // Win distribution winDistributionSection // Side bet frequency sideBetSection // Big Road display bigRoadSection }, onDone: { dismiss() }, doneButtonText: String(localized: "Done") ) } // MARK: - Summary Section private var summarySection: some View { SheetSection(title: "SESSION SUMMARY", icon: "chart.pie.fill") { HStack(spacing: Design.Spacing.xLarge) { StatBox( value: "\(totalRounds)", label: String(localized: "Rounds"), color: .white ) StatBox( value: "\(naturals)", label: String(localized: "Naturals"), color: .yellow ) } .frame(maxWidth: .infinity) } } // MARK: - Win Distribution Section private var winDistributionSection: some View { SheetSection(title: "WIN DISTRIBUTION", icon: "trophy.fill") { VStack(spacing: Design.Spacing.medium) { HStack(spacing: Design.Spacing.medium) { WinStatView( title: String(localized: "Player"), count: playerWins, percentage: percentage(playerWins), color: .blue ) WinStatView( title: String(localized: "Tie"), count: tieCount, percentage: percentage(tieCount), color: .green ) WinStatView( title: String(localized: "Banker"), count: bankerWins, percentage: percentage(bankerWins), color: .red ) } // Win bar visualization if totalRounds > 0 { WinDistributionBar( playerWins: playerWins, tieCount: tieCount, bankerWins: bankerWins ) .frame(height: Design.Spacing.large) .clipShape(.rect(cornerRadius: Design.CornerRadius.small)) } } } } // MARK: - Side Bet Section private var sideBetSection: some View { SheetSection(title: "SIDE BET FREQUENCY", icon: "sparkles") { HStack(spacing: Design.Spacing.xLarge) { PairStatView( title: String(localized: "P Pair"), count: playerPairs, percentage: percentage(playerPairs), color: .blue ) PairStatView( title: String(localized: "B Pair"), count: bankerPairs, percentage: percentage(bankerPairs), color: .red ) } .frame(maxWidth: .infinity) } } // MARK: - Big Road Section private var bigRoadSection: some View { SheetSection(title: "BIG ROAD", icon: "chart.bar.xaxis") { if results.isEmpty { Text(String(localized: "No rounds played yet")) .font(.system(size: Design.BaseFontSize.body)) .foregroundStyle(.white.opacity(Design.Opacity.secondary)) .frame(maxWidth: .infinity) .padding(.vertical, Design.Spacing.xLarge) } else { BigRoadView(results: results) .frame(height: Design.Size.bigRoadHeight) } } } } // MARK: - Supporting Views /// A box displaying a single statistic. private struct StatBox: View { let value: String let label: String let color: Color var body: some View { VStack(spacing: Design.Spacing.xSmall) { Text(value) .font(.system(size: Design.BaseFontSize.xxLarge, weight: .bold, design: .rounded)) .foregroundStyle(color) Text(label) .font(.system(size: Design.BaseFontSize.small)) .foregroundStyle(.white.opacity(Design.Opacity.secondary)) } .frame(minWidth: Design.Size.statBoxMinWidth) .padding(Design.Spacing.medium) .background( RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .fill(Color.black.opacity(Design.Opacity.overlay)) ) } } /// A win stat display with count and percentage. private struct WinStatView: View { let title: String let count: Int let percentage: String let color: Color var body: some View { VStack(spacing: Design.Spacing.xSmall) { Circle() .fill(color) .frame(width: Design.Size.winIndicatorSize, height: Design.Size.winIndicatorSize) Text("\(count)") .font(.system(size: Design.BaseFontSize.title, weight: .bold)) .foregroundStyle(.white) Text(percentage) .font(.system(size: Design.BaseFontSize.small)) .foregroundStyle(.white.opacity(Design.Opacity.secondary)) Text(title) .font(.system(size: Design.BaseFontSize.small)) .foregroundStyle(color) } .frame(maxWidth: .infinity) } } /// A pair stat display. private struct PairStatView: View { let title: String let count: Int let percentage: String let color: Color var body: some View { VStack(spacing: Design.Spacing.xSmall) { Text("\(count)") .font(.system(size: Design.BaseFontSize.title, weight: .bold)) .foregroundStyle(color) Text(percentage) .font(.system(size: Design.BaseFontSize.small)) .foregroundStyle(.white.opacity(Design.Opacity.secondary)) Text(title) .font(.system(size: Design.BaseFontSize.small)) .foregroundStyle(.white.opacity(Design.Opacity.accent)) } } } /// A horizontal bar showing win distribution. private struct WinDistributionBar: View { let playerWins: Int let tieCount: Int let bankerWins: Int private var total: Int { playerWins + tieCount + bankerWins } var body: some View { GeometryReader { geometry in HStack(spacing: 0) { if playerWins > 0 { Rectangle() .fill(Color.blue) .frame(width: geometry.size.width * CGFloat(playerWins) / CGFloat(total)) } if tieCount > 0 { Rectangle() .fill(Color.green) .frame(width: geometry.size.width * CGFloat(tieCount) / CGFloat(total)) } if bankerWins > 0 { Rectangle() .fill(Color.red) .frame(width: geometry.size.width * CGFloat(bankerWins) / CGFloat(total)) } } } } } /// The Big Road scoreboard - a grid showing result patterns. /// Results are arranged in columns, with each column representing a streak of same results. private struct BigRoadView: View { let results: [RoundResult] private let maxRows = 6 private let cellSize: CGFloat = Design.Size.bigRoadCellSize /// Convert results into columns for Big Road display. private var columns: [[RoundResult]] { var cols: [[RoundResult]] = [] var currentCol: [RoundResult] = [] var lastResult: GameResult? for result in results { // Skip ties for column tracking (ties go in the current column) let currentResult = result.result if currentResult == .tie { // Ties don't start new columns, they go with the current streak if !currentCol.isEmpty { currentCol.append(result) } else if !cols.isEmpty { cols[cols.count - 1].append(result) } else { currentCol.append(result) } } else if lastResult == nil || currentResult == lastResult { // Same as last or first result - continue column currentCol.append(result) lastResult = currentResult } else { // Different result - start new column if !currentCol.isEmpty { cols.append(currentCol) } currentCol = [result] lastResult = currentResult } } // Add remaining column if !currentCol.isEmpty { cols.append(currentCol) } return cols } var body: some View { ScrollView(.horizontal) { HStack(spacing: Design.Spacing.xxSmall) { ForEach(Array(columns.enumerated()), id: \.offset) { _, column in VStack(spacing: Design.Spacing.xxSmall) { ForEach(Array(column.prefix(maxRows).enumerated()), id: \.offset) { _, result in BigRoadCell(result: result) } // If column has more than maxRows, show overflow count if column.count > maxRows { Text("+\(column.count - maxRows)") .font(.system(size: Design.BaseFontSize.xxSmall)) .foregroundStyle(.white.opacity(Design.Opacity.secondary)) } Spacer(minLength: 0) } } } .padding(Design.Spacing.small) } .background( RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .fill(Color.black.opacity(Design.Opacity.overlay)) ) .scrollIndicators(.hidden) } } /// A single cell in the Big Road display. private struct BigRoadCell: View { let result: RoundResult private let cellSize: CGFloat = Design.Size.bigRoadCellSize private var color: Color { switch result.result { case .playerWins: return .blue case .bankerWins: return .red case .tie: return .green } } var body: some View { ZStack { // Main circle Circle() .stroke(color, lineWidth: Design.LineWidth.medium) .frame(width: cellSize, height: cellSize) // Pair indicator (small dot at bottom) if result.hasPair { Circle() .fill(Color.yellow) .frame(width: cellSize * 0.25, height: cellSize * 0.25) .offset(y: cellSize * 0.3) } // Natural indicator (small dot at top) if result.isNatural { Circle() .fill(Color.white) .frame(width: cellSize * 0.25, height: cellSize * 0.25) .offset(y: -cellSize * 0.3) } // Tie diagonal line if it's a tie if result.result == .tie { Rectangle() .fill(color) .frame(width: cellSize * 0.8, height: Design.LineWidth.medium) .rotationEffect(.degrees(-45)) } } } } // MARK: - Design Constants Extensions extension Design.Size { static let bigRoadHeight: CGFloat = 200 static let bigRoadCellSize: CGFloat = 24 static let statBoxMinWidth: CGFloat = 80 static let winIndicatorSize: CGFloat = 24 } // MARK: - Preview #Preview { StatisticsSheetView(results: [ RoundResult(result: .playerWins, playerValue: 8, bankerValue: 6, playerPair: true), RoundResult(result: .playerWins, playerValue: 7, bankerValue: 5), RoundResult(result: .bankerWins, playerValue: 4, bankerValue: 7), RoundResult(result: .bankerWins, playerValue: 3, bankerValue: 8, bankerPair: true), RoundResult(result: .bankerWins, playerValue: 2, bankerValue: 6), RoundResult(result: .tie, playerValue: 5, bankerValue: 5), RoundResult(result: .playerWins, playerValue: 9, bankerValue: 3), RoundResult(result: .bankerWins, playerValue: 2, bankerValue: 8, playerPair: true, bankerPair: true) ]) }