Skip to content

Instantly share code, notes, and snippets.

@kingreza
Last active May 26, 2020 17:21
Show Gist options
  • Save kingreza/f449fe7744741a83d0ca4de5f73eb5b5 to your computer and use it in GitHub Desktop.
Save kingreza/f449fe7744741a83d0ca4de5f73eb5b5 to your computer and use it in GitHub Desktop.
A simple poker engine that deals cards and detects various hands.
//
// main.swift
// PokerTest
//
// Created by Reza Shirazian on 11/8/19.
// Copyright © 2019 Reza Shirazian. All rights reserved.
//
import Foundation
enum Suit: CaseIterable {
case heart, spade, diamond, club
}
extension Suit: CustomStringConvertible {
var description: String {
switch self {
case .club:
return "♣️"
case .diamond:
return "♦️"
case .heart:
return "♥️"
case .spade:
return "♠️"
}
}
}
enum Rank: Int, CaseIterable, Comparable {
static func < (lhs: Rank, rhs: Rank) -> Bool {
return lhs.rawValue < rhs.rawValue
}
case two = 2, three, four, five, six, seven, eight, nine, ten, jack, queen, king, ace
}
struct Card: Hashable {
let suit: Suit
let rank: Rank
}
struct Deck {
private(set) var cards: [Card]
private var cardsTakenOut: [Card]
var cardsAvailable: [Card] {
self.cards.filter {possibleCard in
!cardsTakenOut.contains(possibleCard)
}
}
init() {
self.cards = Deck.generateDeck()
self.cardsTakenOut = []
assert(self.cards.count == 52)
}
static func generateDeck() -> [Card] {
var cards: [Card] = []
for suit in Suit.allCases {
for rank in Rank.allCases {
cards.append(Card(suit: suit, rank: rank))
}
}
return cards
}
mutating func deal(count: Int) -> [Card] {
let cardsAvailable = self.cardsAvailable
guard count < cardsAvailable.count else {
return []
}
// 😱 dealing from the bottom of the deck!!
let hand = cardsAvailable.shuffled().suffix(count)
self.cardsTakenOut.append(contentsOf: hand)
return Array(hand)
}
mutating func reset() {
self.cards = Deck.generateDeck()
self.cardsTakenOut = []
}
}
var deck = Deck()
let handOne = deck.deal(count: 5)
let handTwo = deck.deal(count: 5)
enum PokerHand: Int, Comparable, CaseIterable {
static func < (lhs: PokerHand, rhs: PokerHand) -> Bool {
return lhs.rawValue < rhs.rawValue
}
case highCard = 1, pair, twoPair, threeKind, straight, flush, fullHouse, fourOfAKind, straightFlush, royalFlush
}
extension PokerHand {
func isHand(cards: [Card]) -> Bool {
switch self {
case .highCard:
return true
case .pair:
return PokerHand.isPair(cards: cards)
case .twoPair:
return PokerHand.isTwoPair(cards: cards)
case .threeKind:
return PokerHand.isThreeOfAKind(cards: cards)
case .straight:
return PokerHand.isStraight(cards: cards)
case .flush:
return PokerHand.isFlush(cards: cards)
case .fullHouse:
return PokerHand.isFullHouse(cards: cards)
case .fourOfAKind:
return PokerHand.isFourOfAKind(cards: cards)
case .straightFlush:
return PokerHand.isStraightFlush(cards: cards)
case .royalFlush:
return PokerHand.isRoyalFlush(cards: cards)
}
}
private static func isRoyalFlush(cards: [Card]) -> Bool {
let hadAce = cards.filter({$0.rank == .ace}).count > 0
return hadAce && PokerHand.isStraightFlush(cards: cards)
}
private static func isStraightFlush(cards: [Card]) -> Bool {
return PokerHand.isStraight(cards: cards) && PokerHand.isFlush(cards: cards)
}
private static func isFourOfAKind(cards: [Card]) -> Bool {
return cards.allRanksWithCount().values.filter({$0 == 4}).count > 0
}
private static func isFullHouse(cards: [Card]) -> Bool {
return self.isPair(cards: cards) && self.isThreeOfAKind(cards: cards)
}
private static func isFlush(cards: [Card]) -> Bool {
return cards.allUniqueSuits().count == 1
}
private static func isStraight(cards: [Card]) -> Bool {
return cards.isStraight()
}
private static func isThreeOfAKind(cards: [Card]) -> Bool {
return cards.allRanksWithCount().values.filter({$0 == 3}).count > 0
}
private static func isTwoPair(cards: [Card]) -> Bool {
var numberOfPairs = 0
for (_, value) in cards.allRanksWithCount() {
if value == 2 {
numberOfPairs += 1
}
}
return numberOfPairs == 2
}
private static func isPair(cards: [Card]) -> Bool {
return cards.allRanksWithCount().values.filter({$0 == 2}).count > 0
}
}
class PokerEngine {
static func detectHand(cards: [Card]) -> PokerHand? {
guard Set<Card>(cards).count == 5 else {
return nil
}
for possibleHand in PokerHand.allCases.reversed() {
if possibleHand.isHand(cards: cards) {
return possibleHand
}
}
return .highCard
}
}
extension Array where Element == Card {
func allUniqueRanks() -> [Rank] {
Array<Rank>(Set(self.map({$0.rank})))
}
func allUniqueSuits() -> [Suit] {
Array<Suit>(Set(self.map({$0.suit})))
}
func allSuitesWithCount() -> [Suit: Int] {
var result: [Suit: Int] = [:]
for card in self {
result[card.suit] = (result[card.suit] ?? 0) + 1
}
return result
}
func allRanksWithCount() -> [Rank: Int] {
var result: [Rank: Int] = [:]
for card in self {
result[card.rank] = (result[card.rank] ?? 0) + 1
}
return result
}
func isStraight() -> Bool {
let sorted = self.sorted { (lhs, rhs) -> Bool in
lhs.rank < rhs.rank
}
guard var current = sorted.first else {
return false
}
for card in sorted.suffix(sorted.count - 1) {
guard card.rank.rawValue - current.rank.rawValue == 1 else {
return false
}
current = card
}
return true
}
}
var cards = [Card(suit: .club, rank: .ace),
Card(suit: .club, rank: .king),
Card(suit: .club, rank: .queen),
Card(suit: .club, rank: .jack),
Card(suit: .club, rank: .ten)]
var hand = PokerEngine.detectHand(cards: cards)
cards = [Card(suit: .club, rank: .ace),
Card(suit: .club, rank: .king),
Card(suit: .heart, rank: .queen),
Card(suit: .club, rank: .jack),
Card(suit: .club, rank: .ten)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .straight)
cards = [Card(suit: .club, rank: .queen),
Card(suit: .club, rank: .ten),
Card(suit: .club, rank: .jack),
Card(suit: .club, rank: .nine),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .straightFlush)
cards = [Card(suit: .club, rank: .jack),
Card(suit: .heart, rank: .jack),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .jack),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .fourOfAKind)
cards = [Card(suit: .club, rank: .jack),
Card(suit: .heart, rank: .jack),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .eight),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .fullHouse)
cards = [Card(suit: .club, rank: .jack),
Card(suit: .heart, rank: .jack),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .seven),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .threeKind)
cards = [Card(suit: .club, rank: .jack),
Card(suit: .club, rank: .ace),
Card(suit: .club, rank: .two),
Card(suit: .club, rank: .seven),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .flush)
cards = [Card(suit: .club, rank: .seven),
Card(suit: .heart, rank: .jack),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .seven),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .twoPair)
cards = [Card(suit: .club, rank: .seven),
Card(suit: .heart, rank: .two),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .seven),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .pair)
cards = [Card(suit: .club, rank: .seven),
Card(suit: .heart, rank: .two),
Card(suit: .diamond, rank: .jack),
Card(suit: .spade, rank: .six),
Card(suit: .club, rank: .eight)]
hand = PokerEngine.detectHand(cards: cards)
assert(hand! == .highCard)
func displayOdds(_ survey: [PokerHand: Int], _ total: Int) {
let odds = survey.map { (hand: PokerHand, count: Int) in
return (hand, count, Double(count) / Double(total))
}
let sortOdds: ((PokerHand, Int, Double), (PokerHand, Int, Double)) -> Bool = { (rhs, lhs) in
lhs.0 < rhs.0
}
odds.sorted(by: sortOdds).forEach({ (arg0) in
let (hand, count, odd) = arg0
print("odds for \(hand) is: \(String(format: "%.8f", odd)) -- \(count) / \(total)")
})
}
deck = Deck()
let total = 10000000
var survey: [PokerHand: Int] = [:]
for count in 1..<total {
let cards = deck.deal(count: 5)
guard let hand = PokerEngine.detectHand(cards: cards) else {
fatalError("couldn't detect hand for \(cards)")
}
survey[hand] = (survey[hand] ?? 0) + 1
deck.reset()
if count % (total / 10) == 0 {
print("\(count) hands dealt")
displayOdds(survey, count)
}
}
print(survey)
print("odds are:")
displayOdds(survey, total)
// odds for royalFlush is: 0.00000150 -- 15 / 10000000
// odds for straightFlush is: 0.00001200 -- 120 / 10000000
// odds for fourOfAKind is: 0.00023650 -- 2365 / 10000000
// odds for fullHouse is: 0.00142530 -- 14253 / 10000000
// odds for flush is: 0.00197490 -- 19749 / 10000000
// odds for straight is: 0.00351380 -- 35138 / 10000000
// odds for threeKind is: 0.02111000 -- 211100 / 10000000
// odds for twoPair is: 0.04752600 -- 475260 / 10000000
// odds for pair is: 0.42236520 -- 4223652 / 10000000
// odds for highCard is: 0.50183470 -- 5018347 / 10000000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment