import Foundation final class PlayingTable: AbstractTable { let game: GameType let leadingTrumps: Int var indexOfTrickStarter = 0 var didDoubleInCurrentRound = false var hasCompletedTrick: Bool { !players.contains { $0.playedCard == nil } } var nextTrick: [Card] { hasCompletedTrick ? [] : currentTrick } var currentTrick: [Card] { players.rotated(toStartAt: indexOfTrickStarter).compactMap { $0.playedCard } } var completedTrick: Trick? { let trick = currentTrick guard trick.count == maximumPlayersPerTable else { return nil } return trick } var allCardsPlayed: Bool { !players.contains { !$0.cards.isEmpty } } var totalNumberOfDoubles: Int { players.map { $0.numberOfRaises + ($0.didDouble ? 1 : 0) }.reduce(0,+) } override var playedGame: GameType? { game } convenience init(table: BiddingTable, game: GameType, playedBy player: BiddingPlayer) { let calledAce = game.calledSuit?.ace let players = table.players.map { PlayingPlayer(player: $0, leads: $0 == player, calledAce: calledAce) } self.init(table: table, players: players, game: game) } convenience init(wedding table: WeddingTable, offeredBy offerer: WeddingPlayer, acceptedBy player: WeddingPlayer) { let players = table.players.map { PlayingPlayer(player: $0, leads: $0 == player || $0 == offerer, calledAce: nil) } self.init(table: table, players: players, game: .hochzeit) } private init(table: ManageableTable, players: [PlayingPlayer], game: GameType) { self.game = game let selectorCards = players.filter { $0.leadsGame || $0.isCallee }.map { $0.cards }.reduce([], +) let otherCards = players.filter { !$0.leadsGame && !$0.isCallee }.map { $0.cards }.reduce([], +) let selectorTrumps = game.sortingType.consecutiveTrumps(selectorCards) let otherTrumps = game.sortingType.consecutiveTrumps(otherCards) self.leadingTrumps = max(selectorTrumps, otherTrumps) super.init(table: table, players: players) players.forEach { $0.sortCards(for: game) } players.first!.isNextActor = true } override func cardStackPosition(ofPlayerAt index: Int) -> Int { (4 + index - indexOfTrickStarter) % 4 } override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) { guard let player = players.player(named: name) else { log("Player \(name) unexpectedly missing from playing table \(self.name)") return (.tableStateInvalid, nil) } guard action == .doubleDuringGame else { log("Player \(name) wants to perform action \(action) on playing table") return (.tableStateInvalid, nil) } guard player.canPerform(.doubleDuringGame) else { log("Player \(name) is not allowed to raise") return (.tableStateInvalid, nil) } player.numberOfRaises += 1 players.forEach { $0.switchLead() } self.didDoubleInCurrentRound = true return (.success, nil) } override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) { guard let player = players.player(named: name) else { log("Player \(name) unexpectedly missing from playing table \(self.name)") return (.tableStateInvalid, nil) } guard player.isNextActor else { log("Player \(name) wants to play card but is not active") return (.tableStateInvalid, nil) } guard player.canPlay(card: card, for: nextTrick, in: game) else { return (.tableStateInvalid, nil) } if hasCompletedTrick { players.forEach { $0.playedCard = nil } indexOfTrickStarter = players.index(of: player) } player.play(card: card) if let completedTrick = completedTrick { return didFinish(trick: completedTrick, in: game) } else { let next = players.next(after: player) next.isNextActor = true player.isNextActor = false return (.success, nil) } } override func cards(forPlayerAt index: Int) -> [PlayableCard] { players[index].playableCards(for: nextTrick, in: game) } private func didFinish(trick: Trick, in game: GameType) -> (result: PlayerActionResult, table: ManageableTable?) { let index = trick.highCardIndex(forGame: game) let winner = players[(indexOfTrickStarter + index) % 4] players.forEach { $0.isNextActor = false $0.canStillRaise = didDoubleInCurrentRound } winner.wonTricks.append(trick) winner.isNextActor = true if game == .bettel && winner.selectsGame { // A bettel is lost if a single trick is won by the game selector return finishedGame() } didDoubleInCurrentRound = false if allCardsPlayed { return finishedGame() } return (.success, nil) } private func finishedGame() -> (result: PlayerActionResult, table: ManageableTable?) { let table = FinishedTable(table: self) return (.success, table) } }