From 3a95e1c99048b712df795195dc33a880143db19d Mon Sep 17 00:00:00 2001 From: Christoph Hagen Date: Mon, 20 Dec 2021 20:18:19 +0100 Subject: [PATCH] Refactor table + player data, add state --- Public/elements.js | 2 +- Sources/App/Infos/PlayerInfo.swift | 27 +++++----- Sources/App/Infos/TableInfo.swift | 34 +++++-------- Sources/App/Model/PlayerState.swift | 28 ++++++----- Sources/App/Model/Players/BiddingPlayer.swift | 19 ++++--- .../App/Model/Players/CardHoldingPlayer.swift | 40 +++++++++++++++ Sources/App/Model/Players/DealingPlayer.swift | 29 +++++------ .../App/Model/Players/FinishedPlayer.swift | 26 +++++++--- Sources/App/Model/Players/Player.swift | 31 +++++------- Sources/App/Model/Players/PlayingPlayer.swift | 49 +++++++++++++++++-- Sources/App/Model/Players/WaitingPlayer.swift | 11 ----- Sources/App/Model/Players/WeddingPlayer.swift | 28 ++++++----- Sources/App/Model/Tables/AbstractTable.swift | 39 +++++++++++---- Sources/App/Model/Tables/BiddingTable.swift | 28 ++++++----- Sources/App/Model/Tables/DealingTable.swift | 12 +++-- Sources/App/Model/Tables/FinishedTable.swift | 6 ++- Sources/App/Model/Tables/PlayingTable.swift | 6 +-- Sources/App/Model/Tables/WeddingTable.swift | 15 +++--- 18 files changed, 261 insertions(+), 169 deletions(-) create mode 100644 Sources/App/Model/Players/CardHoldingPlayer.swift diff --git a/Public/elements.js b/Public/elements.js index 6e73e81..1565028 100644 --- a/Public/elements.js +++ b/Public/elements.js @@ -280,7 +280,7 @@ function setInfoForPlayer(player, position, game) { state.push(double) } - if (game != null) { + if (game != null && player.hasOwnProperty("points")) { state.push(player.points.toString() + " Punkte") } diff --git a/Sources/App/Infos/PlayerInfo.swift b/Sources/App/Infos/PlayerInfo.swift index 59aa25b..bb5cff8 100644 --- a/Sources/App/Infos/PlayerInfo.swift +++ b/Sources/App/Infos/PlayerInfo.swift @@ -6,33 +6,28 @@ struct PlayerInfo: Codable, Equatable { let name: PlayerName /// Indicates that the player is active, i.e. a session is established - let isConnected: Bool + var isConnected = false /// The player is the next one to perform an action - let isNextActor: Bool + var isNextActor = false /// The card which the player added to the current trick - let playedCard: CardId? + var playedCard: CardId? = nil /// The height of the player card on the table stack - let positionInTrick: Int + var positionInTrick = 0 /// The number of times the player doubled the game cost (initial double and raises) - let numberOfDoubles: Int + var numberOfDoubles = 0 - let leadsGame: Bool + var leadsGame = false - let points: Int? + var points: Int? = nil - init(player: Player, position: Int) { - self.name = player.name - self.isConnected = player.isConnected - self.isNextActor = player.isNextActor - self.positionInTrick = position - self.playedCard = player.playedCard?.id - self.numberOfDoubles = player.numberOfDoubles - self.leadsGame = player.leadsGame - self.points = player.points + var state: [PlayerStateId] = [] + + init(name: PlayerName) { + self.name = name } /// Convert the property names into shorter strings for JSON encoding diff --git a/Sources/App/Infos/TableInfo.swift b/Sources/App/Infos/TableInfo.swift index 2a000bd..e2db578 100644 --- a/Sources/App/Infos/TableInfo.swift +++ b/Sources/App/Infos/TableInfo.swift @@ -6,39 +6,29 @@ struct TableInfo: Codable { let name: String - let player: PlayerInfo + var player: PlayerInfo = .init(name: "") - let playerLeft: PlayerInfo? + var playerLeft: PlayerInfo? = nil - let playerAcross: PlayerInfo? + var playerAcross: PlayerInfo? = nil - let playerRight: PlayerInfo? + var playerRight: PlayerInfo? = nil - let playableGames: [GameId] + var playableGames: [GameId] = [] /// The cards in the hand of the player - let cards: [CardInfo] + var cards: [CardInfo] = [] /// The action the player can perform - let actions: [ActionId] + var actions: [ActionId] = [] - let playerSelectsGame: Bool + var playerSelectsGame = false - let game: GameId? + var game: GameId? = nil - init(table: AbstractTable, index: Int) { - self.id = table.id - self.name = table.name - self.player = table.playerInfo(forIndex: index)! - self.playerLeft = table.playerInfo(forIndex: (index + 1) % 4) - self.playerAcross = table.playerInfo(forIndex: (index + 2) % 4) - self.playerRight = table.playerInfo(forIndex: (index + 3) % 4) - let data = table.playerData(at: index) - self.playableGames = data.games.map { $0.id } - self.actions = data.actions.map { $0.id } - self.cards = data.cards.map { $0.cardInfo } - self.playerSelectsGame = data.selectsGame - self.game = table.playedGame?.id + init(id: TableId, name: TableName) { + self.id = id + self.name = name } } diff --git a/Sources/App/Model/PlayerState.swift b/Sources/App/Model/PlayerState.swift index f76c029..3f11e0d 100644 --- a/Sources/App/Model/PlayerState.swift +++ b/Sources/App/Model/PlayerState.swift @@ -1,23 +1,25 @@ import Foundation +typealias PlayerStateId = String + enum PlayerState: String { - case canDouble - case didDouble + case canDouble = "canDouble" + case didDouble = "doubled" - case isDisconnected + case isDisconnected = "offline" - case mustBid - case didFold - case didBid - case mustPlaceBid + case mustBid = "bidder" + case didFold = "fold" + case didBid = "bid" - case isGameSelector - case isWeddingOfferer - case isCalled + case isGameSelector = "selects" + case isWeddingOfferer = "wedding" + case isCalled = "called" - case didRaise + case didRaise = "raised" + case leadsGame = "leads" - case isWinner - case isLooser + case isWinner = "winner" + case isLooser = "looser" } diff --git a/Sources/App/Model/Players/BiddingPlayer.swift b/Sources/App/Model/Players/BiddingPlayer.swift index c84c277..b2feef3 100644 --- a/Sources/App/Model/Players/BiddingPlayer.swift +++ b/Sources/App/Model/Players/BiddingPlayer.swift @@ -1,14 +1,12 @@ import Foundation import WebSocketKit -final class BiddingPlayer: Player { +final class BiddingPlayer: CardHoldingPlayer { var isStillBidding = true var isAllowedToOfferWedding: Bool - var selectsGame = false - init(player: DealingPlayer) { isAllowedToOfferWedding = true super.init(player: player) @@ -47,9 +45,18 @@ final class BiddingPlayer: Player { return actions } - override var points: Int? { - get { nil } - set { } + private var biddingState: [PlayerState] { + isStillBidding ? [] : [.didFold] + } + + override var states: [PlayerState] { + var states = super.states + if !isStillBidding { + states.append(.didFold) + } else if isNextActor { + states.append(.mustBid) + } + return states } } diff --git a/Sources/App/Model/Players/CardHoldingPlayer.swift b/Sources/App/Model/Players/CardHoldingPlayer.swift new file mode 100644 index 0000000..3a35d26 --- /dev/null +++ b/Sources/App/Model/Players/CardHoldingPlayer.swift @@ -0,0 +1,40 @@ +import Foundation + +class CardHoldingPlayer: Player { + + var didDouble: Bool + + var cards: [Card] + + var selectsGame = false + + override init(player: Player) { + self.cards = [] + self.didDouble = false + super.init(player: player) + } + + init(player: CardHoldingPlayer) { + self.cards = player.cards + self.didDouble = player.didDouble + self.selectsGame = player.selectsGame + super.init(player: player) + } + + override var states: [PlayerState] { + var states = super.states + if didDouble { + states.append(.didDouble) + } + if selectsGame { + states.append(.isGameSelector) + } + return states + } + + override var info: PlayerInfo { + var info = super.info + info.numberOfDoubles = didDouble ? 1 : 0 + return info + } +} diff --git a/Sources/App/Model/Players/DealingPlayer.swift b/Sources/App/Model/Players/DealingPlayer.swift index 4f03220..6c70067 100644 --- a/Sources/App/Model/Players/DealingPlayer.swift +++ b/Sources/App/Model/Players/DealingPlayer.swift @@ -1,35 +1,28 @@ import Foundation import WebSocketKit -final class DealingPlayer: Player { +final class DealingPlayer: CardHoldingPlayer { - var didDouble: Bool? = nil - - override var isNextActor: Bool { - get { didDouble == nil } - set { } - } + var didDecide = false override var actions: [PlayerAction] { - didDouble == nil ? [.initialDoubleCost, .noDoubleCost] : [] + didDecide ? [] : [.initialDoubleCost, .noDoubleCost] } init(player: WaitingPlayer) { super.init(player: player) } - override var numberOfDoubles: Int { - get { didDouble == true ? 1 : 0 } - set { } + override var states: [PlayerState] { + var states = super.states + if !didDecide { + states.append(.canDouble) + } + return states } - override var leadsGame: Bool { - get { false } - set { } - } - - override var points: Int? { - get { nil } + override var isNextActor: Bool { + get { !didDecide } set { } } diff --git a/Sources/App/Model/Players/FinishedPlayer.swift b/Sources/App/Model/Players/FinishedPlayer.swift index 15aac50..24cfeef 100644 --- a/Sources/App/Model/Players/FinishedPlayer.swift +++ b/Sources/App/Model/Players/FinishedPlayer.swift @@ -2,20 +2,32 @@ import Foundation final class FinishedPlayer: Player { - let tricks: [Trick] + let points: Int + + let leadsGame: Bool + + let playedCard: Card init(player: PlayingPlayer) { - self.tricks = player.wonTricks + self.points = player.wonTricks.map { $0.points }.reduce(0, +) + self.leadsGame = player.leadsGame + self.playedCard = player.playedCard! super.init(player: player) } - override var points: Int? { - get { tricks.map { $0.points }.reduce(0, +) } - set { } - } - override var actions: [PlayerAction] { [.deal] } + override var states: [PlayerState] { + super.states + [] // TODO: Finish + } + + override var info: PlayerInfo { + var result = super.info + result.points = points + result.playedCard = playedCard.id + return result + } + } diff --git a/Sources/App/Model/Players/Player.swift b/Sources/App/Model/Players/Player.swift index 9db94b0..90404d0 100644 --- a/Sources/App/Model/Players/Player.swift +++ b/Sources/App/Model/Players/Player.swift @@ -7,44 +7,35 @@ class Player { var socket: WebSocket? - var playedCard: Card? - var isNextActor: Bool - var cards: [Card] - - var numberOfDoubles: Int - - var leadsGame: Bool - - var points: Int? - init(name: PlayerName, socket: WebSocket? = nil) { self.name = name self.socket = socket - self.cards = [] self.isNextActor = false - self.playedCard = nil - self.numberOfDoubles = 0 - self.leadsGame = false - self.points = nil } init(player: Player) { self.name = player.name self.socket = player.socket - self.cards = player.cards self.isNextActor = false - self.playedCard = player.playedCard - self.numberOfDoubles = player.numberOfDoubles - self.leadsGame = player.leadsGame - self.points = player.points } var actions: [PlayerAction] { [] } + var states: [PlayerState] { + isConnected ? [] : [.isDisconnected] + } + + var info: PlayerInfo { + var result = PlayerInfo(name: name) + result.isConnected = isConnected + result.isNextActor = isNextActor + result.state = states.map { $0.rawValue } + return result + } } extension Player: Equatable { diff --git a/Sources/App/Model/Players/PlayingPlayer.swift b/Sources/App/Model/Players/PlayingPlayer.swift index 2a2af2a..a0c459e 100644 --- a/Sources/App/Model/Players/PlayingPlayer.swift +++ b/Sources/App/Model/Players/PlayingPlayer.swift @@ -7,18 +7,26 @@ import WebSocketKit */ private let numberOfCardsToProtectAce = 4 -final class PlayingPlayer: Player { +final class PlayingPlayer: CardHoldingPlayer { var canStillRaise = true var isCalledWithAce: Card? + var didPlayCalledAce = false + + var playedCard: Card? + + var leadsGame: Bool + + var numberOfDoubles = 0 + /// All tricks won by the player in this game var wonTricks: [Trick] = [] - init(player: Player, leads: Bool, calledAce ace: Card?) { - super.init(player: player) + init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) { leadsGame = leads + super.init(player: player) if let ace = ace, cards.contains(ace) { isCalledWithAce = ace } else { @@ -26,8 +34,19 @@ final class PlayingPlayer: Player { } } + private var isUnknownCallee: Bool { + isCalledWithAce != nil && !didPlayCalledAce + } + override var actions: [PlayerAction] { - guard canStillRaise, leadsGame == (isCalledWithAce != nil) else { + guard canStillRaise else { + return [] + } + if isUnknownCallee && leadsGame { + // Player belongs to caller, but other side has raised + return [.doubleDuringGame] + } + guard !leadsGame else { return [] } return [.doubleDuringGame] @@ -38,7 +57,7 @@ final class PlayingPlayer: Player { cards = cards.filter { $0 != card } if card == isCalledWithAce { leadsGame.toggle() - isCalledWithAce = nil + didPlayCalledAce = true } } @@ -136,4 +155,24 @@ final class PlayingPlayer: Player { var currentPoints: Int { wonTricks.map { $0.points }.reduce(0, +) } + + override var states: [PlayerState] { + var states = super.states + if didPlayCalledAce { + states.append(.isCalled) + } + if leadsGame { + states.append(.leadsGame) + } + if numberOfDoubles > 0 { + states.append(.didRaise) + } + return states + } + + override var info: PlayerInfo { + var info = super.info + info.playedCard = playedCard?.id + return info + } } diff --git a/Sources/App/Model/Players/WaitingPlayer.swift b/Sources/App/Model/Players/WaitingPlayer.swift index 25b2f25..56662c8 100644 --- a/Sources/App/Model/Players/WaitingPlayer.swift +++ b/Sources/App/Model/Players/WaitingPlayer.swift @@ -8,15 +8,4 @@ final class WaitingPlayer: Player { override var actions: [PlayerAction] { canStartGame ? [.deal] : [] } - - override var leadsGame: Bool { - get { false } - set { } - } - - override var points: Int? { - get { nil } - set { } - } - } diff --git a/Sources/App/Model/Players/WeddingPlayer.swift b/Sources/App/Model/Players/WeddingPlayer.swift index 7ce78f5..535576a 100644 --- a/Sources/App/Model/Players/WeddingPlayer.swift +++ b/Sources/App/Model/Players/WeddingPlayer.swift @@ -1,6 +1,6 @@ import Foundation -final class WeddingPlayer: Player { +final class WeddingPlayer: CardHoldingPlayer { enum State { case requiresAction @@ -16,7 +16,7 @@ final class WeddingPlayer: Player { state == .requiresAction } - var selectsGame: Bool { + override var selectsGame: Bool { get { state == .selectsGame } @@ -38,6 +38,14 @@ final class WeddingPlayer: Player { super.init(player: player) } + override var states: [PlayerState] { + var states = super.states + if offersWedding { + states.append(.isWeddingOfferer) + } + return states + } + override var actions: [PlayerAction] { guard state == .requiresAction else { return [] @@ -57,16 +65,6 @@ final class WeddingPlayer: Player { set { } } - override var points: Int? { - get { nil } - set { } - } - - override var leadsGame: Bool { - get { offersWedding || selectsGame } - set { } - } - func canExchange(card: Card) -> Bool { cards.filter { !$0.isTrump(in: .hochzeit) }.contains(card) } @@ -86,4 +84,10 @@ final class WeddingPlayer: Player { func replace(_ card: Card, with trumpCard: Card) { cards = (cards.filter { $0 != card } + [trumpCard]).sortedCards(forGame: .hochzeit) } + + override var info: PlayerInfo { + var info = super.info + info.leadsGame = offersWedding || selectsGame + return info + } } diff --git a/Sources/App/Model/Tables/AbstractTable.swift b/Sources/App/Model/Tables/AbstractTable.swift index 1ceef88..bf22c38 100644 --- a/Sources/App/Model/Tables/AbstractTable.swift +++ b/Sources/App/Model/Tables/AbstractTable.swift @@ -45,15 +45,36 @@ class AbstractTable where TablePlayer: Player { (.tableStateInvalid, nil) } - func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { - let player = players[index] - return (actions: player.actions, games: [], cards: player.cards.unplayable, selectsGame: false) - } - func cardStackPosition(ofPlayerAt index: Int) -> Int { index } + func cards(forPlayerAt index: Int) -> [PlayableCard] { + [] + } + + func games(forPlayerAt index: Int) -> [GameConvertible] { + [] + } + + func gameIsSelected(byPlayerAt index: Int) -> Bool { + false + } + + func tableInfo(forPlayerAt index: Int) -> TableInfo { + var info = TableInfo(id: id, name: name) + info.player = playerInfo(forIndex: index)! + info.playerLeft = playerInfo(forIndex: (index + 1) % 4) + info.playerAcross = playerInfo(forIndex: (index + 2) % 4) + info.playerRight = playerInfo(forIndex: (index + 3) % 4) + info.playableGames = games(forPlayerAt: index).map { $0.id } + info.actions = players[index].actions.map { $0.id } + info.cards = cards(forPlayerAt: index).map { $0.cardInfo } + info.playerSelectsGame = gameIsSelected(byPlayerAt: index) + info.game = playedGame?.id + return info + } + } extension AbstractTable: ManageableTable { @@ -111,7 +132,9 @@ extension AbstractTable: ManageableTable { return nil } let height = cardStackPosition(ofPlayerAt: index) - return PlayerInfo(player: player, position: height) + var info = player.info + info.positionInTrick = height + return info } func tableInfo(forPlayer player: PlayerName) -> TableInfo { @@ -119,9 +142,5 @@ extension AbstractTable: ManageableTable { return tableInfo(forPlayerAt: index) } - func tableInfo(forPlayerAt index: Int) -> TableInfo { - .init(table: self, index: index) - } - } diff --git a/Sources/App/Model/Tables/BiddingTable.swift b/Sources/App/Model/Tables/BiddingTable.swift index b9e5b62..21339f4 100644 --- a/Sources/App/Model/Tables/BiddingTable.swift +++ b/Sources/App/Model/Tables/BiddingTable.swift @@ -164,18 +164,22 @@ final class BiddingTable: AbstractTable { return (.success, nil) } - override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { - let player = players[index] - - let games: [GameConvertible] - if isWaitingForGameSelection { - games = gameToOutbid.availableGames.filter(player.canPlay) - } else if index <= indexOfHighestBidder { - games = gameToOutbid.availableClasses - } else { - games = gameToOutbid.classesWhenOutbidding - } - return (player.actions, games, player.cards.unplayable, selectsGame: player.selectsGame) + 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 + } } diff --git a/Sources/App/Model/Tables/DealingTable.swift b/Sources/App/Model/Tables/DealingTable.swift index 80c516d..c129a8d 100644 --- a/Sources/App/Model/Tables/DealingTable.swift +++ b/Sources/App/Model/Tables/DealingTable.swift @@ -4,16 +4,17 @@ final class DealingTable: AbstractTable { init(table: WaitingTable) { let cards = Dealer.dealFirstCards() - for (index, player) in table.players.enumerated() { + let players = table.players.map(DealingPlayer.init) + for (index, player) in players.enumerated() { player.cards = cards[index] } - let players = table.players.map(DealingPlayer.init) super.init(table: table, players: players) + print("\(self.players[0].cards.count) cards") } /// All players either doubled or didn't double var allPlayersActed: Bool { - !players.contains { $0.didDouble == nil } + !players.contains { !$0.didDecide } } /// At least one player placed a bid @@ -41,7 +42,7 @@ final class DealingTable: AbstractTable { } private func perform(double: Bool, forPlayer player: DealingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) { - guard player.didDouble == nil else { + guard !player.didDecide else { return (.tableStateInvalid, nil) } player.didDouble = double @@ -58,4 +59,7 @@ final class DealingTable: AbstractTable { return (.success, table) } + override func cards(forPlayerAt index: Int) -> [PlayableCard] { + players[index].cards.unplayable + } } diff --git a/Sources/App/Model/Tables/FinishedTable.swift b/Sources/App/Model/Tables/FinishedTable.swift index 2ba35eb..741f0ce 100644 --- a/Sources/App/Model/Tables/FinishedTable.swift +++ b/Sources/App/Model/Tables/FinishedTable.swift @@ -47,12 +47,16 @@ final class FinishedTable: AbstractTable { } init(table: PlayingTable) { + let players = table.players.map(FinishedPlayer.init) self.game = table.game leadingPoints = players .filter { $0.leadsGame } - .map { $0.points! } + .map { $0.points } .reduce(0, +) + // TODO: Set isNextActor for winners + // TODO: Check for bettel + // TODO: Set schneider, schwarz, cost super.init(table: table, players: players) } diff --git a/Sources/App/Model/Tables/PlayingTable.swift b/Sources/App/Model/Tables/PlayingTable.swift index 7764c02..8518aa3 100644 --- a/Sources/App/Model/Tables/PlayingTable.swift +++ b/Sources/App/Model/Tables/PlayingTable.swift @@ -108,10 +108,8 @@ final class PlayingTable: AbstractTable { } } - override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { - let player = players[index] - let cards = player.playableCards(for: nextTrick, in: game) - return (actions: player.actions, games: [], cards: cards, selectsGame: false) + 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?) { diff --git a/Sources/App/Model/Tables/WeddingTable.swift b/Sources/App/Model/Tables/WeddingTable.swift index 4f42fff..b5f9d81 100644 --- a/Sources/App/Model/Tables/WeddingTable.swift +++ b/Sources/App/Model/Tables/WeddingTable.swift @@ -82,15 +82,16 @@ final class WeddingTable: AbstractTable { return (.success, table) } - override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { - guard requiresCardSelection else { - return super.playerData(at: index) - } + override func cards(forPlayerAt index: Int) -> [PlayableCard] { let player = players[index] - guard player.selectsGame else { - return super.playerData(at: index) + guard requiresCardSelection, player.selectsGame else { + return player.cards.unplayable } - return (actions: player.actions, games: [], cards: player.exchangeableCards, selectsGame: false) + return player.exchangeableCards + } + + override func gameIsSelected(byPlayerAt index: Int) -> Bool { + players[index].selectsGame } override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {