CasinoGames/Baccarat/Views/RoadMapView.swift

153 lines
5.0 KiB
Swift

//
// RoadMapView.swift
// Baccarat
//
// A visual history display of recent game results (Big Road style).
//
import SwiftUI
import CasinoKit
/// A simple road display showing recent results.
struct RoadMapView: View {
let results: [RoundResult]
// MARK: - Scaled Fonts (Dynamic Type)
@ScaledMetric(relativeTo: .caption2) private var historyFontSize: CGFloat = Design.BaseFontSize.small
@ScaledMetric(relativeTo: .caption2) private var dotSize: CGFloat = 22
// MARK: - Accessibility
private var historySummary: String {
if results.isEmpty {
return String(localized: "No history yet")
}
let playerWins = results.filter { $0.result == .playerWins }.count
let bankerWins = results.filter { $0.result == .bankerWins }.count
let ties = results.filter { $0.result == .tie }.count
let format = String(localized: "historySummaryFormat")
return String(format: format, results.count, playerWins, bankerWins, ties)
}
var body: some View {
VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
Text("HISTORY")
.font(.system(size: historyFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white.opacity(Design.Opacity.accent))
.tracking(1)
ScrollView(.horizontal) {
HStack(spacing: Design.Spacing.xSmall) {
ForEach(results) { result in
RoadDot(
result: result.result,
dotSize: dotSize,
hasPair: result.hasPair,
isNatural: result.isNatural
)
}
}
.padding(.vertical, Design.Spacing.xSmall)
}
.scrollIndicators(.hidden)
}
.padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, Design.Spacing.small)
.background(
RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.fill(Color.black.opacity(Design.Opacity.light))
)
.accessibilityElement(children: .ignore)
.accessibilityLabel(String(localized: "Game history"))
.accessibilityValue(historySummary)
}
}
/// A single dot in the road display.
struct RoadDot: View {
let result: GameResult
let dotSize: CGFloat
var hasPair: Bool = false
var isNatural: Bool = false
// MARK: - Layout Constants
private var markerSize: CGFloat { dotSize * 0.3 }
// MARK: - Scaled Fonts (Dynamic Type)
@ScaledMetric(relativeTo: .caption2) private var labelFontSize: CGFloat = Design.BaseFontSize.small
private var color: Color {
switch result {
case .playerWins: return .blue
case .bankerWins: return .red
case .tie: return .green
}
}
private var label: String {
switch result {
case .playerWins: return "P"
case .bankerWins: return "B"
case .tie: return "T"
}
}
var body: some View {
ZStack {
Circle()
.fill(color)
.frame(width: dotSize, height: dotSize)
Circle()
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
.frame(width: dotSize, height: dotSize)
Text(label)
.font(.system(size: labelFontSize, weight: .bold))
.foregroundStyle(.white)
// Pair marker (bottom-left corner)
if hasPair {
Circle()
.fill(Color.yellow)
.frame(width: markerSize, height: markerSize)
.overlay(
Circle()
.strokeBorder(Color.white, lineWidth: Design.LineWidth.thin)
)
.offset(x: -dotSize * 0.35, y: dotSize * 0.35)
}
// Natural marker (top-right corner - star shape)
if isNatural {
Image(systemName: "star.fill")
.font(.system(size: markerSize))
.foregroundStyle(.yellow)
.offset(x: dotSize * 0.35, y: -dotSize * 0.35)
}
}
}
}
#Preview {
ZStack {
Color.Table.preview
.ignoresSafeArea()
RoadMapView(results: [
RoundResult(result: .playerWins, playerValue: 8, bankerValue: 6, playerPair: true),
RoundResult(result: .bankerWins, playerValue: 4, bankerValue: 7),
RoundResult(result: .tie, playerValue: 5, bankerValue: 5, bankerPair: true),
RoundResult(result: .playerWins, playerValue: 9, bankerValue: 3),
RoundResult(result: .bankerWins, playerValue: 2, bankerValue: 8, playerPair: true, bankerPair: true)
])
.padding()
}
}