288 lines
10 KiB
Swift
288 lines
10 KiB
Swift
//
|
|
// BlackjackTests.swift
|
|
// BlackjackTests
|
|
//
|
|
// Created by Matt Bruce on 12/17/25.
|
|
//
|
|
|
|
import Testing
|
|
@testable import Blackjack
|
|
import CasinoKit
|
|
|
|
struct BlackjackTests {
|
|
|
|
@Test func example() async throws {
|
|
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
|
}
|
|
|
|
// MARK: - Hand Value Tests
|
|
|
|
@Test @MainActor func handValueCalculation() async throws {
|
|
var hand = BlackjackHand()
|
|
|
|
// Test empty hand
|
|
#expect(hand.value == 0, "Empty hand should have value 0")
|
|
|
|
// Test simple values
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .five),
|
|
Card(suit: .hearts, rank: .seven)
|
|
])
|
|
#expect(hand.value == 12, "5 + 7 should equal 12")
|
|
#expect(!hand.isSoft, "Hand without ace should not be soft")
|
|
|
|
// Test face cards
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .clubs, rank: .king),
|
|
Card(suit: .diamonds, rank: .queen)
|
|
])
|
|
#expect(hand.value == 20, "King (10) + Queen (10) should equal 20")
|
|
|
|
// Test blackjack
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .king)
|
|
])
|
|
#expect(hand.value == 21, "Ace + King should equal 21")
|
|
#expect(hand.isBlackjack, "Ace + King should be blackjack")
|
|
#expect(hand.isSoft, "Blackjack hand should be soft")
|
|
|
|
// Test soft hand (ace as 11)
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .diamonds, rank: .ace),
|
|
Card(suit: .clubs, rank: .six)
|
|
])
|
|
#expect(hand.value == 17, "Ace + 6 should equal soft 17")
|
|
#expect(hand.isSoft, "Ace + 6 should be soft")
|
|
|
|
// Test hard hand (ace as 1)
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .five),
|
|
Card(suit: .clubs, rank: .nine)
|
|
])
|
|
#expect(hand.value == 15, "Ace + 5 + 9 = 15 (ace counts as 1)")
|
|
#expect(!hand.isSoft, "Hand over 21 if ace=11 should be hard")
|
|
|
|
// Test bust
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .diamonds, rank: .king),
|
|
Card(suit: .clubs, rank: .queen),
|
|
Card(suit: .hearts, rank: .five)
|
|
])
|
|
#expect(hand.value == 25, "10 + 10 + 5 = 25")
|
|
#expect(hand.isBusted, "Hand over 21 should be busted")
|
|
|
|
// Test multiple aces
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .ace)
|
|
])
|
|
#expect(hand.value == 12, "Ace + Ace = 12 (one as 11, one as 1)")
|
|
#expect(hand.isSoft, "Two aces should be soft")
|
|
|
|
// Test three aces
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .ace),
|
|
Card(suit: .clubs, rank: .ace)
|
|
])
|
|
#expect(hand.value == 13, "Three aces = 13 (one as 11, two as 1)")
|
|
#expect(hand.isSoft, "Three aces should be soft")
|
|
}
|
|
|
|
@Test @MainActor func handBlackjackDetection() async throws {
|
|
// Natural blackjack
|
|
var hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .ten)
|
|
])
|
|
#expect(hand.isBlackjack, "Ace + 10 should be blackjack")
|
|
|
|
// 21 with three cards is not blackjack
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .seven),
|
|
Card(suit: .hearts, rank: .seven),
|
|
Card(suit: .clubs, rank: .seven)
|
|
])
|
|
#expect(hand.value == 21, "Should have value 21")
|
|
#expect(!hand.isBlackjack, "21 with 3 cards is not blackjack")
|
|
|
|
// Split hand 21 is not blackjack
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .king)
|
|
], bet: 100)
|
|
hand.isSplit = true
|
|
#expect(hand.value == 21, "Should have value 21")
|
|
#expect(!hand.isBlackjack, "21 after split is not blackjack")
|
|
}
|
|
|
|
@Test @MainActor func handSplitDetection() async throws {
|
|
// Splittable pair
|
|
var hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .eight),
|
|
Card(suit: .hearts, rank: .eight)
|
|
])
|
|
#expect(hand.canSplit, "Pair of 8s should be splittable")
|
|
|
|
// Face cards of same rank can split
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .king),
|
|
Card(suit: .hearts, rank: .king)
|
|
])
|
|
#expect(hand.canSplit, "Pair of Kings should be splittable")
|
|
|
|
// Face cards of different ranks cannot split
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .king),
|
|
Card(suit: .hearts, rank: .queen)
|
|
])
|
|
#expect(!hand.canSplit, "King + Queen should not be splittable (different ranks)")
|
|
|
|
// Not splittable - different ranks
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .nine),
|
|
Card(suit: .hearts, rank: .ten)
|
|
])
|
|
#expect(!hand.canSplit, "9 + 10 should not be splittable")
|
|
|
|
// Not splittable - more than 2 cards
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .five),
|
|
Card(suit: .hearts, rank: .five),
|
|
Card(suit: .clubs, rank: .ace)
|
|
])
|
|
#expect(!hand.canSplit, "Hand with 3 cards should not be splittable")
|
|
}
|
|
|
|
@Test @MainActor func handDoubleDownDetection() async throws {
|
|
// Can double down with 2 cards
|
|
var hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .five),
|
|
Card(suit: .hearts, rank: .six)
|
|
])
|
|
#expect(hand.canDoubleDown, "Should be able to double down with 2 cards")
|
|
|
|
// Cannot double down after already doubled
|
|
hand.isDoubledDown = true
|
|
#expect(!hand.canDoubleDown, "Should not be able to double down twice")
|
|
|
|
// Cannot double down with 3+ cards
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .five),
|
|
Card(suit: .hearts, rank: .six),
|
|
Card(suit: .clubs, rank: .two)
|
|
])
|
|
#expect(!hand.canDoubleDown, "Should not be able to double down with 3 cards")
|
|
}
|
|
|
|
@Test @MainActor func handHitDetection() async throws {
|
|
// Can hit with normal hand
|
|
var hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .five),
|
|
Card(suit: .hearts, rank: .six)
|
|
])
|
|
#expect(hand.canHit, "Should be able to hit with 11")
|
|
|
|
// Cannot hit after standing
|
|
hand.isStanding = true
|
|
#expect(!hand.canHit, "Should not be able to hit after standing")
|
|
|
|
// Cannot hit when busted
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .king),
|
|
Card(suit: .hearts, rank: .queen),
|
|
Card(suit: .clubs, rank: .five)
|
|
])
|
|
#expect(!hand.canHit, "Should not be able to hit when busted")
|
|
|
|
// Cannot hit with blackjack
|
|
hand = BlackjackHand(cards: [
|
|
Card(suit: .spades, rank: .ace),
|
|
Card(suit: .hearts, rank: .king)
|
|
])
|
|
#expect(!hand.canHit, "Should not be able to hit with blackjack")
|
|
}
|
|
|
|
// MARK: - Card Value Tests
|
|
|
|
@Test func cardHiLoValues() async throws {
|
|
// Low cards (2-6) = +1
|
|
#expect(Card(suit: .spades, rank: .two).hiLoValue == 1)
|
|
#expect(Card(suit: .hearts, rank: .three).hiLoValue == 1)
|
|
#expect(Card(suit: .clubs, rank: .four).hiLoValue == 1)
|
|
#expect(Card(suit: .diamonds, rank: .five).hiLoValue == 1)
|
|
#expect(Card(suit: .spades, rank: .six).hiLoValue == 1)
|
|
|
|
// Neutral cards (7-9) = 0
|
|
#expect(Card(suit: .hearts, rank: .seven).hiLoValue == 0)
|
|
#expect(Card(suit: .clubs, rank: .eight).hiLoValue == 0)
|
|
#expect(Card(suit: .diamonds, rank: .nine).hiLoValue == 0)
|
|
|
|
// High cards (10, J, Q, K, A) = -1
|
|
#expect(Card(suit: .spades, rank: .ten).hiLoValue == -1)
|
|
#expect(Card(suit: .hearts, rank: .jack).hiLoValue == -1)
|
|
#expect(Card(suit: .clubs, rank: .queen).hiLoValue == -1)
|
|
#expect(Card(suit: .diamonds, rank: .king).hiLoValue == -1)
|
|
#expect(Card(suit: .spades, rank: .ace).hiLoValue == -1)
|
|
}
|
|
|
|
// MARK: - Game State Tests
|
|
|
|
@Test @MainActor func gameStateInitialization() async throws {
|
|
let state = GameState(settings: GameSettings())
|
|
|
|
// Initial state
|
|
#expect(state.balance == 10000, "Should start with $10,000")
|
|
#expect(state.currentBet == 0, "Should start with no bet")
|
|
#expect(state.playerHands.isEmpty, "Should start with no hands")
|
|
#expect(state.dealerHand.cards.isEmpty, "Should start with empty dealer hand")
|
|
|
|
// Check phase enum
|
|
switch state.currentPhase {
|
|
case .betting:
|
|
break // Expected
|
|
default:
|
|
Issue.record("Should start in betting phase")
|
|
}
|
|
}
|
|
|
|
@Test @MainActor func gameStateBetting() async throws {
|
|
let state = GameState(settings: GameSettings())
|
|
|
|
// Place a bet
|
|
state.placeBet(amount: 100)
|
|
#expect(state.currentBet == 100, "Bet should be placed")
|
|
#expect(state.balance == 9900, "Balance should decrease")
|
|
|
|
// Add to bet
|
|
state.placeBet(amount: 50)
|
|
#expect(state.currentBet == 150, "Bet should increase")
|
|
#expect(state.balance == 9850, "Balance should decrease more")
|
|
|
|
// Clear bet
|
|
state.clearBet()
|
|
#expect(state.currentBet == 0, "Bet should be cleared")
|
|
#expect(state.balance == 10000, "Balance should be restored")
|
|
}
|
|
|
|
@Test @MainActor func gameStateDealing() async throws {
|
|
let state = GameState(settings: GameSettings())
|
|
|
|
// Place bet and deal
|
|
state.placeBet(amount: 100)
|
|
await state.deal()
|
|
|
|
// Verify hands were dealt
|
|
#expect(state.playerHands.count == 1, "Should have 1 player hand")
|
|
#expect(state.playerHands[0].cards.count == 2, "Player should have 2 cards")
|
|
#expect(state.dealerHand.cards.count == 2, "Dealer should have 2 cards")
|
|
|
|
// Verify values are valid
|
|
#expect(state.playerHands[0].value >= 2, "Player hand should have valid value")
|
|
#expect(state.playerHands[0].value <= 21, "Player hand should not exceed 21 initially")
|
|
}
|
|
|
|
}
|