First working version

This commit is contained in:
Christoph Hagen
2021-12-18 15:08:43 +01:00
parent c9853dee28
commit 49787db1aa
32 changed files with 1416 additions and 415 deletions

View File

@ -1,6 +1,7 @@
import Foundation
import WebSocketKit
class AbstractTable {
class AbstractTable<TablePlayer> where TablePlayer: Player {
/// The unique id of the table
let id: TableId
@ -11,15 +12,116 @@ class AbstractTable {
/// Indicates that the table is visible to all players, and can be joined by anyone
let isPublic: Bool
init(table: AbstractTable) {
/**
The players sitting at the table.
The players are ordered clockwise around the table, with the first player starting the game.
*/
var players: [TablePlayer]
var playedGame: GameType? {
nil
}
init(table: ManageableTable, players: [TablePlayer]) {
self.id = table.id
self.name = table.name
self.isPublic = table.isPublic
self.players = players
}
init(id: TableId, name: TableName, isPublic: Bool) {
init(id: TableId, name: TableName, isPublic: Bool, players: [TablePlayer]) {
self.id = id
self.name = name
self.isPublic = isPublic
self.players = players
}
func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
(.tableStateInvalid, nil)
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
(.tableStateInvalid, nil)
}
func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) {
let player = players[index]
return (actions: player.actions, games: [], cards: player.cards.unplayable, selectsGame: false)
}
func cardStackPosition(ofPlayerAt index: Int) -> Int {
index
}
}
extension AbstractTable: ManageableTable {
var publicInfo: PublicTableInfo {
.init(id: id, name: name, players: playerNames)
}
var playerNames: [PlayerName] {
players.map { $0.name }
}
var allPlayers: [Player] {
players
}
// MARK: Connection
func connect(player name: PlayerName, using socket: WebSocket) -> Bool {
guard let player = players.player(named: name) else {
return false
}
player.connect(using: socket)
sendUpdateToAllPlayers()
return true
}
func disconnect(player name: PlayerName) {
guard let player = players.player(named: name) else {
return
}
guard player.disconnect() else {
return
}
sendUpdateToAllPlayers()
return
}
func sendUpdateToAllPlayers() {
players.enumerated().forEach { playerIndex, player in
guard player.isConnected else {
return
}
let info = self.tableInfo(forPlayerAt: playerIndex)
player.send(info)
}
}
// MARK: Client info
func playerInfo(forIndex index: Int) -> PlayerInfo? {
guard let player = players.at(index) else {
return nil
}
let height = cardStackPosition(ofPlayerAt: index)
return PlayerInfo(player: player, position: height)
}
func tableInfo(forPlayer player: PlayerName) -> TableInfo {
let index = players.index(of: player)
return tableInfo(forPlayerAt: index)
}
func tableInfo(forPlayerAt index: Int) -> TableInfo {
.init(table: self, index: index)
}
}

View File

@ -1,51 +1,181 @@
import Foundation
final class BiddingTable: AbstractTable {
final class BiddingTable: AbstractTable<BiddingPlayer> {
var gameToOutbid: GameType.GameClass = .none
var players: [BiddingPlayer]
var indexOfHighestBidder = 0
var hasSelectedGame: Bool {
// TODO: Implement
false
var remainingBidders: Int {
players.filter { $0.isStillBidding }.count
}
var isWaitingForGameSelection: Bool {
players.contains { $0.selectsGame }
}
init(table: DealingTable) {
self.players = table.players.map {
BiddingPlayer(player: $0, cards: [])
// Add new cards to the players
let newCards = Dealer.dealRemainingCards(of: table.players.map { $0.cards })
let players: [BiddingPlayer] = table.players.enumerated().map { index, player in
player.cards = (player.cards + newCards[index])
.sortedCards(order: NormalCardOrder.self)
player.isNextActor = false
return BiddingPlayer(player: player)
}
super.init(table: table)
players.first!.isNextActor = true
super.init(table: table, players: players)
}
func select(game: GameType, player: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement
return (.tableStateInvalid, nil)
init(wedding table: WeddingTable, outbidBy player: WeddingPlayer) {
gameToOutbid = .hochzeit
indexOfHighestBidder = table.players.index(of: player)
// All players can bid again, except the wedding offerer
let players = table.players.map(BiddingPlayer.init)
players[indexOfHighestBidder].isNextActor = true
super.init(table: table, players: players)
// Choose the player after the one who discarded the wedding
selectNextBidder()
}
func makePlayingTable() -> PlayingTable {
// TODO: Implement
fatalError()
}
}
func select(game: GameType, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from bidding table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.selectsGame else {
print("Player \(name) does not select the game")
return (.tableStateInvalid, nil)
}
guard gameToOutbid.allows(game: game) else {
print("Game \(game) not allowed for class \(gameToOutbid)")
return (.tableStateInvalid, nil)
}
guard player.canPlay(game: game) else {
print("Player \(game) can't play game \(game)")
return (.tableStateInvalid, nil)
}
extension BiddingTable: Table {
var allPlayers: [Player] {
players
let table = PlayingTable(table: self, game: game, playedBy: player)
return (.success, table)
}
var indexOfNextActor: Int {
// TODO: Implement
return 0
@discardableResult
private func selectNextBidder() -> Bool {
guard let index = players.firstIndex(where: { $0.isNextActor }) else {
print("Bidding: No current actor found to select next bidder")
return false
}
players[index].isNextActor = false
let newActor = players.rotated(toStartAt: (index + 1) % 4).first(where: { $0.isStillBidding })!
newActor.isNextActor = true
return true
}
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement bidding actions
return (.tableStateInvalid, nil)
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from bidding table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.canPerform(action) else {
return (.tableStateInvalid, nil)
}
switch action {
case .offerWedding:
return performWeddingOffer(forPlayer: player)
case .increaseOrMatchGame:
return performBidIncrease(forPlayer: player)
case .withdrawFromAuction:
return performWithdrawl(forPlayer: player)
default:
return (.tableStateInvalid, nil)
}
}
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
// TODO: Implement for wedding
return (.tableStateInvalid, nil)
private func performWeddingOffer(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard gameToOutbid.allowsWedding else {
return (.tableStateInvalid, nil)
}
let newTable = WeddingTable(table: self, offerer: player)
return (.success, newTable)
}
private func performBidIncrease(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.isNextActor, player.isStillBidding else {
return (.tableStateInvalid, nil)
}
let index = players.index(of: player)
if index < indexOfHighestBidder {
// Player sits before the current highest bidder, so only needs to match the game
indexOfHighestBidder = index
if gameToOutbid == .solo {
// Can't be outbid, so player selects game
players.forEach { $0.isStillBidding = false }
player.selectsGame = true
return (.success, nil)
}
// TODO: Check that wedding can be offered at the correct times
// There may be a case where a player sitting before the highest bidder
// can't offer a wedding anymore although it should be able to
if !gameToOutbid.allowsWedding {
players.forEach { $0.isAllowedToOfferWedding = false }
}
} else {
// Player sits after the highest bidder, so must outbid the game
// Also the case when first starting bidding
gameToOutbid.increase()
indexOfHighestBidder = index
if !gameToOutbid.allowsWedding {
players.forEach { $0.isAllowedToOfferWedding = false }
}
}
selectNextBidder()
return (.success, nil)
}
private func performWithdrawl(forPlayer player: BiddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.isStillBidding else {
return (.tableStateInvalid, nil)
}
player.isStillBidding = false
switch remainingBidders {
case 0:
// Nobody wants to play something, so abort the game
// This case can only be reached when nobody has bid yet
let table = WaitingTable(oldTableAdvancedByOne: self)
return (.success, table)
case 1:
if gameToOutbid != .none {
// Last player must play
player.isNextActor = false
indexOfHighestBidder = players.firstIndex { $0.isStillBidding == true }!
let highestPlayer = players[indexOfHighestBidder]
highestPlayer.isStillBidding = false
highestPlayer.selectsGame = true
highestPlayer.isNextActor = true
return (.success, nil)
}
default:
break
}
selectNextBidder()
return (.success, nil)
}
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) {
let player = players[index]
let games: [GameConvertible]
if isWaitingForGameSelection {
games = gameToOutbid.availableGames.filter(player.canPlay)
} else if index <= indexOfHighestBidder {
games = gameToOutbid.availableClasses
} else {
games = gameToOutbid.classesWhenOutbidding
}
return (player.actions, games, player.cards.unplayable, selectsGame: player.selectsGame)
}
}

View File

@ -1,39 +1,61 @@
import Foundation
final class DealingTable: AbstractTable {
var players: [DealingPlayer]
final class DealingTable: AbstractTable<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) }
for (index, player) in table.players.enumerated() {
player.cards = cards[index]
}
let players = table.players.map(DealingPlayer.init)
super.init(table: table, players: players)
}
/// All players either doubled or didn't double
var allPlayersActed: Bool {
!players.contains { $0.didDouble == nil }
}
/// At least one player placed a bid
var hasDouble: Bool {
players.contains { $0.didDouble == true }
}
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from dealing table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.canPerform(action) else {
return (.tableStateInvalid, nil)
}
switch action {
case .initialDoubleCost:
return perform(double: true, forPlayer: player)
case .noDoubleCost:
return perform(double: false, forPlayer: player)
default:
return (.tableStateInvalid, nil)
}
}
}
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)
}
private func perform(double: Bool, forPlayer player: DealingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.didDouble == nil else {
return (.tableStateInvalid, nil)
}
player.didDouble = double
guard allPlayersActed else {
return (.success, nil)
}
guard hasDouble else {
// Revert to a waiting table and switch to the next player
let table = WaitingTable(oldTableAdvancedByOne: self)
return (.success, table)
}
// Automatically adds remaining cards to the players
let table = BiddingTable(table: self)
return (.success, table)
}
}

View File

@ -0,0 +1,83 @@
import Foundation
final class FinishedTable: AbstractTable<FinishedPlayer> {
let game: GameType
var winners: [FinishedPlayer] {
leadersHaveWon ? leaders : opponents
}
var loosers: [FinishedPlayer] {
leadersHaveWon ? opponents : leaders
}
var leaders: [FinishedPlayer] {
players.filter { $0.leadsGame }
}
var opponents: [FinishedPlayer] {
players.filter { !$0.leadsGame }
}
var winningPoints: Int {
leadersHaveWon ? leadingPoints : 120 - leadingPoints
}
var loosingPoints: Int {
leadersHaveWon ? 120 - leadingPoints : leadingPoints
}
let leadingPoints: Int
var leadersHaveWon: Bool {
leadingPoints > 60
}
var isSchwarz: Bool {
loosingPoints == 0
}
var isSchneider: Bool {
loosingPoints < (leadersHaveWon ? 30 : 31)
}
override var playedGame: GameType? {
game
}
init(table: PlayingTable) {
let players = table.players.map(FinishedPlayer.init)
self.game = table.game
leadingPoints = players
.filter { $0.leadsGame }
.map { $0.points! }
.reduce(0, +)
super.init(table: table, players: players)
}
/**
Perform a deal action on the finished table.
- Parameter action: The action to perform
- Parameter player: The name of the player
*/
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
// Only dealing is allowed...
guard action == .deal else {
return (.tableStateInvalid, nil)
}
guard let player = players.player(named: name) else {
print("Unexpectedly missing player \(name) for deal action at finished table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.canPerform(.deal) else {
print("Finished table: Player \(name) can't perform deal")
return (.tableStateInvalid, nil)
}
let waiting = WaitingTable(oldTableAdvancedByOne: self)
let table = DealingTable(table: waiting)
return (.success, table)
}
}

View File

@ -0,0 +1,32 @@
import Foundation
import WebSocketKit
protocol ManageableTable {
/// 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 }
var playerNames: [PlayerName] { get }
var allPlayers: [Player] { get }
var publicInfo: PublicTableInfo { get }
func tableInfo(forPlayer player: PlayerName) -> TableInfo
func perform(action: PlayerAction, forPlayer: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?)
func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?)
func connect(player name: PlayerName, using socket: WebSocket) -> Bool
func disconnect(player name: PlayerName)
func sendUpdateToAllPlayers()
}

View File

@ -1,34 +1,144 @@
import Foundation
final class PlayingTable: AbstractTable {
final class PlayingTable: AbstractTable<PlayingPlayer> {
var players: [PlayingPlayer]
let game: GameType
init(table: BiddingTable) {
self.players = table.players.map(PlayingPlayer.init)
super.init(table: table)
var indexOfTrickStarter = 0
var didDoubleInCurrentRound = false
var hasCompletedTrick: Bool {
!players.contains { $0.playedCard == nil }
}
var nextTrick: [Card] {
hasCompletedTrick ? [] : currentTrick
}
var currentTrick: [Card] {
players.rotated(toStartAt: indexOfTrickStarter).compactMap { $0.playedCard }
}
var completedTrick: Trick? {
let trick = currentTrick
guard trick.count == maximumPlayersPerTable else {
return nil
}
return trick
}
var allCardsPlayed: Bool {
!players.contains { !$0.cards.isEmpty }
}
override var playedGame: GameType? {
game
}
convenience init(table: BiddingTable, game: GameType, playedBy player: BiddingPlayer) {
let calledAce = game.calledSuit?.ace
let players = table.players.map {
PlayingPlayer(player: $0, leads: $0 == player, calledAce: calledAce)
}
self.init(table: table, players: players, game: game)
}
convenience init(wedding table: WeddingTable, offeredBy offerer: WeddingPlayer, acceptedBy player: WeddingPlayer) {
let players = table.players.map {
PlayingPlayer(player: $0, leads: $0 == player || $0 == offerer, calledAce: nil)
}
self.init(table: table, players: players, game: .hochzeit)
}
private init(table: ManageableTable, players: [PlayingPlayer], game: GameType) {
self.game = game
super.init(table: table, players: players)
players.forEach { $0.sortCards(for: game) }
players.first!.isNextActor = true
}
override func cardStackPosition(ofPlayerAt index: Int) -> Int {
(4 + index - indexOfTrickStarter) % 4
}
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from playing table \(self.name)")
return (.tableStateInvalid, nil)
}
guard action == .doubleDuringGame else {
print("Player \(name) wants to perform action \(action) on playing table")
return (.tableStateInvalid, nil)
}
guard player.canPerform(.doubleDuringGame) else {
print("Player \(name) is not allowed to raise")
return (.tableStateInvalid, nil)
}
player.numberOfDoubles += 1
players.forEach { $0.switchLead() }
self.didDoubleInCurrentRound = true
return (.success, nil)
}
override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from playing table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.isNextActor else {
print("Player \(name) wants to play card but is not active")
return (.tableStateInvalid, nil)
}
guard player.canPlay(card: card, for: nextTrick, in: game) else {
return (.tableStateInvalid, nil)
}
if hasCompletedTrick {
players.forEach { $0.playedCard = nil }
indexOfTrickStarter = players.index(of: player)
}
player.play(card: card)
if let completedTrick = completedTrick {
return didFinish(trick: completedTrick, in: game)
} else {
let next = players.next(after: player)
next.isNextActor = true
player.isNextActor = false
return (.success, nil)
}
}
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) {
let player = players[index]
let cards = player.playableCards(for: nextTrick, in: game)
return (actions: player.actions, games: [], cards: cards, selectsGame: false)
}
private func didFinish(trick: Trick, in game: GameType) -> (result: PlayerActionResult, table: ManageableTable?) {
let index = trick.highCardIndex(forGame: game)
let winner = players[(indexOfTrickStarter + index) % 4]
players.forEach {
$0.isNextActor = false
$0.canStillRaise = didDoubleInCurrentRound
}
winner.wonTricks.append(trick)
winner.isNextActor = true
if game == .bettel && winner.leadsGame {
// A bettel is lost if a single trick is won by the leader
return finishedGame()
}
didDoubleInCurrentRound = false
if allCardsPlayed {
return finishedGame()
}
return (.success, nil)
}
private func finishedGame() -> (result: PlayerActionResult, table: ManageableTable?) {
let table = FinishedTable(table: self)
print("\(table.winners) have won with \(table.winningPoints) to \(table.loosingPoints) points")
return (.success, 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

@ -1,121 +0,0 @@
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

@ -3,22 +3,20 @@ 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] = []
final class WaitingTable: AbstractTable<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)
init(id: TableId, name: TableName, isPublic: Bool, players: [PlayerName]) {
let players = players.map { WaitingPlayer(name: $0) }
players.first!.isNextActor = true
super.init(id: id, name: name, isPublic: isPublic, players: players)
if isFull {
self.players.forEach { $0.canStartGame = true }
}
}
/**
@ -26,8 +24,10 @@ final class WaitingTable: AbstractTable {
- 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)
init(newTable name: TableName, isPublic: Bool, creator: PlayerName) {
let player = WaitingPlayer(name: creator)
player.isNextActor = true
super.init(id: .newToken(), name: name, isPublic: isPublic, players: [player])
}
/**
@ -35,13 +35,35 @@ final class WaitingTable: AbstractTable {
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.
- Parameter player: The name of 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)
init(oldTable: ManageableTable, removing player: PlayerName) {
let players = oldTable.allPlayers
.filter {
guard $0.name == player else {
return true
}
_ = $0.disconnect()
return false
}
.map { WaitingPlayer(name: $0.name, socket: $0.socket) }
players.first!.isNextActor = true
super.init(table: oldTable, players: players)
}
/**
Convert another table to a waiting table.
This is needed when a player leaves an active table.
- Parameter oldTable: The table to convert
*/
init(oldTableAdvancedByOne table: ManageableTable) {
let players = table.allPlayers
.rotatedByOne()
.map { WaitingPlayer(name: $0.name, socket: $0.socket) }
super.init(table: table, players: players)
players.forEach { $0.canStartGame = true }
players.first!.isNextActor = true
}
/**
@ -69,7 +91,7 @@ final class WaitingTable: AbstractTable {
- Parameter action: The action to perform
- Parameter player: The name of the player
*/
func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: Table?) {
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
// Only dealing is allowed...
guard action == .deal else {
return (.tableStateInvalid, nil)
@ -78,7 +100,7 @@ final class WaitingTable: AbstractTable {
guard isFull else {
return (.tableStateInvalid, nil)
}
guard let player = player(named: name) else {
guard let player = players.player(named: name) else {
print("Unexpected action \(action) for missing player \(name) at table \(self.name)")
return (.tableStateInvalid, nil)
}
@ -90,20 +112,8 @@ final class WaitingTable: AbstractTable {
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?) {
override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
// No cards playable while waiting
(.tableStateInvalid, nil)
}

View File

@ -0,0 +1,121 @@
import Foundation
final class WeddingTable: AbstractTable<WeddingPlayer> {
var indexOfWeddingOffer: Int
init(table: BiddingTable, offerer: BiddingPlayer) {
let players = table.players.map { WeddingPlayer(player: $0, offersWedding: $0 == offerer) }
indexOfWeddingOffer = table.players.index(of: offerer)
super.init(table: table, players: players)
}
var hasRemainingActors: Bool {
players.contains { $0.requiresAction }
}
var requiresCardSelection: Bool {
players.contains { $0.selectsGame }
}
override var playedGame: GameType? {
.hochzeit
}
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from wedding table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.canPerform(action) else {
return (.tableStateInvalid, nil)
}
switch action {
case .acceptWedding:
return performWeddingAccept(forPlayer: player)
case .withdrawFromAuction:
return performWithdrawl(forPlayer: player)
case .increaseOrMatchGame:
fatalError()
default:
return (.tableStateInvalid, nil)
}
}
private func performWeddingAccept(forPlayer player: WeddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.requiresAction else {
return (.tableStateInvalid, nil)
}
player.state = .wouldAcceptWedding
guard !hasRemainingActors else {
return (.success, nil)
}
// Nobody wants to play a higher game, so let the first player accept the wedding
players.first { $0.wouldAcceptWedding }!.selectsGame = true
return (.success, nil)
}
private func performWithdrawl(forPlayer player: WeddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.requiresAction else {
return (.tableStateInvalid, nil)
}
player.state = .withdrawnFromAuction
guard !hasRemainingActors else {
return (.success, nil)
}
// Nobody wants to play a higher game, so let the first player accept the wedding
guard let player = players.first(where: { $0.wouldAcceptWedding }) else {
// Nobody wants to accept the wedding or play something higher, so abort the game
let table = WaitingTable(oldTableAdvancedByOne: self)
return (.success, table)
}
player.selectsGame = true
return (.success, nil)
}
private func performOutbid(forPlayer player: WeddingPlayer) -> (result: PlayerActionResult, table: ManageableTable?) {
guard player.requiresAction else {
return (.tableStateInvalid, nil)
}
let table = BiddingTable(wedding: self, outbidBy: player)
return (.success, table)
}
override func playerData(at index: Int) -> (actions: [PlayerAction], games: [GameConvertible], cards: [PlayableCard], selectsGame: Bool) {
guard requiresCardSelection else {
return super.playerData(at: index)
}
let player = players[index]
guard player.selectsGame else {
return super.playerData(at: index)
}
return (actions: player.actions, games: [], cards: player.exchangeableCards, selectsGame: false)
}
override func play(card: Card, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard requiresCardSelection else {
print("Wedding is not in stage where card should be selected")
return (.tableStateInvalid, nil)
}
guard let player = players.player(named: name) else {
print("Player \(name) unexpectedly missing from wedding table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.selectsGame else {
print("Player \(name) is not the one selecting a wedding card")
return (.tableStateInvalid, nil)
}
guard player.canExchange(card: card) else {
print("Invalid card \(card) to exchange in wedding")
return (.tableStateInvalid, nil)
}
let offerer = players[indexOfWeddingOffer]
let trumpCard = offerer.replaceWeddingCard(with: card)
player.replace(card, with: trumpCard)
let table = PlayingTable(wedding: self, offeredBy: offerer, acceptedBy: player)
return (.success, table)
}
}