CasinoGames/CasinoKit/Sources/CasinoKit/Models/Card.swift

167 lines
5.1 KiB
Swift

//
// Card.swift
// CasinoKit
//
// A playing card model with suit, rank, and value calculations.
//
import Foundation
/// Represents the four suits in a standard deck of cards.
public enum Suit: String, CaseIterable, Identifiable, Sendable {
case hearts = ""
case diamonds = ""
case clubs = ""
case spades = ""
public var id: String { rawValue }
/// Whether this suit is red (hearts or diamonds).
public var isRed: Bool {
self == .hearts || self == .diamonds
}
/// Accessibility name for VoiceOver.
public var accessibilityName: String {
switch self {
case .hearts: return String(localized: "Hearts", bundle: .module)
case .diamonds: return String(localized: "Diamonds", bundle: .module)
case .clubs: return String(localized: "Clubs", bundle: .module)
case .spades: return String(localized: "Spades", bundle: .module)
}
}
}
/// Represents the rank of a card from Ace through King.
public enum Rank: Int, CaseIterable, Identifiable, Sendable {
case ace = 1
case two = 2
case three = 3
case four = 4
case five = 5
case six = 6
case seven = 7
case eight = 8
case nine = 9
case ten = 10
case jack = 11
case queen = 12
case king = 13
public var id: Int { rawValue }
/// The display symbol for this rank.
public var symbol: String {
switch self {
case .ace: return "A"
case .jack: return "J"
case .queen: return "Q"
case .king: return "K"
default: return String(rawValue)
}
}
/// The standard point value (Ace = 1, 2-10 = face value, J/Q/K = 10).
public var standardValue: Int {
switch self {
case .ace: return 1
case .jack, .queen, .king: return 10
default: return rawValue
}
}
/// The baccarat point value (Ace = 1, 2-9 = face value, 10/J/Q/K = 0).
public var baccaratValue: Int {
switch self {
case .ace: return 1
case .two, .three, .four, .five, .six, .seven, .eight, .nine:
return rawValue
case .ten, .jack, .queen, .king:
return 0
}
}
/// The blackjack value (Ace = 1 or 11, 2-10 = face value, J/Q/K = 10).
/// Note: Ace flexibility (1 or 11) should be handled by game logic.
public var blackjackValue: Int {
switch self {
case .ace: return 11 // Game logic should handle soft/hard hands
case .jack, .queen, .king: return 10
default: return rawValue
}
}
/// Accessibility name for VoiceOver.
public var accessibilityName: String {
switch self {
case .ace: return String(localized: "Ace", bundle: .module)
case .two: return String(localized: "Two", bundle: .module)
case .three: return String(localized: "Three", bundle: .module)
case .four: return String(localized: "Four", bundle: .module)
case .five: return String(localized: "Five", bundle: .module)
case .six: return String(localized: "Six", bundle: .module)
case .seven: return String(localized: "Seven", bundle: .module)
case .eight: return String(localized: "Eight", bundle: .module)
case .nine: return String(localized: "Nine", bundle: .module)
case .ten: return String(localized: "Ten", bundle: .module)
case .jack: return String(localized: "Jack", bundle: .module)
case .queen: return String(localized: "Queen", bundle: .module)
case .king: return String(localized: "King", bundle: .module)
}
}
}
/// Represents a single playing card with a suit and rank.
public struct Card: Identifiable, Equatable, Sendable {
public let id: UUID
public let suit: Suit
public let rank: Rank
public init(suit: Suit, rank: Rank, id: UUID = UUID()) {
self.id = id
self.suit = suit
self.rank = rank
}
/// The baccarat point value of this card.
public var baccaratValue: Int {
rank.baccaratValue
}
/// The blackjack value of this card.
public var blackjackValue: Int {
rank.blackjackValue
}
/// Display string showing rank and suit together.
public var display: String {
"\(rank.symbol)\(suit.rawValue)"
}
/// Accessibility description for VoiceOver.
public var accessibilityDescription: String {
"\(rank.accessibilityName) of \(suit.accessibilityName)"
}
/// The PDF page index (0-51) for this card.
/// PDF order: Spades (0-12), Hearts (13-25), Clubs (26-38), Diamonds (39-51)
/// Each suit: 2,3,4,5,6,7,8,9,10,J,Q,K,A
public var pdfIndex: Int {
let suitOffset: Int
switch suit {
case .spades: suitOffset = 0
case .hearts: suitOffset = 13
case .clubs: suitOffset = 26
case .diamonds: suitOffset = 39
}
// PDF order: 2=0, 3=1, ..., 10=8, J=9, Q=10, K=11, A=12
let rankOffset: Int
switch rank {
case .ace: rankOffset = 12 // Ace is last in PDF
default: rankOffset = rank.rawValue - 2 // 2=0, 3=1, etc.
}
return suitOffset + rankOffset
}
}