Schafkopf-Server/Sources/App/Model/Players/PlayingPlayer.swift

180 lines
5.1 KiB
Swift
Raw Normal View History

import Foundation
import WebSocketKit
2021-12-18 15:08:43 +01:00
/**
Specifies the number of cards of the called suit that a player must have
to be allowed to play any card of the suit instead of having to play the ace.
*/
private let numberOfCardsToProtectAce = 4
final class PlayingPlayer: CardHoldingPlayer {
2021-12-18 15:08:43 +01:00
var canStillRaise = true
2021-12-18 15:08:43 +01:00
var isCalledWithAce: Card?
var didPlayCalledAce = false
var playedCard: Card?
var leadsGame: Bool
var numberOfDoubles = 0
2021-12-18 15:08:43 +01:00
/// All tricks won by the player in this game
var wonTricks: [Trick] = []
init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) {
2021-12-18 15:08:43 +01:00
leadsGame = leads
super.init(player: player)
2021-12-18 15:08:43 +01:00
if let ace = ace, cards.contains(ace) {
isCalledWithAce = ace
} else {
isCalledWithAce = nil
}
}
private var isUnknownCallee: Bool {
isCalledWithAce != nil && !didPlayCalledAce
}
2021-12-18 15:08:43 +01:00
override var actions: [PlayerAction] {
guard canStillRaise else {
return []
}
2021-12-21 11:16:54 +01:00
guard !isUnknownCallee else {
// Player belongs to caller, but other side has raised
2021-12-21 11:16:54 +01:00
return leadsGame ? [.doubleDuringGame] : []
}
guard !leadsGame else {
return []
}
return [.doubleDuringGame]
}
2021-12-18 15:08:43 +01:00
func play(card: Card) {
playedCard = card
2021-12-21 11:16:54 +01:00
canStillRaise = false
2021-12-18 15:08:43 +01:00
cards = cards.filter { $0 != card }
if card == isCalledWithAce {
leadsGame.toggle()
didPlayCalledAce = true
2021-12-18 15:08:43 +01:00
}
}
func switchLead() {
leadsGame.toggle()
}
func sortCards(for game: GameType) {
cards = cards.sortedCards(forGame: game)
}
func canPlay(card: Card, for trick: Trick, in game: GameType) -> Bool {
playableCards(for: trick, in: game).contains { $0.card == card && $0.isPlayable }
}
func playableCards(for trick: Trick, in game: GameType) -> [PlayableCard] {
guard isNextActor else {
return cards.unplayable
}
guard cards.count > 1 else {
// Last card can always be played
return cards.playable
}
guard let firstCard = trick.first else {
return playableCardsForStarter(game: game)
}
let sorter = game.sortingType
guard sorter.isTrump(firstCard) else {
return playableCardsFollowing(suit: firstCard.suit, game: game)
}
guard !sorter.hasTrump(in: cards) else {
// Must follow with trump
return cards.map { $0.playable(sorter.isTrump($0)) }
}
// Can play any card if not in calling game
guard let suit = game.calledSuit else {
return cards.playable
}
// Can play any card, except the called ace
let ace = Card(suit, .ass)
return cards.map { $0.playable($0 != ace) }
}
private func playableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) -> [PlayableCard] {
let sorter = game.sortingType
let suitCards = sorter.cards(with: playedSuit, in: cards)
func followSuit() -> [PlayableCard] {
cards.map { $0.playable(!sorter.isTrump($0) && $0.suit == playedSuit) }
}
guard let calledSuit = game.calledSuit else {
return suitCards.isEmpty ? cards.playable : followSuit()
}
let ace = Card(calledSuit, .ass)
guard !suitCards.isEmpty else {
// Exclude called ace, all others allowed
return cards.map { $0.playable($0 != ace) }
}
guard calledSuit == playedSuit else {
// Must follow suit (called ace not present)
return followSuit()
}
// The called suit is played, must commit ace
guard cards.contains(ace) else {
// Must follow suit
return followSuit()
}
// Must play ace
return cards.map { $0.playable($0 == ace) }
}
private func playableCardsForStarter(game: GameType) -> [PlayableCard] {
guard let suit = game.calledSuit else {
return cards.playable
}
let ace = Card(suit, .ass)
// Check if called ace exists, to prohibit other cards of the same suit
guard cards.contains(ace) else {
return cards.playable
}
// Jodeln
if cards.count == numberOfCardsPerPlayer,
cards.suitCount(suit, in: game) >= numberOfCardsToProtectAce {
return cards.playable
}
// Only ace allowed for the called suit
return cards.map { $0.playable($0.suit != suit || $0.symbol.isTrumpOrAce) }
}
var currentPoints: Int {
wonTricks.map { $0.points }.reduce(0, +)
}
override var states: [PlayerState] {
var states = super.states
if didPlayCalledAce {
states.append(.isCalled)
}
if leadsGame {
states.append(.leadsGame)
}
if numberOfDoubles > 0 {
states.append(.didRaise)
}
return states
}
override var info: PlayerInfo {
var info = super.info
info.playedCard = playedCard?.id
return info
}
}