Refactor table + player data, add state

This commit is contained in:
Christoph Hagen 2021-12-20 20:18:19 +01:00
parent 49787db1aa
commit 3a95e1c990
18 changed files with 261 additions and 169 deletions

View File

@ -280,7 +280,7 @@ function setInfoForPlayer(player, position, game) {
state.push(double) state.push(double)
} }
if (game != null) { if (game != null && player.hasOwnProperty("points")) {
state.push(player.points.toString() + " Punkte") state.push(player.points.toString() + " Punkte")
} }

View File

@ -6,33 +6,28 @@ struct PlayerInfo: Codable, Equatable {
let name: PlayerName let name: PlayerName
/// Indicates that the player is active, i.e. a session is established /// 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 /// 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 /// 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 /// 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) /// 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) { var state: [PlayerStateId] = []
self.name = player.name
self.isConnected = player.isConnected init(name: PlayerName) {
self.isNextActor = player.isNextActor self.name = name
self.positionInTrick = position
self.playedCard = player.playedCard?.id
self.numberOfDoubles = player.numberOfDoubles
self.leadsGame = player.leadsGame
self.points = player.points
} }
/// Convert the property names into shorter strings for JSON encoding /// Convert the property names into shorter strings for JSON encoding

View File

@ -6,39 +6,29 @@ struct TableInfo: Codable {
let name: String 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 /// The cards in the hand of the player
let cards: [CardInfo] var cards: [CardInfo] = []
/// The action the player can perform /// 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<T>(table: AbstractTable<T>, index: Int) { init(id: TableId, name: TableName) {
self.id = table.id self.id = id
self.name = table.name self.name = 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
} }
} }

View File

@ -1,23 +1,25 @@
import Foundation import Foundation
typealias PlayerStateId = String
enum PlayerState: String { enum PlayerState: String {
case canDouble case canDouble = "canDouble"
case didDouble case didDouble = "doubled"
case isDisconnected case isDisconnected = "offline"
case mustBid case mustBid = "bidder"
case didFold case didFold = "fold"
case didBid case didBid = "bid"
case mustPlaceBid
case isGameSelector case isGameSelector = "selects"
case isWeddingOfferer case isWeddingOfferer = "wedding"
case isCalled case isCalled = "called"
case didRaise case didRaise = "raised"
case leadsGame = "leads"
case isWinner case isWinner = "winner"
case isLooser case isLooser = "looser"
} }

View File

@ -1,14 +1,12 @@
import Foundation import Foundation
import WebSocketKit import WebSocketKit
final class BiddingPlayer: Player { final class BiddingPlayer: CardHoldingPlayer {
var isStillBidding = true var isStillBidding = true
var isAllowedToOfferWedding: Bool var isAllowedToOfferWedding: Bool
var selectsGame = false
init(player: DealingPlayer) { init(player: DealingPlayer) {
isAllowedToOfferWedding = true isAllowedToOfferWedding = true
super.init(player: player) super.init(player: player)
@ -47,9 +45,18 @@ final class BiddingPlayer: Player {
return actions return actions
} }
override var points: Int? { private var biddingState: [PlayerState] {
get { nil } isStillBidding ? [] : [.didFold]
set { } }
override var states: [PlayerState] {
var states = super.states
if !isStillBidding {
states.append(.didFold)
} else if isNextActor {
states.append(.mustBid)
}
return states
} }
} }

View File

@ -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
}
}

View File

@ -1,35 +1,28 @@
import Foundation import Foundation
import WebSocketKit import WebSocketKit
final class DealingPlayer: Player { final class DealingPlayer: CardHoldingPlayer {
var didDouble: Bool? = nil var didDecide = false
override var isNextActor: Bool {
get { didDouble == nil }
set { }
}
override var actions: [PlayerAction] { override var actions: [PlayerAction] {
didDouble == nil ? [.initialDoubleCost, .noDoubleCost] : [] didDecide ? [] : [.initialDoubleCost, .noDoubleCost]
} }
init(player: WaitingPlayer) { init(player: WaitingPlayer) {
super.init(player: player) super.init(player: player)
} }
override var numberOfDoubles: Int { override var states: [PlayerState] {
get { didDouble == true ? 1 : 0 } var states = super.states
set { } if !didDecide {
states.append(.canDouble)
}
return states
} }
override var leadsGame: Bool { override var isNextActor: Bool {
get { false } get { !didDecide }
set { }
}
override var points: Int? {
get { nil }
set { } set { }
} }

View File

@ -2,20 +2,32 @@ import Foundation
final class FinishedPlayer: Player { final class FinishedPlayer: Player {
let tricks: [Trick] let points: Int
let leadsGame: Bool
let playedCard: Card
init(player: PlayingPlayer) { 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) super.init(player: player)
} }
override var points: Int? {
get { tricks.map { $0.points }.reduce(0, +) }
set { }
}
override var actions: [PlayerAction] { override var actions: [PlayerAction] {
[.deal] [.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
}
} }

View File

@ -7,44 +7,35 @@ class Player {
var socket: WebSocket? var socket: WebSocket?
var playedCard: Card?
var isNextActor: Bool var isNextActor: Bool
var cards: [Card]
var numberOfDoubles: Int
var leadsGame: Bool
var points: Int?
init(name: PlayerName, socket: WebSocket? = nil) { init(name: PlayerName, socket: WebSocket? = nil) {
self.name = name self.name = name
self.socket = socket self.socket = socket
self.cards = []
self.isNextActor = false self.isNextActor = false
self.playedCard = nil
self.numberOfDoubles = 0
self.leadsGame = false
self.points = nil
} }
init(player: Player) { init(player: Player) {
self.name = player.name self.name = player.name
self.socket = player.socket self.socket = player.socket
self.cards = player.cards
self.isNextActor = false self.isNextActor = false
self.playedCard = player.playedCard
self.numberOfDoubles = player.numberOfDoubles
self.leadsGame = player.leadsGame
self.points = player.points
} }
var actions: [PlayerAction] { 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 { extension Player: Equatable {

View File

@ -7,18 +7,26 @@ import WebSocketKit
*/ */
private let numberOfCardsToProtectAce = 4 private let numberOfCardsToProtectAce = 4
final class PlayingPlayer: Player { final class PlayingPlayer: CardHoldingPlayer {
var canStillRaise = true var canStillRaise = true
var isCalledWithAce: Card? var isCalledWithAce: Card?
var didPlayCalledAce = false
var playedCard: Card?
var leadsGame: Bool
var numberOfDoubles = 0
/// All tricks won by the player in this game /// All tricks won by the player in this game
var wonTricks: [Trick] = [] var wonTricks: [Trick] = []
init(player: Player, leads: Bool, calledAce ace: Card?) { init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) {
super.init(player: player)
leadsGame = leads leadsGame = leads
super.init(player: player)
if let ace = ace, cards.contains(ace) { if let ace = ace, cards.contains(ace) {
isCalledWithAce = ace isCalledWithAce = ace
} else { } else {
@ -26,8 +34,19 @@ final class PlayingPlayer: Player {
} }
} }
private var isUnknownCallee: Bool {
isCalledWithAce != nil && !didPlayCalledAce
}
override var actions: [PlayerAction] { 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 []
} }
return [.doubleDuringGame] return [.doubleDuringGame]
@ -38,7 +57,7 @@ final class PlayingPlayer: Player {
cards = cards.filter { $0 != card } cards = cards.filter { $0 != card }
if card == isCalledWithAce { if card == isCalledWithAce {
leadsGame.toggle() leadsGame.toggle()
isCalledWithAce = nil didPlayCalledAce = true
} }
} }
@ -136,4 +155,24 @@ final class PlayingPlayer: Player {
var currentPoints: Int { var currentPoints: Int {
wonTricks.map { $0.points }.reduce(0, +) 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
}
} }

View File

@ -8,15 +8,4 @@ final class WaitingPlayer: Player {
override var actions: [PlayerAction] { override var actions: [PlayerAction] {
canStartGame ? [.deal] : [] canStartGame ? [.deal] : []
} }
override var leadsGame: Bool {
get { false }
set { }
}
override var points: Int? {
get { nil }
set { }
}
} }

View File

@ -1,6 +1,6 @@
import Foundation import Foundation
final class WeddingPlayer: Player { final class WeddingPlayer: CardHoldingPlayer {
enum State { enum State {
case requiresAction case requiresAction
@ -16,7 +16,7 @@ final class WeddingPlayer: Player {
state == .requiresAction state == .requiresAction
} }
var selectsGame: Bool { override var selectsGame: Bool {
get { get {
state == .selectsGame state == .selectsGame
} }
@ -38,6 +38,14 @@ final class WeddingPlayer: Player {
super.init(player: player) super.init(player: player)
} }
override var states: [PlayerState] {
var states = super.states
if offersWedding {
states.append(.isWeddingOfferer)
}
return states
}
override var actions: [PlayerAction] { override var actions: [PlayerAction] {
guard state == .requiresAction else { guard state == .requiresAction else {
return [] return []
@ -57,16 +65,6 @@ final class WeddingPlayer: Player {
set { } set { }
} }
override var points: Int? {
get { nil }
set { }
}
override var leadsGame: Bool {
get { offersWedding || selectsGame }
set { }
}
func canExchange(card: Card) -> Bool { func canExchange(card: Card) -> Bool {
cards.filter { !$0.isTrump(in: .hochzeit) }.contains(card) cards.filter { !$0.isTrump(in: .hochzeit) }.contains(card)
} }
@ -86,4 +84,10 @@ final class WeddingPlayer: Player {
func replace(_ card: Card, with trumpCard: Card) { func replace(_ card: Card, with trumpCard: Card) {
cards = (cards.filter { $0 != card } + [trumpCard]).sortedCards(forGame: .hochzeit) cards = (cards.filter { $0 != card } + [trumpCard]).sortedCards(forGame: .hochzeit)
} }
override var info: PlayerInfo {
var info = super.info
info.leadsGame = offersWedding || selectsGame
return info
}
} }

View File

@ -45,15 +45,36 @@ class AbstractTable<TablePlayer> where TablePlayer: Player {
(.tableStateInvalid, nil) (.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 { func cardStackPosition(ofPlayerAt index: Int) -> Int {
index 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 { extension AbstractTable: ManageableTable {
@ -111,7 +132,9 @@ extension AbstractTable: ManageableTable {
return nil return nil
} }
let height = cardStackPosition(ofPlayerAt: index) 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 { func tableInfo(forPlayer player: PlayerName) -> TableInfo {
@ -119,9 +142,5 @@ extension AbstractTable: ManageableTable {
return tableInfo(forPlayerAt: index) return tableInfo(forPlayerAt: index)
} }
func tableInfo(forPlayerAt index: Int) -> TableInfo {
.init(table: self, index: index)
}
} }

View File

@ -164,18 +164,22 @@ final class BiddingTable: AbstractTable<BiddingPlayer> {
return (.success, nil) return (.success, nil)
} }
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { override func cards(forPlayerAt index: Int) -> [PlayableCard] {
let player = players[index] players[index].cards.unplayable
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 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
}
} }

View File

@ -4,16 +4,17 @@ final class DealingTable: AbstractTable<DealingPlayer> {
init(table: WaitingTable) { init(table: WaitingTable) {
let cards = Dealer.dealFirstCards() 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] player.cards = cards[index]
} }
let players = table.players.map(DealingPlayer.init)
super.init(table: table, players: players) super.init(table: table, players: players)
print("\(self.players[0].cards.count) cards")
} }
/// All players either doubled or didn't double /// All players either doubled or didn't double
var allPlayersActed: Bool { var allPlayersActed: Bool {
!players.contains { $0.didDouble == nil } !players.contains { !$0.didDecide }
} }
/// At least one player placed a bid /// At least one player placed a bid
@ -41,7 +42,7 @@ final class DealingTable: AbstractTable<DealingPlayer> {
} }
private func perform(double: Bool, forPlayer player: DealingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) { private func perform(double: Bool, forPlayer player: DealingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.didDouble == nil else { guard !player.didDecide else {
return (.tableStateInvalid, nil) return (.tableStateInvalid, nil)
} }
player.didDouble = double player.didDouble = double
@ -58,4 +59,7 @@ final class DealingTable: AbstractTable<DealingPlayer> {
return (.success, table) return (.success, table)
} }
override func cards(forPlayerAt index: Int) -> [PlayableCard] {
players[index].cards.unplayable
}
} }

View File

@ -47,12 +47,16 @@ final class FinishedTable: AbstractTable<FinishedPlayer> {
} }
init(table: PlayingTable) { init(table: PlayingTable) {
let players = table.players.map(FinishedPlayer.init) let players = table.players.map(FinishedPlayer.init)
self.game = table.game self.game = table.game
leadingPoints = players leadingPoints = players
.filter { $0.leadsGame } .filter { $0.leadsGame }
.map { $0.points! } .map { $0.points }
.reduce(0, +) .reduce(0, +)
// TODO: Set isNextActor for winners
// TODO: Check for bettel
// TODO: Set schneider, schwarz, cost
super.init(table: table, players: players) super.init(table: table, players: players)
} }

View File

@ -108,10 +108,8 @@ final class PlayingTable: AbstractTable<PlayingPlayer> {
} }
} }
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { override func cards(forPlayerAt index: Int) -> [PlayableCard] {
let player = players[index] players[index].playableCards(for: nextTrick, in: game)
let cards = player.playableCards(for: nextTrick, in: game)
return (actions: player.actions, games: [], cards: cards, selectsGame: false)
} }
private func didFinish(trick: Trick, in game: GameType) -> (result: PlayerActionResult, table: ManageableTable?) { private func didFinish(trick: Trick, in game: GameType) -> (result: PlayerActionResult, table: ManageableTable?) {

View File

@ -82,15 +82,16 @@ final class WeddingTable: AbstractTable<WeddingPlayer> {
return (.success, table) return (.success, table)
} }
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) { override func cards(forPlayerAt index: Int) -> [PlayableCard] {
guard requiresCardSelection else {
return super.playerData(at: index)
}
let player = players[index] let player = players[index]
guard player.selectsGame else { guard requiresCardSelection, player.selectsGame else {
return super.playerData(at: index) 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?) { override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {