Advertisement

← poker-tutor

Sources/PokerCore/Card.swift

3.1 KB Download file
import Foundation

public enum Suit: UInt8, CaseIterable, Sendable, Codable, Hashable {
    case clubs = 0, diamonds, hearts, spades

    public var symbol: String {
        switch self {
        case .clubs: return "♣"
        case .diamonds: return "♦"
        case .hearts: return "♥"
        case .spades: return "♠"
        }
    }

    public var letter: Character {
        switch self {
        case .clubs: return "c"
        case .diamonds: return "d"
        case .hearts: return "h"
        case .spades: return "s"
        }
    }
}

public enum Rank: UInt8, CaseIterable, Comparable, Sendable, Codable, Hashable {
    case two = 2, three, four, five, six, seven, eight, nine, ten, jack, queen, king, ace

    public static func < (a: Rank, b: Rank) -> Bool { a.rawValue < b.rawValue }

    /// Bit position used in 13-bit rank masks: deuce is bit 0, ace is bit 12.
    public var bit: UInt16 { 1 << UInt16(rawValue - 2) }

    public var letter: Character {
        switch self {
        case .two: return "2"
        case .three: return "3"
        case .four: return "4"
        case .five: return "5"
        case .six: return "6"
        case .seven: return "7"
        case .eight: return "8"
        case .nine: return "9"
        case .ten: return "T"
        case .jack: return "J"
        case .queen: return "Q"
        case .king: return "K"
        case .ace: return "A"
        }
    }

    public init?(letter: Character) {
        guard let r = Rank.allCases.first(where: { $0.letter == Character(letter.uppercased()) }) else { return nil }
        self = r
    }
}

/// A playing card, stored as a single index 0..<52 so decks and evaluators can stay cheap.
public struct Card: Hashable, Sendable, Codable, CustomStringConvertible {
    public let index: UInt8

    public init(rank: Rank, suit: Suit) {
        self.index = (rank.rawValue - 2) * 4 + suit.rawValue
    }

    public init(index: UInt8) {
        precondition(index < 52, "card index out of range")
        self.index = index
    }

    /// Parses shorthand like "As", "Td", "7h".
    public init?(_ text: String) {
        let chars = Array(text)
        guard chars.count == 2,
              let rank = Rank(letter: chars[0]),
              let suit = Suit.allCases.first(where: { $0.letter == Character(String(chars[1]).lowercased()) })
        else { return nil }
        self.init(rank: rank, suit: suit)
    }

    public var rank: Rank { Rank(rawValue: index / 4 + 2)! }
    public var suit: Suit { Suit(rawValue: index % 4)! }

    public var description: String { "\(rank.letter)\(suit.letter)" }
    public var pretty: String { "\(rank.letter)\(suit.symbol)" }

    public static let all: [Card] = (0..<52).map { Card(index: UInt8($0)) }
}

public extension Array where Element == Card {
    /// Parses a space-separated list like "As Kd 7h".
    static func parse(_ text: String) -> [Card] {
        text.split(whereSeparator: { $0 == " " || $0 == "," }).compactMap { Card(String($0)) }
    }

    var shorthand: String { map(\.description).joined(separator: " ") }
}