Refactor tables and players for clarity

This commit is contained in:
Christoph Hagen
2021-12-09 11:11:17 +01:00
parent 33f72c43cf
commit 289458bfd8
17 changed files with 718 additions and 64 deletions

View File

@ -0,0 +1,25 @@
import Foundation
class AbstractTable {
/// The unique id of the table
let id: TableId
/// The name of the table
let name: TableName
/// Indicates that the table is visible to all players, and can be joined by anyone
let isPublic: Bool
init(table: AbstractTable) {
self.id = table.id
self.name = table.name
self.isPublic = table.isPublic
}
init(id: TableId, name: TableName, isPublic: Bool) {
self.id = id
self.name = name
self.isPublic = isPublic
}
}

View File

@ -0,0 +1,51 @@
import Foundation
final class BiddingTable: AbstractTable {
var players: [BiddingPlayer]
var hasSelectedGame: Bool {
// TODO: Implement
false
}
init(table: DealingTable) {
self.players = table.players.map {
BiddingPlayer(player: $0, cards: [])
}
super.init(table: table)
}
func select(game: GameType, player: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement
return (.tableStateInvalid, nil)
}
func makePlayingTable() -> PlayingTable {
// TODO: Implement
fatalError()
}
}
extension BiddingTable: Table {
var allPlayers: [Player] {
players
}
var indexOfNextActor: Int {
// TODO: Implement
return 0
}
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement bidding actions
return (.tableStateInvalid, nil)
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement for wedding
return (.tableStateInvalid, nil)
}
}

View File

@ -0,0 +1,39 @@
import Foundation
final class DealingTable: AbstractTable {
var players: [DealingPlayer]
init(table: WaitingTable) {
self.players = table.players.map(DealingPlayer.init)
super.init(table: table)
let cards = Dealer.dealFirstCards()
for (index, player) in players.enumerated() {
player.cards = cards[index].map { .init(card: $0, isPlayable: false) }
}
}
}
extension DealingTable: Table {
var allPlayers: [Player] {
players
}
var indexOfNextActor: Int {
// TODO: Implement
return 0
}
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement doubling, additional cards
return (.tableStateInvalid, nil)
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// No cards playable while dealing
(.tableStateInvalid, nil)
}
}

View File

@ -0,0 +1,34 @@
import Foundation
final class PlayingTable: AbstractTable {
var players: [PlayingPlayer]
init(table: BiddingTable) {
self.players = table.players.map(PlayingPlayer.init)
super.init(table: table)
}
}
extension PlayingTable: Table {
var allPlayers: [Player] {
players
}
var indexOfNextActor: Int {
// TODO: Implement
return 0
}
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement raises
return (.tableStateInvalid, nil)
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement playing of cards
return (.tableStateInvalid, nil)
}
}

View File

@ -0,0 +1,121 @@
import Foundation
import WebSocketKit
protocol Table: AbstractTable {
/// The unique id of the table
var id: TableId { get }
/// The name of the table
var name: TableName { get }
/// The table is visible in the list of tables and can be joined by anyone
var isPublic: Bool { get }
/**
The players sitting at the table.
The players are ordered clockwise around the table, with the first player starting the game.
*/
var allPlayers: [Player] { get }
var indexOfNextActor: Int { get }
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: Table?)
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?)
}
extension Table {
var playerNames: [String] {
allPlayers.map { $0.name }
}
func index(of player: PlayerName) -> Int {
allPlayers.firstIndex { $0.name == player }!
}
func player(named name: PlayerName) -> Player? {
allPlayers.first { $0.name == name }
}
func contains(player: PlayerName) -> Bool {
allPlayers.contains { $0.name == player }
}
// MARK: Connection
func sendUpdateToAllPlayers() {
allPlayers.enumerated().forEach { playerIndex, player in
guard player.isConnected else {
return
}
let info = self.tableInfo(forPlayerAt: playerIndex)
player.send(info)
}
}
func connect(player name: PlayerName, using socket: WebSocket) -> Bool {
guard let player = player(named: name) else {
return false
}
player.connect(using: socket)
sendUpdateToAllPlayers()
return true
}
func disconnect(player name: PlayerName) {
guard let player = player(named: name) else {
return
}
guard player.disconnect() else {
return
}
sendUpdateToAllPlayers()
return
}
// MARK: Client info
var publicInfo: PublicTableInfo {
.init(id: id, name: name, players: playerNames)
}
private func player(forIndex index: Int) -> Player? {
let players = allPlayers
guard index < players.count else {
return nil
}
return players[index]
}
private func playerInfo(forIndex index: Int) -> PlayerInfo? {
guard let player = player(forIndex: index) else {
return nil
}
let isNext = indexOfNextActor == index
return PlayerInfo(player: player, isNextActor: isNext, position: index)
}
func tableInfo(forPlayer player: PlayerName) -> TableInfo {
let index = index(of: player)
return tableInfo(forPlayerAt: index)
}
func tableInfo(forPlayerAt index: Int) -> TableInfo {
let player = player(forIndex: index)!
let own = playerInfo(forIndex: index)!
let left = playerInfo(forIndex: (index + 1) % 4)
let across = playerInfo(forIndex: (index + 2) % 4)
let right = playerInfo(forIndex: (index + 3) % 4)
return .init(
id: id, name: name,
own: own, left: left,
across: across, right: right,
actions: player.actions,
cards: player.cards)
}
}

View File

@ -0,0 +1,110 @@
import Foundation
/**
Represents a table where players are still joining and leaving.
*/
final class WaitingTable: AbstractTable {
/**
The players sitting at the table.
The players are ordered clockwise around the table, with the first player starting the game.
*/
var players: [WaitingPlayer] = []
/// The table contains enough players to start a game
var isFull: Bool {
players.count >= maximumPlayersPerTable
}
override init(id: TableId, name: TableName, isPublic: Bool) {
super.init(id: id, name: name, isPublic: isPublic)
}
/**
Create a new table.
- Parameter name: The name of the table
- Parameter isPublic: The table is visible and joinable by everyone
*/
init(newTable name: TableName, isPublic: Bool) {
super.init(id: .newToken(), name: name, isPublic: isPublic)
}
/**
Convert another table to a waiting table.
This is needed when a player leaves an active table.
- Parameter oldTable: The table to convert
- Parameter player: The player to remove from the table.
*/
init(oldTable: Table, removing player: PlayerName) {
self.players = oldTable.allPlayers
.filter { $0.name != player }
.map(WaitingPlayer.init)
super.init(table: oldTable)
}
/**
Add a player to the table.
- Parameter player: The name of the player to add
- Returns: `true`, if the player could be added, `false` if the table is full
*/
func add(player: PlayerName) -> Bool {
guard !isFull else {
return false
}
let player = WaitingPlayer(name: player)
players.append(player)
// Allow dealing of cards if table is full
if isFull {
players.forEach { $0.canStartGame = true }
}
return true
}
/**
Perform an action on the waiting table.
Only dealing is a valid action (if the table is full)
- Parameter action: The action to perform
- Parameter player: The name of the player
*/
func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// Only dealing is allowed...
guard action == .deal else {
return (.tableStateInvalid, nil)
}
// and only when table is full
guard isFull else {
return (.tableStateInvalid, nil)
}
guard let player = player(named: name) else {
print("Unexpected action \(action) for missing player \(name) at table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.canPerform(.deal) else {
print("Player \(name) cant perform deal, although table is full")
return (.tableStateInvalid, nil)
}
let table = DealingTable(table: self)
return (.success, table)
}
}
extension WaitingTable: Table {
var allPlayers: [Player] {
players as [Player]
}
var indexOfNextActor: Int {
// The first player at the table starts the game
0
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// No cards playable while waiting
(.tableStateInvalid, nil)
}
}