diff --git a/Sources/App/Model/Player.swift b/Sources/App/Model/OldPlayer.swift similarity index 96% rename from Sources/App/Model/Player.swift rename to Sources/App/Model/OldPlayer.swift index fef4f9f..f35732a 100644 --- a/Sources/App/Model/Player.swift +++ b/Sources/App/Model/OldPlayer.swift @@ -13,7 +13,7 @@ private let numberOfCardsToProtectAce = 4 let numberOfCardsPerPlayer = 8 -final class Player { +final class OldPlayer { let name: PlayerName @@ -28,9 +28,12 @@ final class Player { /// The players plays/played the first card for the current trick var startedCurrentTrick = false + + /// Indicate the currently highest bidder during bidding + var isHighestBidder = false /// The action available to the player - var actions: [Action] = [] + var actions: [PlayerAction] = [] /// Indicates if the player doubled ("legen") var didDoubleAfterFourCards: Bool? = nil @@ -111,7 +114,7 @@ final class Player { return true } - func canPerform(_ action: Action) -> Bool { + func canPerform(_ action: PlayerAction) -> Bool { actions.contains(action) } @@ -124,6 +127,7 @@ final class Player { didDoubleAfterFourCards = nil isStillBidding = true isGameLeader = false + isHighestBidder = false handCards = [] playedCard = nil wonTricks = [] @@ -184,6 +188,7 @@ final class Player { func didPerformBid() { isNextActor = false + isHighestBidder = true actions = [] } @@ -211,6 +216,7 @@ final class Player { func auctionEnded() { actions = [] isStillBidding = false + isHighestBidder = false isNextActor = false } @@ -447,13 +453,9 @@ final class Player { handCards[i].isPlayable = playable } } - - func info(masked: Bool, positionInTrick: Int) -> PlayerInfo { - .init(player: self, isMasked: masked, trickPosition: positionInTrick) - } } -extension Player { +extension OldPlayer { /// Indicate that the player is connected when at a table var isConnected: Bool { @@ -468,9 +470,9 @@ extension Player { } } -extension Player: Equatable { +extension OldPlayer: Equatable { - static func == (lhs: Player, rhs: Player) -> Bool { + static func == (lhs: OldPlayer, rhs: OldPlayer) -> Bool { lhs.name == rhs.name } } diff --git a/Sources/App/Model/Table.swift b/Sources/App/Model/OldTable.swift similarity index 82% rename from Sources/App/Model/Table.swift rename to Sources/App/Model/OldTable.swift index 2318524..34cf67d 100644 --- a/Sources/App/Model/Table.swift +++ b/Sources/App/Model/OldTable.swift @@ -8,7 +8,7 @@ private extension Int { } } -final class Table { +final class OldTable { let id: TableId @@ -16,13 +16,13 @@ final class Table { let isPublic: Bool - var players: [Player] = [] + var players: [OldPlayer] = [] var phase: GamePhase = .waitingForPlayers var gameType: GameType? = nil - var minimumPlayableGame: GameType.GameClass? + var minimumPlayableGame: GameType.GameClass = .none /// Indicates if any player doubled during the current round, extending it to the next round var didDoubleInCurrentRound = false @@ -53,7 +53,7 @@ final class Table { players.filter { $0.isStillBidding }.count } - var auctionWinner: Player { + var auctionWinner: OldPlayer { players.first { $0.isStillBidding }! } @@ -93,7 +93,7 @@ final class Table { guard !isFull else { return false } - let player = Player(name: player) + let player = OldPlayer(name: player) players.append(player) if isFull { prepareTableForFirstGame() @@ -107,54 +107,36 @@ final class Table { } /// The player to play the first card of the current game - var firstPlayer: Player { + var firstPlayer: OldPlayer { players.first { $0.playsFirstCard }! } - func select(player: PlayerName) -> Player? { + func select(player: PlayerName) -> OldPlayer? { players.first { $0.name == player } } var indexOfTrickStarter: Int { players.firstIndex { $0.startedCurrentTrick }! } - - func playerInfo(at index: Int, masked: Bool) -> PlayerInfo? { - let starter = indexOfTrickStarter - let layer = (4 - starter + index) % 4 - return player(at: index)?.info(masked: masked, positionInTrick: layer) - } - func playerInfo(leftOf index: Int, masked: Bool) -> PlayerInfo? { - playerInfo(at: (index + 1) % 4, masked: masked) - } - - func playerInfo(acrossOf index: Int, masked: Bool) -> PlayerInfo? { - playerInfo(at: (index + 2) % 4, masked: masked) - } - - func playerInfo(rightOf index: Int, masked: Bool) -> PlayerInfo? { - playerInfo(at: (index + 3) % 4, masked: masked) - } - - func player(at index: Int) -> Player? { + func player(at index: Int) -> OldPlayer? { guard index < players.count else { return nil } return players[index] } - func index(of player: Player) -> Int { + func index(of player: OldPlayer) -> Int { players.firstIndex(of: player)! } - func nextPlayer(after player: Player) -> Player { + func nextPlayer(after player: OldPlayer) -> OldPlayer { let i = index(of: player) let newIndex = (i + 1) % maximumPlayersPerTable return players[newIndex] } - func nextBidder(after player: Player) -> Player { + func nextBidder(after player: OldPlayer) -> OldPlayer { // Find next player to place bid let index = index(of: player) for i in 1..<4 { @@ -166,6 +148,11 @@ final class Table { } return player } + + func availableGames(forPlayerAt index: Int) -> [GameType] { + + return [] + } func remove(player: PlayerName) { guard let index = players.firstIndex(where: { $0.name == player }) else { @@ -202,7 +189,7 @@ final class Table { private func prepareTableForFirstGame() { phase = .waitingForPlayers gameType = nil - minimumPlayableGame = nil // Not relevant in this phase + minimumPlayableGame = .none // Not relevant in this phase didDoubleInCurrentRound = false // Not relevant in this phase let index = players.firstIndex { $0.playsFirstCard } ?? 0 for i in 0.. PlayerActionResult { + func perform(action: PlayerAction, forPlayer player: PlayerName) -> PlayerActionResult { let player = select(player: player)! guard player.canPerform(action) else { print("Player \(player) wants to \(action.path), but only allowed: \(player.actions)") @@ -332,7 +313,7 @@ final class Table { return .success } - func perform(double: Bool, forPlayer player: Player) -> PlayerActionResult { + func perform(double: Bool, forPlayer player: OldPlayer) -> PlayerActionResult { player.didDouble(double) guard allPlayersFinishedDoubling else { return .success @@ -351,17 +332,17 @@ final class Table { player.assignRemainingCards(cards[index]) } players.forEach { $0.startAuction() } - minimumPlayableGame = nil + minimumPlayableGame = .none phase = .bidding } - private func performWeddingCall(forPlayer player: Player) -> PlayerActionResult { + private func performWeddingCall(forPlayer player: OldPlayer) -> PlayerActionResult { guard phase == .bidding else { print("Invalid phase \(phase) for wedding call") return .tableStateInvalid } - guard minimumPlayableGame == nil || minimumPlayableGame == .ruf else { - print("Invalid minimum game \(minimumPlayableGame!) for wedding call") + guard minimumPlayableGame.allowsWedding else { + print("Invalid minimum game \(minimumPlayableGame) for wedding call") return .tableStateInvalid } guard player.canOfferWedding else { @@ -381,7 +362,7 @@ final class Table { return .success } - private func performBidIncrease(forPlayer player: Player) -> PlayerActionResult { + private func performBidIncrease(forPlayer player: OldPlayer) -> PlayerActionResult { guard phase == .bidding else { return .tableStateInvalid } @@ -392,37 +373,41 @@ final class Table { guard player.isNextActor else { return .tableStateInvalid } - if minimumPlayableGame == nil { - minimumPlayableGame = .ruf - } else { - minimumPlayableGame!.increase() + // TODO: Check if new player sits before old player + // then don't increase game + minimumPlayableGame.increase() + if !minimumPlayableGame.allowsWedding { // Remove wedding offers players.forEach { $0.weddingOutbid() } } + #warning("Fix bidding") + // TODO: Remove highest bidder from old player player.didPerformBid() + if numberOfRemainingBidders == 1 { selectGame(player: player) + return .success } // Find next player to place bid nextBidder(after: player).requiresBid(hasWedding: false) return .success } - private func handleWeddingOutbid(forPlayer player: Player) -> PlayerActionResult { + private func handleWeddingOutbid(forPlayer player: OldPlayer) -> PlayerActionResult { if player.offersWedding { // A player offering a wedding can't outbid itself return .tableStateInvalid } players.forEach { $0.weddingOutbid() } - nextBidder(after: player).requiresBid(hasWedding: true) + nextBidder(after: player).requiresBid(hasWedding: false) return .success } - private func handleWeddingAccept(forPlayer player: Player) -> PlayerActionResult { + private func handleWeddingAccept(forPlayer player: OldPlayer) -> PlayerActionResult { guard phase == .bidding else { return .tableStateInvalid } - guard minimumPlayableGame == nil || minimumPlayableGame == .ruf else { + guard minimumPlayableGame.allowsWedding else { return .tableStateInvalid } guard weddingOfferExists else { @@ -450,19 +435,19 @@ final class Table { return .success } - private func selectedWedding(player: Player) { - minimumPlayableGame = nil + private func selectedWedding(player: OldPlayer) { + minimumPlayableGame = .none gameType = .hochzeit phase = .selectWeddingCard players.forEach { $0.auctionEnded() } player.mustSelectWeddingCard() } - private func selectedCardForWedding(card: Card, player: Player) -> PlayCardResult { + private func selectedCardForWedding(card: Card, player: OldPlayer) -> PlayCardResult { guard player.isNextActor, player.wouldAcceptWedding, weddingOfferExists else { - return .invalidTableState + return .tableStateInvalid } guard !card.isTrump(in: .hochzeit), player.has(card: card) else { @@ -481,7 +466,7 @@ final class Table { return .success } - private func performWithdrawl(forPlayer player: Player) -> PlayerActionResult { + private func performWithdrawl(forPlayer player: OldPlayer) -> PlayerActionResult { guard phase == .bidding, player.isNextActor, player.isStillBidding else { @@ -490,7 +475,7 @@ final class Table { player.withdrawFromBidding() switch numberOfRemainingBidders { case 1: - if minimumPlayableGame != nil { + if minimumPlayableGame != .none { // Will only be called when at least one player placed a bid selectGame(player: auctionWinner) return .success @@ -515,7 +500,7 @@ final class Table { prepareTableForFirstGame() } - private func selectGame(player: Player) { + private func selectGame(player: OldPlayer) { gameType = nil phase = .selectGame players.forEach { $0.auctionEnded() } @@ -527,14 +512,14 @@ final class Table { guard phase == .selectGame, player.selectsGame, game != .hochzeit else { return .tableStateInvalid } - guard minimumPlayableGame == nil || game.gameClass >= minimumPlayableGame! else { + guard game.gameClass >= minimumPlayableGame else { return .tableStateInvalid } defer { sendUpdateToAllPlayers() } guard let suit = game.calledSuit else { phase = .playing gameType = game - minimumPlayableGame = nil + minimumPlayableGame = .none players.forEach { $0.start(game: game) } player.switchLeadership() @@ -546,7 +531,7 @@ final class Table { } phase = .playing gameType = game - minimumPlayableGame = nil + minimumPlayableGame = .none players.forEach { $0.start(game: game) } player.switchLeadership() // Find called player @@ -555,7 +540,7 @@ final class Table { return .success } - private func performDoubleDuringGame(forPlayer player: Player) -> PlayerActionResult { + private func performDoubleDuringGame(forPlayer player: OldPlayer) -> PlayerActionResult { guard phase == .playing, !player.isGameLeader else { return .tableStateInvalid } @@ -568,14 +553,14 @@ final class Table { private func reset() { phase = .waitingForPlayers gameType = nil - minimumPlayableGame = nil + minimumPlayableGame = .none for player in players { player.prepareForNewGame(isFirstPlayer: player.playsFirstCard) } } } -extension Table { +extension OldTable { var isFull: Bool { players.count == maximumPlayersPerTable @@ -588,12 +573,5 @@ extension Table { 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) - } + } diff --git a/Sources/App/Model/PlayerAction.swift b/Sources/App/Model/PlayerAction.swift index 77f9ae0..0dc3f6d 100644 --- a/Sources/App/Model/PlayerAction.swift +++ b/Sources/App/Model/PlayerAction.swift @@ -1,35 +1,32 @@ import Foundation -extension Player { +enum PlayerAction: String, Codable { + /// The player can request cards to be dealt + case deal = "deal" - 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 doubles on the initial four cards - case initialDoubleCost = "double" + /// The player does not double on the initial four cards + case noDoubleCost = "skip" - /// 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 offers a wedding (one trump card) - case offerWedding = "wedding" + /// The player can choose to accept the wedding + case acceptWedding = "accept" - /// 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 matches or increases the game during auction - case increaseOrMatchGame = "bid" + /// The player does not want to play + case withdrawFromAuction = "out" - /// 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 - } + /// 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 } } diff --git a/Sources/App/Results/PlayCardResult.swift b/Sources/App/Results/PlayCardResult.swift index e245218..b529d3d 100644 --- a/Sources/App/Results/PlayCardResult.swift +++ b/Sources/App/Results/PlayCardResult.swift @@ -8,7 +8,7 @@ enum PlayCardResult { case noTableJoined - case invalidTableState + case tableStateInvalid case invalidCard } diff --git a/Sources/App/Results/DealCardsResult.swift b/Sources/App/Results/PlayerActionResult.swift similarity index 89% rename from Sources/App/Results/DealCardsResult.swift rename to Sources/App/Results/PlayerActionResult.swift index 937b98b..e723a2b 100644 --- a/Sources/App/Results/DealCardsResult.swift +++ b/Sources/App/Results/PlayerActionResult.swift @@ -12,4 +12,6 @@ enum PlayerActionResult { case tableNotFull case tableStateInvalid + + case invalidCard } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 4c31724..2df3319 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -284,7 +284,7 @@ func routes(_ app: Application) throws { throw Abort(.badRequest) } let result: PlayerActionResult - if let action = Player.Action(rawValue: actionString) { + if let action = PlayerAction(rawValue: actionString) { result = database.performAction(playerToken: token, action: action) } else if let game = GameType(rawValue: actionString) { result = database.select(game: game, playerToken: token) @@ -302,6 +302,8 @@ func routes(_ app: Application) throws { throw Abort(.preconditionFailed) // 412 case .tableStateInvalid: throw Abort(.preconditionFailed) // 412 + case .invalidCard: + throw Abort(.preconditionFailed) // 412 } } @@ -318,11 +320,12 @@ func routes(_ app: Application) throws { throw Abort(.unauthorized) // 401 case .noTableJoined: throw Abort(.preconditionFailed) // 412 - case .invalidTableState: + case .tableStateInvalid: throw Abort(.preconditionFailed) // 412 case .invalidCard: throw Abort(.preconditionFailed) // 412 - + case .tableNotFull: + throw Abort(.preconditionFailed) // 412 } } }