Created
December 10, 2018 03:38
-
-
Save onevcat/987f107119f55c55b49e04e80bbe1319 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//Solution goes in Sources | |
public struct Card: CustomStringConvertible { | |
enum Suit: String { | |
case spade = "♤", heart = "♡", club = "♧", diamond = "♢" | |
// It is not the standard Texas poker rule. All suits are considered equal. | |
// However, there is a test case to "brake tide by suit", in which "heart" is the largest. | |
var value: Int { | |
switch self { | |
case .spade: return 3 | |
case .heart: return 2 | |
case .club: return 1 | |
case .diamond: return 0 | |
} | |
} | |
} | |
enum Rank: String { | |
case A, K, Q, J | |
case _10 = "10" | |
case _9 = "9" | |
case _8 = "8" | |
case _7 = "7" | |
case _6 = "6" | |
case _5 = "5" | |
case _4 = "4" | |
case _3 = "3" | |
case _2 = "2" | |
var number: Int { | |
switch self { | |
case .A: return 14 | |
case .K: return 13 | |
case .Q: return 12 | |
case .J: return 11 | |
default: return Int(rawValue)! | |
} | |
} | |
} | |
let rank: Rank | |
let suit: Suit | |
init?(rawCard: String) { | |
guard rawCard.count == 2 || rawCard.count == 3 else { return nil } | |
guard let s = Suit(rawValue: String(rawCard.last!)) else { return nil } | |
guard let number = Rank(rawValue: String(rawCard.dropLast())) else { return nil } | |
rank = number | |
suit = s | |
} | |
public var description: String { | |
return "\(rank.rawValue)\(suit.rawValue)" | |
} | |
} | |
extension Card: Comparable { | |
public static func < (lhs: Card, rhs: Card) -> Bool { | |
if lhs.rank < rhs.rank { return true } | |
if lhs.rank > rhs.rank { return false } | |
return lhs.suit < rhs.suit | |
} | |
} | |
extension Card.Rank: Comparable { | |
static func < (lhs: Card.Rank, rhs: Card.Rank) -> Bool { | |
return lhs.number < rhs.number | |
} | |
} | |
extension Card.Suit: Comparable { | |
static func < (lhs: Card.Suit, rhs: Card.Suit) -> Bool { | |
return lhs.value < rhs.value | |
} | |
} | |
typealias Hand = String | |
class PokerHand: CustomStringConvertible { | |
var rank: HandRank! | |
let cards: [Card] | |
init?(_ hand: Hand) { | |
let rawCards = hand.split(separator: " ") | |
guard rawCards.count == 5 else { return nil } | |
cards = rawCards.compactMap { Card(rawCard: String($0)) } | |
guard cards.count == 5 else { return nil } | |
if isStraightFlush { | |
rank = .straightFlush(leadingCardInStraight) | |
} else if let leading = kind(of: 4) { | |
rank = .fourOfAKind(leading) | |
} else if let leading = kind(of: 3), let _ = kind(of: 2) { | |
rank = .fullHouse(leading) | |
} else if flush { | |
rank = .flush(cards) | |
} else if straight { | |
rank = .straight(leadingCardInStraight) | |
} else if let leading = kind(of: 3) { | |
rank = .threeOfAKind(leading, cards.filter { $0.rank != leading.rank }) | |
} else if let leading = twoPairs() { | |
rank = .twoPair(leading.0, leading.1, cards.filter { ($0.rank != leading.0.rank) && ($0.rank != leading.1.rank) }.first! ) | |
} else if let leading = kind(of: 2) { | |
rank = .onePair(leading, cards.filter { $0.rank != leading.rank }) | |
} else { | |
rank = .highCard(cards) | |
} | |
} | |
var description: Hand { | |
return cards | |
.map { $0.description } | |
.joined(separator: " ") | |
} | |
lazy var sortedCard: [Card] = { return cards.sorted() }() | |
var maxCard: Card { return sortedCard[4] } | |
lazy var straight: Bool = { | |
let ranks = sortedCard.map { $0.rank } | |
if ranks == [._2, ._3, ._4, ._5, .A] { | |
return true | |
} | |
let maxDiff = ranks.max()!.number - ranks.min()!.number | |
return maxDiff == 4 && Set(ranks).count == 5 | |
}() | |
var leadingCardInStraight: Card { | |
if let _ = (cards.first { $0.rank == ._2 }) { | |
// Use A as 1, in A,2,3,4,5. Sorted cards will be 2345A. Return 5 as the leading card. | |
return sortedCard.dropLast().last! | |
} else { | |
return cards.max()! | |
} | |
} | |
lazy var flush: Bool = { | |
return Set(cards.map { $0.suit }).count == 1 | |
}() | |
func kind(of n: Int, input: [Card]? = nil) -> Card? { | |
for card in input ?? cards { | |
let sameRanks = cards.filter { $0.rank == card.rank } | |
if sameRanks.count == n { | |
return card | |
} | |
} | |
return nil | |
} | |
func twoPairs() -> (Card, Card)? { | |
guard let lower = kind(of: 2, input: sortedCard), | |
let higher = kind(of: 2, input: sortedCard.reversed()) else | |
{ | |
return nil | |
} | |
if lower.rank == higher.rank { | |
return nil | |
} | |
return (higher, lower) | |
} | |
} | |
extension PokerHand { | |
var isStraightFlush: Bool { | |
return straight && flush | |
} | |
} | |
extension PokerHand: Comparable { | |
static func == (lhs: PokerHand, rhs: PokerHand) -> Bool { | |
return lhs.rank == rhs.rank | |
} | |
static func < (lhs: PokerHand, rhs: PokerHand) -> Bool { | |
return lhs.rank < rhs.rank | |
} | |
} | |
extension Array: Comparable where Element == Card { | |
public static func < (lhs: Array<Element>, rhs: Array<Element>) -> Bool { | |
return comparedLess(lhs: lhs, rhs: rhs, withSuit: false) | |
} | |
public static func > (lhs: Array<Element>, rhs: Array<Element>) -> Bool { | |
return comparedGreater(lhs: lhs, rhs: rhs, withSuit: false) | |
} | |
static func comparedLess(lhs: Array<Element>, rhs: Array<Element>, withSuit: Bool) -> Bool { | |
guard lhs.count == rhs.count else { fatalError() } | |
guard lhs.count != 0 else { return false } | |
let zipped = zip(lhs.sorted(), rhs.sorted()).reversed() | |
for v in zipped { | |
if v.0.rank < v.1.rank { return true } | |
if v.0.rank > v.1.rank { return false } | |
} | |
if withSuit { | |
let first = zipped.first! | |
return first.0.suit < first.1.suit | |
} | |
return false | |
} | |
static func comparedGreater(lhs: Array<Element>, rhs: Array<Element>, withSuit: Bool) -> Bool { | |
guard lhs.count == rhs.count else { fatalError() } | |
guard lhs.count != 0 else { return false } | |
let zipped = zip(lhs.sorted(), rhs.sorted()).reversed() | |
for v in zipped { | |
if v.0.rank > v.1.rank { return true } | |
if v.0.rank < v.1.rank { return false } | |
} | |
if withSuit { | |
let first = zipped.first! | |
return first.0.suit > first.1.suit | |
} | |
return false | |
} | |
} | |
enum HandRank: Comparable { | |
static func < (lhs: HandRank, rhs: HandRank) -> Bool { | |
if lhs.rankLevel < rhs.rankLevel { return true } | |
if lhs.rankLevel > rhs.rankLevel { return false } | |
switch (lhs, rhs) { | |
case (.highCard(let lCards), .highCard(let rCards)): | |
return Array.comparedLess(lhs: lCards, rhs: rCards, withSuit: true) | |
case (.onePair(let lLeading, let lCards), .onePair(let rLeading, let rCards)): | |
if lLeading.rank < rLeading.rank { return true } | |
if lLeading.rank > rLeading.rank { return false } | |
if lCards < rCards { return true } | |
if lCards > rCards { return false } | |
return lLeading.suit < rLeading.suit | |
case (.twoPair(let lHigherLeading, let lLowerLeading, let lLeft), | |
.twoPair(let rHigherLeading, let rLowerLeading, let rLeft)): | |
if lHigherLeading.rank < rHigherLeading.rank { return true } | |
if lHigherLeading.rank > rHigherLeading.rank { return false } | |
if lLowerLeading.rank < rLowerLeading.rank { return true } | |
if lLowerLeading.rank > rLowerLeading.rank { return false } | |
if lLeft.rank < rLeft.rank { return true } | |
if lLeft.rank > rLeft.rank { return false } | |
return lHigherLeading.suit < rHigherLeading.suit | |
case (.threeOfAKind(let lLeading, let lCards), .threeOfAKind(let rLeading, let rCards)): | |
if lLeading.rank < rLeading.rank { return true } | |
if lLeading.rank > rLeading.rank { return false } | |
if lCards < rCards { return true } | |
if lCards > rCards { return false } | |
return lLeading.suit < rLeading.suit | |
case (.straight(let l), .straight(let r)): | |
return l < r | |
case (.flush(let l), .flush(let r)): | |
return Array.comparedLess(lhs: l, rhs: r, withSuit: true) | |
case (.fullHouse(let lLeading), .fullHouse(let rLeading)): | |
return lLeading < rLeading | |
case (.fourOfAKind(let lLeading), .fourOfAKind(let rLeading)): | |
return lLeading < rLeading | |
case (.straightFlush(let lLeading), .straightFlush(let rLeading)): | |
return lLeading < rLeading | |
default: fatalError() | |
} | |
} | |
case highCard([Card]) // High cards | |
case onePair(Card, [Card]) // Pair leading, then high card | |
case twoPair(Card, Card, Card) // Pair leading, pair leading, then high card | |
case threeOfAKind(Card, [Card]) // Three leading, then left high cards | |
case straight(Card) // Leading card | |
case flush([Card]) // Compared each cards | |
case fullHouse(Card) // three leading cards | |
case fourOfAKind(Card) // leading card | |
case straightFlush(Card) // leading card | |
var rankLevel: Int { | |
switch self { | |
case .highCard: return 0 | |
case .onePair: return 1 | |
case .twoPair: return 2 | |
case .threeOfAKind: return 3 | |
case .straight: return 4 | |
case .flush: return 5 | |
case .fullHouse: return 6 | |
case .fourOfAKind: return 7 | |
case .straightFlush: return 8 | |
} | |
} | |
} | |
func bestHand(_ inputHands: [Hand]) -> Hand? { | |
guard let best = inputHands.compactMap(PokerHand.init).max() else { | |
return nil | |
} | |
return best.description | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment