Schafkopf-Server/Sources/App/Model/Tables/BiddingTable.swift
2023-02-06 22:03:02 +01:00

194 lines
7.2 KiB
Swift

import Foundation
final class BiddingTable: AbstractTable<BiddingPlayer> {
var gameToOutbid: GameType.GameClass = .none
var indexOfHighestBidder = 0
var remainingBidders: Int {
players.filter { $0.isStillBidding }.count
}
var isWaitingForGameSelection: Bool {
players.contains { $0.selectsGame }
}
init(table: DealingTable) {
// 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)
}
players.first!.isNextActor = true
super.init(table: table, players: players)
}
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 select(game: GameType, player name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
log("Player \(name) unexpectedly missing from bidding table \(self.name)")
return (.tableStateInvalid, nil)
}
guard player.selectsGame else {
log("Player \(name) does not select the game")
return (.tableStateInvalid, nil)
}
guard gameToOutbid.allows(game: game) else {
log("Game \(game) not allowed for class \(gameToOutbid)")
return (.tableStateInvalid, nil)
}
guard player.canPlay(game: game) else {
log("Player \(game) can't play game \(game)")
return (.tableStateInvalid, nil)
}
let table = PlayingTable(table: self, game: game, playedBy: player)
return (.success, table)
}
@discardableResult
private func selectNextBidder() -> Bool {
guard let index = players.firstIndex(where: { $0.isNextActor }) else {
log("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
}
override func perform(action: PlayerAction, forPlayer name: PlayerName) -> (result: PlayerActionResult, table: ManageableTable?) {
guard let player = players.player(named: name) else {
log("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)
}
}
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 }
}
}
if remainingBidders == 1 {
moveToGameSelection()
} else {
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
moveToGameSelection()
return (.success, nil)
}
default:
break
}
selectNextBidder()
return (.success, nil)
}
private func moveToGameSelection() {
indexOfHighestBidder = players.firstIndex { $0.isStillBidding == true }!
let highestPlayer = players[indexOfHighestBidder]
highestPlayer.isStillBidding = false
highestPlayer.selectsGame = true
highestPlayer.isNextActor = true
}
override func cards(forPlayerAt index: Int) -> [PlayableCard] {
players[index].cards.unplayable
}
override func games(forPlayerAt index: Int) -> [GameConvertible] {
if isWaitingForGameSelection {
let player = players[index]
return gameToOutbid.availableGames.filter(player.canPlay)
}
if index <= indexOfHighestBidder {
return gameToOutbid.availableClasses
}
return gameToOutbid.classesWhenOutbidding
}
override func gameIsSelected(byPlayerAt index: Int) -> Bool {
players[index].selectsGame
}
}