185 lines
5.2 KiB
Swift
185 lines
5.2 KiB
Swift
import Foundation
|
|
import WebSocketKit
|
|
|
|
/**
|
|
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 {
|
|
|
|
var canStillRaise = true
|
|
|
|
var isCalledWithAce: Card?
|
|
|
|
var didPlayCalledAce = false
|
|
|
|
var playedCard: Card?
|
|
|
|
var leadsGame: Bool
|
|
|
|
var numberOfRaises = 0
|
|
|
|
/// All tricks won by the player in this game
|
|
var wonTricks: [Trick] = []
|
|
|
|
init(player: CardHoldingPlayer, leads: Bool, calledAce ace: Card?) {
|
|
leadsGame = leads
|
|
super.init(player: player)
|
|
if let ace = ace, cards.contains(ace) {
|
|
isCalledWithAce = ace
|
|
} else {
|
|
isCalledWithAce = nil
|
|
}
|
|
}
|
|
|
|
/// The players has been called in a call game
|
|
var isCallee: Bool {
|
|
isCalledWithAce != nil
|
|
}
|
|
|
|
private var isUnknownCallee: Bool {
|
|
isCallee && !didPlayCalledAce
|
|
}
|
|
|
|
override var actions: [PlayerAction] {
|
|
guard canStillRaise else {
|
|
return []
|
|
}
|
|
guard !isUnknownCallee else {
|
|
// Player belongs to caller, but other side has raised
|
|
return leadsGame ? [.doubleDuringGame] : []
|
|
}
|
|
guard !leadsGame else {
|
|
return []
|
|
}
|
|
return [.doubleDuringGame]
|
|
}
|
|
|
|
func play(card: Card) {
|
|
playedCard = card
|
|
canStillRaise = false
|
|
cards = cards.filter { $0 != card }
|
|
if card == isCalledWithAce {
|
|
leadsGame.toggle()
|
|
didPlayCalledAce = true
|
|
}
|
|
}
|
|
|
|
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 numberOfRaises > 0 {
|
|
states.append(.didRaise)
|
|
}
|
|
return states
|
|
}
|
|
|
|
override var info: PlayerInfo {
|
|
var info = super.info
|
|
info.playedCard = playedCard?.id
|
|
return info
|
|
}
|
|
}
|