Home > Enterprise >  Total value of cards in blackjack
Total value of cards in blackjack

Time:03-09

I'm coding up a blackjack card game to try to slowly get myself back into Swift.

I am trying to get a total value of the cards in a player's hand.

The problem I have is when I come to an ace which can be either 1 or 11, and I'm not sure how to account for this when I do a XCTest against it.

// Card model
struct Card {
    
    // Suit enumerated
    enum Suit : Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }
    
    // Rank enumerated
    enum Rank : Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace
                    
        struct Values {
            let first: Int, second : Int?
        }
        
        var values: Values {
            switch self {
            case .ace:
                return Values(first: 1, second: 11)
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }
    
    let rank: Rank, suit : Suit
    
    var value : Rank.Values {
        return self.rank.values
    }
}

When I come to test it,

// XCTest
func testSumOfCardsEquals21() {
    let card1 = Card(rank: .jack, suit: .diamonds) //10
    let card2 = Card(rank: .four, suit: .diamonds) //4
    let card3 = Card(rank: .three, suit: .diamonds) //3
    let card4 = Card(rank: .ace, suit: .diamonds) //1 or 11
    
    let hand = [card1, card2, card3, card4]
    
    var total = 0
    for card in hand {
        let values = card.value
        if (values.second == nil) {
            total  = values.first
        } else {
            let secondValue = values.second!
            total  = secondValue
        }
    }
    
    // expected 21
    XCTAssertEqual(total, 21) // this will fail and return 28
}

In this scenario you want the ace to be low.

In a scenario where you might just have 1 card (ace) you want it to be high.

I'm not sure how to resolve such a test requirement.

...

To clarify, how do you make a function, test to calculate the total value of the hand of cards to return the expected 21 total when a card can be two values?

I appreciate your help and time.

CodePudding user response:

That hand makes 18, not 21.

The Values type may not be useful—these cards don't really have raw values.

You can't know how to calculate the score without accumulating all of the cards first.

enum Rank: CaseIterable {
  case two, three, four, five, six, seven, eight, nine, ten
  case jack, queen, king
  case ace
}
struct Hand {
  init<Cards: Sequence>(cards: Cards) where Cards.Element == Card {
    let reduction = cards.reduce(into: (score: 0, aceCount: 0)) {
      switch $1.rank {
      case .jack, .queen, .king:
        $0.score  = 10
      case .ace:
        $0.score  = 1
        $0.aceCount  = 1
      default:
        $0.score  = try! $1.rank.caseIndex   2
      }
    }

    var score = reduction.score
    reduction.aceCount.iterations
      .prefix { score <= 12 }
      .forEach { score  = 9 }
    self.score = score
  }

  let score: Int
}
Hand(cards: [card1, card2, card3, card4]).score

public extension ExpressibleByIntegerLiteral where Self: Strideable, Stride: SignedInteger {
  /// *This many* iterations that produce no values.
  var iterations: LazyMapSequence<Range<Self>, Void> {
    (0..<self).lazy.map { _ in }
  }
}
public extension CaseIterable where Self: Equatable {
  /// The first match for this case in `allCases`.
  /// - Throws: `AllCasesError<Self>.noIndex`
  var caseIndex: AllCases.Index {
    get throws {
      do { return try Self.allCases.firstIndex(of: self).unwrapped }
      catch { throw AllCasesError.noIndex(self) }
    }
  }
}

public enum AllCasesError<Case: CaseIterable>: Error {
  /// No `AllCases.Index` corresponds to this case.
  case noIndex(Case)
}
public extension Optional {
  /// Represents that an `Optional` was `nil`.
  enum UnwrapError: Error {
    case `nil`
    case typeMismatch
  }

  /// [An alterative to overloading `??` to throw errors upon `nil`.](
  /// https://forums.swift.org/t/unwrap-or-throw-make-the-safe-choice-easier/14453/7)
  /// - Note: Useful for emulating `break`, with `map`, `forEach`, etc.
  /// - Throws: `UnwrapError` when `nil`.
  var unwrapped: Wrapped {
    get throws {
      switch self {
      case let wrapped?:
        return wrapped
      case nil:
        throw UnwrapError.nil
      }
    }
  }
}
  • Related