import Foundation import WebSocketKit private extension Int { mutating func advanceInTable() { self = (self + 1) % maximumPlayersPerTable } } final class Table { let id: TableId let name: TableName let isPublic: Bool var players: [Player] = [] var phase: GamePhase = .waitingForPlayers var gameType: GameType? = nil var minimumPlayableGame: GameType.GameClass = .ruf /// Indicates if doubles are still allowed var canDoubleDuringGame = false /// Indicates if any player doubled during the current round, extending it to the next round var didDoubleInCurrentRound = false /// Indicates that all players acted after the first four cards var allPlayersFinishedDoubling: Bool { !players.contains { $0.didDoubleAfterFourCards == nil } } init(id: TableId, name: TableName, isPublic: Bool) { self.id = id self.name = name self.isPublic = isPublic } init(newTable name: TableName, isPublic: Bool) { self.id = .newToken() self.name = name self.isPublic = isPublic } func add(player: PlayerName) -> Bool { guard !isFull else { return false } let player = Player(name: player) players.append(player) if isFull { prepareTableForFirstGame() } sendUpdateToAllPlayers() return true } func contains(player: PlayerName) -> Bool { players.contains { $0.name == player } } func select(player: PlayerName) -> Player? { players.first { $0.name == player } } func player(leftOf index: Int) -> Player? { player(at: (index + 1) % 4) } func player(acrossOf index: Int) -> Player? { player(at: (index + 2) % 4) } func player(rightOf index: Int) -> Player? { player(at: (index + 3) % 4) } func player(at index: Int) -> Player? { guard index < players.count else { return nil } return players[index] } func remove(player: PlayerName) { guard contains(player: player) else { return } players = players.filter { $0.name != player } reset() } func connect(player name: PlayerName, using socket: WebSocket) -> Bool { guard let player = select(player: name) else { return false } player.connect(using: socket) sendUpdateToAllPlayers() return true } func disconnect(player name: PlayerName) { guard let player = select(player: name) else { return } guard player.disconnect() else { return } sendUpdateToAllPlayers() return } private func prepareTableForFirstGame() { self.phase = .waitingForPlayers self.gameType = nil self.minimumPlayableGame = .ruf // Not relevant in this phase self.canDoubleDuringGame = true // Not relevant in this phase self.didDoubleInCurrentRound = false // Not relevant in this phase let index = players.firstIndex { $0.playsFirstCard } ?? 0 for i in 0.. PlayerActionResult { defer { sendUpdateToAllPlayers() } switch action { case .deal: return dealInitialCards() case .initialDoubleCost: return perform(double: true, forPlayer: player) case .noDoubleCost: return perform(double: false, forPlayer: player) case .offerWedding: fatalError() case .acceptWedding: fatalError() case .increaseOrMatchGame: fatalError() case .withdrawFromAuction: fatalError() case .doubleDuringGame: fatalError() } } private func dealInitialCards() -> PlayerActionResult { guard isFull else { return .tableNotFull } guard phase == .waitingForPlayers else { return .tableStateInvalid } phase = .collectingDoubles gameType = nil minimumPlayableGame = .ruf let cards = Dealer.dealFirstCards() for (index, player) in players.enumerated() { player.assignFirstCards(cards[index]) } return .success } func perform(double: Bool, forPlayer name: PlayerName) -> PlayerActionResult { let player = select(player: player)! player.didDouble(double) if allPlayersFinishedDoubling { dealAdditionalCards() } return .success } private func dealAdditionalCards() { let cards = Dealer.dealRemainingCards(of: players.map { $0.rawCards }) for (index, player) in players.enumerated() { player.assignRemainingCards(cards[index]) } return .success } private func startAuction() { players.forEach { $0.startAuction() } minimumPlayableGame = .ruf } private func reset() { phase = .waitingForPlayers gameType = nil minimumPlayableGame = .ruf } } extension Table { var isFull: Bool { players.count == maximumPlayersPerTable } var publicInfo: PublicTableInfo { .init(id: id, name: name, players: playerNames) } var playerNames: [PlayerName] { players.map { $0.name } } func compileInfo(for player: PlayerName) -> TableInfo? { guard let index = players.firstIndex(where: { $0.name == player }) else { return nil } return TableInfo(self, forPlayerAt: index) } }