import Foundation struct FreeCellRules: GameRules { let variant: GameVariant = .freeCell func deal(deck: [Card]) -> GameSnapshot { var remaining = deck var tableaus: [[Card]] = [] // Deal all 52 cards to 8 tableaus, all face-up // First 4 tableaus get 7 cards, last 4 get 6 cards for i in 0..<8 { let cardCount = i < 4 ? 7 : 6 var column: [Card] = [] for _ in 0.. Bool { guard let firstCard = cards.first else { return false } switch to { case .foundation(let index): guard cards.count == 1 else { return false } guard index >= 0, index < state.foundations.count else { return false } return MoveValidator.canPlaceOnFoundation(firstCard, topCard: state.foundations[index].last) case .tableau(let index): guard index >= 0, index < state.tableaus.count else { return false } let maxMovable = calculateMaxMovableCards(state: state, targetEmpty: state.tableaus[index].isEmpty) guard cards.count <= maxMovable else { return false } let tableau = state.tableaus[index] if tableau.isEmpty { return true } guard let topCard = tableau.last else { return false } return MoveValidator.isAlternatingColor(firstCard, with: topCard) && MoveValidator.isDescending(firstCard, onto: topCard) case .freeCell(let index): guard cards.count == 1 else { return false } guard index >= 0, index < state.freeCells.count else { return false } return state.freeCells[index] == nil default: return false } } func drawFromStock(state: inout GameSnapshot, drawCount: Int) -> MoveAction? { nil // FreeCell has no stock } func scoreForMove(from: CardLocation, to: CardLocation) -> Int { switch (from, to) { case (_, .foundation): return 10 case (.freeCell, .tableau): return 5 default: return 0 } } func isWon(state: GameSnapshot) -> Bool { state.foundations.allSatisfy { $0.count == 13 } } func canAutoComplete(state: GameSnapshot) -> Bool { // All tableau cards must be face-up and in descending order let allFaceUp = state.tableaus.allSatisfy { $0.allSatisfy { $0.isFaceUp } } guard allFaceUp else { return false } // Check that all tableaus are in valid descending alternating-color order for tableau in state.tableaus { for i in 1.. Int { let emptyFreeCells = state.freeCells.filter { $0 == nil }.count // Don't count the target tableau as empty if we're moving to an empty tableau let emptyTableaus = state.tableaus.filter { $0.isEmpty }.count - (targetEmpty ? 1 : 0) let adjustedEmptyTableaus = max(0, emptyTableaus) return (1 + emptyFreeCells) * Int(pow(2.0, Double(adjustedEmptyTableaus))) } func findHints(state: GameSnapshot, settings: DifficultySettings) -> [HintResult] { guard settings.hintsEnabled else { return [] } var hints: [HintResult] = [] // Priority 1: Aces to foundation (from tableaus and free cells) for (tabIndex, tableau) in state.tableaus.enumerated() { guard let topCard = tableau.last, MoveValidator.isAce(topCard) else { continue } for (fIndex, foundation) in state.foundations.enumerated() { if MoveValidator.canPlaceOnFoundation(topCard, topCard: foundation.last) { hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .foundation(fIndex), priority: 1)) } } } for (cellIndex, cell) in state.freeCells.enumerated() { guard let card = cell, MoveValidator.isAce(card) else { continue } for (fIndex, foundation) in state.foundations.enumerated() { if MoveValidator.canPlaceOnFoundation(card, topCard: foundation.last) { hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .foundation(fIndex), priority: 1)) } } } // Priority 2: Other foundation moves for (tabIndex, tableau) in state.tableaus.enumerated() { guard let topCard = tableau.last, !MoveValidator.isAce(topCard) else { continue } for (fIndex, foundation) in state.foundations.enumerated() { if MoveValidator.canPlaceOnFoundation(topCard, topCard: foundation.last) { hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .foundation(fIndex), priority: 2)) } } } for (cellIndex, cell) in state.freeCells.enumerated() { guard let card = cell, !MoveValidator.isAce(card) else { continue } for (fIndex, foundation) in state.foundations.enumerated() { if MoveValidator.canPlaceOnFoundation(card, topCard: foundation.last) { hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .foundation(fIndex), priority: 2)) } } } // Priority 3: Tableau to tableau moves for (tabIndex, tableau) in state.tableaus.enumerated() { guard let topCard = tableau.last else { continue } for (destIndex, _) in state.tableaus.enumerated() { guard destIndex != tabIndex else { continue } if canMove(cards: [topCard], from: .tableau(tabIndex), to: .tableau(destIndex), state: state) { hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .tableau(destIndex), priority: 3)) } } } // Priority 4: Free cell to tableau for (cellIndex, cell) in state.freeCells.enumerated() { guard let card = cell else { continue } for (tabIndex, _) in state.tableaus.enumerated() { if canMove(cards: [card], from: .freeCell(cellIndex), to: .tableau(tabIndex), state: state) { hints.append(HintResult(cards: [card], from: .freeCell(cellIndex), to: .tableau(tabIndex), priority: 4)) } } } // Priority 5: Move to free cell if state.freeCells.contains(where: { $0 == nil }) { for (tabIndex, tableau) in state.tableaus.enumerated() { guard let topCard = tableau.last else { continue } if let emptyCell = state.freeCells.firstIndex(where: { $0 == nil }) { hints.append(HintResult(cards: [topCard], from: .tableau(tabIndex), to: .freeCell(emptyCell), priority: 5)) } } } return hints.sorted { $0.priority < $1.priority } } func canStackOnTableau(card: Card, onto target: Card) -> Bool { MoveValidator.isAlternatingColor(card, with: target) && MoveValidator.isDescending(card, onto: target) } func canPickUp(cards: [Card], from: CardLocation, state: GameSnapshot) -> Bool { guard !cards.isEmpty else { return false } switch from { case .tableau(let index): guard index >= 0, index < state.tableaus.count else { return false } // All cards must be face-up and form a valid alternating-color descending sequence guard cards.allSatisfy({ $0.isFaceUp }) else { return false } for i in 1..= 0, index < state.freeCells.count else { return false } return state.freeCells[index] == cards.first case .foundation(let index): guard cards.count == 1, index >= 0, index < state.foundations.count else { return false } return cards[0] == state.foundations[index].last default: return false } } }