234 lines
6.2 KiB
Swift
234 lines
6.2 KiB
Swift
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)
|
||
}
|
||
}
|