import Foundation import WebSocketKit /** Specifies the number of cards of the called suit that a player must have to be allowed to play any card of the suit instead of having to play the ace. */ private let numberOfCardsToProtectAce = 4 final class PlayingPlayer: CardHoldingPlayer { var canStillRaise = true var isCalledWithAce: Card? var didPlayCalledAce = false var playedCard: Card? var leadsGame: Bool var numberOfRaises = 0 /// All tricks won by the player in this game var wonTricks: [Trick] = [] init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) { leadsGame = leads super.init(player: player) if let ace = ace, cards.contains(ace) { isCalledWithAce = ace } else { isCalledWithAce = nil } } /// The players has been called in a call game var isCallee: Bool { isCalledWithAce != nil } private var isUnknownCallee: Bool { isCallee && !didPlayCalledAce } override var actions: [PlayerAction] { guard canStillRaise else { return [] } guard !isUnknownCallee else { // Player belongs to caller, but other side has raised return leadsGame ? [.doubleDuringGame] : [] } guard !leadsGame else { return [] } return [.doubleDuringGame] } func play(card: Card) { playedCard = card canStillRaise = false cards = cards.filter { $0 != card } if card == isCalledWithAce { leadsGame.toggle() didPlayCalledAce = true } } func switchLead() { leadsGame.toggle() } func sortCards(for game: GameType) { cards = cards.sortedCards(forGame: game) } func canPlay(card: Card, for trick: Trick, in game: GameType) -> Bool { playableCards(for: trick, in: game).contains { $0.card == card && $0.isPlayable } } func playableCards(for trick: Trick, in game: GameType) -> [PlayableCard] { guard isNextActor else { return cards.unplayable } guard cards.count > 1 else { // Last card can always be played return cards.playable } guard let firstCard = trick.first else { return playableCardsForStarter(game: game) } let sorter = game.sortingType guard sorter.isTrump(firstCard) else { return playableCardsFollowing(suit: firstCard.suit, game: game) } guard !sorter.hasTrump(in: cards) else { // Must follow with trump return cards.map { $0.playable(sorter.isTrump($0)) } } // Can play any card if not in calling game guard let suit = game.calledSuit else { return cards.playable } // Can play any card, except the called ace let ace = Card(suit, .ass) return cards.map { $0.playable($0 != ace) } } private func playableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) -> [PlayableCard] { let sorter = game.sortingType let suitCards = sorter.cards(with: playedSuit, in: cards) func followSuit() -> [PlayableCard] { cards.map { $0.playable(!sorter.isTrump($0) && $0.suit == playedSuit) } } guard let calledSuit = game.calledSuit else { return suitCards.isEmpty ? cards.playable : followSuit() } let ace = Card(calledSuit, .ass) guard !suitCards.isEmpty else { // Exclude called ace, all others allowed return cards.map { $0.playable($0 != ace) } } guard calledSuit == playedSuit else { // Must follow suit (called ace not present) return followSuit() } // The called suit is played, must commit ace guard cards.contains(ace) else { // Must follow suit return followSuit() } // Must play ace return cards.map { $0.playable($0 == ace) } } private func playableCardsForStarter(game: GameType) -> [PlayableCard] { guard let suit = game.calledSuit else { return cards.playable } let ace = Card(suit, .ass) // Check if called ace exists, to prohibit other cards of the same suit guard cards.contains(ace) else { return cards.playable } // Jodeln if cards.count == numberOfCardsPerPlayer, cards.suitCount(suit, in: game) >= numberOfCardsToProtectAce { return cards.playable } // Only ace allowed for the called suit return cards.map { $0.playable($0.suit != suit || $0.symbol.isTrumpOrAce) } } var currentPoints: Int { wonTricks.map { $0.points }.reduce(0, +) } override var states: [PlayerState] { var states = super.states if didPlayCalledAce { states.append(.isCalled) } if leadsGame { states.append(.leadsGame) } if numberOfRaises > 0 { states.append(.didRaise) } return states } override var info: PlayerInfo { var info = super.info info.playedCard = playedCard?.id return info } }