← poker-tutor
import PokerCore
import TestKit
func rankOf(_ text: String) -> HandRank {
HandEvaluator.evaluate([Card].parse(text))
}
func combinations(of cards: [Card], choose k: Int) -> [[Card]] {
guard k > 0 else { return [[]] }
guard cards.count >= k else { return [] }
if cards.count == k { return [cards] }
let head = cards[0]
let tail = Array(cards.dropFirst())
return combinations(of: tail, choose: k - 1).map { [head] + $0 } + combinations(of: tail, choose: k)
}
func handEvaluatorTests(_ t: TestRun) {
t.suite("hand ranking") { t in
t.test("every category is identified") {
t.equal(rankOf("As Ks Qs Js Ts").category, .straightFlush)
t.equal(rankOf("5s 4s 3s 2s As").category, .straightFlush, "steel wheel")
t.equal(rankOf("9c 9d 9h 9s 2c").category, .fourOfAKind)
t.equal(rankOf("9c 9d 9h 2s 2c").category, .fullHouse)
t.equal(rankOf("Ac Tc 8c 5c 2c").category, .flush)
t.equal(rankOf("9c 8d 7h 6s 5c").category, .straight)
t.equal(rankOf("5c 4d 3h 2s Ac").category, .straight, "the wheel")
t.equal(rankOf("9c 9d 9h 5s 2c").category, .threeOfAKind)
t.equal(rankOf("9c 9d 5h 5s 2c").category, .twoPair)
t.equal(rankOf("9c 9d 7h 5s 2c").category, .pair)
t.equal(rankOf("Ac Jd 8h 5s 2c").category, .highCard)
}
t.test("categories rank in the right order") {
let ascending = [
"Ac Jd 8h 5s 2c", // high card
"9c 9d 7h 5s 2c", // pair
"9c 9d 5h 5s 2c", // two pair
"9c 9d 9h 5s 2c", // trips
"9c 8d 7h 6s 5c", // straight
"Ac Tc 8c 5c 2c", // flush
"9c 9d 9h 2s 2c", // full house
"9c 9d 9h 9s 2c", // quads
"9c 8c 7c 6c 5c", // straight flush
].map(rankOf)
for (a, b) in zip(ascending, ascending.dropFirst()) {
t.lessThan(a, b, "\(a) should rank below \(b)")
}
}
t.test("the wheel is the worst straight") {
t.lessThan(rankOf("5c 4d 3h 2s Ac"), rankOf("6c 5d 4h 3s 2c"))
t.lessThan(rankOf("5s 4s 3s 2s As"), rankOf("6s 5s 4s 3s 2s"))
}
t.test("kickers decide within a category") {
t.greaterThan(rankOf("Kc Kd Ah 5s 2c"), rankOf("Kc Kd Qh 5s 2c"))
t.greaterThan(rankOf("Ac Ad 9h 9s Kc"), rankOf("Ac Ad 9h 9s Qc"))
t.greaterThan(rankOf("Ac Kc Qc Jc 9c"), rankOf("Ac Kc Qc Jc 8c"))
t.greaterThan(rankOf("9c 9d 9h Ks 2c"), rankOf("9c 9d 9h Qs 2c"))
t.equal(rankOf("Kc Kd Ah 5s 2c"), rankOf("Ks Kh Ad 5c 2d"), "suits never break ties")
}
t.test("higher of the same category wins") {
t.greaterThan(rankOf("Ac Ad Ah As 2c"), rankOf("Kc Kd Kh Ks Ac"), "quad aces beat quad kings")
t.greaterThan(rankOf("9c 9d 9h 2s 2c"), rankOf("8c 8d 8h As Ac"), "the trips decide a full house")
t.greaterThan(rankOf("Tc 9d 8h 7s 6c"), rankOf("9c 8d 7h 6s 5c"))
}
t.test("seven cards pick the best five") {
t.equal(rankOf("Ac 2c 7c 9c Kc 5d 5h").category, .flush, "flush beats the paired board")
t.equal(rankOf("8s 8d 8h 4c 4d Ks 2c").category, .fullHouse)
let twoSets = rankOf("9c 9d 9h 4c 4d 4h Ks")
t.equal(twoSets.category, .fullHouse)
t.equal(twoSets.kickers[0], 9, "higher set plays as the trips")
t.equal(twoSets.kickers[1], 4)
t.equal(rankOf("9c 8d 7h 6s 5c 4d 2h"), rankOf("9c 8d 7h 6s 5c"), "six-card straight uses the top five")
t.equal(rankOf("9c 8c 7c 6d 5h Ac Kc").category, .flush, "flush over the straight on the same seven")
}
t.test("straight flush beats quads") {
t.greaterThan(rankOf("9c 8c 7c 6c 5c"), rankOf("Ac Ad Ah As Kc"))
}
t.test("seven-card path agrees with brute force over all 21 subsets") {
var rng = SeededRandom(seed: 4242)
for _ in 0..<400 {
var deck = Deck(rng: &rng)
let seven = deck.deal(7)
let direct = HandEvaluator.evaluate(seven)
var brute = HandEvaluator.evaluate(Array(seven.prefix(5)))
for combo in combinations(of: seven, choose: 5) {
brute = max(brute, HandEvaluator.evaluate(combo))
}
t.equal(direct, brute, "mismatch on \(seven.shorthand)")
}
}
}
}