import Foundation final class BiddingTable: AbstractTable { var gameToOutbid: GameType.GameClass = .none var indexOfHighestBidder = 0 var remainingBidders: Int { players.filter { $0.isStillBidding }.count } var isWaitingForGameSelection: Bool { players.contains { $0.selectsGame } } init(table: DealingTable) { // Add new cards to the players let newCards = Dealer.dealRemainingCards(of: table.players.map { $0.cards }) let players: [BiddingPlayer] = table.players.enumerated().map { index, player in player.cards = (player.cards + newCards[index]) .sortedCards(order: NormalCardOrder.self) player.isNextActor = false return BiddingPlayer(player: player) } players.first!.isNextActor = true super.init(table: table, players: players) } init(wedding table: WeddingTable, outbidBy player: WeddingPlayer) { gameToOutbid = .hochzeit indexOfHighestBidder = table.players.index(of: player) // All players can bid again, except the wedding offerer let players = table.players.map(BiddingPlayer.init) players[indexOfHighestBidder].isNextActor = true super.init(table: table, players: players) // Choose the player after the one who discarded the wedding selectNextBidder() } func select(game: GameType, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) { guard let player = players.player(named: name) else { log("Player \(name) unexpectedly missing from bidding table \(self.name)") return (.tableStateInvalid, nil) } guard player.selectsGame else { log("Player \(name) does not select the game") return (.tableStateInvalid, nil) } guard gameToOutbid.allows(game: game) else { log("Game \(game) not allowed for class \(gameToOutbid)") return (.tableStateInvalid, nil) } guard player.canPlay(game: game) else { log("Player \(game) can't play game \(game)") return (.tableStateInvalid, nil) } let table = PlayingTable(table: self, game: game, playedBy: player) return (.success, table) } @discardableResult private func selectNextBidder() -> Bool { guard let index = players.firstIndex(where: { $0.isNextActor }) else { log("Bidding: No current actor found to select next bidder") return false } players[index].isNextActor = false let newActor = players.rotated(toStartAt: (index + 1) % 4).first(where: { $0.isStillBidding })! newActor.isNextActor = true return true } 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 bidding table \(self.name)") return (.tableStateInvalid, nil) } guard player.canPerform(action) else { return (.tableStateInvalid, nil) } switch action { case .offerWedding: return performWeddingOffer(forPlayer: player) case .increaseOrMatchGame: return performBidIncrease(forPlayer: player) case .withdrawFromAuction: return performWithdrawl(forPlayer: player) default: return (.tableStateInvalid, nil) } } private func performWeddingOffer(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) { guard gameToOutbid.allowsWedding else { return (.tableStateInvalid, nil) } let newTable = WeddingTable(table: self, offerer: player) return (.success, newTable) } private func performBidIncrease(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) { guard player.isNextActor, player.isStillBidding else { return (.tableStateInvalid, nil) } let index = players.index(of: player) if index < indexOfHighestBidder { // Player sits before the current highest bidder, so only needs to match the game indexOfHighestBidder = index if gameToOutbid == .solo { // Can't be outbid, so player selects game players.forEach { $0.isStillBidding = false } player.selectsGame = true return (.success, nil) } // TODO: Check that wedding can be offered at the correct times // There may be a case where a player sitting before the highest bidder // can't offer a wedding anymore although it should be able to if !gameToOutbid.allowsWedding { players.forEach { $0.isAllowedToOfferWedding = false } } } else { // Player sits after the highest bidder, so must outbid the game // Also the case when first starting bidding gameToOutbid.increase() indexOfHighestBidder = index if !gameToOutbid.allowsWedding { players.forEach { $0.isAllowedToOfferWedding = false } } } if remainingBidders == 1 { moveToGameSelection() } else { selectNextBidder() } return (.success, nil) } private func performWithdrawl(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) { guard player.isStillBidding else { return (.tableStateInvalid, nil) } player.isStillBidding = false switch remainingBidders { case 0: // Nobody wants to play something, so abort the game // This case can only be reached when nobody has bid yet let table = WaitingTable(oldTableAdvancedByOne: self) return (.success, table) case 1: if gameToOutbid != .none { // Last player must play player.isNextActor = false moveToGameSelection() return (.success, nil) } default: break } selectNextBidder() return (.success, nil) } private func moveToGameSelection() { indexOfHighestBidder = players.firstIndex { $0.isStillBidding == true }! let highestPlayer = players[indexOfHighestBidder] highestPlayer.isStillBidding = false highestPlayer.selectsGame = true highestPlayer.isNextActor = true } override func cards(forPlayerAt index: Int) -> [PlayableCard] { players[index].cards.unplayable } override func games(forPlayerAt index: Int) -> [GameConvertible] { if isWaitingForGameSelection { let player = players[index] return gameToOutbid.availableGames.filter(player.canPlay) } if index <= indexOfHighestBidder { return gameToOutbid.availableClasses } return gameToOutbid.classesWhenOutbidding } override func gameIsSelected(byPlayerAt index: Int) -> Bool { players[index].selectsGame } }