Schafkopf-Server/Sources/App/Model/Table.swift
Christoph Hagen 3db9652cad Sync push
2021-12-03 18:03:29 +01:00

234 lines
6.2 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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..<maximumPlayersPerTable {
players[i].prepareForFirstGame(isFirstPlayer: i == index)
}
}
private func sendUpdateToAllPlayers() {
players.enumerated().forEach { playerIndex, player in
guard player.isConnected else {
return
}
let info = TableInfo(self, forPlayerAt: playerIndex)
player.send(info)
}
}
// MARK: Player actions
func perform(action: Player.Action, forPlayer player: PlayerName) -> 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)
}
}