Schafkopf-Server/Sources/App/Model/OldPlayer.swift
2021-12-09 11:10:20 +01:00

479 lines
13 KiB
Swift

import Foundation
import WebSocketKit
import CloudKit
private let encoder = JSONEncoder()
/**
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
let numberOfCardsPerPlayer = 8
final class OldPlayer {
let name: PlayerName
/// The player is the first to play a card in a new game
var playsFirstCard = false
/// The player is the next to perform an action (e.g. play a card)
var isNextActor = false
/// The player must select the game to play after winning the auction
var selectsGame = false
/// The players plays/played the first card for the current trick
var startedCurrentTrick = false
/// Indicate the currently highest bidder during bidding
var isHighestBidder = false
/// The action available to the player
var actions: [PlayerAction] = []
/// Indicates if the player doubled ("legen")
var didDoubleAfterFourCards: Bool? = nil
/// Indicates if the player is still involved in the bidding process
var isStillBidding = true
/// Indicates that the player leads the game ("Spieler")
var isGameLeader = false
/// Indicates the number of raises ("Schuss") of the player
var numberOfRaises = 0
/// The remaining cards of the player
var handCards: [PlayableCard] = []
/// The card played for the current trick
var playedCard: Card? = nil
/// All tricks won by the player in this game
var wonTricks: [Trick] = []
var socket: WebSocket? = nil
var canOfferWedding: Bool {
rawCards.canOfferWedding
}
var offersWedding = false
var wouldAcceptWedding = false
init(name: PlayerName) {
self.name = name
}
var rawCards: [Card] {
handCards.map { $0.card }
}
func has(card: Card) -> Bool {
handCards.contains { $0.card == card }
}
func hasPlayable(card: Card) -> Bool {
handCards.contains { $0.card == card && $0.isPlayable }
}
func remove(card: Card) {
handCards = handCards.filter { $0.card != card }
}
func play(card: Card) {
remove(card: card)
playedCard = card
actions = actions.filter { $0 != .doubleDuringGame }
}
func connect(using socket: WebSocket) {
_ = self.socket?.close()
self.socket = socket
}
func send(_ info: TableInfo) {
try? socket?.send(encodeJSON(info))
}
func disconnect() -> Bool {
guard let socket = socket else {
return false
}
do {
try socket.close().wait()
} catch {
print("Failed to close socket for player: \(name): \(error)")
}
self.socket = nil
return true
}
func canPerform(_ action: PlayerAction) -> Bool {
actions.contains(action)
}
func prepareForNewGame(isFirstPlayer: Bool) {
playsFirstCard = isFirstPlayer
isNextActor = isFirstPlayer
selectsGame = false
startedCurrentTrick = isFirstPlayer
actions = [.deal]
didDoubleAfterFourCards = nil
isStillBidding = true
isGameLeader = false
isHighestBidder = false
handCards = []
playedCard = nil
wonTricks = []
}
func assignFirstCards(_ cards: Hand) {
actions = [.initialDoubleCost, .noDoubleCost]
handCards = cards.map { .init(card: $0, isPlayable: false) }
}
func didDouble(_ double: Bool) {
actions = []
didDoubleAfterFourCards = double
}
func assignRemainingCards(_ cards: Hand) {
handCards = (rawCards + cards)
.sortedCards(order: NormalCardOrder.self)
.map { .init(card: $0, isPlayable: false) }
}
func startAuction() {
if playsFirstCard {
actions = [.withdrawFromAuction, .increaseOrMatchGame]
} else {
actions = []
}
if canOfferWedding {
actions.append(.offerWedding)
}
}
func offerWedding() {
offersWedding = true
isStillBidding = false
actions = []
}
func weddingOfferExists() {
guard isStillBidding else {
return
}
actions = [.increaseOrMatchGame, .withdrawFromAuction]
}
func hasWeddingOffer() {
guard isStillBidding else {
return
}
actions = [.acceptWedding, .increaseOrMatchGame, .withdrawFromAuction]
}
func weddingOutbid() {
isNextActor = false
actions = []
offersWedding = false
}
func didPerformBid() {
isNextActor = false
isHighestBidder = true
actions = []
}
func requiresBid(hasWedding: Bool) {
isNextActor = true
actions = [.increaseOrMatchGame, .withdrawFromAuction]
if hasWedding {
actions.append(.acceptWedding)
}
}
func acceptWedding() {
wouldAcceptWedding = true
actions = []
}
func weddingAccepted() {
guard isStillBidding else {
actions = []
return
}
actions = [.increaseOrMatchGame, .withdrawFromAuction]
}
func auctionEnded() {
actions = []
isStillBidding = false
isHighestBidder = false
isNextActor = false
}
func mustSelectWeddingCard() {
isNextActor = true
// Only cards which are not trump can be given to the other player
handCards = handCards.map {
let card = $0.card
return .init(card: card, isPlayable: !card.isTrump(in: .hochzeit))
}
// Hochzeit costs double
numberOfRaises += 1
}
func canPlay(game: GameType) -> Bool {
guard let suit = game.calledSuit else {
if game == .hochzeit {
return canOfferWedding
}
return true
}
let sorter = game.sortingType
let cards = rawCards
guard sorter.hasCardToCall(suit, in: cards) else {
// Player needs at least one card of the called suit
return false
}
let ace = Card(suit, .ass)
return !cards.contains(ace)
}
func mustSelectGame() {
isNextActor = true
selectsGame = true
}
func replace(card: Card, with other: Card) {
remove(card: card)
handCards.append(.init(card: other, isPlayable: false))
}
func replaceWeddingCard(with card: Card) -> Card {
let index = handCards.firstIndex { $0.card.isTrump(in: .hochzeit) }!
let removed = handCards.remove(at: index).card
handCards.append(.init(card: card, isPlayable: false))
return removed
}
func start(game: GameType) {
isNextActor = playsFirstCard
startedCurrentTrick = playsFirstCard
selectsGame = false
actions = [.doubleDuringGame]
isGameLeader = false
handCards = game.sortingType.sort(rawCards).map { .init(card: $0, isPlayable: false) }
if playsFirstCard {
setPlayableCardsForStarter(game: game)
}
}
func switchLeadership() {
isGameLeader.toggle()
if isGameLeader {
actions = actions.filter { $0 != .doubleDuringGame }
} else if !actions.contains(.doubleDuringGame) {
actions.append(.doubleDuringGame)
}
}
func withdrawFromBidding() {
isNextActor = false
isStillBidding = false
actions = []
}
func didFinish(trick: Trick, winner: Bool, canDoubleInNextRound: Bool) {
isNextActor = winner
//startedCurrentTrick = winner
if winner {
wonTricks.append(trick)
}
if canDoubleInNextRound, !isGameLeader {
actions = [.doubleDuringGame]
} else {
actions = []
}
}
func didFinishGame() {
actions = [.deal]
}
func clearLastTrick() {
playedCard = nil
// This flag is not set until the last trick is cleared, because
// it would mess up the stacking of the cards on the table
// which relies on this property
startedCurrentTrick = isNextActor
}
func setPlayableCards(forCurrentTrick trick: Trick, in game: GameType?) {
guard let game = game, isNextActor else {
for i in 0..<handCards.count {
handCards[i].isPlayable = false
}
return
}
let cards = rawCards
guard cards.count > 1 else {
// Last card can always be played
setAllCards(playable: true)
return
}
guard let firstCard = trick.first else {
setPlayableCardsForStarter(game: game)
return
}
let sorter = game.sortingType
guard sorter.isTrump(firstCard) else {
setPlayableCardsFollowing(suit: firstCard.suit, game: game)
return
}
guard !sorter.hasTrump(in: cards) else {
// Must follow with trump
handCards = cards.map {
.init(card: $0, isPlayable: sorter.isTrump($0))
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when having to follow trump")
}
return
}
// Can play any card if not in calling game
guard let suit = game.calledSuit else {
setAllCards(playable: true)
return
}
// Can play any card, except the called ace
let ace = Card(suit, .ass)
handCards = cards.map {
.init(card: $0, isPlayable: $0 != ace)
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when not having to follow trump in a called game")
}
}
private func setPlayableCardsFollowing(suit playedSuit: Card.Suit, game: GameType) {
let cards = rawCards
let sorter = game.sortingType
let suitCards = sorter.cards(with: playedSuit, in: cards)
func followSuit() {
handCards = cards.map {
.init(card: $0, isPlayable: !sorter.isTrump($0) && $0.suit == playedSuit)
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when following suit")
}
}
guard let calledSuit = game.calledSuit else {
if suitCards.isEmpty {
// Can play any card
setAllCards(playable: true)
} else {
// Must follow suit
followSuit()
}
return
}
print("Has called suit \(calledSuit)")
let ace = Card(calledSuit, .ass)
guard !suitCards.isEmpty else {
// Exclude called ace, all others allowed
handCards = cards.map {
.init(card: $0, isPlayable: $0 != ace)
}
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when following called suit without suit cards")
}
return
}
guard calledSuit == playedSuit else {
print("Following uncalled suit since no suitable cards")
// Must follow suit (called ace not present)
followSuit()
return
}
// The called suit is played, must commit ace
guard cards.contains(ace) else {
// Must follow suit
followSuit()
return
}
// Must play ace
handCards = cards.map { .init(card: $0, isPlayable: $0 == ace) }
if !handCards.contains(where: { $0.isPlayable }) {
print("No cards to play when having to play ace of called suit")
}
}
private func setPlayableCardsForStarter(game: GameType) {
guard let suit = game.calledSuit else {
setAllCards(playable: true)
return
}
let cards = rawCards
let ace = Card(suit, .ass)
// Check if called ace exists, to prohibit other cards of the same suit
guard cards.contains(ace) else {
setAllCards(playable: true)
return
}
// Jodeln
if cards.count == numberOfCardsPerPlayer,
cards.suitCount(suit, in: game) >= numberOfCardsToProtectAce {
setAllCards(playable: true)
return
}
// Only ace allowed for the called suit
handCards = cards.map { card in
let notPlayable = card.suit == suit && !card.symbol.isTrumpOrAce
return PlayableCard(card: card, isPlayable: !notPlayable)
}
}
private func setAllCards(playable: Bool) {
for i in 0..<handCards.count {
handCards[i].isPlayable = playable
}
}
}
extension OldPlayer {
/// Indicate that the player is connected when at a table
var isConnected: Bool {
guard let socket = socket else {
return false
}
guard !socket.isClosed else {
self.socket = nil
return false
}
return true
}
}
extension OldPlayer: Equatable {
static func == (lhs: OldPlayer, rhs: OldPlayer) -> Bool {
lhs.name == rhs.name
}
}