Schafkopf-Server/Sources/App/Management/TableManagement.swift
2021-12-06 11:43:30 +01:00

182 lines
6.2 KiB
Swift

import Foundation
import WebSocketKit
import Vapor
let maximumPlayersPerTable = 4
typealias TableId = String
typealias TableName = String
final class TableManagement: DiskWriter {
/// All tables indexed by their id
private var tables = [TableId : Table]()
/// The handle to the file where the tables are persisted
let storageFile: FileHandle
/// The url to the file where the tables are persisted
let storageFileUrl: URL
/**
Load the tables from a file in the storage folder
- Parameter storageFolder: The url to the folder where the table file is stored
- Throws: Errors when the file could not be read
*/
init(storageFolder: URL) throws {
let url = storageFolder.appendingPathComponent("tables.txt")
storageFileUrl = url
storageFile = try Self.prepareFile(at: url)
var entries = [TableId : (name: TableName, isPublic: Bool, players: [PlayerName])]()
try readLinesFromDisk().forEach { line in
// Each line has parts: ID | NAME | PLAYER, PLAYER, ...
let parts = line.components(separatedBy: ":")
guard parts.count == 4 else {
print("Invalid line in table file")
return
}
let id = parts[0]
let name = parts[1]
let isPublic = parts[2] == "public"
let players = parts[3].components(separatedBy: ",")
if name == "" {
entries[id] = nil
} else {
entries[id] = (name, isPublic, players)
}
}
entries.forEach { id, tableData in
let table = Table(id: id, name: tableData.name, isPublic: tableData.isPublic)
tableData.players.forEach { _ = table.add(player: $0) }
tables[id] = table
}
print("Loaded \(tables.count) tables")
}
/**
Writes the table info to disk.
Currently only the id, name, visibility and players are stored, all other information is lost.
- Parameter table: The changed table information to persist
- Returns: `true`, if the entry was written, `false` on error
*/
@discardableResult
private func writeTableToDisk(table: Table) -> Bool {
let visible = table.isPublic ? "public" : "private"
let players = table.playerNames.joined(separator: ",")
let entry = [table.id, table.name, visible, players].joined(separator: ":")
return writeToDisk(line: entry)
}
/**
Writes the deletion of a table to disk.
The deletion is written as a separate entry and appended to the file, in order to reduce disk I/O.
- Parameter tableId: The id of the deleted table
- Returns: `true`, if the entry was written, `false` on error
*/
@discardableResult
private func writeTableDeletionEntry(tableId: TableId) -> Bool {
let entry = [tableId, "", "", ""].joined(separator: ":")
return writeToDisk(line: entry)
}
/**
Create a new table with optional players.
- Parameter name: The name of the table
- Parameter players: The player creating the table
- Parameter isPublic: Indicates that this is a game joinable by everyone
- Returns: The table id
*/
func createTable(named name: TableName, player: PlayerName, isPublic: Bool) -> TableInfo {
let table = Table(newTable: name, isPublic: isPublic)
_ = table.add(player: player)
tables[table.id] = table
writeTableToDisk(table: table)
return table.compileInfo(for: player)!
}
/// A list of all public tables
var publicTableList: [PublicTableInfo] {
tables.values.filter { $0.isPublic }.map { $0.publicInfo }
}
/**
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)?.compileInfo(for: player)
}
private func currentTable(for player: PlayerName) -> Table? {
tables.values.first(where: { $0.contains(player: player) })
}
/**
Join a table.
- Parameter tableId: The table to join
- Parameter player: The name of the player who wants to join.
- Returns: The result of the join operation
*/
func join(tableId: TableId, player: PlayerName) -> Result<TableInfo, JoinTableResult> {
if let existing = currentTable(for: player) {
guard existing.id == tableId else {
return .failure(.alreadyJoinedOtherTable)
}
return .success(existing.compileInfo(for: player)!)
}
guard let table = tables[tableId] else {
return .failure(.tableNotFound)
}
guard table.add(player: player) else {
return .failure(.tableIsFull)
}
writeTableToDisk(table: table)
return .success(table.compileInfo(for: player)!)
}
/**
A player leaves the table it previously joined
- Parameter player: The name of the player
*/
func leaveTable(player: PlayerName) {
guard let table = currentTable(for: player) else {
return
}
table.remove(player: player)
writeTableToDisk(table: table)
}
func connect(player: PlayerName, using socket: WebSocket) -> Bool {
guard let table = currentTable(for: player) else {
return false
}
return table.connect(player: player, using: socket)
}
func disconnect(player: PlayerName) {
guard let table = currentTable(for: player) else {
return
}
table.disconnect(player: player)
}
func performAction(player: PlayerName, action: Player.Action) -> PlayerActionResult {
guard let table = currentTable(for: player) else {
return .noTableJoined
}
return table.perform(action: action, forPlayer: player)
}
func play(card: Card, player: PlayerName) -> PlayCardResult {
guard let table = currentTable(for: player) else {
return .noTableJoined
}
return table.play(card: card, player: player)
}
}