Schafkopf-Server/Sources/App/Model/Tables/BiddingTable.swift

182 lines
7.1 KiB
Swift
Raw Normal View History

import Foundation
2021-12-18 15:08:43 +01:00
final class BiddingTable: AbstractTable<BiddingPlayer> {
var gameToOutbid: GameType.GameClass = .none
2021-12-18 15:08:43 +01:00
var indexOfHighestBidder = 0
2021-12-18 15:08:43 +01:00
var remainingBidders: Int {
players.filter { $0.isStillBidding }.count
}
var isWaitingForGameSelection: Bool {
players.contains { $0.selectsGame }
}
init(table: DealingTable) {
2021-12-18 15:08:43 +01:00
// 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)
}
2021-12-18 15:08:43 +01:00
players.first!.isNextActor = true
super.init(table: table, players: players)
}
2021-12-18 15:08:43 +01:00
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()
}
2021-12-18 15:08:43 +01:00
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)
}
let table = PlayingTable(table: self, game: game, playedBy: player)
return (.success, table)
}
2021-12-18 15:08:43 +01:00
@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
}
2021-12-18 15:08:43 +01:00
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)
}
}
2021-12-18 15:08:43 +01:00
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)
}
2021-12-18 15:08:43 +01:00
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)
}
2021-12-18 15:08:43 +01:00
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)
}
}