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