import Foundation import WebSocketKit import CloudKit private let encoder = JSONEncoder() final class Player { let name: PlayerName /// The player is the first to play a card in a new game var playsFirstCard = false /// The player is the next to perform an action (e.g. play a card) var isNextActor = false /// The player must select the game to play after winning the auction var selectsGame = false /// The players plays/played the first card for the current trick var startedCurrentTrick = false /// The action available to the player var actions: [Action] = [] /// Indicates if the player doubled ("legen") var didDoubleAfterFourCards: Bool? = nil /// Indicates if the player is still involved in the bidding process var isStillBidding = true /// Indicates that the player leads the game ("Spieler") var isGameLeader = false /// Indicates the number of raises ("Schuss") of the player var numberOfRaises = 0 /// The remaining cards of the player var handCards: [PlayableCard] = [] /// The card played for the current trick var playedCard: Card? = nil /// All tricks won by the player in this game var wonTricks: [Trick] = [] var socket: WebSocket? = nil init(name: PlayerName) { self.name = name } var rawCards: [Card] { handCards.map { $0.card } } func connect(using socket: WebSocket) { _ = self.socket?.close() self.socket = socket } func send(_ info: TableInfo) { try? socket?.send(encodeJSON(info)) } func disconnect() -> Bool { guard let socket = socket else { return false } do { try socket.close().wait() } catch { print("Failed to close socket for player: \(name): \(error)") } self.socket = nil return true } func prepareForFirstGame(isFirstPlayer: Bool) { playsFirstCard = isFirstPlayer isNextActor = isFirstPlayer selectsGame = false // Not relevant in this phase startedCurrentTrick = isFirstPlayer actions = [.deal] didDoubleAfterFourCards = nil // Not relevant in this phase isStillBidding = false // Not relevant in this phase isGameLeader = false // Not relevant in this phase numberOfRaises = 0 // Not relevant in this phase handCards = [] playedCard = nil wonTricks = [] } func assignFirstCards(_ cards: Hand) { selectsGame = false // Not relevant in this phase actions = [.initialDoubleCost, .noDoubleCost] didDoubleAfterFourCards = nil isStillBidding = false // Not relevant in this phase isGameLeader = false // Not relevant in this phase numberOfRaises = 0 // Not relevant in this phase handCards = cards.map { .init(card: $0, isPlayable: false) } playedCard = nil wonTricks = [] } func didDouble(_ double: Bool) { selectsGame = false // Not relevant in this phase actions = [] didDoubleAfterFourCards = double isStillBidding = false // Not relevant in this phase isGameLeader = false // Not relevant in this phase numberOfRaises = 0 // Not relevant in this phase playedCard = nil wonTricks = [] } func assignRemainingCards(_ cards: Hand) { isStillBidding = true isGameLeader = false numberOfRaises = 0 handCards = (rawCards + cards) .sorted(CardOrderType: .normal) .map { .init(card: $0, isPlayable: false) } playedCard = nil wonTricks = [] } func startAuction() { selectsGame = false // Not relevant in this phase if playsFirstCard { actions = [.withdrawFromAuction, .increaseOrMatchGame] } else { } actions = [] isStillBidding = true isGameLeader = false // Not relevant in this phase numberOfRaises = 0 // Not relevant in this phase playedCard = nil wonTricks = [] } func info(masked: Bool) -> PlayerInfo { .init(player: self, isMasked: masked) } } extension Player { /// Indicate that the player is connected when at a table var isConnected: Bool { guard let socket = socket else { return false } guard !socket.isClosed else { self.socket = nil return false } return true } } extension Player { enum Action: String, Codable { /// The player can request cards to be dealt case deal = "deal" /// The player doubles on the initial four cards case initialDoubleCost = "double" /// The player does not double on the initial four cards case noDoubleCost = "skip" /// The player offers a wedding (one trump card) case offerWedding = "wedding" /// The player can choose to accept the wedding case acceptWedding = "accept" /// The player matches or increases the game during auction case increaseOrMatchGame = "bid" /// The player does not want to play case withdrawFromAuction = "out" /// The player claims to win and doubles the game cost ("schießen") case doubleDuringGame = "raise" /// The url path for the client to call (e.g. /player/deal) var path: String { rawValue } } }