import Testing @testable import SoliCards @Suite("FreeCell Rules Tests") struct FreeCellRulesTests { let rules = FreeCellRules() @Test("Deal distributes all 52 cards face-up") func dealLayout() { let deck = Deck.standard() let snapshot = rules.deal(deck: deck) #expect(snapshot.tableaus.count == 8) // First 4 tableaus: 7 cards, last 4: 6 cards for i in 0..<4 { #expect(snapshot.tableaus[i].count == 7) } for i in 4..<8 { #expect(snapshot.tableaus[i].count == 6) } // All face-up let allCards = snapshot.tableaus.flatMap { $0 } #expect(allCards.count == 52) #expect(allCards.allSatisfy { $0.isFaceUp }) // 4 empty free cells #expect(snapshot.freeCells.count == 4) #expect(snapshot.freeCells.allSatisfy { $0 == nil }) // No stock #expect(snapshot.stock.isEmpty) } @Test("Power move calculation") func powerMoves() { // (1 + empty_freecells) × 2^empty_tableaus var snapshot = emptySnapshot() // All 4 free cells empty, target is empty (so 7 empty tableaus count) let max1 = rules.calculateMaxMovableCards(state: snapshot, targetEmpty: true) // (1 + 4) × 2^7 = 5 × 128 = 640 #expect(max1 == 640) // 2 free cells occupied, no empty tableaus snapshot.freeCells = [Card(suit: .hearts, rank: .ace, isFaceUp: true), Card(suit: .spades, rank: .two, isFaceUp: true), nil, nil] for i in 0..<8 { snapshot.tableaus[i] = [Card(suit: .clubs, rank: .king, isFaceUp: true)] } let max2 = rules.calculateMaxMovableCards(state: snapshot, targetEmpty: false) // (1 + 2) × 2^0 = 3 #expect(max2 == 3) } @Test("Can move single card to empty free cell") func moveToFreeCell() { let card = Card(suit: .hearts, rank: .five, isFaceUp: true) var snapshot = emptySnapshot() snapshot.tableaus[0] = [card] #expect(rules.canMove(cards: [card], from: .tableau(0), to: .freeCell(0), state: snapshot)) } @Test("Cannot move multiple cards to free cell") func multipleToFreeCell() { let card1 = Card(suit: .spades, rank: .six, isFaceUp: true) let card2 = Card(suit: .hearts, rank: .five, isFaceUp: true) var snapshot = emptySnapshot() snapshot.tableaus[0] = [card1, card2] #expect(!rules.canMove(cards: [card1, card2], from: .tableau(0), to: .freeCell(0), state: snapshot)) } // MARK: - Helpers private func emptySnapshot() -> GameSnapshot { GameSnapshot( tableaus: Array(repeating: [], count: 8), foundations: [[], [], [], []], stock: [], waste: [], freeCells: [nil, nil, nil, nil], moves: 0, score: 0 ) } }