Schafkopf-Server/Sources/App/Management/TableManagement.swift

203 lines
7.3 KiB
Swift
Raw Normal View History

2021-12-01 22:47:19 +01:00
import Foundation
import WebSocketKit
import Vapor
import Fluent
2021-12-01 22:47:19 +01:00
let maximumPlayersPerTable = 4
typealias TableId = String
typealias TableName = String
final class TableManagement {
2021-12-01 22:47:19 +01:00
2021-12-03 18:03:29 +01:00
/// All tables indexed by their id
private var tables = [UUID : ManageableTable]()
2021-12-01 22:47:19 +01:00
2021-12-03 18:03:29 +01:00
/**
Load the tables from a file in the storage folder
- Throws: Errors when the file could not be read
*/
init(db: Database) throws {
Table.query(on: db).with(\.$players).all().whenSuccess { loadedTables in
for table in loadedTables {
guard !table.players.isEmpty else {
_ = table.delete(on: db)
continue
}
let id = table.id!
self.tables[id] = WaitingTable(id: id, name: table.name, isPublic: table.isPublic, players: table.players)
2021-12-01 22:47:19 +01:00
}
print("\(self.tables.count) tables loaded")
2021-12-01 22:47:19 +01:00
}
2021-12-21 15:50:49 +01:00
}
2021-12-01 22:47:19 +01:00
/**
Create a new table with optional players.
- Parameter name: The name of the table
- Parameter players: The player creating the table
2021-12-03 18:03:29 +01:00
- Parameter isPublic: Indicates that this is a game joinable by everyone
2021-12-01 22:47:19 +01:00
- Returns: The table id
*/
func createTable(named name: TableName, player: User, isPublic: Bool, in database: Database) -> EventLoopFuture<TableInfo> {
let table = Table(name: name, isPublic: isPublic)
return table.create(on: database).flatMap {
player.$table.id = table.id
return player.update(on: database)
}.flatMap {
Table.query(on: database).with(\.$players).filter(\.$id == table.id!).first()
}.unwrap(or: Abort(.notFound))
.map { storedTable in
let table = WaitingTable(newTable: storedTable)
self.tables[table.id] = table
return table.tableInfo(forPlayer: player.name)
}
2021-12-01 22:47:19 +01:00
}
2021-12-03 18:03:29 +01:00
/// A list of all public tables
var publicTableList: [PublicTableInfo] {
tables.values.filter { $0.isPublic }.map { $0.publicInfo }
2021-12-01 22:47:19 +01:00
}
2021-12-03 18:03:29 +01:00
/**
Get the table info for a player
- Parameter player: The name of the player
- Returns: The table info, if the player has joined a table
*/
func tableInfo(player: PlayerName) -> TableInfo? {
currentTable(for: player)?.tableInfo(forPlayer: player)
2021-12-01 22:47:19 +01:00
}
2021-12-18 15:08:43 +01:00
private func currentTable(for player: PlayerName) -> ManageableTable? {
tables.values.first(where: { $0.playerNames.contains(player) })
2021-12-01 22:47:19 +01:00
}
/**
Join a table.
2021-12-03 18:03:29 +01:00
- Parameter tableId: The table to join
- Parameter player: The name of the player who wants to join.
2021-12-01 22:47:19 +01:00
- Returns: The result of the join operation
*/
func join(tableId: UUID, player: User, in database: Database) -> EventLoopFuture<TableInfo> {
return database.eventLoop.future().flatMapThrowing { _ -> ManageableTable in
if let existing = self.currentTable(for: player.name) {
guard existing.id == tableId else {
throw Abort(.forbidden) // 403
}
return existing
2021-12-03 18:03:29 +01:00
}
guard let table = self.tables[tableId] else {
throw Abort(.gone) // 410
}
guard let joinableTable = table as? WaitingTable,
joinableTable.add(player: player.name, points: player.points) else {
throw Abort(.expectationFailed) // 417
}
return joinableTable
}.flatMap { table -> EventLoopFuture<ManageableTable> in
player.$table.id = table.id
return player.update(on: database).map { table }
}.map { table in
table.sendUpdateToAllPlayers()
return table.tableInfo(forPlayer: player.name)
2021-12-01 22:47:19 +01:00
}
}
2021-12-03 18:03:29 +01:00
/**
A player leaves the table it previously joined
- Parameter player: The player leaving the table
2021-12-03 18:03:29 +01:00
*/
func leaveTable(player: User, in database: Database) -> EventLoopFuture<Void> {
guard let oldTable = currentTable(for: player.name) else {
return database.eventLoop.makeSucceededVoidFuture()
2021-12-01 22:47:19 +01:00
}
2021-12-23 11:16:29 +01:00
player.$table.id = nil
guard let table = WaitingTable(oldTable: oldTable, removing: player.name) else {
tables[oldTable.id] = nil
2021-12-23 12:52:28 +01:00
return player.update(on: database).flatMap {
2021-12-25 16:53:58 +01:00
Table.query(on: database).filter(\.$id == oldTable.id).delete()
2021-12-23 11:16:29 +01:00
}
}
2021-12-18 15:08:43 +01:00
/// `player.canStartGame` is automatically set to false, because table is not full
tables[table.id] = table
table.sendUpdateToAllPlayers()
// TODO: Update points for all players
return player.update(on: database)
2021-12-01 22:47:19 +01:00
}
func connect(player: PlayerName, using socket: WebSocket) -> Bool {
2021-12-03 18:03:29 +01:00
guard let table = currentTable(for: player) else {
2021-12-01 22:47:19 +01:00
return false
}
2021-12-03 18:03:29 +01:00
return table.connect(player: player, using: socket)
2021-12-01 22:47:19 +01:00
}
func disconnect(player: PlayerName) {
2021-12-03 18:03:29 +01:00
guard let table = currentTable(for: player) else {
2021-12-01 22:47:19 +01:00
return
}
2021-12-03 18:03:29 +01:00
table.disconnect(player: player)
2021-12-01 22:47:19 +01:00
}
func performAction(player: PlayerName, action: PlayerAction) -> PlayerActionResult {
2021-12-03 18:03:29 +01:00
guard let table = currentTable(for: player) else {
2021-12-09 11:18:26 +01:00
print("Player \(player) wants to \(action.id), but no table joined")
2021-12-01 22:47:19 +01:00
return .noTableJoined
}
let (result, newTable) = table.perform(action: action, forPlayer: player)
guard result == .success else {
return result
}
guard let newTable = newTable else {
table.sendUpdateToAllPlayers()
return .success
}
tables[newTable.id] = newTable
newTable.sendUpdateToAllPlayers()
2021-12-21 15:50:49 +01:00
if newTable is FinishedTable || newTable is DealingTable {
// TODO: Save new table
2021-12-21 15:50:49 +01:00
}
return .success
2021-12-01 22:47:19 +01:00
}
2021-12-06 11:43:30 +01:00
func select(game: GameType, player: PlayerName) -> PlayerActionResult {
guard let aTable = currentTable(for: player) else {
print("Player \(player) wants to play \(game.rawValue), but no table joined")
return .noTableJoined
}
guard let table = aTable as? BiddingTable else {
return .tableStateInvalid
}
let (result, newTable) = table.select(game: game, player: player)
guard result == .success else {
return result
}
guard let newTable = newTable else {
print("Game selected by \(player), but no playing table \(table.name) created")
table.sendUpdateToAllPlayers()
return result
}
tables[newTable.id] = newTable
newTable.sendUpdateToAllPlayers()
// TODO: Save new table
return .success
}
func play(card: Card, player: PlayerName) -> PlayerActionResult {
2021-12-06 11:43:30 +01:00
guard let table = currentTable(for: player) else {
return .noTableJoined
}
let (result, newTable) = table.play(card: card, player: player)
guard result == .success else {
return result
}
guard let newTable = newTable else {
table.sendUpdateToAllPlayers()
return .success
}
tables[newTable.id] = newTable
newTable.sendUpdateToAllPlayers()
// TODO: Save new table
return .success
2021-12-06 11:43:30 +01:00
}
2021-12-01 22:47:19 +01:00
}